コード例 #1
0
def gather(*, schema: types.Schema, schemas: types.Schemas,
           logical_name: str) -> types.ObjectArtifacts:
    """
    Gather artifacts for constructing a reference to another model from within an array.

    Args:
        schema: The schema of the array reference.
        schemas: All the model schemas used to resolve any $ref within the array
            reference schema.
        logical_name: The name of thearray reference within its parent schema.

    Returns:
        The artifacts required to construct the array reference.

    """
    # Resolve any allOf and $ref
    schema = helpers.schema.prepare(schema=schema, schemas=schemas)

    # Get item schema
    item_schema = schema.get("items")
    if item_schema is None:
        raise exceptions.MalformedRelationshipError(
            "An array property must include items property.")

    # Retrieve artifacts for the object reference within the array
    artifacts = object_ref.artifacts.gather(schema=item_schema,
                                            logical_name=logical_name,
                                            schemas=schemas)

    # Check for uselist
    if (artifacts.relationship.back_reference is not None
            and artifacts.relationship.back_reference.uselist is not None):
        raise exceptions.MalformedRelationshipError(
            "x-uselist is not supported for one to many nor many to many relationships."
        )
    # Check for nullable
    if artifacts.nullable is not None:
        raise exceptions.MalformedRelationshipError(
            "nullable is not supported for one to many nor many to many relationships."
        )

    # Check referenced specification
    ref_schema = helpers.schema.prepare(schema=artifacts.spec, schemas=schemas)
    ref_tablename = helpers.ext_prop.get(source=ref_schema, name="x-tablename")
    if ref_tablename is None:
        raise exceptions.MalformedRelationshipError(
            "One to many relationships must reference a schema with "
            "x-tablename defined.")

    # Add description
    try:
        description = helpers.peek.description(schema=schema, schemas={})
    except exceptions.MalformedSchemaError as exc:
        raise exceptions.MalformedRelationshipError(str(exc))
    if description is not None:
        artifacts.description = description

    return artifacts
コード例 #2
0
def _handle_ref(
    *, logical_name: str, schema: types.Schema, schemas: types.Schemas
) -> _IntermediaryObjectArtifacts:
    """
    Gather artifacts from a $ref.

    Args:
        schema: The schema of the object reference.
        logical_name: The property name of the object reference.
        schemas: Used to resolve any $ref.

    Returns:
        The name of the referenced schema.

    """
    ref_model_name, ref_schema = helpers.ref.resolve(
        name=logical_name, schema=schema, schemas=schemas
    )
    ref_schema = helpers.merge_all_of(schema=ref_schema, schemas=schemas)

    # Check referenced schema
    try:
        type_ = helpers.peek.type_(schema=ref_schema, schemas=schemas)
    except exceptions.TypeMissingError:
        raise exceptions.MalformedRelationshipError(
            "The referenced schema does not have a type."
        )
    if type_ != "object":
        raise exceptions.MalformedRelationshipError(
            "A reference in a relationship must resolve to an object."
        )

    # Read other parameters
    backref = helpers.ext_prop.get(source=ref_schema, name="x-backref")
    uselist = helpers.ext_prop.get(source=ref_schema, name="x-uselist")
    secondary = helpers.ext_prop.get(source=ref_schema, name="x-secondary")
    fk_column_name = helpers.ext_prop.get(
        source=ref_schema, name="x-foreign-key-column"
    )
    if fk_column_name is None:
        fk_column_name = "id"
    nullable = helpers.peek.nullable(schema=ref_schema, schemas={})

    return _IntermediaryObjectArtifacts(
        ref_model_name=ref_model_name,
        fk_column_name=fk_column_name,
        ref_schema=ref_schema,
        backref=backref,
        uselist=uselist,
        secondary=secondary,
        nullable=nullable,
    )
コード例 #3
0
def _handle_schema(
    *, logical_name: str, schema: types.Schema, schemas: types.Schemas
) -> _IntermediaryObjectArtifacts:
    """
    Gather artifacts from the schema.

    Args:
        schema: The schema of the object reference.
        logical_name: The property name of the object reference.
        schemas: Used to resolve any $ref.

    Returns:
        The name of the referenced schema.

    """
    # Read $ref and allOf
    ref = schema.get("$ref")
    all_of = schema.get("allOf")

    if ref is not None:
        intermediary_obj_artifacts = _handle_ref(
            logical_name=logical_name, schema=schema, schemas=schemas
        )
    elif all_of is not None:
        intermediary_obj_artifacts = _handle_all_of(
            logical_name=logical_name, all_of_schema=all_of, schemas=schemas
        )
    else:
        raise exceptions.MalformedRelationshipError(
            "Relationships are defined using either $ref or allOf."
        )

    return intermediary_obj_artifacts
コード例 #4
0
def _handle_key_single(
    *, key: str, schema: types.Schema, default: _TValue, exception_message: str
) -> _TValue:
    """
    Read value and enforce that it only exists once.

    Raise MalformedRelationshipError is default is not None and the key exists in the
        schema.

    ARgs:
        key: The key to read the value of.
        schema: The schema to read the value from.
        default: The default value to return.
        exception_message: The message raised with the exception.

    Returns:
        The value of the key or the default value,

    """
    if key.startswith("x-"):
        if key == "x-kwargs":
            sub_value = helpers.ext_prop.get_kwargs(
                source=schema, reserved={"backref", "secondary"}
            )
        else:
            sub_value = helpers.ext_prop.get(source=schema, name=key)
    else:
        sub_value = schema.get(key)
    if sub_value is not None:
        if default is not None:
            raise exceptions.MalformedRelationshipError(exception_message)

        return sub_value
    return default
コード例 #5
0
def gather(
    *, schema: types.Schema, logical_name: str, schemas: types.Schemas
) -> types.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:
        schema: 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.

    """
    intermediary_obj_artifacts = _handle_schema(
        logical_name=logical_name, schema=schema, schemas=schemas
    )

    # Check if uselist is defined and backref is not
    if (
        intermediary_obj_artifacts.uselist is not None
        and intermediary_obj_artifacts.backref is None
    ):
        raise exceptions.MalformedRelationshipError(
            "Relationships with x-uselist defined must also define x-backref."
        )

    # Construct back reference
    back_reference = None
    if intermediary_obj_artifacts.backref is not None:
        back_reference = types.BackReferenceArtifacts(
            property_name=intermediary_obj_artifacts.backref,
            uselist=intermediary_obj_artifacts.uselist,
        )

    return types.ObjectArtifacts(
        spec=intermediary_obj_artifacts.ref_schema,
        fk_column=intermediary_obj_artifacts.fk_column_name,
        relationship=types.RelationshipArtifacts(
            model_name=intermediary_obj_artifacts.ref_model_name,
            back_reference=back_reference,
            secondary=intermediary_obj_artifacts.secondary,
            kwargs=intermediary_obj_artifacts.kwargs,
        ),
        nullable=intermediary_obj_artifacts.nullable,
        description=intermediary_obj_artifacts.description,
    )
コード例 #6
0
def _check_object_all_of(*, all_of_spec: types.AllOfSpec) -> None:
    """
    Check format of allOf for an object reference.

    Raise MalformedRelationshipError if the allOf schema is not as expected.

    Args:
        all_of_spec: The allOf specification to check.

    """
    # Checking for $ref and x-backref counts
    ref_count = 0
    backref_count = 0
    fk_column_count = 0
    uselist_count = 0
    secondary_count = 0
    for sub_spec in all_of_spec:
        if sub_spec.get("$ref") is not None:
            ref_count += 1
        if sub_spec.get("x-backref") is not None:
            backref_count += 1
        if sub_spec.get("x-foreign-key-column") is not None:
            fk_column_count += 1
        if sub_spec.get("x-uselist") is not None:
            uselist_count += 1
        if sub_spec.get("x-secondary") is not None:
            secondary_count += 1
    if ref_count != 1:
        raise exceptions.MalformedRelationshipError(
            "Relationships defined with allOf must have exactly one $ref in the allOf "
            "list.")
    if backref_count > 1:
        raise exceptions.MalformedRelationshipError(
            "Relationships may have at most 1 x-backref defined.")
    if fk_column_count > 1:
        raise exceptions.MalformedRelationshipError(
            "Relationships may have at most 1 x-foreign-key-column defined.")
    if uselist_count > 1:
        raise exceptions.MalformedRelationshipError(
            "Relationships may have at most 1 x-uselist defined.")
    if secondary_count > 1:
        raise exceptions.MalformedRelationshipError(
            "Relationships may have at most 1 x-secondary defined.")
コード例 #7
0
def check_required(
    *,
    artifacts: types.ColumnArtifacts,
    fk_logical_name: str,
    model_schema: types.Schema,
    schemas: types.Schemas,
) -> bool:
    """
    Check whether a foreign key has already been defined.

    Assume model_schema has already resolved any $ref and allOf at the object level.
    They may not have been resolved at the property level.

    Check whether the proposed logical name is already defined on the model schema. If
    it has been, check that the type is correct and that the foreign key reference has
    been defined and points to the correct column.

    Raise MalformedRelationshipError if a property has already been defined with the
    same name as is proposed for the foreign key but it has the wrong type or does not
    define the correct foreign key constraint.

    Args:
        artifacts: The artifacts of the foreign key.
        fk_logical_name: The proposed name for the foreign key property.
        model_schema: The schema for the model on which the foreign key is proposed to
            be added.
        schemas: Used to resolve any $ref at the property level.

    Returns:
        Whether defining the foreign key is necessary given the model schema.

    """
    properties = model_schema["properties"]
    model_fk_schema = properties.get(fk_logical_name)
    if model_fk_schema is None:
        return True
    model_fk_schema = helpers.schema.prepare(schema=model_fk_schema,
                                             schemas=schemas)

    # Check type
    model_fk_type = model_fk_schema.get("type")
    if model_fk_type is None:
        raise exceptions.MalformedRelationshipError(
            f"{fk_logical_name} does not have a type. ")
    if model_fk_type != artifacts.open_api.type:
        raise exceptions.MalformedRelationshipError(
            "The foreign key required for the relationship has a different type than "
            "the property already defined under that name. "
            f"The required type is {artifacts.open_api.type}. "
            f"The {fk_logical_name} property has the {model_fk_type} type.")

    # Check foreign key constraint
    model_foreign_key = helpers.ext_prop.get(source=model_fk_schema,
                                             name="x-foreign-key")
    if model_foreign_key is None:
        raise exceptions.MalformedRelationshipError(
            f"The property already defined under {fk_logical_name} does not define a "
            'foreign key constraint. Use the "x-foreign-key" extension property to '
            "define a foreign key constraint, for example: "
            f'{artifacts.extension.foreign_key}".')
    foreign_key = artifacts.extension.foreign_key
    # Should not happen
    if foreign_key is None:
        raise exceptions.MalformedRelationshipError(
            "Artifacts for constructing a foreign key does not include a foreign key "
            "constraint.")
    if model_foreign_key != artifacts.extension.foreign_key:
        raise exceptions.MalformedRelationshipError(
            "The foreign key required for the relationship has a different foreign "
            "key constraint than the property already defined under that name. "
            f"The required constraint is {artifacts.extension.foreign_key}. "
            f"The {fk_logical_name} property has the {model_foreign_key} constraint."
        )

    return False
コード例 #8
0
def set_(
    *,
    ref_model_name: str,
    model_schema: types.Schema,
    schemas: types.Schemas,
    fk_column: str,
) -> None:
    """
    Set the foreign key on an existing model or add it to the schemas.

    For an array reference that is defined in the form of a one to many relationship,
    the referenced model requires the addition of a foreign key that would not generally
    be defined by the user. Therefore, the appropriate schema has to be calculated and
    then somehow added to the referenced model. At the time of processing, the
    referenced model may already have been constructed. This requires a check on
    open_alchemy.models for the referenced model. If it is there, it is modified by
    adding a new column to it. Otherwise, the schema of the referenced model is altered
    to include the column so that it is constructed when that model is processed.

    Raise MalformedRelationshipError of the referenced model is not found in the
    schemas.

    Args:
        ref_model_name: The name of the referenced model.
        model_schema: The schema which contains the one to many relationship.
        schemas: All the model schemas used to look for the referenced model and to
            resolve any $ref.
        fk_column: The name of the foreign key column to be added.

    """
    # Check that referenced model is in schemas
    ref_schema = schemas.get(ref_model_name)
    if ref_schema is None:
        raise exceptions.MalformedRelationshipError(
            f"{ref_model_name} referenced in relationship was not found in the "
            "schemas.")
    # Prepare schema for construction. Note any top level $ref must already be resolved.
    ref_schema = helpers.merge_all_of(schema=ref_schema, schemas=schemas)

    # Calculate foreign key artifacts
    fk_logical_name, fk_artifacts = object_ref.foreign_key.gather_artifacts(
        model_schema=model_schema, schemas=schemas, fk_column=fk_column)

    # Check whether the foreign key has already been defined in the referenced model
    fk_required = object_ref.foreign_key.check_required(
        artifacts=fk_artifacts,
        fk_logical_name=fk_logical_name,
        model_schema=ref_schema,
        schemas=schemas,
    )
    if not fk_required:
        return

    # Handle model already constructed by altering the model on open_aclehmy.model
    ref_model: TOptUtilityBase = facades.models.get_model(name=ref_model_name)
    if ref_model is not None:
        fk_column = column.construct_column(artifacts=fk_artifacts)
        setattr(ref_model, fk_logical_name, fk_column)
        return

    # Handle model not constructed by adding the foreign key schema to the model schema
    fk_schema: types.Schema = column.calculate_schema(  # type: ignore
        artifacts=fk_artifacts, dict_ignore=True)
    fk_object_schema = {
        "type": "object",
        "properties": {
            fk_logical_name: {
                **fk_schema,
                "x-foreign-key": fk_artifacts.extension.foreign_key,
            }
        },
    }
    if "allOf" not in schemas[ref_model_name]:
        # Add new top level allOf
        schemas[ref_model_name] = {
            "allOf": [schemas[ref_model_name], fk_object_schema]
        }
        return
    # Append to existing allOf
    schemas[ref_model_name]["allOf"].append(fk_object_schema)
コード例 #9
0
def _handle_all_of(
    *,
    logical_name: str,
    all_of_schema: typing.List[types.Schema],
    schemas: types.Schemas,
) -> _IntermediaryObjectArtifacts:
    """
    Gather artifacts from a allOf.

    Raise MalformedRelationshipError if there are no or multiple $ref in the allOf list.

    Args:
        schema: The schema of the object reference.
        logical_name: The property name of the object reference.
        schemas: Used to resolve any $ref.

    Returns:
        The name of the referenced schema.

    """
    # Initial values
    obj_artifacts: typing.Optional[_IntermediaryObjectArtifacts] = None
    secondary: typing.Optional[str] = None
    backref: typing.Optional[str] = None
    uselist: typing.Optional[bool] = None
    fk_column_name: typing.Optional[str] = None
    nullable: typing.Optional[bool] = None
    description: typing.Optional[str] = None
    kwargs: types.TOptKwargs = None

    # Exceptions with their messages
    incorrect_number_of_ref = exceptions.MalformedRelationshipError(
        "Relationships defined with allOf must have exactly one $ref in the allOf "
        "list."
    )

    for sub_schema in all_of_schema:
        # Handle $ref
        if sub_schema.get("$ref") is not None:
            # Check whether $ref was already found
            if obj_artifacts is not None:
                raise incorrect_number_of_ref

            obj_artifacts = _handle_ref(
                logical_name=logical_name, schema=sub_schema, schemas=schemas
            )

        # Handle backref
        backref = _handle_key_single(
            key="x-backref",
            schema=sub_schema,
            default=backref,
            exception_message="Relationships may have at most 1 x-backref defined.",
        )
        # Handle uselist
        uselist = _handle_key_single(
            key="x-uselist",
            schema=sub_schema,
            default=uselist,
            exception_message="Relationships may have at most 1 x-uselist defined.",
        )
        # Handle secondary
        secondary = _handle_key_single(
            key="x-secondary",
            schema=sub_schema,
            default=secondary,
            exception_message="Relationships may have at most 1 x-secondary defined.",
        )
        # Handle fk_column_name
        fk_column_name = _handle_key_single(
            key="x-foreign-key-column",
            schema=sub_schema,
            default=fk_column_name,
            exception_message=(
                "Relationships may have at most 1 x-foreign-key-column defined."
            ),
        )
        # Handle nullable
        nullable = _handle_key_single(
            key="nullable",
            schema=sub_schema,
            default=nullable,
            exception_message="Relationships may have at most 1 nullable defined.",
        )
        # Handle description
        description = _handle_key_single(
            key="description",
            schema=sub_schema,
            default=description,
            exception_message="Relationships may have at most 1 description defined.",
        )
        # Handle kwargs
        kwargs = _handle_key_single(
            key="x-kwargs",
            schema=sub_schema,
            default=kwargs,
            exception_message="Relationships may have at most 1 x-kwargs defined.",
        )

    # Check that $ref was found once
    if obj_artifacts is None:
        raise incorrect_number_of_ref
    if backref is not None:
        obj_artifacts.backref = backref
    if uselist is not None:
        obj_artifacts.uselist = uselist
    if secondary is not None:
        obj_artifacts.secondary = secondary
    if fk_column_name is not None:
        obj_artifacts.fk_column_name = fk_column_name
    if nullable is not None:
        obj_artifacts.nullable = nullable
    if description is not None:
        obj_artifacts.description = description
    if kwargs is not None:
        obj_artifacts.kwargs = kwargs

    return obj_artifacts
コード例 #10
0
def handle_object(
    *,
    schema: oa_types.Schema,
    schemas: oa_types.Schemas,
    required: typing.Optional[bool],
    logical_name: str,
    model_name: str,
    model_schema: oa_types.Schema,
) -> typing.Tuple[types.TReturnValue, oa_types.ObjectRefSchema]:
    """
    Generate properties for a reference to another object.

    Assume that, when any $ref and allOf are resolved, the schema is an object.

    Args:
        schema: 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.
        model_schema: The schema of the model.

    Returns:
        The logical name, the SQLAlchemy column for the foreign key and the logical
        name and relationship for the reference to the object and the specification to
        record for the object reference.

    """
    # Retrieve artifacts required for object
    obj_artifacts = artifacts.gather(schema=schema,
                                     logical_name=logical_name,
                                     schemas=schemas)

    # Check for secondary
    if obj_artifacts.relationship.secondary is not None:
        raise exceptions.MalformedRelationshipError(
            "Many to one and one to one relationships do not support x-secondary."
        )

    # Record any backref
    helpers.backref.record(
        artifacts=obj_artifacts,
        ref_from_array=False,
        model_name=model_name,
        schemas=schemas,
    )

    # Construct foreign key
    fk_logical_name, fk_artifacts = foreign_key.gather_artifacts_helper(
        obj_artifacts=obj_artifacts, schemas=schemas, required=required)
    fk_required = foreign_key.check_required(
        artifacts=fk_artifacts,
        fk_logical_name=fk_logical_name,
        model_schema=model_schema,
        schemas=schemas,
    )
    return_value: types.TReturnValue
    if fk_required:
        fk_column = column.construct_column(artifacts=fk_artifacts)
        return_value = [(fk_logical_name, fk_column)]
    else:
        return_value = []

    return_schema = _schema.calculate(artifacts=obj_artifacts)
    # Create relationship
    relationship = facades.sqlalchemy.relationship(
        artifacts=obj_artifacts.relationship)
    return_value.append((logical_name, relationship))
    return (return_value, return_schema)
コード例 #11
0
def handle_array(
    *,
    spec: types.Schema,
    model_schema: types.Schema,
    schemas: types.Schemas,
    logical_name: str,
) -> typing.Tuple[typing.List[typing.Tuple[str, typing.Type]], types.Schema]:
    """
    Generate properties for a reference to another object through an array.

    Assume that when any allOf and $ref are resolved in the root spec the type is
    array.

    Args:
        spec: The schema for the column.
        model_schema: The schema of the one to many parent.
        schemas: Used to resolve any $ref.
        logical_name: The logical name in the specification for the schema.

    Returns:
        The logical name and the relationship for the referenced object.

    """
    # Resolve any allOf and $ref
    spec = helpers.prepare_schema(schema=spec, schemas=schemas)

    # Get item specification
    item_spec = spec.get("items")
    if item_spec is None:
        raise exceptions.MalformedRelationshipError(
            "An array property must include items property.")
    obj_artifacts = object_ref.gather_object_artifacts(
        spec=item_spec, logical_name=logical_name, schemas=schemas)

    # Check for uselist
    if obj_artifacts.uselist is not None:
        raise exceptions.MalformedRelationshipError(
            "x-uselist is not supported for one to many relationships.")

    # Check referenced specification
    ref_spec = helpers.prepare_schema(schema=obj_artifacts.spec,
                                      schemas=schemas)
    ref_type = ref_spec.get("type")
    if ref_type != "object":
        raise exceptions.MalformedRelationshipError(
            "One to many relationships must reference an object type schema.")
    ref_tablename = helpers.get_ext_prop(source=ref_spec, name="x-tablename")
    if ref_tablename is None:
        raise exceptions.MalformedRelationshipError(
            "One to many relationships must reference a schema with "
            "x-tablename defined.")

    # Construct relationship
    relationship_return = (
        logical_name,
        sqlalchemy.orm.relationship(
            obj_artifacts.ref_logical_name,
            backref=obj_artifacts.backref,
            secondary=obj_artifacts.secondary,
        ),
    )
    # Construct entry for the addition for the model schema
    spec_return = {
        "type": "array",
        "items": {
            "type": "object",
            "x-de-$ref": obj_artifacts.ref_logical_name
        },
    }
    # Add foreign key to referenced schema
    if obj_artifacts.secondary is None:
        _set_foreign_key(
            ref_model_name=obj_artifacts.ref_logical_name,
            model_schema=model_schema,
            schemas=schemas,
            fk_column=obj_artifacts.fk_column,
        )
    else:
        table = _construct_association_table(
            parent_schema=model_schema,
            child_schema=obj_artifacts.spec,
            schemas=schemas,
            tablename=obj_artifacts.secondary,
        )
        facades.models.set_association(table=table,
                                       name=obj_artifacts.secondary)

    return [relationship_return], spec_return
コード例 #12
0
def _set_foreign_key(
    *,
    ref_model_name: str,
    model_schema: types.Schema,
    schemas: types.Schemas,
    fk_column: str,
) -> None:
    """
    Set the foreign key on an existing model or add it to the schemas.

    Args:
        ref_model_name: The name of the referenced model.
        model_schema: The schema of the one to many parent.
        schemas: All the model schemas.
        fk_column: The name of the foreign key column.

    """
    # Check that model is in schemas
    if ref_model_name not in schemas:
        raise exceptions.MalformedRelationshipError(
            f"{ref_model_name} referenced in relationship was not found in the "
            "schemas.")

    # Calculate foreign key specification
    fk_spec = object_ref.handle_object_reference(spec=model_schema,
                                                 schemas=schemas,
                                                 fk_column=fk_column)

    # Calculate values for foreign key
    tablename = helpers.get_ext_prop(source=model_schema, name="x-tablename")
    fk_logical_name = f"{tablename}_{fk_column}"

    # Gather referenced schema
    ref_schema = schemas[ref_model_name]
    # Any top level $ref must already be resolved
    ref_schema = helpers.merge_all_of(schema=ref_schema, schemas=schemas)
    fk_required = object_ref.check_foreign_key_required(
        fk_spec=fk_spec,
        fk_logical_name=fk_logical_name,
        model_schema=ref_schema,
        schemas=schemas,
    )
    if not fk_required:
        return

    # Handle model already constructed
    ref_model: TOptUtilityBase = facades.models.get_model(name=ref_model_name)
    if ref_model is not None:
        # Construct foreign key
        _, fk_column = column.handle_column(schema=fk_spec)
        setattr(ref_model, fk_logical_name, fk_column)
        return

    # Handle model not constructed
    schemas[ref_model_name] = {
        "allOf": [
            schemas[ref_model_name],
            {
                "type": "object",
                "properties": {
                    fk_logical_name: {
                        **fk_spec, "x-dict-ignore": True
                    }
                },
            },
        ]
    }
コード例 #13
0
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)
コード例 #14
0
def handle_object(
    *,
    spec: types.Schema,
    schemas: types.Schemas,
    required: typing.Optional[bool] = None,
    logical_name: str,
    model_schema: types.Schema,
) -> typing.Tuple[typing.List[typing.Tuple[str, typing.Union[
        sqlalchemy.Column, typing.Type]]], types.Schema, ]:
    """
    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.
        model_schema: The schema of the model.

    Returns:
        The logical name, the SQLAlchemy column for the foreign key and the logical
        name and relationship for the reference to the object and the specification to
        record for the object reference.

    """
    # Retrieve artifacts required for object
    obj_artifacts = gather_object_artifacts(spec=spec,
                                            logical_name=logical_name,
                                            schemas=schemas)

    # Check for secondary
    if obj_artifacts.secondary is not None:
        raise exceptions.MalformedRelationshipError(
            "Many to one and one to one relationships do not support x-secondary."
        )

    # Construct foreign key
    foreign_key_spec = handle_object_reference(
        spec=obj_artifacts.spec,
        schemas=schemas,
        fk_column=obj_artifacts.fk_column)
    fk_logical_name = f"{logical_name}_{obj_artifacts.fk_column}"
    fk_required = check_foreign_key_required(
        fk_spec=foreign_key_spec,
        fk_logical_name=fk_logical_name,
        model_schema=model_schema,
        schemas=schemas,
    )
    if fk_required:
        _, fk_column = column.handle_column(schema=foreign_key_spec,
                                            required=required)
        return_value = [(fk_logical_name, fk_column)]
    else:
        return_value = []

    # Creating relationship
    backref = None
    if obj_artifacts.backref is not None:
        backref = sqlalchemy.orm.backref(obj_artifacts.backref,
                                         uselist=obj_artifacts.uselist)
    return_value.append((
        logical_name,
        sqlalchemy.orm.relationship(obj_artifacts.ref_logical_name,
                                    backref=backref),
    ))
    return return_value, {
        "type": "object",
        "x-de-$ref": obj_artifacts.ref_logical_name
    }