diff --git a/docs/source/conf.py b/docs/source/conf.py index ec93258..092f85a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -127,6 +127,8 @@ #keep_warnings = False autoclass_content='both' +autodoc_class_signature='mix' + # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for @@ -478,7 +480,10 @@ def _process_variables(self, object, path): config_vars = [] config_nested = {} for field_name, field in object.__fields__.items(): - if BaseModel in field.type_.__mro__: + + if 'providers' == field_name: + config_nested[field_name] = [' List of auth provider classes to use (defaults to AAD)', ''] + elif BaseModel in field.type_.__mro__: # This is recursive here config_nested[field_name] = self._process_variables(field.type_, f'{path}.{field_name}') else: @@ -486,7 +491,7 @@ def _process_variables(self, object, path): if field.default: if not SecretStr in field.type_.__mro__: if Path in field.type_.__mro__: - field.default = Path(field.default).relative_to(Path(field.default).parents[2]) + field.default = str(Path(field.default).relative_to(Path(field.default).parents[2])) if field_name == 'user_klass': default_str = f' [default: :class:`{field.default.replace("`", "").replace(":", ".")}`]' else: @@ -511,10 +516,18 @@ def _process_variables(self, object, path): config_vars.append(f' ``{path}.{field_name}``:') for var in config_nested[field_name]: config_vars.append(f' {var}') + config_vars.append('') return config_vars +def autodoc_process_pydantic_signature(app, what, name, obj, options, signature, return_annotation): + if what=='class' and BaseModel in obj.__mro__: + signature = '(**kwargs)' + return signature, return_annotation + + def setup(app): from sphinx.util.texescape import tex_replacements tex_replacements += [(u'£', u"\\")] app.add_autodocumenter(ConfigDocumenter) + app.connect('autodoc-process-signature', autodoc_process_pydantic_signature) diff --git a/docs/source/module/fastapi_aad_auth.config.rst b/docs/source/module/fastapi_aad_auth.config.rst index c194e6f..2086134 100644 --- a/docs/source/module/fastapi_aad_auth.config.rst +++ b/docs/source/module/fastapi_aad_auth.config.rst @@ -1,5 +1,5 @@ fastapi_aad_auth.config *********************** - + .. automodule:: fastapi_aad_auth.config - :members: \ No newline at end of file + :members: diff --git a/src/fastapi_aad_auth/auth.py b/src/fastapi_aad_auth/auth.py index 3d480ba..e1dc60e 100644 --- a/src/fastapi_aad_auth/auth.py +++ b/src/fastapi_aad_auth/auth.py @@ -26,17 +26,17 @@ class Authenticator(LoggingMixin): """Authenticator class. - Creates the key components based on the provided configurations + Creates the key components based on the provided configurations. """ def __init__(self, config: Config = None, add_to_base_routes: bool = True, base_context: Optional[Dict[str, Any]] = None, user_klass: Optional[type] = None): """Initialise the Authenticator based on the provided configuration. Keyword Args: - config (fastapi_aad_auth.config.Config): Authentication configuration (includes ui and routing, as well as AAD Application and Tenant IDs) - add_to_base_routes (bool): Add the authentication to the router - base_context (Dict[str, Any]): a base context to provide - user_klass (type): The user class to use as part of the auth state + * config (fastapi_aad_auth.config.Config): Authentication configuration (includes ui and routing, as well as AAD Application and Tenant IDs) + * add_to_base_routes (bool): Add the authentication to the router + * base_context (Dict[str, Any]): a base context to provide + * user_klass (type): The user class to use as part of the auth state """ super().__init__() if config is None: diff --git a/src/fastapi_aad_auth/config.py b/src/fastapi_aad_auth/config.py index 0527065..753cd27 100644 --- a/src/fastapi_aad_auth/config.py +++ b/src/fastapi_aad_auth/config.py @@ -13,6 +13,11 @@ class BaseSettings(DeprecatableFieldsMixin, _BaseSettings): """Allow deprecations in the BaseSettings object.""" + def __init__(self, *args, **kwargs): # type: ignore[no-redef] + """Initialise the config object.""" + # For handling docstrings + super().__init__(*args, **kwargs) + _DEPRECATION_VERSION = '0.2.0' @@ -98,9 +103,9 @@ class AuthSessionConfig(BaseSettings): variables in a multi-worker/multi-processing environment to enable authentication across workers) """ - secret: SecretStr = Field(str(uuid.uuid4()), description="Secret used for encoding authentication information", + secret: SecretStr = Field(default_factory=lambda: str(uuid.uuid4()), description="Secret used for encoding authentication information", env='SESSION_AUTH_SECRET') - salt: SecretStr = Field(str(uuid.uuid4()), description="Salt used for encoding authentication information", + salt: SecretStr = Field(default_factory=lambda: str(uuid.uuid4()), description="Salt used for encoding authentication information", env='SESSION_AUTH_SALT') class Config: # noqa D106 @@ -117,7 +122,7 @@ class SessionConfig(BaseSettings): Provides configuration for the fastapi session middleware """ - secret_key: SecretStr = Field(str(uuid.uuid4()), description="Secret used for the session middleware", + secret_key: SecretStr = Field(default_factory=lambda: str(uuid.uuid4()), description="Secret used for the session middleware", env='SESSION_SECRET') session_cookie: str = Field('session', description="Cookie name for the session information", env='SESSION_COOKIE') diff --git a/src/fastapi_aad_auth/providers/aad.py b/src/fastapi_aad_auth/providers/aad.py index 390140d..2d8a117 100644 --- a/src/fastapi_aad_auth/providers/aad.py +++ b/src/fastapi_aad_auth/providers/aad.py @@ -273,21 +273,21 @@ def __init__( """Initialise the auth backend. Args: - session_serializer: Session serializer object - client_id: Client ID from Azure App Registration - tenant_id: Tenant ID to connect to for Azure App Registration + * session_serializer: Session serializer object + * client_id: Client ID from Azure App Registration + * tenant_id: Tenant ID to connect to for Azure App Registration Keyword Args: - prompt: Prompt options for Azure AD - client_secret: Client secret value - scopes: Additional scopes requested - enabled: Boolean flag to enable this backend - client_app_ids: List of client apps to accept tokens from - strict_token: Strictly evaluate token - api_audience: Api Audience declared in Azure AD App registration - redirect_uri: Full URI for post authentication callbacks - domain_hint: Hint for the domain - user_klass: Class to use as a user. + * prompt: Prompt options for Azure AD + * client_secret: Client secret value + * scopes: Additional scopes requested + * enabled: Boolean flag to enable this backend + * client_app_ids: List of client apps to accept tokens from + * strict_token: Strictly evaluate token + * api_audience: Api Audience declared in Azure AD App registration + * redirect_uri: Full URI for post authentication callbacks + * domain_hint: Hint for the domain + * user_klass: Class to use as a user. """ redirect_path = self._build_oauth_url(oauth_base_route, 'redirect') token_validator = AADTokenValidator(client_id=client_id, tenant_id=tenant_id, api_audience=api_audience, diff --git a/src/fastapi_aad_auth/utilities/__init__.py b/src/fastapi_aad_auth/utilities/__init__.py index 6ff9603..63b81f5 100644 --- a/src/fastapi_aad_auth/utilities/__init__.py +++ b/src/fastapi_aad_auth/utilities/__init__.py @@ -1,7 +1,9 @@ """Utilities.""" import importlib +from pathlib import Path from typing import List, Union +from pydantic import SecretStr from pydantic.main import ModelMetaclass from starlette.requests import Request @@ -49,8 +51,20 @@ def expand_doc(klass: ModelMetaclass) -> ModelMetaclass: docs = ['', '', 'Keyword Args:'] for name, field in klass.__fields__.items(): # type: ignore default_str = '' + # if field.default: - default_str = f' [default: ``{field.default}``]' + default_str = '' + if field.default: + if SecretStr not in field.type_.__mro__: + default = field.default + if Path in field.type_.__mro__: + default = str(Path(default).relative_to(Path(default).parents[2])) + if field.name == 'user_klass': + default_str = f' [default: :class:`{default.replace("`", "").replace(":", ".")}`]' + else: + default_str = f' [default: ``{default}``]' + else: + default_str = ' [default: ``uuid.uuid4()``]' module = field.outer_type_.__module__ if module != 'builtins': if hasattr(field.outer_type_, '__origin__'): diff --git a/src/fastapi_aad_auth/utilities/deprecate.py b/src/fastapi_aad_auth/utilities/deprecate.py index 087283e..84497fa 100644 --- a/src/fastapi_aad_auth/utilities/deprecate.py +++ b/src/fastapi_aad_auth/utilities/deprecate.py @@ -43,6 +43,7 @@ def DeprecatedField(*args, **kwargs): # noqa: D103 class DeprecatableFieldsMixin: """Mixin for deprecatable fields.""" + def __new__(cls, *args, **kwargs): """Initialise the Field Deprecation Validator.""" for field_name, field in cls.__fields__.items(): @@ -110,7 +111,7 @@ def _update_docstring(deprecation_message, docstring=None): docstring = '' else: docstring += '\n\n' - docstring += f"DEPRECATED - {deprecation_message}" + docstring += f"*DEPRECATED* - {deprecation_message}" return docstring diff --git a/tox.ini b/tox.ini index ed089b3..5101d0d 100644 --- a/tox.ini +++ b/tox.ini @@ -28,7 +28,7 @@ commands = lint: flake8 src/ lint: pipenv check test: pytest {posargs: -rs tests/unit --log-level=WARNING --cov=fastapi_aad_auth --cov-report xml:{toxinidir}/reports/{envname}-coverage.xml} - docs: python -m sphinx -b html -a {toxinidir}/docs/source {toxinidir}/docs/html + docs: python -m sphinx -E -b html -a {toxinidir}/docs/source {toxinidir}/docs/html build: python setup.py sdist --format=zip build: python setup.py sdist --format=gztar build: python setup.py bdist_wheel