pyracmon.stub API documentation
Description: This module exports functions to output type stub of model types.

# Module `pyracmon.stub`

This module exports functions to output type stub of model types.

Expand source code

    """
    This module exports functions to output type stub of model types.
    """
    from keyword import iskeyword
    import inspect
    import os
    from pathlib import Path
    import types
    from typing import Union, get_origin, get_args
    from typing_extensions import dataclass_transform
    from pyracmon.model import Model
    from pyracmon.mixin import CRUDMixin
    from pyracmon.model_graph import GraphEntityMixin
    from pyracmon.testing import TestingMixin

    default_imports = [
        ("typing", ["Any", "Optional"]),
        ("pyracmon", ["Model", "CRUDMixin"]),
        ("pyracmon.model_graph", ["GraphEntityMixin"]),
        ("pyracmon.stub", ["ModelTransform"]),
        ("pyracmon.testing", ["TestingMixin"]),
    ]

    @dataclass_transform(kw_only_default=True)
    class ModelTransform:
        pass

    def render_models(
        models: list[type[Model]],
        dialect: types.ModuleType,
        mixins: list[type],
        testing: bool = False,
    ) -> list[str]:
        """
        Generate lines of type stub file (.pyi).

        Args:
            models: List of model types.
            dialect: Dialect type of DB.
            mixins: Mixin types used to declare model types.
        Returns:
            Lines of type stub file.
        """
        lines = []

        super_types: list[str] = []
        additional_imports: dict[str, set[str]] = {}

        for mx in mixins + dialect.mixins:
            if isinstance(mx, type):
                mod = inspect.getmodule(mx)
                if mod:
                    additional_imports.setdefault(mod.__name__, set()).add(mx.__name__)
                    super_types.append(mx.__name__)

        for m in models:
            for c in m.columns:
                mod = inspect.getmodule(c.ptype)
                if mod and mod.__name__ != 'builtins':
                    additional_imports.setdefault(mod.__name__, set()).add(c.ptype.__name__)

        # import
        for mod, names in (default_imports + [(m,list(ns)) for m, ns in additional_imports.items()]):
            lines.append(f"from {mod} import {', '.join(names)}")

        base_mixins = [CRUDMixin, GraphEntityMixin, ModelTransform, Model]
        if testing:
            base_mixins[0:0] = [TestingMixin]
        super_types.extend([m.__name__ for m in base_mixins])

        def coltype(t: type) -> str:
            org = get_origin(t)
            org = org or t

            if org in (object, dict):
                return "Any"
            elif org is list:
                elems = [coltype(et) for et in get_args(t)]
                if elems:
                    return f"list[{', '.join(elems)}]"
                else:
                    return "list"
            else:
                return t.__name__

        # model classes
        for m in models:
            lines.append("")
            lines.append(f"class {m.name}({', '.join(super_types)}):")
            for c in m.columns:
                ct = coltype(c.ptype)
                if c.nullable:
                    ct = f"Optional[{ct}]"
                if c.name.isidentifier() and not iskeyword(c.name):
                    lines.append(f"    {c.name}: {ct} = ...")
                else:
                    lines.append(f"#    {c.name}: {ct} = ...")

        return lines

    def output_stub(
        stubdir: Union[str, Path, None],
        module: types.ModuleType,
        models: list[type[Model]],
        dialect: types.ModuleType,
        mixins: list[type],
        testing: bool = False,
    ):
        """
        Output type stub file (.pyi) into the specified location.

        Args:
            stubdir: Directory to output. If `None` , stub file will be output in the same directory of the module.
            module: Module where model types are declared.
            models: Model types.
            dialect: Dialect type of DB.
            mixins: Mixin types used to declare model types.
        """
        modpath = module.__name__.split(".")

        path: Path
        if stubdir:
            path = stubdir if isinstance(stubdir, Path) else Path(stubdir)
            path = path.joinpath(*modpath[0:-1])
            os.makedirs(path, exist_ok=True)
        elif module.__file__:
            path = Path(module.__file__).parent
        else:
            raise ValueError(f"Directory to output stub file could not be determined.")

        path = path.joinpath(f"{modpath[-1]}.pyi")

        pyi = render_models(models, dialect, mixins, testing)

        with open(path, "w") as f:
            for line in pyi:
                f.write(line)
                f.write('\n')

## Functions

`def iskeyword(...)`

  x.contains(y) <==> y in x.

`def output_stub(stubdir: Union[str, pathlib.Path, ForwardRef(None)], module: module, models: list[type[Model]], dialect: module, mixins: list[type], testing: bool = False)`

  Output type stub file (.pyi) into the specified location.

  ## Args

  `stubdir`
    Directory to output. If `None` , stub file will be output in the same directory of the module.
  `module`
    Module where model types are declared.
  `models`
    Model types.
  `dialect`
    Dialect type of DB.
  `mixins`
    Mixin types used to declare model types.
  Expand source code

      def output_stub(
          stubdir: Union[str, Path, None],
          module: types.ModuleType,
          models: list[type[Model]],
          dialect: types.ModuleType,
          mixins: list[type],
          testing: bool = False,
      ):
          """
          Output type stub file (.pyi) into the specified location.

          Args:
              stubdir: Directory to output. If `None` , stub file will be output in the same directory of the module.
              module: Module where model types are declared.
              models: Model types.
              dialect: Dialect type of DB.
              mixins: Mixin types used to declare model types.
          """
          modpath = module.__name__.split(".")

          path: Path
          if stubdir:
              path = stubdir if isinstance(stubdir, Path) else Path(stubdir)
              path = path.joinpath(*modpath[0:-1])
              os.makedirs(path, exist_ok=True)
          elif module.__file__:
              path = Path(module.__file__).parent
          else:
              raise ValueError(f"Directory to output stub file could not be determined.")

          path = path.joinpath(f"{modpath[-1]}.pyi")

          pyi = render_models(models, dialect, mixins, testing)

          with open(path, "w") as f:
              for line in pyi:
                  f.write(line)
                  f.write('\n')

`def render_models(models: list[type[Model]], dialect: module, mixins: list[type], testing: bool = False) ‑> list[str]`

  Generate lines of type stub file (.pyi).

  ## Args

  `models`
    List of model types.
  `dialect`
    Dialect type of DB.
  `mixins`
    Mixin types used to declare model types.

  ## Returns

  Lines of type stub file.

  Expand source code

      def render_models(
          models: list[type[Model]],
          dialect: types.ModuleType,
          mixins: list[type],
          testing: bool = False,
      ) -> list[str]:
          """
          Generate lines of type stub file (.pyi).

          Args:
              models: List of model types.
              dialect: Dialect type of DB.
              mixins: Mixin types used to declare model types.
          Returns:
              Lines of type stub file.
          """
          lines = []

          super_types: list[str] = []
          additional_imports: dict[str, set[str]] = {}

          for mx in mixins + dialect.mixins:
              if isinstance(mx, type):
                  mod = inspect.getmodule(mx)
                  if mod:
                      additional_imports.setdefault(mod.__name__, set()).add(mx.__name__)
                      super_types.append(mx.__name__)

          for m in models:
              for c in m.columns:
                  mod = inspect.getmodule(c.ptype)
                  if mod and mod.__name__ != 'builtins':
                      additional_imports.setdefault(mod.__name__, set()).add(c.ptype.__name__)

          # import
          for mod, names in (default_imports + [(m,list(ns)) for m, ns in additional_imports.items()]):
              lines.append(f"from {mod} import {', '.join(names)}")

          base_mixins = [CRUDMixin, GraphEntityMixin, ModelTransform, Model]
          if testing:
              base_mixins[0:0] = [TestingMixin]
          super_types.extend([m.__name__ for m in base_mixins])

          def coltype(t: type) -> str:
              org = get_origin(t)
              org = org or t

              if org in (object, dict):
                  return "Any"
              elif org is list:
                  elems = [coltype(et) for et in get_args(t)]
                  if elems:
                      return f"list[{', '.join(elems)}]"
                  else:
                      return "list"
              else:
                  return t.__name__

          # model classes
          for m in models:
              lines.append("")
              lines.append(f"class {m.name}({', '.join(super_types)}):")
              for c in m.columns:
                  ct = coltype(c.ptype)
                  if c.nullable:
                      ct = f"Optional[{ct}]"
                  if c.name.isidentifier() and not iskeyword(c.name):
                      lines.append(f"    {c.name}: {ct} = ...")
                  else:
                      lines.append(f"#    {c.name}: {ct} = ...")

          return lines

## Classes

`class ModelTransform`
  Expand source code

      @dataclass_transform(kw_only_default=True)
      class ModelTransform:
          pass