From 116f517992665c7e3f3f77c443afcc9d3957df66 Mon Sep 17 00:00:00 2001 From: Xuan Hu Date: Fri, 4 Aug 2023 03:34:58 +0000 Subject: [PATCH 1/6] Upgrade settings module using pydantic setting v2. --- constraints/default.txt | 64 +++++++++++++------------ requirements.txt | 2 +- src/serious_scaffold/settings.py | 14 +++--- src/{{ module_name }}/settings.py.jinja | 14 +++--- 4 files changed, 49 insertions(+), 45 deletions(-) diff --git a/constraints/default.txt b/constraints/default.txt index c81f6447..29673ee3 100644 --- a/constraints/default.txt +++ b/constraints/default.txt @@ -1,62 +1,66 @@ alabaster==0.7.13 -autodoc-pydantic==1.8.0 +annotated-types==0.5.0 +autodoc-pydantic==2.0.1 Babel==2.12.1 beautifulsoup4==4.12.2 -black==23.3.0 +black==23.7.0 bleach==6.0.0 build==0.10.0 -certifi==2023.5.7 +certifi==2023.7.22 cffi==1.15.1 -charset-normalizer==3.1.0 -click==8.1.3 +charset-normalizer==3.2.0 +click==8.1.6 colorama==0.4.6 -coverage==7.2.6 -cryptography==40.0.2 +coverage==7.2.7 +cryptography==41.0.3 docutils==0.20.1 -exceptiongroup==1.1.1 -furo==2023.5.20 +exceptiongroup==1.1.2 +furo==2023.7.26 idna==3.4 imagesize==1.4.1 -importlib-metadata==6.6.0 +importlib-metadata==6.8.0 iniconfig==2.0.0 isort==5.12.0 -jaraco.classes==3.2.3 +jaraco.classes==3.3.0 jeepney==0.8.0 Jinja2==3.1.2 -keyring==23.13.1 +keyring==24.2.0 livereload==2.6.3 -markdown-it-py==2.2.0 -MarkupSafe==2.1.2 +markdown-it-py==3.0.0 +MarkupSafe==2.1.3 mdurl==0.1.2 -more-itertools==9.1.0 -mypy==1.3.0 +more-itertools==10.0.0 +mypy==1.4.1 mypy-extensions==1.0.0 packaging==23.1 -pathspec==0.11.1 +pathspec==0.11.2 pkginfo==1.9.6 -platformdirs==3.5.1 -pluggy==1.0.0 +platformdirs==3.10.0 +pluggy==1.2.0 pycparser==2.21 -pydantic==1.10.8 +pydantic==2.1.1 +pydantic-settings==2.0.2 +pydantic_core==2.4.0 Pygments==2.15.1 pyproject_hooks==1.0.0 -pytest==7.3.1 +pytest==7.4.0 pytest-cov==4.1.0 -readme-renderer==37.3 +python-dotenv==1.0.0 +readme-renderer==40.0 requests==2.31.0 requests-toolbelt==1.0.0 rfc3986==2.0.0 -rich==13.3.5 -ruff==0.0.270 +rich==13.5.2 +ruff==0.0.282 SecretStorage==3.3.3 setuptools-scm==7.1.0 shellingham==1.5.0.post1 six==1.16.0 snowballstemmer==2.2.0 soupsieve==2.4.1 -Sphinx==7.0.1 +Sphinx==7.1.2 sphinx-autobuild==2021.3.14 -sphinx-basic-ng==1.0.0b1 +sphinx-basic-ng==1.0.0b2 sphinx-click==4.4.0 sphinxcontrib-applehelp==1.0.4 sphinxcontrib-devhelp==1.0.2 @@ -66,11 +70,11 @@ sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 toml-sort==0.23.1 tomli==2.0.1 -tomlkit==0.11.8 +tomlkit==0.12.1 tornado==6.3.2 twine==4.0.2 typer==0.9.0 -typing_extensions==4.6.2 -urllib3==2.0.2 +typing_extensions==4.7.1 +urllib3==2.0.4 webencodings==0.5.1 -zipp==3.15.0 +zipp==3.16.2 diff --git a/requirements.txt b/requirements.txt index 7bb7bbe3..7faba444 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ # Requirements for runtime. -pydantic +pydantic-settings typer[all] diff --git a/src/serious_scaffold/settings.py b/src/serious_scaffold/settings.py index d3f3ee1c..666d58e4 100644 --- a/src/serious_scaffold/settings.py +++ b/src/serious_scaffold/settings.py @@ -1,20 +1,20 @@ """Settings Module.""" +from __future__ import annotations + import logging from logging import getLevelName -from typing import Optional -from pydantic import BaseSettings +from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """Project specific settings.""" - logging_level: Optional[str] = getLevelName(logging.INFO) - - class Config: - """Config for settings.""" + model_config = SettingsConfigDict( + env_prefix="SERIOUS_SCAFFOLD_", + ) - env_prefix = "SERIOUS_SCAFFOLD_" + logging_level: str | None = getLevelName(logging.INFO) class GlobalSettings(BaseSettings): diff --git a/src/{{ module_name }}/settings.py.jinja b/src/{{ module_name }}/settings.py.jinja index f46d00ee..4940a291 100644 --- a/src/{{ module_name }}/settings.py.jinja +++ b/src/{{ module_name }}/settings.py.jinja @@ -1,20 +1,20 @@ """Settings Module.""" +from __future__ import annotations + import logging from logging import getLevelName -from typing import Optional -from pydantic import BaseSettings +from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """Project specific settings.""" - logging_level: Optional[str] = getLevelName(logging.INFO) - - class Config: - """Config for settings.""" + model_config = SettingsConfigDict( + env_prefix="{{ module_name|upper }}_", + ) - env_prefix = "{{ module_name|upper }}_" + logging_level: str | None = getLevelName(logging.INFO) class GlobalSettings(BaseSettings): From c43ae2ae4c1489fd26053e107b49959c045783d3 Mon Sep 17 00:00:00 2001 From: Xuan Hu Date: Fri, 4 Aug 2023 06:44:14 +0000 Subject: [PATCH 2/6] Update. --- Makefile | 4 ++-- docs/api/index.rst | 7 +++++++ docs/{modules => api}/index.rst.jinja | 0 .../serious_scaffold.settings.rst => api/settings.rst} | 1 - .../settings.rst.jinja} | 1 - docs/conf.py | 4 +++- docs/conf.py.jinja | 4 +++- docs/index.rst | 2 +- docs/modules/index.rst | 7 ------- src/serious_scaffold/settings.py | 6 ++++-- src/{{ module_name }}/settings.py.jinja | 6 ++++-- 11 files changed, 24 insertions(+), 18 deletions(-) create mode 100644 docs/api/index.rst rename docs/{modules => api}/index.rst.jinja (100%) rename docs/{modules/serious_scaffold.settings.rst => api/settings.rst} (87%) rename docs/{modules/{{ module_name }}.settings.rst.jinja => api/settings.rst.jinja} (88%) delete mode 100644 docs/modules/index.rst diff --git a/Makefile b/Makefile index 6dd060c0..638c45a0 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ clean: Pipfile* \ coverage.xml \ dist \ - docs/_build + public find . -name '*.egg-info' -print0 | xargs -0 rm -rf find . -name '*.pyc' -print0 | xargs -0 rm -f find . -name '*.swp' -print0 | xargs -0 rm -f @@ -75,4 +75,4 @@ docs: ${PIPRUN} python -m sphinx.cmd.build docs public docs-autobuild: - ${PIPRUN} python -m sphinx_autobuild docs public + ${PIPRUN} python -m sphinx_autobuild docs public --watch src diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 00000000..aa2b6578 --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,7 @@ +API Reference +============= + +.. toctree:: + :maxdepth: 1 + + settings diff --git a/docs/modules/index.rst.jinja b/docs/api/index.rst.jinja similarity index 100% rename from docs/modules/index.rst.jinja rename to docs/api/index.rst.jinja diff --git a/docs/modules/serious_scaffold.settings.rst b/docs/api/settings.rst similarity index 87% rename from docs/modules/serious_scaffold.settings.rst rename to docs/api/settings.rst index 3a750116..9aa92fbe 100644 --- a/docs/modules/serious_scaffold.settings.rst +++ b/docs/api/settings.rst @@ -2,4 +2,3 @@ serious_scaffold.settings ========================= .. automodule:: serious_scaffold.settings - :members: diff --git a/docs/modules/{{ module_name }}.settings.rst.jinja b/docs/api/settings.rst.jinja similarity index 88% rename from docs/modules/{{ module_name }}.settings.rst.jinja rename to docs/api/settings.rst.jinja index cb92c2f9..a2332f8e 100644 --- a/docs/modules/{{ module_name }}.settings.rst.jinja +++ b/docs/api/settings.rst.jinja @@ -2,4 +2,3 @@ ========================= .. automodule:: {{ module_name }}.settings - :members: diff --git a/docs/conf.py b/docs/conf.py index 5a8ac2a9..06e57d75 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -30,7 +30,9 @@ templates_path = ["_templates"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] -autodoc_pydantic_settings_show_field_summary = False +autodoc_default_options = { + "members": None, +} autodoc_pydantic_settings_show_json = False # -- Options for HTML output ------------------------------------------------- diff --git a/docs/conf.py.jinja b/docs/conf.py.jinja index 1d169607..4d051c94 100644 --- a/docs/conf.py.jinja +++ b/docs/conf.py.jinja @@ -30,7 +30,9 @@ extensions = [ templates_path = ["_templates"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] -autodoc_pydantic_settings_show_field_summary = False +autodoc_default_options = { + "members": None, +} autodoc_pydantic_settings_show_json = False # -- Options for HTML output ------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index 20e61e8e..84ef3dde 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,7 +5,7 @@ Welcome to Serious Scaffold Python's documentation! :maxdepth: 2 cli/index - modules/index + api/index Indices and tables ================== diff --git a/docs/modules/index.rst b/docs/modules/index.rst deleted file mode 100644 index f8c1b9cd..00000000 --- a/docs/modules/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Modules -======= - -.. toctree:: - :maxdepth: 1 - - serious_scaffold.settings diff --git a/src/serious_scaffold/settings.py b/src/serious_scaffold/settings.py index 666d58e4..7efbc1ef 100644 --- a/src/serious_scaffold/settings.py +++ b/src/serious_scaffold/settings.py @@ -10,17 +10,19 @@ class Settings(BaseSettings): """Project specific settings.""" + logging_level: str | None = getLevelName(logging.INFO) + """Default logging level for the project.""" + model_config = SettingsConfigDict( env_prefix="SERIOUS_SCAFFOLD_", ) - logging_level: str | None = getLevelName(logging.INFO) - class GlobalSettings(BaseSettings): """System level settings.""" ci: bool = False + """Indicator for whether or not in CI/CD environment.""" #: Instance for project specific settings. diff --git a/src/{{ module_name }}/settings.py.jinja b/src/{{ module_name }}/settings.py.jinja index 4940a291..1e3ca275 100644 --- a/src/{{ module_name }}/settings.py.jinja +++ b/src/{{ module_name }}/settings.py.jinja @@ -10,17 +10,19 @@ from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """Project specific settings.""" + logging_level: str | None = getLevelName(logging.INFO) + """Default logging level for the project.""" + model_config = SettingsConfigDict( env_prefix="{{ module_name|upper }}_", ) - logging_level: str | None = getLevelName(logging.INFO) - class GlobalSettings(BaseSettings): """System level settings.""" ci: bool = False + """Indicator for whether or not in CI/CD environment.""" #: Instance for project specific settings. From d1e096439cd697c572aaf9ab160ea8bd0c7c68c0 Mon Sep 17 00:00:00 2001 From: Xuan Hu Date: Fri, 4 Aug 2023 06:49:08 +0000 Subject: [PATCH 3/6] Fix consistency. --- Makefile.jinja | 4 ++-- docs/api/index.rst.jinja | 6 +++--- docs/index.rst.jinja | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile.jinja b/Makefile.jinja index 8e42e8b7..53ddeb8a 100644 --- a/Makefile.jinja +++ b/Makefile.jinja @@ -17,7 +17,7 @@ clean: Pipfile* \ coverage.xml \ dist \ - docs/_build + public find . -name '*.egg-info' -print0 | xargs -0 rm -rf find . -name '*.pyc' -print0 | xargs -0 rm -f find . -name '*.swp' -print0 | xargs -0 rm -f @@ -77,4 +77,4 @@ docs: ${PIPRUN} python -m sphinx.cmd.build docs public docs-autobuild: - ${PIPRUN} python -m sphinx_autobuild docs public + ${PIPRUN} python -m sphinx_autobuild docs public --watch src diff --git a/docs/api/index.rst.jinja b/docs/api/index.rst.jinja index a5a3e818..aa2b6578 100644 --- a/docs/api/index.rst.jinja +++ b/docs/api/index.rst.jinja @@ -1,7 +1,7 @@ -Modules -======= +API Reference +============= .. toctree:: :maxdepth: 1 - {{ module_name }}.settings + settings diff --git a/docs/index.rst.jinja b/docs/index.rst.jinja index 64582c56..0f5643a2 100644 --- a/docs/index.rst.jinja +++ b/docs/index.rst.jinja @@ -5,7 +5,7 @@ Welcome to {{ project_name }}'s documentation! :maxdepth: 2 cli/index - modules/index + api/index Indices and tables ================== From 01b3f20a1111d1a2e841ea839411a3e10e4868a7 Mon Sep 17 00:00:00 2001 From: Xuan Hu Date: Fri, 4 Aug 2023 09:06:37 +0000 Subject: [PATCH 4/6] Try to hack the compatibility problem. --- pyproject.toml.jinja | 2 +- src/serious_scaffold/settings.py | 9 ++++++--- src/{{ module_name }}/settings.py.jinja | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/pyproject.toml.jinja b/pyproject.toml.jinja index 4071b95f..0c2a880c 100644 --- a/pyproject.toml.jinja +++ b/pyproject.toml.jinja @@ -39,7 +39,7 @@ keywords = [ ] name = "{{ package_name }}" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">={{ minimal_python_version }}" [project.scripts] {{ package_name }}-cli = "{{ module_name }}.cli:app" diff --git a/src/serious_scaffold/settings.py b/src/serious_scaffold/settings.py index 7efbc1ef..244b9f33 100644 --- a/src/serious_scaffold/settings.py +++ b/src/serious_scaffold/settings.py @@ -1,8 +1,7 @@ """Settings Module.""" -from __future__ import annotations - import logging from logging import getLevelName +from typing import Optional from pydantic_settings import BaseSettings, SettingsConfigDict @@ -10,7 +9,11 @@ class Settings(BaseSettings): """Project specific settings.""" - logging_level: str | None = getLevelName(logging.INFO) + # NOTE(huxuan): Pydantic cannot leverage future annotations at runtime prior to + # Python 3.10, so `from __future__ import annotations` cannot be used here, and the + # lint error need to be ignored unless the minimal Python version >= 3.10. + # Reference: https://github.com/pydantic/pydantic/issues/3300#issuecomment-1034007897 + logging_level: Optional[str] = getLevelName(logging.INFO) # noqa: FA100 """Default logging level for the project.""" model_config = SettingsConfigDict( diff --git a/src/{{ module_name }}/settings.py.jinja b/src/{{ module_name }}/settings.py.jinja index 1e3ca275..d548f81b 100644 --- a/src/{{ module_name }}/settings.py.jinja +++ b/src/{{ module_name }}/settings.py.jinja @@ -1,8 +1,10 @@ +{% from pathjoin("includes", "version_compare.jinja") import version_lt -%} """Settings Module.""" -from __future__ import annotations - import logging from logging import getLevelName +{%- if version_lt(minimal_python_version, "3.10") | bool %} +from typing import Optional +{%- endif %} from pydantic_settings import BaseSettings, SettingsConfigDict @@ -10,7 +12,15 @@ from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """Project specific settings.""" +{%- if version_lt(minimal_python_version, "3.10") | bool %} + # NOTE(huxuan): Pydantic cannot leverage future annotations at runtime prior to + # Python 3.10, so `from __future__ import annotations` cannot be used here, and the + # lint error need to be ignored unless the minimal Python version >= 3.10. + # Reference: https://github.com/pydantic/pydantic/issues/3300#issuecomment-1034007897 + logging_level: Optional[str] = getLevelName(logging.INFO) # noqa: FA100 +{%- else %} logging_level: str | None = getLevelName(logging.INFO) +{%- endif %} """Default logging level for the project.""" model_config = SettingsConfigDict( From 0764fe3878291da54b6b68502c821b6d7c52ff89 Mon Sep 17 00:00:00 2001 From: Xuan Hu Date: Fri, 4 Aug 2023 09:14:01 +0000 Subject: [PATCH 5/6] Order. --- src/serious_scaffold/settings.py | 21 +++++++++++++-------- src/{{ module_name }}/settings.py.jinja | 21 +++++++++++++-------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/serious_scaffold/settings.py b/src/serious_scaffold/settings.py index 244b9f33..5dfb7eff 100644 --- a/src/serious_scaffold/settings.py +++ b/src/serious_scaffold/settings.py @@ -6,6 +6,13 @@ from pydantic_settings import BaseSettings, SettingsConfigDict +class GlobalSettings(BaseSettings): + """System level settings.""" + + ci: bool = False + """Indicator for whether or not in CI/CD environment.""" + + class Settings(BaseSettings): """Project specific settings.""" @@ -21,15 +28,13 @@ class Settings(BaseSettings): ) -class GlobalSettings(BaseSettings): - """System level settings.""" - - ci: bool = False - """Indicator for whether or not in CI/CD environment.""" +# NOTE(huxuan): `#:` style docstring is required for module attributes to satisfy both +# autodoc [1] and `check-docstring-first` in `pre-commit` [2]. +# [1] https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autoattribute +# [2] https://github.com/pre-commit/pre-commit-hooks/issues/159#issuecomment-559886109 +#: Instance for system level settings. +global_settings = GlobalSettings() #: Instance for project specific settings. settings = Settings() - -#: Instance for system level settings. -global_settings = GlobalSettings() diff --git a/src/{{ module_name }}/settings.py.jinja b/src/{{ module_name }}/settings.py.jinja index d548f81b..e64fade3 100644 --- a/src/{{ module_name }}/settings.py.jinja +++ b/src/{{ module_name }}/settings.py.jinja @@ -9,6 +9,13 @@ from typing import Optional from pydantic_settings import BaseSettings, SettingsConfigDict +class GlobalSettings(BaseSettings): + """System level settings.""" + + ci: bool = False + """Indicator for whether or not in CI/CD environment.""" + + class Settings(BaseSettings): """Project specific settings.""" @@ -28,15 +35,13 @@ class Settings(BaseSettings): ) -class GlobalSettings(BaseSettings): - """System level settings.""" - - ci: bool = False - """Indicator for whether or not in CI/CD environment.""" +# NOTE(huxuan): `#:` style docstring is required for module attributes to satisfy both +# autodoc [1] and `check-docstring-first` in `pre-commit` [2]. +# [1] https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autoattribute +# [2] https://github.com/pre-commit/pre-commit-hooks/issues/159#issuecomment-559886109 +#: Instance for system level settings. +global_settings = GlobalSettings() #: Instance for project specific settings. settings = Settings() - -#: Instance for system level settings. -global_settings = GlobalSettings() From 73a61b2c0dce09db5e66962cdebe3c623e65d01c Mon Sep 17 00:00:00 2001 From: Xuan Hu Date: Fri, 4 Aug 2023 09:34:56 +0000 Subject: [PATCH 6/6] Add pydantic v2 badge. --- README.md | 1 + README.md.jinja | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 753ec2a3..481b5552 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ A development-focused Python project template with various integrations, configu [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) +[![Pydantic v2](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/5697b1e4c4a9790ece607654e6c02a160620c7e1/docs/badge/v2.json)](https://pydantic.dev) [![Serious Scaffold Python](https://img.shields.io/badge/serious%20scaffold-python-blue)](https://github.com/serious-scaffold/serious-scaffold-python) [![PyPI](https://img.shields.io/pypi/v/serious-scaffold)](https://pypi.org/project/serious-scaffold/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/serious-scaffold)](https://pypi.org/project/serious-scaffold/) diff --git a/README.md.jinja b/README.md.jinja index e4a4e971..92883a6f 100644 --- a/README.md.jinja +++ b/README.md.jinja @@ -10,6 +10,7 @@ [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) +[![Pydantic v2](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/5697b1e4c4a9790ece607654e6c02a160620c7e1/docs/badge/v2.json)](https://pydantic.dev) [![Serious Scaffold Python](https://img.shields.io/badge/serious%20scaffold-python-blue)](https://github.com/serious-scaffold/serious-scaffold-python) [![PyPI](https://img.shields.io/pypi/v/{{ package_name }})](https://pypi.org/project/{{ package_name }}/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/{{ package_name }})](https://pypi.org/project/{{ package_name }}/)