@@ -85,6 +85,59 @@ def __init__(self, service_url: str, session: requests.Session):
8585 self .session = session
8686 self ._schema : Optional [Dict [str , Any ]] = None
8787
88+ @staticmethod
89+ def _extract_type_name (full_type : str ) -> str :
90+ """Extract type name from fully-qualified OData type.
91+
92+ Handles standard "Namespace.TypeName" format as well as bracket-quoted
93+ identifiers like "[Schema].[Type]" or "Namespace.[Type]".
94+
95+ Args:
96+ full_type: Fully-qualified type string (e.g., "Namespace.Type")
97+
98+ Returns:
99+ Just the type name portion
100+ """
101+ if not full_type :
102+ return ""
103+
104+ # Handle bracket-quoted identifiers: "[Schema].[Type]" or "Namespace.[Type]"
105+ # Split on dots that are NOT inside brackets
106+ # Strategy: find the last segment, which may be bracket-quoted
107+ if "[" in full_type :
108+ # Find the last '[' that starts a bracket-quoted identifier
109+ last_bracket = full_type .rfind ("[" )
110+ if last_bracket > 0 :
111+ # Check if there's a dot before the bracket
112+ if full_type [last_bracket - 1 ] == "." :
113+ return full_type [last_bracket :]
114+ # If the whole thing starts with '[', it might be the full type name
115+ if full_type .startswith ("[" ):
116+ # Look for pattern like "[Schema].[Type]" - return last bracketed segment
117+ parts = []
118+ current = ""
119+ in_bracket = False
120+ for char in full_type :
121+ if char == "[" :
122+ in_bracket = True
123+ current += char
124+ elif char == "]" :
125+ in_bracket = False
126+ current += char
127+ elif char == "." and not in_bracket :
128+ if current :
129+ parts .append (current )
130+ current = ""
131+ else :
132+ current += char
133+ if current :
134+ parts .append (current )
135+ if parts :
136+ return parts [- 1 ]
137+
138+ # Standard case: "Namespace.TypeName" -> "TypeName"
139+ return full_type .split ("." )[- 1 ]
140+
88141 @property
89142 def schema (self ) -> Dict [str , Any ]:
90143 """Lazily load and return schema from $metadata."""
@@ -291,7 +344,8 @@ def _parse_entity_sets(
291344
292345 if name :
293346 # EntityType format may be "Namespace.TypeName", extract just the type name
294- type_name = entity_type .split ("." )[- 1 ] if entity_type else ""
347+ # Handle bracket-quoted identifiers like "[Schema].[Type]" or "Namespace.[Type]"
348+ type_name = self ._extract_type_name (entity_type ) if entity_type else ""
295349 entity_sets .append (
296350 {
297351 "name" : name ,
@@ -309,6 +363,34 @@ def get_entity_set_names(self) -> List[str]:
309363 """
310364 return [es ["name" ] for es in self .schema ["entity_sets" ]]
311365
366+ def _find_entity_set (self , entity_set_name : str ) -> Optional [Dict [str , str ]]:
367+ """Find EntitySet by name with case-insensitive fallback.
368+
369+ Args:
370+ entity_set_name: Name of the EntitySet to find
371+
372+ Returns:
373+ EntitySet dict if found, None otherwise
374+ """
375+ # Try exact match first
376+ entity_set = next (
377+ (es for es in self .schema ["entity_sets" ] if es ["name" ] == entity_set_name ),
378+ None ,
379+ )
380+ if entity_set :
381+ return entity_set
382+
383+ # Try case-insensitive match
384+ entity_set = next (
385+ (
386+ es
387+ for es in self .schema ["entity_sets" ]
388+ if es ["name" ].lower () == entity_set_name .lower ()
389+ ),
390+ None ,
391+ )
392+ return entity_set
393+
312394 def get_entity_properties (
313395 self , entity_set_name : str , select : Optional [str ] = None
314396 ) -> List [str ]:
@@ -324,26 +406,13 @@ def get_entity_properties(
324406 Raises:
325407 ValueError: If EntitySet or EntityType not found in schema
326408 """
327- # Find the entity type for this set
328- entity_set = next (
329- (es for es in self .schema ["entity_sets" ] if es ["name" ] == entity_set_name ),
330- None ,
331- )
409+ # Find the entity set with case-insensitive fallback
410+ entity_set = self ._find_entity_set (entity_set_name )
332411 if not entity_set :
333412 raise ValueError (f"EntitySet { entity_set_name !r} not found in schema" )
334413
335414 type_name = entity_set ["entity_type" ]
336- entity_type = self .schema ["entity_types" ].get (type_name )
337- if not entity_type :
338- # Try case-insensitive match
339- entity_type = next (
340- (
341- et
342- for name , et in self .schema ["entity_types" ].items ()
343- if name .lower () == type_name .lower ()
344- ),
345- None ,
346- )
415+ entity_type = self ._find_entity_type (type_name )
347416 if not entity_type :
348417 raise ValueError (
349418 f"EntityType { type_name !r} not found in schema for EntitySet { entity_set_name !r} "
@@ -365,6 +434,31 @@ def get_entity_properties(
365434
366435 return ordered
367436
437+ def _find_entity_type (self , type_name : str ) -> Optional [Dict [str , Any ]]:
438+ """Find EntityType by name with case-insensitive fallback.
439+
440+ Args:
441+ type_name: Name of the EntityType to find
442+
443+ Returns:
444+ EntityType dict if found, None otherwise
445+ """
446+ # Try exact match first
447+ entity_type = self .schema ["entity_types" ].get (type_name )
448+ if entity_type :
449+ return entity_type
450+
451+ # Try case-insensitive match
452+ entity_type = next (
453+ (
454+ et
455+ for name , et in self .schema ["entity_types" ].items ()
456+ if name .lower () == type_name .lower ()
457+ ),
458+ None ,
459+ )
460+ return entity_type
461+
368462 def get_navigation_properties (self , entity_set_name : str ) -> List [str ]:
369463 """Return list of navigation property names for an EntitySet.
370464
@@ -377,25 +471,12 @@ def get_navigation_properties(self, entity_set_name: str) -> List[str]:
377471 Raises:
378472 ValueError: If EntitySet or EntityType not found in schema
379473 """
380- entity_set = next (
381- (es for es in self .schema ["entity_sets" ] if es ["name" ] == entity_set_name ),
382- None ,
383- )
474+ entity_set = self ._find_entity_set (entity_set_name )
384475 if not entity_set :
385476 raise ValueError (f"EntitySet { entity_set_name !r} not found in schema" )
386477
387478 type_name = entity_set ["entity_type" ]
388- entity_type = self .schema ["entity_types" ].get (type_name )
389- if not entity_type :
390- # Try case-insensitive match
391- entity_type = next (
392- (
393- et
394- for name , et in self .schema ["entity_types" ].items ()
395- if name .lower () == type_name .lower ()
396- ),
397- None ,
398- )
479+ entity_type = self ._find_entity_type (type_name )
399480 if not entity_type :
400481 raise ValueError (
401482 f"EntityType { type_name !r} not found in schema for EntitySet { entity_set_name !r} "
@@ -429,7 +510,9 @@ def query_entities(
429510 Raises:
430511 ODataV4Error: If the query fails
431512 """
432- base_url = f"{ self .service_url } /{ entity_set_name } "
513+ # URL-encode entity set name to handle special characters like brackets
514+ encoded_name = quote (entity_set_name , safe = "" )
515+ base_url = f"{ self .service_url } /{ encoded_name } "
433516
434517 params : Dict [str , str ] = {}
435518 if select :
@@ -528,7 +611,9 @@ def count_entities(
528611 Returns:
529612 Count of entities, or None if count not supported by service
530613 """
531- base_url = f"{ self .service_url } /{ entity_set_name } /$count"
614+ # URL-encode entity set name to handle special characters like brackets
615+ encoded_name = quote (entity_set_name , safe = "" )
616+ base_url = f"{ self .service_url } /{ encoded_name } /$count"
532617
533618 params : Dict [str , str ] = {}
534619 if filter_expr :
0 commit comments