From 735c3ac65b47dab2b6a3aa443381874ceb053862 Mon Sep 17 00:00:00 2001 From: Jakub Filak Date: Wed, 15 Apr 2020 22:35:14 +0200 Subject: [PATCH] tmp: annotate exceptions --- pyodata/client.py | 4 ++ pyodata/exceptions.txt | 17 +++++++ pyodata/v2/model.py | 105 +++++++++++++++++++++++++++++++++++++++++ pyodata/v2/service.py | 32 +++++++++++++ 4 files changed, 158 insertions(+) create mode 100644 pyodata/exceptions.txt diff --git a/pyodata/client.py b/pyodata/client.py index a5e8b612..1d7aa308 100644 --- a/pyodata/client.py +++ b/pyodata/client.py @@ -18,11 +18,13 @@ def _fetch_metadata(connection, url, logger): resp.content) if resp.status_code != 200: + # ANNOT_EX: fatal runtime communication initialization error on service side raise HttpError( 'Metadata request failed, status code: {}, body:\n{}'.format(resp.status_code, resp.content), resp) mime_type = resp.headers['content-type'] if not any((typ in ['application/xml', 'text/xml'] for typ in mime_type.split(';'))): + # ANNOT_EX: fatal runtime communication initialization error on service side raise HttpError( 'Metadata request did not return XML, MIME type: {}, body:\n{}'.format(mime_type, resp.content), resp) @@ -54,6 +56,7 @@ def __new__(cls, url, connection, odata_version=ODATA_VERSION_2, namespaces=None logger.info('Using static metadata') if config is not None and namespaces is not None: + # ANNOT_EX: fatal invalid usage error raise PyODataException('You cannot pass namespaces and config at the same time') if config is None: @@ -73,4 +76,5 @@ def __new__(cls, url, connection, odata_version=ODATA_VERSION_2, namespaces=None return service + # ANNOT_EX: fatal runtime initialization error on pyodata side raise PyODataException('No implementation for selected odata version {}'.format(odata_version)) diff --git a/pyodata/exceptions.txt b/pyodata/exceptions.txt new file mode 100644 index 00000000..547b0cb5 --- /dev/null +++ b/pyodata/exceptions.txt @@ -0,0 +1,17 @@ +internal error: +- unimplemented handlers +- unimplemented if/else branch +- type mismatch when resolving odata metadata references ( + +service error: +- invalid metadata +-- unknown reference target (ValueHelper annotation, Assocition role, ...) +-- redefinition of something +-- malformed metadata (e.g. missing Navigation role XML nodes) +- runtime +-- malformed http responses +-- malformed odata values + +programmer error: +- request unknown type / property +- assigns invalid data diff --git a/pyodata/v2/model.py b/pyodata/v2/model.py index 6e9b9cd0..3a0e5663 100644 --- a/pyodata/v2/model.py +++ b/pyodata/v2/model.py @@ -35,6 +35,8 @@ def __init__(self, name): self.name = name def __getattr__(self, item): + # TODO: inappropriate message - nobody can resolve "this" - replace with something meaningful + # ANNOT_EX: fatal invalid metadata raise PyODataModelError('Cannot access this association. An error occurred during parsing ' 'association metadata due to that annotation has been omitted.') @@ -44,6 +46,8 @@ def __init__(self, name): self.name = name def __getattr__(self, item): + # TODO: inappropriate message - nobody can resolve "this" - replace with something meaningful + # ANNOT_EX: fatal invalid metadata raise PyODataModelError(f'Cannot access this type. An error occurred during parsing ' f'type stated in xml({self.name}) was not found, therefore it has been replaced with NullType.') @@ -271,6 +275,7 @@ def to_literal(edm_type, value): # pylint: disable=no-self-use if not edm_type: + # ANNOT_EX: fatal runtime error on caller side raise PyODataException('Cannot encode value {} without complex type information'.format(value)) result = {} @@ -285,6 +290,7 @@ def from_json(edm_type, value): # pylint: disable=no-self-use if not edm_type: + # ANNOT_EX: fatal runtime error on service side raise PyODataException('Cannot decode value {} without complex type information'.format(value)) result = {} @@ -299,6 +305,7 @@ def from_literal(edm_type, value): # pylint: disable=no-self-use if not edm_type: + # ANNOT_EX: fatal runtime error on service side raise PyODataException('Cannot decode value {} without complex type information'.format(value)) result = {} @@ -343,6 +350,7 @@ def to_literal(self, value): def from_literal(self, value): matches = re.match("^{}'(.*)'$".format(self._prefix), value) if not matches: + # ANNOT_EX: fatal runtime error on service side raise PyODataModelError( "Malformed value {0} for primitive Edm type. Expected format is {1}'value'".format(value, self._prefix)) return matches.group(1) @@ -375,6 +383,7 @@ def to_literal(self, value): """ if not isinstance(value, datetime.datetime): + # ANNOT_EX: fatal runtime error on programmer side raise PyODataModelError( 'Cannot convert value of type {} to literal. Datetime format is required.'.format(type(value))) @@ -396,6 +405,7 @@ def from_json(self, value): matches = re.match(r"^/Date\((.*)\)/$", value) if not matches: + # ANNOT_EX: fatal runtime error on service side raise PyODataModelError( "Malformed value {0} for primitive Edm type. Expected format is /Date(value)/".format(value)) value = matches.group(1) @@ -404,6 +414,7 @@ def from_json(self, value): # https://stackoverflow.com/questions/36179914/timestamp-out-of-range-for-platform-localtime-gmtime-function value = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) + datetime.timedelta(milliseconds=int(value)) except ValueError: + # ANNOT_EX: fatal runtime error on service side raise PyODataModelError('Cannot decode datetime from value {}.'.format(value)) return value @@ -424,6 +435,7 @@ def from_literal(self, value): try: value = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M') except ValueError: + # ANNOT_EX: fatal runtime error on service side raise PyODataModelError('Cannot decode datetime from value {}.'.format(value)) return value @@ -468,9 +480,11 @@ def to_literal(self, value): # pylint: disable=no-self-use def from_json(self, value): + # ANNOT_EX: potential ValueError: fatal runtime error on service side return int(value) def from_literal(self, value): + # ANNOT_EX: potential ValueError: fatal runtime error on service side return int(value) @@ -484,8 +498,10 @@ def to_literal(self, value): # pylint: disable=no-self-use def from_json(self, value): if value[-1] == 'L': + # ANNOT_EX: potential ValueError: fatal runtime error on service side return int(value[:-1]) + # ANNOT_EX: potential ValueError: fatal runtime error on service side return int(value) def from_literal(self, value): @@ -626,6 +642,7 @@ def traits(self): # pylint: disable=no-self-use def to_literal(self, value): if not isinstance(value, list): + # ANNOT_EX: fatal runtime error on programmer side raise PyODataException('Bad format: invalid list value {}'.format(value)) return [self._item_type.traits.to_literal(v) for v in value] @@ -633,6 +650,7 @@ def to_literal(self, value): # pylint: disable=no-self-use def from_json(self, value): if not isinstance(value, list): + # ANNOT_EX: fatal runtime error on service side raise PyODataException('Bad format: invalid list value {}'.format(value)) return [self._item_type.traits.from_json(v) for v in value] @@ -677,9 +695,11 @@ def typ(self): @typ.setter def typ(self, value): if self._typ is not None: + # ANNOT_EX: fatal model building error - most probably invalid metadat raise RuntimeError('Cannot replace {0} of {1} by {2}'.format(self._typ, self, value)) if value.name != self._type_info[1]: + # ANNOT_EX: fatal model building error - most probably invalid metadata raise RuntimeError('{0} cannot be the type of {1}'.format(value, self)) self._typ = value @@ -702,6 +722,7 @@ def scale(self): def _check_scale_value(self): if self._scale > self._precision: + # ANNOT_EX: fatal model building error - most probably invalid metadata raise PyODataModelError('Scale value ({}) must be less than or equal to precision value ({})' .format(self._scale, self._precision)) @@ -776,6 +797,7 @@ def __getitem__(self, key): try: return super(Schema.Declarations, self).__getitem__(key) except KeyError: + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('There is no Schema Namespace {}'.format(key)) def __init__(self, config: Config): @@ -805,6 +827,7 @@ def typ(self, type_name, namespace=None): except KeyError: pass + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('Type {} does not exist in Schema{}' .format(type_name, ' Namespace ' + namespace if namespace else '')) @@ -813,6 +836,7 @@ def entity_type(self, type_name, namespace=None): try: return self._decls[namespace].entity_types[type_name] except KeyError: + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('EntityType {} does not exist in Schema Namespace {}'.format(type_name, namespace)) for decl in list(self._decls.values()): @@ -821,6 +845,7 @@ def entity_type(self, type_name, namespace=None): except KeyError: pass + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('EntityType {} does not exist in any Schema Namespace'.format(type_name)) def complex_type(self, type_name, namespace=None): @@ -828,6 +853,7 @@ def complex_type(self, type_name, namespace=None): try: return self._decls[namespace].complex_types[type_name] except KeyError: + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('ComplexType {} does not exist in Schema Namespace {}'.format(type_name, namespace)) for decl in list(self._decls.values()): @@ -836,6 +862,7 @@ def complex_type(self, type_name, namespace=None): except KeyError: pass + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('ComplexType {} does not exist in any Schema Namespace'.format(type_name)) def enum_type(self, type_name, namespace=None): @@ -843,6 +870,7 @@ def enum_type(self, type_name, namespace=None): try: return self._decls[namespace].enum_types[type_name] except KeyError: + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError(f'EnumType {type_name} does not exist in Schema Namespace {namespace}') for decl in list(self._decls.values()): @@ -851,6 +879,7 @@ def enum_type(self, type_name, namespace=None): except KeyError: pass + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError(f'EnumType {type_name} does not exist in any Schema Namespace') def get_type(self, type_info): @@ -882,6 +911,7 @@ def get_type(self, type_info): except KeyError: pass + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise PyODataModelError( 'Neither primitive types nor types parsed from service metadata contain requested type {}' .format(type_info.name)) @@ -903,6 +933,7 @@ def entity_set(self, set_name, namespace=None): try: return self._decls[namespace].entity_sets[set_name] except KeyError: + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('EntitySet {} does not exist in Schema Namespace {}'.format(set_name, namespace)) for decl in list(self._decls.values()): @@ -911,6 +942,7 @@ def entity_set(self, set_name, namespace=None): except KeyError: pass + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('EntitySet {} does not exist in any Schema Namespace'.format(set_name)) @property @@ -922,6 +954,7 @@ def function_import(self, function_import, namespace=None): try: return self._decls[namespace].function_imports[function_import] except KeyError: + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('FunctionImport {} does not exist in Schema Namespace {}' .format(function_import, namespace)) @@ -931,6 +964,7 @@ def function_import(self, function_import, namespace=None): except KeyError: pass + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('FunctionImport {} does not exist in any Schema Namespace'.format(function_import)) @property @@ -942,6 +976,7 @@ def association(self, association_name, namespace=None): try: return self._decls[namespace].associations[association_name] except KeyError: + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('Association {} does not exist in namespace {}'.format(association_name, namespace)) for decl in list(self._decls.values()): try: @@ -958,12 +993,14 @@ def association_set_by_association(self, association_name, namespace=None): for association_set in list(self._decls[namespace].association_sets.values()): if association_set.association_type.name == association_name: return association_set + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('Association Set for Association {} does not exist in Schema Namespace {}'.format( association_name, namespace)) for decl in list(self._decls.values()): for association_set in list(decl.association_sets.values()): if association_set.association_type.name == association_name: return association_set + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('Association Set for Association {} does not exist in any Schema Namespace'.format( association_name)) @@ -972,6 +1009,7 @@ def association_set(self, set_name, namespace=None): try: return self._decls[namespace].association_sets[set_name] except KeyError: + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise KeyError('Association set {} does not exist in namespace {}'.format(set_name, namespace)) for decl in list(self._decls.values()): try: @@ -988,16 +1026,23 @@ def check_role_property_names(self, role, entity_type_name, namespace): try: entity_type = self.entity_type(entity_type_name, namespace) except KeyError: + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise PyODataModelError('EntityType {} does not exist in Schema Namespace {}' .format(entity_type_name, namespace)) try: entity_type.proprty(proprty) except KeyError: + # ANNOT_EX: fatal model usage error - programmer error or invalid service metadata raise PyODataModelError('Property {} does not exist in {}'.format(proprty, entity_type.name)) # pylint: disable=too-many-locals,too-many-branches,too-many-statements @staticmethod def from_etree(schema_nodes, config: Config): + # ANNOT_EX: any PyODataModel exception linked to Model raised in this + # method should be reported as invalid service metadata + # ANNOT_EX: any lxml exception should in this + # method should be reported as invalid service metadata + schema = Schema(config) # Parse Schema nodes by parts to get over the problem of not-yet known @@ -1075,6 +1120,7 @@ def from_etree(schema_nodes, config: Config): end_role.entity_type = etype except KeyError: + # ANNOT_EX: invalid metadata error raise PyODataModelError( f'EntityType {end_role.entity_type_info.name} does not exist in Schema ' f'Namespace {end_role.entity_type_info.namespace}') @@ -1085,6 +1131,7 @@ def from_etree(schema_nodes, config: Config): # Check if the role was defined in the current association if principal_role.name not in role_names: + # ANNOT_EX: invalid metadata error raise RuntimeError( 'Role {} was not defined in association {}'.format(principal_role.name, assoc.name)) @@ -1097,6 +1144,7 @@ def from_etree(schema_nodes, config: Config): # Check if the role was defined in the current association if dependent_role.name not in role_names: + # ANNOT_EX: invalid metadata error raise RuntimeError( 'Role {} was not defined in association {}'.format(dependent_role.name, assoc.name)) @@ -1155,6 +1203,7 @@ def from_etree(schema_nodes, config: Config): assoc_set.association_type = schema.association(assoc_set.association_type_name, assoc_set.association_type_namespace) except KeyError: + # ANNOT_EX: invalid metadata error raise PyODataModelError( 'Association {} does not exist in namespace {}' .format(assoc_set.association_type_name, assoc_set.association_type_namespace)) @@ -1166,10 +1215,12 @@ def from_etree(schema_nodes, config: Config): entity_set = schema.entity_set(end.entity_set_name, namespace) end.entity_set = entity_set except KeyError: + # ANNOT_EX: invalid metadata error raise PyODataModelError('EntitySet {} does not exist in Schema Namespace {}' .format(end.entity_set_name, namespace)) # Check if role is defined in Association if assoc_set.association_type.end_by_role(end.role) is None: + # ANNOT_EX: invalid metadata error raise PyODataModelError('Role {} is not defined in association {}' .format(end.role, assoc_set.association_type_name)) except (PyODataModelError, KeyError) as ex: @@ -1193,6 +1244,7 @@ def from_etree(schema_nodes, config: Config): annotation.entity_set = schema.entity_set( annotation.collection_path, namespace=annotation.element_namespace) except KeyError: + # ANNOT_EX: invalid metadata error raise RuntimeError(f'Entity Set {annotation.collection_path} ' f'for {annotation} does not exist') @@ -1200,12 +1252,14 @@ def from_etree(schema_nodes, config: Config): vh_type = schema.typ(annotation.proprty_entity_type_name, namespace=annotation.element_namespace) except KeyError: + # ANNOT_EX: invalid metadata error raise RuntimeError(f'Target Type {annotation.proprty_entity_type_name} ' f'of {annotation} does not exist') try: target_proprty = vh_type.proprty(annotation.proprty_name) except KeyError: + # ANNOT_EX: invalid metadata error raise RuntimeError(f'Target Property {annotation.proprty_name} ' f'of {vh_type} as defined in {annotation} does not exist') @@ -1252,6 +1306,8 @@ def from_etree(cls, type_node, config: Config): stp = StructTypeProperty.from_etree(proprty) if stp.name in stype._properties: + # ANNOT_EX: invalid metadata error + # TODO: add permissive policy raise KeyError('{0} already has property {1}'.format(stype, stp.name)) stype._properties[stp.name] = stp @@ -1329,6 +1385,7 @@ def __str__(self): def __getattr__(self, item): member = next(filter(lambda x: x.name == item, self._member), None) if member is None: + # ANNOT_EX: model usage error raise PyODataException(f'EnumType {self} has no member {item}') return member @@ -1340,6 +1397,7 @@ def __getitem__(self, item): member = next(filter(lambda x: x.value == int(item), self._member), None) if member is None: + # ANNOT_EX: model usage error raise PyODataException(f'EnumType {self} has no member with value {item}') return member @@ -1361,6 +1419,7 @@ def from_etree(type_node, namespace, config: Config): } if underlying_type not in valid_types: + # ANNOT_EX: metadata error raise PyODataParserError( f'Type {underlying_type} is not valid as underlying type for EnumType - must be one of {valid_types}') @@ -1379,6 +1438,7 @@ def from_etree(type_node, namespace, config: Config): vtype = valid_types[underlying_type] if not vtype[0] < next_value < vtype[1]: + # ANNOT_EX: metadata error raise PyODataParserError(f'Value {next_value} is out of range for type {underlying_type}') emember = EnumMember(etype, name, next_value) @@ -1432,6 +1492,7 @@ def from_etree(cls, type_node, config: Config): navp = NavigationTypeProperty.from_etree(proprty) if navp.name in etype._nav_properties: + # ANNOT_EX: metadata error raise KeyError('{0} already has navigation property {1}'.format(etype, navp.name)) etype._nav_properties[navp.name] = navp @@ -1468,9 +1529,11 @@ def entity_type(self): @entity_type.setter def entity_type(self, value): if self._entity_type is not None: + # ANNOT_EX: metadata error - redefinition raise RuntimeError('Cannot replace {0} of {1} to {2}'.format(self._entity_type, self, value)) if value.name != self.entity_type_info[1]: + # ANNOT_EX: metadata error or rather bug in pyodata internall error - declared type name does not match runtime type name raise RuntimeError('{0} cannot be the type of {1}'.format(value, self)) self._entity_type = value @@ -1577,6 +1640,7 @@ def struct_type(self): def struct_type(self, value): if self._struct_type is not None: + # ANNOT_EX: metadata error - redefinition raise RuntimeError('Cannot replace {0} of {1} to {2}'.format(self._struct_type, self, value)) self._struct_type = value @@ -1586,6 +1650,8 @@ def struct_type(self, value): self._text_proprty = self._struct_type.proprty(self._text_proprty_name) except KeyError: # TODO: resolve EntityType of text property + # ANNOT_EX: metadata error or rather bug in pyodata internall error - how this can happen? + # TODO: the text looks invalid if '/' not in self._text_proprty_name: raise RuntimeError('The attribute sap:text of {1} is set to non existing Property \'{0}\'' .format(self._text_proprty_name, self)) @@ -1727,9 +1793,11 @@ def association(self): def association(self, value): if self._association is not None: + # ANNOT_EX: metadata error - redefinition raise PyODataModelError('Cannot replace {0} of {1} to {2}'.format(self._association, self, value)) if value.name != self._association_info.name: + # ANNOT_EX: metadata error or rather bug in pyodata internall error - declared type name does not match runtime type name raise PyODataModelError('{0} cannot be the type of {1}'.format(value, self)) self._association = value @@ -1779,9 +1847,11 @@ def entity_type(self): def entity_type(self, value): if self._entity_type is not None: + # ANNOT_EX: metadata error - redefinition raise PyODataModelError('Cannot replace {0} of {1} to {2}'.format(self._entity_type, self, value)) if value.name != self._entity_type_info.name: + # ANNOT_EX: metadata error or rather bug in pyodata internall error - declared type name does not match runtime type name raise PyODataModelError('{0} cannot be the type of {1}'.format(value, self)) self._entity_type = value @@ -1842,30 +1912,36 @@ def dependent(self): def from_etree(referential_constraint_node, config: Config): principal = referential_constraint_node.xpath('edm:Principal', namespaces=config.namespaces) if len(principal) != 1: + # ANNOT_EX: metadata error raise RuntimeError('Referential constraint must contain exactly one principal element') principal_name = principal[0].get('Role') if principal_name is None: + # ANNOT_EX: metadata error raise RuntimeError('Principal role name was not specified') principal_refs = [] for property_ref in principal[0].xpath('edm:PropertyRef', namespaces=config.namespaces): principal_refs.append(property_ref.get('Name')) if not principal_refs: + # ANNOT_EX: metadata error raise RuntimeError('In role {} should be at least one principal property defined'.format(principal_name)) dependent = referential_constraint_node.xpath('edm:Dependent', namespaces=config.namespaces) if len(dependent) != 1: + # ANNOT_EX: metadata error raise RuntimeError('Referential constraint must contain exactly one dependent element') dependent_name = dependent[0].get('Role') if dependent_name is None: + # ANNOT_EX: metadata error raise RuntimeError('Dependent role name was not specified') dependent_refs = [] for property_ref in dependent[0].xpath('edm:PropertyRef', namespaces=config.namespaces): dependent_refs.append(property_ref.get('Name')) if len(principal_refs) != len(dependent_refs): + # ANNOT_EX: metadata error raise RuntimeError('Number of properties should be equal for the principal {} and the dependent {}' .format(principal_name, dependent_name)) @@ -1904,6 +1980,7 @@ def end_by_role(self, end_role): try: return next((item for item in self._end_roles if item.role == end_role)) except StopIteration: + # ANNOT_EX: metadata error raise KeyError('Association {} has no End with Role {}'.format(self._name, end_role)) @property @@ -1918,14 +1995,17 @@ def from_etree(association_node, config: Config): for end in association_node.xpath('edm:End', namespaces=config.namespaces): end_role = EndRole.from_etree(end) if end_role.entity_type_info is None: + # ANNOT_EX: metadata error raise RuntimeError('End type is not specified in the association {}'.format(name)) association._end_roles.append(end_role) if len(association._end_roles) != 2: + # ANNOT_EX: metadata error raise RuntimeError('Association {} does not have two end roles'.format(name)) refer = association_node.xpath('edm:ReferentialConstraint', namespaces=config.namespaces) if len(refer) > 1: + # ANNOT_EX: metadata error raise RuntimeError('In association {} is defined more than one referential constraint'.format(name)) if not refer: @@ -1962,9 +2042,11 @@ def entity_set(self): @entity_set.setter def entity_set(self, value): if self._entity_set: + # ANNOT_EX: metadata error - redefinition raise PyODataModelError('Cannot replace {0} of {1} to {2}'.format(self._entity_set, self, value)) if value.name != self._entity_set_name: + # ANNOT_EX: metadata error or rather bug in pyodata internall error - declared type name does not match runtime type name raise PyODataModelError( 'Assigned entity set {0} differentiates from the declared {1}'.format(value, self._entity_set_name)) @@ -2013,17 +2095,20 @@ def end_by_role(self, end_role): try: return next((end for end in self._end_roles if end.role == end_role)) except StopIteration: + # ANNOT_EX: metadata error raise KeyError('Association set {} has no End with Role {}'.format(self._name, end_role)) def end_by_entity_set(self, entity_set): try: return next((end for end in self._end_roles if end.entity_set_name == entity_set)) except StopIteration: + # ANNOT_EX: metadata error raise KeyError('Association set {} has no End with Entity Set {}'.format(self._name, entity_set)) @association_type.setter def association_type(self, value): if self._association_type is not None: + # ANNOT_EX: metadata error - redefinition raise RuntimeError('Cannot replace {} of {} with {}'.format(self._association_type, self, value)) self._association_type = value @@ -2035,6 +2120,7 @@ def from_etree(association_set_node, config: Config): end_roles_list = association_set_node.xpath('edm:End', namespaces=config.namespaces) if len(end_roles) > 2: + # ANNOT_EX: metadata error raise PyODataModelError('Association {} cannot have more than 2 end roles'.format(name)) for end_role in end_roles_list: @@ -2132,9 +2218,11 @@ def proprty(self): @proprty.setter def proprty(self, value): if self._proprty is not None: + # ANNOT_EX: metadata error - redefinition raise RuntimeError('Cannot replace {0} of {1} with {2}'.format(self._proprty, self, value)) if value.struct_type.name != self.proprty_entity_type_name or value.name != self.proprty_name: + # ANNOT_EX: metadata error or rather bug in pyodata internall error - declared type name does not match runtime type name raise RuntimeError('{0} cannot be an annotation of {1}'.format(self, value)) self._proprty = value @@ -2145,6 +2233,7 @@ def proprty(self, value): try: param.local_property = etype.proprty(param.local_property_name) except KeyError: + # ANNOT_EX: metadata error raise RuntimeError('{0} of {1} points to an non existing LocalDataProperty {2} of {3}'.format( param, self, param.local_property_name, etype)) @@ -2159,9 +2248,11 @@ def entity_set(self): @entity_set.setter def entity_set(self, value): if self._entity_set is not None: + # ANNOT_EX: metadata error - redefinition raise RuntimeError('Cannot replace {0} of {1} with {2}'.format(self._entity_set, self, value)) if value.name != self.collection_path: + # ANNOT_EX: metadata error or rather bug in pyodata internall error - declared type name does not match runtime type name raise RuntimeError('{0} cannot be assigned to {1}'.format(self, value)) self._entity_set = value @@ -2172,6 +2263,7 @@ def entity_set(self, value): try: param.list_property = etype.proprty(param.list_property_name) except KeyError: + # ANNOT_EX: metadata error raise RuntimeError('{0} of {1} points to an non existing ValueListProperty {2} of {3}'.format( param, self, param.list_property_name, etype)) @@ -2188,6 +2280,7 @@ def local_property_param(self, name): if prm.local_property.name == name: return prm + # ANNOT_EX: model usage error raise KeyError('{0} has no local property {1}'.format(self, name)) def list_property_param(self, name): @@ -2195,6 +2288,7 @@ def list_property_param(self, name): if prm.list_property.name == name: return prm + # ANNOT_EX: model usage error raise KeyError('{0} has no list property {1}'.format(self, name)) @staticmethod @@ -2253,6 +2347,7 @@ def value_helper(self): @value_helper.setter def value_helper(self, value): if self._value_helper is not None: + # ANNOT_EX: metadata error - redefinition raise RuntimeError('Cannot replace {0} of {1} with {2}'.format(self._value_helper, self, value)) self._value_helper = value @@ -2272,6 +2367,7 @@ def local_property(self): @local_property.setter def local_property(self, value): if self._local_property is not None: + # ANNOT_EX: metadata error - redefinition raise RuntimeError('Cannot replace {0} of {1} with {2}'.format(self._local_property, self, value)) self._local_property = value @@ -2287,6 +2383,7 @@ def list_property(self): @list_property.setter def list_property(self, value): if self._list_property is not None: + # ANNOT_EX: metadata error - redefinition raise RuntimeError('Cannot replace {0} of {1} with {2}'.format(self._list_property, self, value)) self._list_property = value @@ -2328,9 +2425,11 @@ def return_type(self): @return_type.setter def return_type(self, value): if self._return_type is not None: + # ANNOT_EX: metadata error - redefinition raise RuntimeError('Cannot replace {0} of {1} by {2}'.format(self._return_type, self, value)) if value.name != self.return_type_info[1]: + # ANNOT_EX: metadata error or rather bug in pyodata internall error - declared type name does not match runtime type name raise RuntimeError('{0} cannot be the type of {1}'.format(value, self)) self._return_type = value @@ -2413,6 +2512,7 @@ def sap_attribute_get_bool(node, attr, default): if value == 'false': return False + # ANNOT_EX: metadata error raise TypeError('Not a bool attribute: {0} = {1}'.format(attr, value)) @@ -2466,6 +2566,7 @@ def build(self): elif isinstance(self._xml, bytes): mdf = io.BytesIO(self._xml) else: + # ANNOT_EX: usage error raise TypeError('Expected bytes or str type on metadata_xml, got : {0}'.format(type(self._xml))) namespaces = self._config.namespaces @@ -2475,17 +2576,20 @@ def build(self): try: dataservices = next((child for child in edmx if etree.QName(child.tag).localname == 'DataServices')) except StopIteration: + # ANNOT_EX: invalid metadata xml raise PyODataParserError('Metadata document is missing the element DataServices') try: schema = next((child for child in dataservices if etree.QName(child.tag).localname == 'Schema')) except StopIteration: + # ANNOT_EX: invalid metadata xml raise PyODataParserError('Metadata document is missing the element Schema') if 'edmx' not in self._config.namespaces: namespace = etree.QName(edmx.tag).namespace if namespace not in self.EDMX_WHITELIST: + # ANNOT_EX: unsupported metadata raise PyODataParserError(f'Unsupported Edmx namespace - {namespace}') namespaces['edmx'] = namespace @@ -2494,6 +2598,7 @@ def build(self): namespace = etree.QName(schema.tag).namespace if namespace not in self.EDM_WHITELIST: + # ANNOT_EX: unsupported metadata raise PyODataParserError(f'Unsupported Schema namespace - {namespace}') namespaces['edm'] = namespace diff --git a/pyodata/v2/service.py b/pyodata/v2/service.py index 95a27dcf..bdf3bf45 100644 --- a/pyodata/v2/service.py +++ b/pyodata/v2/service.py @@ -172,6 +172,7 @@ def __init__(self, entity_type, single_key=None, **args): # check that entity type key consists of exactly one property if len(self._key) != 1: + # ANNOT_EX: usage error or service error - caller must decide raise PyODataException(('Key of entity type {} consists of multiple properties {} ' 'and cannot be initialized by single value').format( self._entity_type.name, ', '.join([prop.name for prop in self._key]))) @@ -187,6 +188,7 @@ def __init__(self, entity_type, single_key=None, **args): else: for key_prop in self._key: if key_prop.name not in args: + # ANNOT_EX: usage error or service error - caller must decide raise PyODataException('Missing value for key property {}'.format(key_prop.name)) self._type = EntityKey.TYPE_COMPLEX @@ -366,6 +368,7 @@ def stream_handler(response): """Returns $value from HTTP Response""" if response.status_code != requests.codes.ok: + # ANNOT_EX: odata request error analysis of status codes could help raise HttpError('HTTP GET for $value failed with status code {}' .format(response.status_code), response) @@ -461,6 +464,7 @@ def _build_values(entity_type, entity): nav_prop = entity_type.nav_proprty(key) val = EntityCreateRequest._build_values(nav_prop.typ, val) except KeyError: + # ANNOT_EX: invalid usage by programmer raise PyODataException('Property {} is not declared in {} entity type'.format( key, entity_type.name)) @@ -544,6 +548,7 @@ def set(self, **kwargs): try: val = self._entity_type.proprty(key).typ.traits.to_json(val) except KeyError: + # ANNOT_EX: invalid usage by programmer raise PyODataException( 'Property {} is not declared in {} entity type'.format(key, self._entity_type.name)) @@ -680,6 +685,7 @@ def parameter(self, name, value): # add parameter as custom query argument self.custom(param.name, param.typ.traits.to_literal(value)) except KeyError: + # ANNOT_EX: invalid usage by programmer raise PyODataException('Function import {0} does not have pararmeter {1}' .format(self._function_import.name, name)) @@ -757,6 +763,7 @@ def __init__(self, service, entity_set, entity_type, proprties=None, entity_key= for entity in proprties[prop.name]['results']: self._cache[prop.name].append(EntityProxy(service, None, prop_etype, entity)) else: + # ANNOT_EX: invalid usage by programmer or wrong data by service - caller must decide raise PyODataException('Unknown multiplicity {0} of association role {1}' .format(prop.to_role.multiplicity, prop.to_role.name)) @@ -787,6 +794,7 @@ def __getattr__(self, attr): self._cache[attr] = value return value except KeyError as ex: + # ANNOT_EX: invalid usage by programmer raise AttributeError('EntityType {0} does not have Property {1}: {2}' .format(self._entity_type.name, attr, str(ex))) @@ -797,6 +805,7 @@ def nav(self, nav_property): try: navigation_property = self._entity_type.nav_proprty(nav_property) except KeyError: + # ANNOT_EX: invalid usage by programmer related to odata model raise PyODataException('Navigation property {} is not declared in {} entity type'.format( nav_property, self._entity_type)) @@ -812,6 +821,7 @@ def nav(self, nav_property): navigation_entity_set = self._service.schema.entity_set(end.entity_set_name, association_info.namespace) if not navigation_entity_set: + # ANNOT_EX: invalid usage by programmer - this does not look possible - one would expect model.py detects this problem - move the check to model.py raise PyODataException('No association set for role {}'.format(navigation_property.to_role)) roles = navigation_property.association.end_roles @@ -838,6 +848,7 @@ def proprty_get_handler(key, proprty, response): """Gets property value from HTTP Response""" if response.status_code != requests.codes.ok: + # ANNOT_EX: odata request error analysis of status codes could help raise HttpError('HTTP GET for Attribute {0} of Entity {1} failed with status code {2}' .format(proprty.name, key, response.status_code), response) @@ -857,6 +868,7 @@ def value_get_handler(key, response): """Gets property value from HTTP Response""" if response.status_code != requests.codes.ok: + # ANNOT_EX: odata request error analysis of status codes could help raise HttpError('HTTP GET for $value of Entity {0} failed with status code {1}' .format(key, response.status_code), response) @@ -921,6 +933,7 @@ def build_expression(operator, operands): """Creates a expression by joining the operands with the operator""" if len(operands) < 2: + # ANNOT_EX: invalid usage by programmer (not odata related) raise ExpressionError('The $filter operator \'{}\' needs at least two operands'.format(operator)) return '({})'.format(' {} '.format(operator).join(operands)) @@ -1006,6 +1019,7 @@ def nav(self, nav_property, key): try: navigation_property = self._entity_set.entity_type.nav_proprty(nav_property) except KeyError: + # ANNOT_EX: invalid usage by programmer related to odata model raise PyODataException('Navigation property {} is not declared in {} entity type'.format( nav_property, self._entity_set.entity_type)) @@ -1020,6 +1034,7 @@ def nav(self, nav_property, key): navigation_entity_set = self._service.schema.entity_set(end.entity_set_name) if not navigation_entity_set: + # ANNOT_EX: invalid usage by programmer - this does not look possible - one would expect model.py detects this problem - move the check to model.py raise PyODataException( 'No association set for role {} {}'.format(navigation_property.to_role, association_set.end_roles)) @@ -1040,6 +1055,7 @@ def get_entity_handler(parent, nav_property, navigation_entity_set, response): """Gets entity from HTTP response""" if response.status_code != requests.codes.ok: + # ANNOT_EX: odata request error analysis of status codes could help raise HttpError('HTTP GET for Entity {0} failed with status code {1}' .format(self._name, response.status_code), response) @@ -1068,6 +1084,7 @@ def get_entity_handler(response): """Gets entity from HTTP response""" if response.status_code != requests.codes.ok: + # ANNOT_EX: odata request error analysis of status codes could help raise HttpError('HTTP GET for Entity {0} failed with status code {1}' .format(self._name, response.status_code), response) @@ -1091,6 +1108,7 @@ def get_entities_handler(response): """Gets entity set from HTTP Response""" if response.status_code != requests.codes.ok: + # ANNOT_EX: odata request error analysis of status codes could help raise HttpError('HTTP GET for Entity Set {0} failed with status code {1}' .format(self._name, response.status_code), response) @@ -1119,6 +1137,7 @@ def create_entity_handler(response): """Gets newly created entity encoded in HTTP Response""" if response.status_code != return_code: + # ANNOT_EX: odata request error analysis of status codes could help raise HttpError('HTTP POST for Entity Set {0} failed with status code {1}' .format(self._name, response.status_code), response) @@ -1136,6 +1155,7 @@ def update_entity_handler(response): """Gets modified entity encoded in HTTP Response""" if response.status_code != 204: + # ANNOT_EX: odata request error analysis of status codes could help raise HttpError('HTTP modify request for Entity Set {} failed with status code {}' .format(self._name, response.status_code), response) @@ -1156,6 +1176,7 @@ def delete_entity_handler(response): """Check if entity deletion was successful""" if response.status_code != 204: + # ANNOT_EX: odata request error analysis of status codes could help raise HttpError(f'HTTP POST for Entity delete {self._name} ' f'failed with status code {response.status_code}', response) @@ -1185,6 +1206,7 @@ def __getattr__(self, name): try: return self._entity_sets[name] except KeyError: + # ANNOT_EX: programmer's error related to model raise AttributeError( 'EntitySet {0} not defined in {1}.'.format(name, ','.join(list(self._entity_sets.keys())))) @@ -1206,6 +1228,7 @@ def __init__(self, service): def __getattr__(self, name): if name not in self._functions: + # ANNOT_EX: programmer's error related to model raise AttributeError( 'Function {0} not defined in {1}.'.format(name, ','.join(list(self._functions.keys())))) @@ -1215,28 +1238,34 @@ def function_import_handler(fimport, response): """Get function call response from HTTP Response""" if 300 <= response.status_code < 400: + # ANNOT_EX: odata request error raise HttpError(f'Function Import {fimport.name} requires Redirection which is not supported', response) if response.status_code == 401: + # ANNOT_EX: odata request error raise HttpError(f'Not authorized to call Function Import {fimport.name}', response) if response.status_code == 403: + # ANNOT_EX: odata request error raise HttpError(f'Missing privileges to call Function Import {fimport.name}', response) if response.status_code == 405: + # ANNOT_EX: odata request error raise HttpError( f'Despite definition Function Import {fimport.name} does not support HTTP {fimport.http_method}', response) if 400 <= response.status_code < 500: + # ANNOT_EX: odata request error raise HttpError( f'Function Import {fimport.name} call has failed with status code {response.status_code}', response) if response.status_code >= 500: + # ANNOT_EX: odata request error raise HttpError(f'Server has encountered an error while processing Function Import {fimport.name}', response) @@ -1375,6 +1404,7 @@ def changeset_handler(changeset, parts): # raise error (even for successfull status codes) since such changeset response # always means something wrong happened on server response = ODataHttpResponse.from_string(parts[0]) + # ANNOT_EX: odata request error raise HttpError('Changeset cannot be processed due to single response received, status code: {}'.format( response.status_code), response) @@ -1383,6 +1413,7 @@ def changeset_handler(changeset, parts): req) if isinstance(req, MultipartRequest): + # ANNOT_EX: invalid odata response raise PyODataException('Changeset cannot contain nested multipart content') # part represents single request, we have to parse @@ -1438,6 +1469,7 @@ def http_response_handler(request, response): """Process HTTP response to mutipart HTTP request""" if response.status_code != 202: # 202 Accepted + # ANNOT_EX: odata request error analysis of status codes could help raise HttpError('HTTP POST for multipart request {0} failed with status code {1}' .format(request.id, response.status_code), response)