def test_resolve_ref_not_defined(): """ GIVEN schema that references a schema that doesn't exist WHEN resolve_ref is called with the schema THEN SchemaNotFoundError is raised. """ schema = {"$ref": "#/components/schemas/RefSchema"} schemas = {} with pytest.raises(exceptions.SchemaNotFoundError): helpers.resolve_ref(name="name 1", schema=schema, schemas=schemas)
def test_resolve_ref_not_schema(): """ GIVEN schema that references something that is not a schema WHEN resolve_ref is called with the schema THEN SchemaNotFoundError is raised. """ schema = {"$ref": "#/components/not/schema"} schemas = {} with pytest.raises(exceptions.SchemaNotFoundError): helpers.resolve_ref(name="name 1", schema=schema, schemas=schemas)
def test_resolve_ref_single(): """ GIVEN schema that references another schema and schemas WHEN resolve_ref is called with the schema and schemas THEN the referenced schema and logical name is returned. """ ref_schema = {"type": "boolean"} ref_name = "RefSchema" schema = {"$ref": f"#/components/schemas/{ref_name}"} schemas = {ref_name: copy.deepcopy(ref_schema)} (return_name, return_schema) = helpers.resolve_ref(name="name 1", schema=schema, schemas=schemas) assert return_name == ref_name assert return_schema == ref_schema
def test_resolve_ref_not_ref_schema(): """ GIVEN schema that does not have $ref and name WHEN resolve_ref is called with the schema and name THEN the schema and name are returned. """ name = "name 1" schema = {"type": "integer"} schemas = {} (return_name, return_schema) = helpers.resolve_ref(name=name, schema=copy.deepcopy(schema), schemas=schemas) assert return_name == name assert return_schema == schema
def _handle_object( *, spec: types.Schema, schemas: types.Schemas, required: typing.Optional[bool] = None, logical_name: str, ) -> typing.List[typing.Tuple[str, typing.Union[sqlalchemy.Column, typing.Type]]]: """ Generate properties for a reference to another object. Assume that, when any $ref and allOf are resolved, the schema is an object. Args: spec: The schema for the column. schemas: Used to resolve any $ref. required: Whether the object property is required. logical_name: The logical name in the specification for the schema. Returns: The logical name and the SQLAlchemy column for the foreign key and the logical name and relationship for the reference to the object. """ # Default backref backref = None # Checking for $ref and allOf ref = spec.get("$ref") all_of = spec.get("allOf") if ref is not None: # Handling $ref ref_logical_name, spec = helpers.resolve_ref(name=logical_name, schema=spec, schemas=schemas) backref = helpers.get_ext_prop(source=spec, name="x-backref") elif all_of is not None: # Checking for $ref and x-backref counts ref_count = 0 backref_count = 0 for sub_spec in all_of: if sub_spec.get("$ref") is not None: ref_count += 1 if sub_spec.get("x-backref") is not None: backref_count += 1 if ref_count != 1: raise exceptions.MalformedManyToOneRelationshipError( "Many to One relationships defined with allOf must have exactly one " "$ref in the allOf list.") if backref_count > 1: raise exceptions.MalformedManyToOneRelationshipError( "Many to One relationships may have at most 1 x-backref defined." ) # Handling allOf for sub_spec in all_of: backref = helpers.get_ext_prop(source=sub_spec, name="x-backref") if sub_spec.get("$ref") is not None: ref_logical_name, spec = helpers.resolve_ref(name=logical_name, schema=sub_spec, schemas=schemas) else: raise exceptions.MalformedManyToOneRelationshipError( "Many to One relationships are defined using either $ref or allOf." ) # Resolving allOf spec = helpers.merge_all_of(schema=spec, schemas=schemas) # Handling object foreign_key_spec = _handle_object_reference(spec=spec, schemas=schemas) return_value = _handle_column(logical_name=f"{logical_name}_id", spec=foreign_key_spec, required=required) # Creating relationship return_value.append((logical_name, sqlalchemy.orm.relationship(ref_logical_name, backref=backref))) return return_value
def gather_object_artifacts(*, spec: types.Schema, logical_name: str, schemas: types.Schemas) -> ObjectArtifacts: """ Collect artifacts from a specification for constructing an object reference. Get the prepared specification, reference logical name, back reference and foreign key column name from a raw object specification. Raise MalformedRelationshipError if neither $ref nor $allOf is found. Raise MalformedRelationshipError if uselist is defined but backref is not. Raise MalformedRelationshipError if multiple $ref, x-backref, x-secondary, x-foreign-key-column or x-uselist are found. Args: spec: The schema for the column. schemas: Used to resolve any $ref. logical_name: The logical name in the specification for the schema. Returns: The prepared specification, reference logical name, back reference and foreign key column. """ # Default backref backref = None # Default uselist uselist = None # Default secondary secondary = None # Initial foreign key column fk_column = None # Checking for $ref and allOf ref = spec.get("$ref") all_of = spec.get("allOf") if ref is not None: # Handle $ref ref_logical_name, spec = helpers.resolve_ref(name=logical_name, schema=spec, schemas=schemas) elif all_of is not None: # Checking for $ref, and x-backref and x-foreign-key-column counts _check_object_all_of(all_of_spec=all_of) # Handle allOf for sub_spec in all_of: backref = helpers.get_ext_prop(source=sub_spec, name="x-backref", default=backref) uselist = helpers.get_ext_prop(source=sub_spec, name="x-uselist", default=uselist) secondary = helpers.get_ext_prop(source=sub_spec, name="x-secondary", default=secondary) fk_column = helpers.get_ext_prop(source=sub_spec, name="x-foreign-key-column", default=fk_column) if sub_spec.get("$ref") is not None: ref_logical_name, spec = helpers.resolve_ref(name=logical_name, schema=sub_spec, schemas=schemas) else: raise exceptions.MalformedRelationshipError( "Relationships are defined using either $ref or allOf.") # Resolving allOf spec = helpers.merge_all_of(schema=spec, schemas=schemas) # If backref has not been found look in referenced schema if backref is None: backref = helpers.get_ext_prop(source=spec, name="x-backref") # If uselist has not been found look in referenced schema if uselist is None: uselist = helpers.get_ext_prop(source=spec, name="x-uselist") # If secondary has not been found look in referenced schema if secondary is None: secondary = helpers.get_ext_prop(source=spec, name="x-secondary") # If foreign key column has not been found look in referenced schema if fk_column is None: fk_column = helpers.get_ext_prop(source=spec, name="x-foreign-key-column") # If foreign key column is still None, default to id if fk_column is None: fk_column = "id" # Check if uselist is defined and backref is not if uselist is not None and backref is None: raise exceptions.MalformedRelationshipError( "Relationships with x-uselist defined must also define x-backref.") return ObjectArtifacts(spec, ref_logical_name, backref, fk_column, uselist, secondary)