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