Skip to content
This repository has been archived by the owner on Apr 17, 2024. It is now read-only.

Commit

Permalink
feat: class docstring overrides default description from __schema__, f…
Browse files Browse the repository at this point in the history
  • Loading branch information
apirogov committed Jan 2, 2023
1 parent ab1d708 commit 7c5b259
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/phantom/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import abc
from typing import Any
from typing import Optional
from typing import Callable
from typing import ClassVar
from typing import Generic
Expand Down Expand Up @@ -107,6 +108,11 @@ class Phantom(PhantomBase, Generic[T]):
abstract.
* ``abstract: bool`` - Set to ``True`` to create an abstract phantom type. This
allows deferring definitions of ``predicate`` and ``bound`` to concrete subtypes.
* ``use_docstring: bool`` - Set to ``True`` to override the schema description
with the class docstring. This will take precedence over any descriptions
given in ``__schema__``. The behavior is inherited (i.e. the respective class
docstring will be used as description) until a subclass sets ``use_docstring``
back to ``False``.
"""

__predicate__: Predicate[T]
Expand All @@ -125,13 +131,26 @@ def __init_subclass__(
predicate: Predicate[T] | None = None,
bound: type[T] | None = None,
abstract: bool = False,
use_docstring: Optional[bool] = None,
**kwargs: Any,
) -> None:
if kwargs:
raise RuntimeError("Unknown phantom type argument(s): {kwargs}")

super().__init_subclass__(**kwargs)
resolve_class_attr(cls, "__abstract__", abstract)
resolve_class_attr(cls, "__predicate__", predicate)
cls._resolve_bound(bound)

if use_docstring is not None: # manual override
setattr(cls, "__use_docstring__", use_docstring)
elif not hasattr(cls, "__use_docstring__"): # missing, set default
setattr(cls, "__use_docstring__", False)
if getattr(cls, "__use_docstring__") and not cls.__doc__:
msg = f"{cls} has no docstring, but use_docstring is set or inherited!"
raise RuntimeError(msg)


@classmethod
def _interpret_implicit_bound(cls) -> BoundType:
def discover_bounds() -> Iterable[type]:
Expand Down
25 changes: 25 additions & 0 deletions src/phantom/schema.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from typing import Optional
from typing import Sequence

from typing_extensions import ClassVar
from typing_extensions import Literal
from typing_extensions import TypedDict
from typing_extensions import final

import textwrap


class Schema(TypedDict, total=False):
title: str
Expand All @@ -22,7 +25,26 @@ class Schema(TypedDict, total=False):
maxLength: Optional[int]


def desc_from_docstring(cls) -> str:
# ds = next(filter(lambda x: x.__doc__, cls.__mro__), cls).__doc__
# ds = (ds or "").strip()
ds = cls.__doc__

if not ds:
return ""
lines = ds.split("\n", maxsplit=1)
if len(lines) == 1:
return ds
rest = textwrap.dedent(lines[1])
if fst := lines[0].strip():
return f"{fst}\n{rest}"
else:
return rest


class SchemaField:
__use_docstring__: ClassVar[str]

@classmethod
@final
def __modify_schema__(cls, field_schema: dict) -> None:
Expand All @@ -35,6 +57,9 @@ def __modify_schema__(cls, field_schema: dict) -> None:
field_schema.update(
{key: value for key, value in cls.__schema__().items() if value is not None}
)
if cls.__use_docstring__:
if ds := desc_from_docstring(cls):
field_schema["description"] = ds

@classmethod
def __schema__(cls) -> Schema:
Expand Down

0 comments on commit 7c5b259

Please sign in to comment.