From 0d39c83b7407e5863661fcdfe503eaa13da89b66 Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Sun, 25 Jun 2023 19:25:33 -0500 Subject: [PATCH] docs: full api documentation --- .gitignore | 1 + docs/api/asgi.rst | 8 ++ docs/api/cli.rst | 16 ++++ docs/api/domain/calculator/controllers.rst | 8 ++ docs/api/domain/calculator/index.rst | 11 +++ docs/api/domain/calculator/schema.rst | 8 ++ docs/api/domain/init.rst | 8 ++ docs/api/domain/system/controllers.rst | 8 ++ docs/api/domain/system/index.rst | 11 +++ docs/api/domain/urls.rst | 8 ++ docs/api/domain/web/controllers.rst | 8 ++ docs/api/domain/web/index.rst | 11 +++ docs/api/index.rst | 30 ++++++- docs/api/lib/cors.rst | 8 ++ docs/api/lib/exceptions.rst | 8 ++ docs/api/lib/log/controller.rst | 9 +++ docs/api/lib/log/index.rst | 11 +++ docs/api/lib/log/init.rst | 9 +++ docs/api/lib/log/utils.rst | 9 +++ docs/api/lib/openapi.rst | 8 ++ docs/api/lib/schema.rst | 10 +++ docs/api/lib/serialization.rst | 8 ++ docs/api/lib/settings.rst | 8 ++ docs/api/lib/static_files.rst | 8 ++ docs/api/lib/template.rst | 8 ++ docs/api/metadata.rst | 8 ++ docs/api/utils.rst | 8 ++ docs/conf.py | 44 +++++++++-- docs/nitpick-exceptions | 49 ++++++++++++ docs/nitpick-exceptions-regex | 19 +++++ niapi/domain/__init__.py | 12 +-- niapi/domain/calculator/__init__.py | 9 +++ .../domain/calculator/controllers/__init__.py | 8 ++ .../calculator/controllers/calculator.py | 12 +++ niapi/domain/calculator/schema.py | 30 +++++++ niapi/domain/system/controllers/system.py | 12 +++ niapi/domain/urls.py | 4 + niapi/utils.py | 78 +++++++++++++++++++ 38 files changed, 520 insertions(+), 13 deletions(-) create mode 100644 docs/api/asgi.rst create mode 100644 docs/api/cli.rst create mode 100644 docs/api/domain/calculator/controllers.rst create mode 100644 docs/api/domain/calculator/index.rst create mode 100644 docs/api/domain/calculator/schema.rst create mode 100644 docs/api/domain/init.rst create mode 100644 docs/api/domain/system/controllers.rst create mode 100644 docs/api/domain/system/index.rst create mode 100644 docs/api/domain/urls.rst create mode 100644 docs/api/domain/web/controllers.rst create mode 100644 docs/api/domain/web/index.rst create mode 100644 docs/api/lib/cors.rst create mode 100644 docs/api/lib/exceptions.rst create mode 100644 docs/api/lib/log/controller.rst create mode 100644 docs/api/lib/log/index.rst create mode 100644 docs/api/lib/log/init.rst create mode 100644 docs/api/lib/log/utils.rst create mode 100644 docs/api/lib/openapi.rst create mode 100644 docs/api/lib/schema.rst create mode 100644 docs/api/lib/serialization.rst create mode 100644 docs/api/lib/settings.rst create mode 100644 docs/api/lib/static_files.rst create mode 100644 docs/api/lib/template.rst create mode 100644 docs/api/metadata.rst create mode 100644 docs/api/utils.rst create mode 100644 docs/nitpick-exceptions create mode 100644 docs/nitpick-exceptions-regex create mode 100644 niapi/domain/calculator/controllers/__init__.py create mode 100644 niapi/domain/calculator/controllers/calculator.py create mode 100644 niapi/domain/calculator/schema.py diff --git a/.gitignore b/.gitignore index 77680ed..78f19c3 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,4 @@ coverage.* setup.py !/niapi/lib/ !/tests/**/lib/ +!/docs/**/lib/ diff --git a/docs/api/asgi.rst b/docs/api/asgi.rst new file mode 100644 index 0000000..98c83a0 --- /dev/null +++ b/docs/api/asgi.rst @@ -0,0 +1,8 @@ +==== +asgi +==== + +Entry point for ASGI-compatible application. + +.. automodule:: niapi.asgi + :members: diff --git a/docs/api/cli.rst b/docs/api/cli.rst new file mode 100644 index 0000000..fea862e --- /dev/null +++ b/docs/api/cli.rst @@ -0,0 +1,16 @@ +=== +cli +=== + +Command line interface for the application. + +CLI Usage +--------- + +.. click:: niapi.cli:run_all_app + :prog: run_all_app + :nested: full + +.. click:: niapi.cli:run_app + :prog: app + :nested: full diff --git a/docs/api/domain/calculator/controllers.rst b/docs/api/domain/calculator/controllers.rst new file mode 100644 index 0000000..26eacae --- /dev/null +++ b/docs/api/domain/calculator/controllers.rst @@ -0,0 +1,8 @@ +=========== +controllers +=========== + +Controllers for the application calculator. + +.. automodule:: niapi.domain.calculator.controllers + :members: diff --git a/docs/api/domain/calculator/index.rst b/docs/api/domain/calculator/index.rst new file mode 100644 index 0000000..c61b6a7 --- /dev/null +++ b/docs/api/domain/calculator/index.rst @@ -0,0 +1,11 @@ +========== +calculator +========== + +.. toctree:: + :titlesonly: + :caption: NIAPI Calculator Domain API Reference + :glob: + :hidden: + + * diff --git a/docs/api/domain/calculator/schema.rst b/docs/api/domain/calculator/schema.rst new file mode 100644 index 0000000..1114315 --- /dev/null +++ b/docs/api/domain/calculator/schema.rst @@ -0,0 +1,8 @@ +====== +schema +====== + +Schema for the application calculator. + +.. automodule:: niapi.domain.calculator.schema + :members: diff --git a/docs/api/domain/init.rst b/docs/api/domain/init.rst new file mode 100644 index 0000000..ea662e2 --- /dev/null +++ b/docs/api/domain/init.rst @@ -0,0 +1,8 @@ +================== +Domain Initializer +================== + +Initializes the domain module. + +.. automodule:: niapi.domain.__init__ + :members: diff --git a/docs/api/domain/system/controllers.rst b/docs/api/domain/system/controllers.rst new file mode 100644 index 0000000..74d60b2 --- /dev/null +++ b/docs/api/domain/system/controllers.rst @@ -0,0 +1,8 @@ +=========== +controllers +=========== + +Controllers for the application system. + +.. automodule:: niapi.domain.system.controllers + :members: diff --git a/docs/api/domain/system/index.rst b/docs/api/domain/system/index.rst new file mode 100644 index 0000000..0bfe183 --- /dev/null +++ b/docs/api/domain/system/index.rst @@ -0,0 +1,11 @@ +====== +system +====== + +.. toctree:: + :titlesonly: + :caption: NIAPI System Domain API Reference + :glob: + :hidden: + + * diff --git a/docs/api/domain/urls.rst b/docs/api/domain/urls.rst new file mode 100644 index 0000000..3bc68b6 --- /dev/null +++ b/docs/api/domain/urls.rst @@ -0,0 +1,8 @@ +==== +urls +==== + +URL patterns for the NIAPI app. + +.. automodule:: niapi.domain.urls + :members: diff --git a/docs/api/domain/web/controllers.rst b/docs/api/domain/web/controllers.rst new file mode 100644 index 0000000..6b62fc3 --- /dev/null +++ b/docs/api/domain/web/controllers.rst @@ -0,0 +1,8 @@ +=========== +controllers +=========== + +Controllers for the application web interface. + +.. automodule:: niapi.domain.web.controllers.web + :members: diff --git a/docs/api/domain/web/index.rst b/docs/api/domain/web/index.rst new file mode 100644 index 0000000..5cbbaee --- /dev/null +++ b/docs/api/domain/web/index.rst @@ -0,0 +1,11 @@ +=== +web +=== + +.. toctree:: + :titlesonly: + :caption: NIAPI Web Domain API Reference + :glob: + :hidden: + + * diff --git a/docs/api/index.rst b/docs/api/index.rst index 0f630f4..7922f6f 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -1,6 +1,34 @@ +============= API Reference ============= .. toctree:: :titlesonly: - :maxdepth: 1 + :caption: API Reference Documentation + :glob: + :hidden: + + asgi + cli + metadata + utils + +.. toctree:: + :titlesonly: + :caption: Domain API Reference + :glob: + :hidden: + + domain/* + domain/web/index + domain/system/index + domain/calculator/index + +.. toctree:: + :titlesonly: + :caption: Library API Reference + :glob: + :hidden: + + lib/* + lib/log/index diff --git a/docs/api/lib/cors.rst b/docs/api/lib/cors.rst new file mode 100644 index 0000000..5a83239 --- /dev/null +++ b/docs/api/lib/cors.rst @@ -0,0 +1,8 @@ +==== +cors +==== + +CORS config + +.. automodule:: niapi.lib.cors + :members: diff --git a/docs/api/lib/exceptions.rst b/docs/api/lib/exceptions.rst new file mode 100644 index 0000000..3930242 --- /dev/null +++ b/docs/api/lib/exceptions.rst @@ -0,0 +1,8 @@ +========== +exceptions +========== + +Application exceptions + +.. automodule:: niapi.lib.exceptions + :members: diff --git a/docs/api/lib/log/controller.rst b/docs/api/lib/log/controller.rst new file mode 100644 index 0000000..7458212 --- /dev/null +++ b/docs/api/lib/log/controller.rst @@ -0,0 +1,9 @@ +========== +controller +========== + +Log config for the controllers + +.. automodule:: niapi.lib.log.controller + :members: + :noindex: diff --git a/docs/api/lib/log/index.rst b/docs/api/lib/log/index.rst new file mode 100644 index 0000000..fe6ef01 --- /dev/null +++ b/docs/api/lib/log/index.rst @@ -0,0 +1,11 @@ +======= +logging +======= + +.. toctree:: + :titlesonly: + :caption: Library Logging API Reference Documentation + :glob: + :hidden: + + * diff --git a/docs/api/lib/log/init.rst b/docs/api/lib/log/init.rst new file mode 100644 index 0000000..f00f7ad --- /dev/null +++ b/docs/api/lib/log/init.rst @@ -0,0 +1,9 @@ +==== +init +==== + +Log initialization + +.. automodule:: niapi.lib.log.__init__ + :members: + :noindex: diff --git a/docs/api/lib/log/utils.rst b/docs/api/lib/log/utils.rst new file mode 100644 index 0000000..fe2a84b --- /dev/null +++ b/docs/api/lib/log/utils.rst @@ -0,0 +1,9 @@ +===== +utils +===== + +Log utils + +.. automodule:: niapi.lib.log.utils + :members: + :noindex: diff --git a/docs/api/lib/openapi.rst b/docs/api/lib/openapi.rst new file mode 100644 index 0000000..634eaff --- /dev/null +++ b/docs/api/lib/openapi.rst @@ -0,0 +1,8 @@ +======= +openapi +======= + +OpenAPI config + +.. automodule:: niapi.lib.openapi + :members: diff --git a/docs/api/lib/schema.rst b/docs/api/lib/schema.rst new file mode 100644 index 0000000..116f939 --- /dev/null +++ b/docs/api/lib/schema.rst @@ -0,0 +1,10 @@ +====== +schema +====== + +Schema for the API and application. + + +.. automodule:: niapi.lib.schema + :members: + :noindex: diff --git a/docs/api/lib/serialization.rst b/docs/api/lib/serialization.rst new file mode 100644 index 0000000..9eb67b1 --- /dev/null +++ b/docs/api/lib/serialization.rst @@ -0,0 +1,8 @@ +============= +serialization +============= + +Application serialization utilities. + +.. automodule:: niapi.lib.serialization + :members: diff --git a/docs/api/lib/settings.rst b/docs/api/lib/settings.rst new file mode 100644 index 0000000..27b8c0e --- /dev/null +++ b/docs/api/lib/settings.rst @@ -0,0 +1,8 @@ +======== +settings +======== + +Application settings + +.. automodule:: niapi.lib.settings + :members: diff --git a/docs/api/lib/static_files.rst b/docs/api/lib/static_files.rst new file mode 100644 index 0000000..f111407 --- /dev/null +++ b/docs/api/lib/static_files.rst @@ -0,0 +1,8 @@ +============ +static files +============ + +Static file config + +.. automodule:: niapi.lib.static_files + :members: diff --git a/docs/api/lib/template.rst b/docs/api/lib/template.rst new file mode 100644 index 0000000..55cdeff --- /dev/null +++ b/docs/api/lib/template.rst @@ -0,0 +1,8 @@ +========== +template +========== + +Template engine config + +.. automodule:: niapi.lib.template + :members: diff --git a/docs/api/metadata.rst b/docs/api/metadata.rst new file mode 100644 index 0000000..126bb72 --- /dev/null +++ b/docs/api/metadata.rst @@ -0,0 +1,8 @@ +======== +metadata +======== + +Metadata for the application. + +.. automodule:: niapi.metadata + :members: diff --git a/docs/api/utils.rst b/docs/api/utils.rst new file mode 100644 index 0000000..72e2619 --- /dev/null +++ b/docs/api/utils.rst @@ -0,0 +1,8 @@ +===== +utils +===== + +Utilities for the application. + +.. automodule:: niapi.utils + :members: diff --git a/docs/conf.py b/docs/conf.py index 93d90b7..45058d7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -3,12 +3,17 @@ import importlib.metadata import os +import sys +from pathlib import Path from dotenv import load_dotenv from niapi.metadata import __project__ as project # -- Environmental Data ------------------------------------------------------ +path = Path("..").resolve() +tls_verify = False +sys.path.insert(0, path.as_posix()) load_dotenv() # -- Project information ----------------------------------------------------- @@ -41,7 +46,8 @@ "click": ("https://click.palletsprojects.com/en/8.1.x/", None), "structlog": ("https://www.structlog.org/en/stable/", None), "opentelemetry": ("https://opentelemetry-python.readthedocs.io/en/latest/", None), - "litestar": ("https://docs.litestar.dev/2", None), + "litestar": ("https://docs.litestar.dev/2/", None), + "msgspec": ("https://jcristharif.com/msgspec/", None), } napoleon_google_docstring = True @@ -51,13 +57,37 @@ napoleon_use_admonition_for_references = False napoleon_attr_annotations = True -autoclass_content = "class" -autodoc_class_signature = "separated" -autodoc_default_options = {"special-members": "__init__", "show-inheritance": True, "members": True} -autodoc_member_order = "bysource" -autodoc_typehints_format = "short" +autoclass_content = "both" +autodoc_default_options = { + "members": True, + "member-order": "bysource", + "special-members": "__init__", + "exclude-members": "__weakref__", + "show-inheritance": True, + "class-signature": "separated", + "typehints-format": "short", +} + +nitpicky = False +nitpick_ignore = [] +nitpick_ignore_regex = [] + +with Path("nitpick-exceptions").open() as file: + for line in file: + if line.strip() == "" or line.startswith("#"): + continue + dtype, target = line.split(None, 1) + target = target.strip() + nitpick_ignore.append((dtype, target)) + +with Path("nitpick-exceptions-regex").open() as file: + for line in file: + if line.strip() == "" or line.startswith("#"): + continue + dtype, target = line.split(None, 1) + target = target.strip() + nitpick_ignore_regex.append((dtype, target)) -nitpicky = True autosectionlabel_prefix_document = True suppress_warnings = [ "autosectionlabel.*", diff --git a/docs/nitpick-exceptions b/docs/nitpick-exceptions new file mode 100644 index 0000000..9e83d52 --- /dev/null +++ b/docs/nitpick-exceptions @@ -0,0 +1,49 @@ +# external library / undocumented external +py:class BaseModel +py:class pydantic.main.BaseModel +py:class pydantic.generics.GenericModel +py:class redis.asyncio.Redis +py:class sqlalchemy.orm.decl_api.DeclarativeMeta +py:class sqlalchemy.sql.sqltypes.TupleType +py:class sqlalchemy.dialects.postgresql.named_types.ENUM +py:class _orm.Mapper +py:class _orm.registry +py:class _schema.MetaData +py:class _schema.Table +py:class _RegistryType +py:class abc.Collection +py:class TypeEngine +py:class ExternalType +py:class UserDefinedType +py:class _types.TypeDecorator +py:meth _types.TypeDecorator.process_bind_param +py:meth _types.TypeDecorator.process_result_value +py:meth type_engine + +# type vars and aliases / intentionally undocumented +py:class RouteHandlerType +py:obj litestar.security.base.AuthType +py:class ControllerRouterHandler +py:class PathParameterDefinition +py:class BaseSessionBackendT +py:class litestar.contrib.repository.abc.CollectionT +py:class litestar.contrib.sqlalchemy.repository.SelectT +py:class AnyIOBackend +py:class T +py:class C +py:class EmptyType + +# intentionally undocumented +py:class NoneType +py:class litestar._signature.field.SignatureField +py:class litestar.utils.signature.ParsedType +py:class litestar.utils.signature.ParsedSignature +py:class litestar.utils.signature.ParsedParameter +py:class litestar.utils.sync.AsyncCallable +py:class BacklogStrategy +py:class ExceptionT + +# types in changelog that no longer exist +py:class litestar.response_containers.Template +py:class litestar.response_containers.Redirect +py:class litestar.response.RedirectResponse diff --git a/docs/nitpick-exceptions-regex b/docs/nitpick-exceptions-regex new file mode 100644 index 0000000..68576cd --- /dev/null +++ b/docs/nitpick-exceptions-regex @@ -0,0 +1,19 @@ +# things +py:.*, r"litestar\.types.* +py:.*, r"litestar.*\.T +py:.*, r".*R_co +py:.*, r"ModelT +py:.*, r"litestar.contrib.sqlalchemy.repository.ModelT +py:.*, r".*UserType +py:.*, r"litestar\.middleware\.session\.base\.BaseSessionBackendT +r"py:obj", r"typing\..* +py:.*, r"httpx.* + +# type vars +py:.*, r"litestar\.pagination\.C +py:.*, r"litestar.middleware.session.base.ConfigT +py:.*, r"multidict\..* +py:.*, r"litestar\.connection\.base\.UserT +py:.*, r"litestar\.connection\.base\.AuthT +py:.*, r"litestar\.connection\.base\.StateT +py:.*, r"litestar\.connection\.base\.HandlerT diff --git a/niapi/domain/__init__.py b/niapi/domain/__init__.py index 9cf4db9..ec666ad 100644 --- a/niapi/domain/__init__.py +++ b/niapi/domain/__init__.py @@ -15,11 +15,6 @@ from litestar.types import ControllerRouterHandler -routes: list[ControllerRouterHandler] = [ - # system.controllers.system.SystemController, - web.controllers.web.WebController, -] - __all__ = [ "system", "web", @@ -28,8 +23,15 @@ "signature_namespace", ] +routes: list[ControllerRouterHandler] = [ + # system.controllers.system.SystemController, + web.controllers.web.WebController, +] +"""Routes for the application.""" + signature_namespace: Mapping[str, Any] = { "FilterTypes": FilterTypes, "EmailStr": EmailStr, "OffsetPagination": OffsetPagination, } +"""Namespace for the application signature.""" diff --git a/niapi/domain/calculator/__init__.py b/niapi/domain/calculator/__init__.py index e69de29..d9d20a4 100644 --- a/niapi/domain/calculator/__init__.py +++ b/niapi/domain/calculator/__init__.py @@ -0,0 +1,9 @@ +"""NIAPI calculator domain.""" +from __future__ import annotations + +from . import controllers, schema + +__all__ = [ + "schema", + "controllers", +] diff --git a/niapi/domain/calculator/controllers/__init__.py b/niapi/domain/calculator/controllers/__init__.py new file mode 100644 index 0000000..b845a3b --- /dev/null +++ b/niapi/domain/calculator/controllers/__init__.py @@ -0,0 +1,8 @@ +"""NIAPI calculator domain controllers.""" +from __future__ import annotations + +from . import calculator + +__all__ = [ + "calculator", +] diff --git a/niapi/domain/calculator/controllers/calculator.py b/niapi/domain/calculator/controllers/calculator.py new file mode 100644 index 0000000..954f676 --- /dev/null +++ b/niapi/domain/calculator/controllers/calculator.py @@ -0,0 +1,12 @@ +"""Calculator Controller.""" +from __future__ import annotations + +from litestar import Controller + +__all__ = ["CalculatorController"] + + +class CalculatorController(Controller): + """Calculator Controller.""" + + opt = {"exclude_from_auth": True} diff --git a/niapi/domain/calculator/schema.py b/niapi/domain/calculator/schema.py new file mode 100644 index 0000000..69c6bf4 --- /dev/null +++ b/niapi/domain/calculator/schema.py @@ -0,0 +1,30 @@ +"""NIAPI calculator schema.""" +from __future__ import annotations + +from typing import Annotated + +from pydantic import BaseModel, Field + +__all__ = ("NetworkData",) + + +class NetworkData(BaseModel): + """Network Data Schema.""" + + ip: Annotated[ + int, + Field( + ..., + description="The IP address in standard IPv4 format.", + regex=r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", + ), + ] + + cidr: Annotated[ + int, + Field( + ..., + description="The CIDR notation.", + regex=r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]|[1-2][0-9]|3[0-2])$", + ), + ] diff --git a/niapi/domain/system/controllers/system.py b/niapi/domain/system/controllers/system.py index e69de29..626fcab 100644 --- a/niapi/domain/system/controllers/system.py +++ b/niapi/domain/system/controllers/system.py @@ -0,0 +1,12 @@ +"""System Controller.""" +from __future__ import annotations + +from litestar import Controller + +__all__ = ["SystemController"] + + +class SystemController(Controller): + """System Controller.""" + + opt = {"exclude_from_auth": True} diff --git a/niapi/domain/urls.py b/niapi/domain/urls.py index 223aab0..4b16348 100644 --- a/niapi/domain/urls.py +++ b/niapi/domain/urls.py @@ -4,6 +4,10 @@ from typing import Final INDEX: Final = "/" +"""Index URL.""" SITE_ROOT: Final = "/{path:str}" +"""Site root URL.""" OPENAPI_SCHEMA: Final = "/api" +"""OpenAPI schema URL.""" SYSTEM_HEALTH: Final = "/health" +"""System health URL.""" diff --git a/niapi/utils.py b/niapi/utils.py index bdaf519..8997bb5 100644 --- a/niapi/utils.py +++ b/niapi/utils.py @@ -27,12 +27,30 @@ def check_email(email: str) -> str: + """Check if the email is valid. + + Args: + email: The email to check. + + Returns: + The email if it is valid. + """ if "@" not in email: raise ValueError("Invalid email!") return email.lower() def slugify(value: str, allow_unicode: bool = False, separator: str | None = None) -> str: + """Convert a string to a slug. + + Args: + value: The string to slugify. + allow_unicode: Whether to allow unicode characters. + separator: The separator to use. + + Returns: + The slugified string. + """ if allow_unicode: value = unicodedata.normalize("NFKC", value) else: @@ -44,14 +62,40 @@ def slugify(value: str, allow_unicode: bool = False, separator: str | None = Non def camel_case(string: str) -> str: + """Convert a string to camel case. + + Args: + string: The string to convert. + + Returns: + The camel cased string. + """ return "".join(word if index == 0 else word.capitalize() for index, word in enumerate(string.split("_"))) def case_insensitive_string_compare(a: str, b: str, /) -> bool: + """Compare two strings case insensitively. + + Args: + a: The first string. + b: The second string. + + Returns: + Whether the strings are equal. + """ return a.strip().lower() == b.strip().lower() def dataclass_as_dict_shallow(dataclass: Any, *, exclude_none: bool = False) -> dict[str, Any]: + """Convert a dataclass to a dict. + + Args: + dataclass: The dataclass to convert. + exclude_none: Whether to exclude None values. + + Returns: + The dataclass as a dict. + """ ret: dict[str, Any] = {} for field in dataclasses.fields(dataclass): value = getattr(dataclass, field.name) @@ -63,6 +107,14 @@ def dataclass_as_dict_shallow(dataclass: Any, *, exclude_none: bool = False) -> @lru_cache def module_to_os_path(dotted_path: str = "app") -> Path: + """Get the path to a module. + + Args: + dotted_path: The dotted path to the module. + + Returns: + The path to the module. + """ src = pkgutil.get_loader(dotted_path) if not isinstance(src, SourceFileLoader): raise TypeError("Couldn't find the path for %s", dotted_path) @@ -70,12 +122,38 @@ def module_to_os_path(dotted_path: str = "app") -> Path: def import_string(dotted_path: str) -> Any: + """Import a class/function from a dotted path. + + Args: + dotted_path: The dotted path to the class/function. + + Returns: + The imported class/function. + """ + def _is_loaded(module: ModuleType | None) -> bool: + """Check if a module is loaded. + + Args: + module: The module to check. + + Returns: + Whether the module is loaded. + """ spec = getattr(module, "__spec__", None) initializing = getattr(spec, "_initializing", False) return bool(module and spec and not initializing) def _cached_import(module_path: str, class_name: str) -> Any: + """Import a class/function from a dotted path. + + Args: + module_path: The dotted path to the module. + class_name: The name of the class/function. + + Returns: + The imported class/function. + """ module = sys.modules.get(module_path) if not _is_loaded(module): module = import_module(module_path)