Source code for cruds.interface

import importlib
from logging import getLogger
import os
from collections.abc import Callable
from typing import Any

from jsonschema import validate
import yaml


logger = getLogger(__name__)

INTERFACE_SCHEMA = f"{os.path.dirname(__file__)}/interface_schema.yaml"


[docs] class ModelFactory: """ Class Factory that is used as a descriptor """ def __init__(self, docstring: str, uri: str, methods: dict) -> None: self.docstring = docstring self.uri = uri self.methods = methods def __set_name__(self, owner: object, name: str) -> None: self.owner = owner self.name = name def __delete__(self, obj) -> None: """ Remove the Model Class so it can be recreated. """ del self.model def __get__(self, obj: object, objtype=None) -> Any: """ Create a Model Class with the owner for client access, and the URI for making CRUDs to the API. """ if not hasattr(self, "model"): Model: Any = type( self.name, (object,), { "_owner": obj, "_uri": self.uri, **self.methods, }, ) Model.__doc__ = self.docstring self.model = Model() return self.model def __set__(self, obj, value) -> None: """ Setting the attribute will do nothing. """ pass
def _create_interfaces_v1(config: dict): """ Processes the Interface configuration and creates the Interface Classes. """ for api in config.get("api") or []: if package_name := api.get("package"): package = importlib.import_module(package_name) interface_code = package.__dict__ else: interface_code = {} models: dict[str, object] = {} for model in api.get("models") or []: method_list: list[str] = [] if api.get("required_model_methods"): method_list += api["required_model_methods"] # Method declaration priority order. Default is only used # if the model doesn't have it. method_list += ( model.get("methods") or api.get("default_model_methods") or [] ) method_map: dict[str, object] = { name: interface_code.get(name) for name in method_list } models[model["name"].lower()] = ModelFactory( docstring=model.get("docstring"), uri=model.get("uri"), methods=method_map, ) interface_methods: dict[str, Callable | None] = { name: interface_code.get(name) for name in api.get("methods") or ["__init__"] } Interface: Any = type( api["name"], (object,), { **interface_methods, **models, }, ) Interface.__doc__ = api.get("docstring") yield (api["name"], Interface)
[docs] def load_config(file_name: str): """ Request the creation of Interface classes using the configuration file. """ with open(file_name) as config_file: config = yaml.safe_load(config_file) with open(INTERFACE_SCHEMA) as schema_file: config_schema = yaml.safe_load(schema_file) logger.info("Validating interface configuration schema") validate(instance=config, schema=config_schema) if config.get("version") == 1: yield from _create_interfaces_v1(config) else: # The validation should catch this situation, but added for consistency # and testing. raise ValueError("Configuration has no valid version")