diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..1347041 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,38 @@ +name: Publish to PyPI + +on: + release: + types: + - published + +permissions: + contents: read + +env: + PYTHON_VERSION: 3.11 + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install hatch + run: | + python -m pip install hatch + + - name: Build package + run: hatch build + + - name: Publish + run: hatch build + env: + HATCH_INDEX_USER: ${{ vars.HATCH_INDEX_USER }} + HATCH_INDEX_AUTH: ${{ secrets.HATCH_INDEX_AUTH }} diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 04cc3dd..ee0c294 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -10,9 +10,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -env: - POETRY_VERSION: 1.7.1 - jobs: testing: runs-on: ubuntu-latest @@ -28,35 +25,15 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - - name: Load cached Poetry installation - id: poetry - uses: actions/cache@v3 - with: - path: ~/.local - key: poetry-install-${{env.POETRY_VERSION}}-${{ runner.os }}-${{ matrix.python-version }} - - - name: Install and configure Poetry - uses: snok/install-poetry@v1 - if: steps.poetry.outputs.cache-hit != 'true' - with: - version: ${{env.POETRY_VERSION}} - virtualenvs-create: true - virtualenvs-in-project: false - - - name: Cache Poetry cache - uses: actions/cache@v3 - id: cache - with: - path: ~/.cache/pypoetry - key: poetry-cache-${{env.POETRY_VERSION}}-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }} - - - name: Install dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: poetry install -v --all-extras + + - name: Install hatch + run: | + python -m pip install hatch - name: Run pytest - run: poetry run pytest --cov=./ --cov-report=xml + run: | + hatch build --hooks-only + hatch run +py=${{ matrix.python-version }} testing:test --cov=./ --cov-report=xml - name: Coverage report uses: codecov/codecov-action@v1 diff --git a/.gitignore b/.gitignore index 6a0cfe6..f729d30 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,3 @@ cover .mypy_cache .idea .vscode -poetry.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index c7f5f33..25d8819 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- Use the standard lib for `toml` in Python >= 3.11 +- Switched to `hatch` instead of `poetry` ## [0.9.1] - 2023-08-06 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fdf9e11..733020f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to python-configuration -:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: +First off, thanks for taking the time to contribute! ## Setting up a dev environment @@ -12,14 +12,23 @@ cd python-configuration ``` -1. Use [`poetry`](https://python-poetry.org/docs/) to install the dependencies: +1. Use [`hatch`](https://hatch.pypa.io/) to generate a version file and install the dependencies - ```shell - poetry install - ``` +```shell +hatch build --hooks-only # generate a version file from the git commit +# or +hatch build +``` ### Running the tests +To run the tests (which include linting and type checks), run: +```shell +hatch run test:test +``` + +Before opening a PR, make sure to run ```shell -poetry run pytest +hatch run testing:test ``` +which executes the previous test command on all Python versions supported by the library. diff --git a/LICENSE b/LICENSE index 7f523d0..f7c0605 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Tiago Requeijo +Copyright (c) 2024 Tiago Requeijo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 4895120..91ba16b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # python-configuration > A library to load configuration parameters hierarchically from multiple sources and formats +[![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch) [![version](https://img.shields.io/pypi/v/python-configuration)](https://pypi.org/project/python-configuration/) ![python](https://img.shields.io/pypi/pyversions/python-configuration) ![wheel](https://img.shields.io/pypi/wheel/python-configuration) @@ -15,20 +16,20 @@ This library is intended as a helper mechanism to load configuration files hiera The `python-configuration` library supports the following configuration formats and sources: -- Python files: ... -- Dictionaries: ... -- Environment variables: ... -- Filesystem paths: ... -- JSON files: ... -- INI files: ... -- dotenv type files: ... +- Python files +- Dictionaries +- Environment variables +- Filesystem paths +- JSON files +- INI files +- dotenv type files - Optional support for: - YAML files: requires `yaml` - - TOML files: requires `toml` - - Azure Key Vault credentials: ... - - AWS Secrets Manager credentials: ... - - GCP Secret Manager credentials: ... - - Hashicorp Vault credentials: ... + - TOML files: requires `tomli` for Python < 3.11 + - Azure Key Vault credentials: requires `azure-keyvault` + - AWS Secrets Manager credentials: requires `boto3` + - GCP Secret Manager credentials: requires `google-cloud-secret-manager` + - Hashicorp Vault credentials: requires `hvac` ## Installing @@ -45,8 +46,9 @@ To include the optional TOML and/or YAML loaders, install the optional dependenc pip install python-configuration[toml,yaml] ``` -Without the optional dependencies, the TOML and YAML loaders will not be available, +Without the optional dependencies, the TOML (Python < 3.11) and YAML loaders will not be available, and attempting to use them will raise an exception. + ## Getting started `python-configuration` converts the various config types into dictionaries with dotted-based keys. For example, given this JSON configuration @@ -381,7 +383,7 @@ The `config.contrib` package contains extra implementations of the `Configuratio * Ability to override with environment variables * Merge parameters from different configuration types -## Contributing :tada: +## Contributing If you'd like to contribute, please fork the repository and use a feature branch. Pull requests are welcome. diff --git a/pyproject.toml b/pyproject.toml index d3afd77..0535ad6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,52 +1,101 @@ -[tool.poetry] -authors = ["Tiago Requeijo "] -description = "A library to load configuration parameters from multiple sources and formats" -homepage = "https://github.com/tr11/python-configuration" -include = ["config/py.typed"] -keywords = ['configuration', 'settings', 'json', 'yaml', 'toml', 'ini'] -license = "MIT" +[project] name = "python-configuration" -packages = [{ include = "config" }] +dynamic = ["version"] +description = "A library to load configuration parameters from multiple sources and formats" readme = 'README.md' -repository = "https://github.com/tr11/python-configuration" -version = "0.9.1" - -[tool.poetry.dependencies] -python = "^3.8.1" -azure-identity = { version = "^1.13.0", optional = true } -azure-keyvault = { version = "^4.2.0", optional = true } -boto3 = { version = "^1.28.20", optional = true } -google-cloud-secret-manager = { version = "^2.16.3", optional = true } -hvac = { version = "^1.1.1", optional = true } -pyyaml = { version = "^6.0", optional = true } -jsonschema = { version = "^4.18.6", optional = true } -tomli = { version = "^2.0.1", optional = true } - -[tool.poetry.group.dev.dependencies] -mypy = "^1.4.1" -pydocstyle = "^6.0" -pytest = "^7.4.0" -pytest-black = "^0.3.12" -pytest-cov = "^4.1.0" -pytest-mypy = "^0.10.3" -pytest-mock = "^3.5.0" -sphinx = "^7.1.2" -sphinx-autodoc-typehints = "^1.24.0" -black = "^23.7.0" -ruff = "^0.0.284" -pytest-ruff = "^0.2.1" - -[tool.poetry.extras] -aws = ["boto3"] -azure = ["azure-keyvault", "azure-identity"] -gcp = ["google-cloud-secret-manager"] -toml = ["tomli"] -vault = ["hvac"] -yaml = ["pyyaml"] -validation = ["jsonschema"] +requires-python = ">=3.8" +keywords = ['configuration', 'settings', 'json', 'yaml', 'toml', 'ini'] +license = { text = "MIT" } +authors = [{ name = "Tiago Requeijo", email = "tiago.requeijo.dev@gmail.com" }] +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dependencies = [] + +[project.optional-dependencies] +# cloud +aws = ["boto3>=1.28.20"] +azure = ["azure-keyvault>=5.0.0"] +gcp = ["google-cloud-secret-manager>=2.16.3"] +vault = ["hvac>=1.1.1"] +# file formats +toml = ["tomli>=2.0.1"] +yaml = ["pyyaml>=6.0"] +# utilities +validation = ["jsonschema>=4.18.6"] +# groups +cloud = ["python-configuration[aws,azure,gcp,vault]"] +file-formats = ["python-configuration[toml,yaml]"] + +[project.urls] +Documentation = "https://github.com/tr11/python-configuration" +Issues = "https://github.com/tr11/python-configuration/issues" +Source = "https://github.com/tr11/python-configuration" + +[tool.hatch.version] +source = "vcs" + +[tool.hatch.build.hooks.vcs] +version-file = "src/config/_version.py" + +[tool.hatch.build.targets.wheel] +packages = ["src/config"] + +[tool.hatch.publish.index] +disable = true + +[tool.hatch.envs.default] + +[tool.hatch.envs.docs] +dependencies = ["sphinx>=7.1.2", "sphinx-autodoc-typehints>=1.24.0"] +scripts = { build = "" } + +[tool.hatch.envs.lint] +detached = true +dependencies = ["mypy>=1.4.1", "pydocstyle>=6.0", "ruff>=0.0.284"] +features = ["cloud", "file-formats", "validation"] + +[tool.hatch.envs.lint.scripts] +lint = "ruff src" +typing = "mypy src" +docs = "pydocstyle src" + +[tool.hatch.envs.test] +template = "lint" +dependencies = [ + "pytest-cov>=4.1.0", + "pytest-mock>=3.5.0", + "pytest-mypy>=0.10.3", + "pytest-ruff>=0.2.1", + "pytest>=7.4.0", +] + +[tool.hatch.envs.test.scripts] +test = "pytest {args:tests}" + +[tool.hatch.envs.testing] +description = "Testing Environment to run all\nthe tests across different\nPython versions." +template = "test" + +[[tool.hatch.envs.testing.matrix]] +python = ["3.8", "3.9", "3.10", "3.11", "3.12"] + +[tool.hatch.envs.dev] +template = "test" +path = ".venv" +features = ["cloud", "file-formats", "validation"] [tool.ruff] line-length = 88 +exclude = ["tests", "docs", "src/config/_version.py"] +ignore = ['D203', 'D213'] select = [ 'F', 'E', @@ -62,21 +111,7 @@ select = [ 'Q', 'SIM', ] -exclude = ["tests", "docs"] - - -[tool.tox] -legacy_tox_ini = """ -[tox] -isolated_build = true -envlist = py38, py39, py310, py311, py312 -[testenv] -allowlist_externals = poetry -commands = - poetry install -v --all-extras - poetry run pytest -""" [tool.mypy] warn_return_any = true @@ -108,14 +143,16 @@ ignore_missing_imports = true [tool.coverage.run] branch = true -include = ['config/*'] +include = ['src/*'] +omit = ['src/config/_version.py'] [tool.coverage.html] directory = 'cover' [tool.pytest.ini_options] minversion = "6.0" -addopts = '--cov --cov-report=html --cov-report term-missing --ruff --mypy --black' +pythonpath = ["src"] +addopts = '--cov --cov-report=html --cov-report term-missing --ruff --mypy' filterwarnings = [ 'ignore::pytest.PytestDeprecationWarning', 'ignore::DeprecationWarning', @@ -123,5 +160,5 @@ filterwarnings = [ ] [build-system] -build-backend = "poetry.masonry.api" -requires = ["poetry>=1.5.0"] +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" diff --git a/src/config/.gitignore b/src/config/.gitignore new file mode 100644 index 0000000..1dea0bd --- /dev/null +++ b/src/config/.gitignore @@ -0,0 +1,2 @@ +_version.py +__pycache__ \ No newline at end of file diff --git a/config/__init__.py b/src/config/__init__.py similarity index 99% rename from config/__init__.py rename to src/config/__init__.py index 23b2dd3..fa03152 100644 --- a/config/__init__.py +++ b/src/config/__init__.py @@ -22,6 +22,7 @@ import tomllib as toml +from ._version import __version__, __version_tuple__ # noqa: F401 from .configuration import Configuration from .configuration_set import ConfigurationSet from .helpers import InterpolateEnumType, InterpolateType diff --git a/config/configuration.py b/src/config/configuration.py similarity index 100% rename from config/configuration.py rename to src/config/configuration.py diff --git a/config/configuration_set.py b/src/config/configuration_set.py similarity index 100% rename from config/configuration_set.py rename to src/config/configuration_set.py diff --git a/config/contrib/__init__.py b/src/config/contrib/__init__.py similarity index 100% rename from config/contrib/__init__.py rename to src/config/contrib/__init__.py diff --git a/config/contrib/aws.py b/src/config/contrib/aws.py similarity index 100% rename from config/contrib/aws.py rename to src/config/contrib/aws.py diff --git a/config/contrib/azure.py b/src/config/contrib/azure.py similarity index 100% rename from config/contrib/azure.py rename to src/config/contrib/azure.py diff --git a/config/contrib/gcp.py b/src/config/contrib/gcp.py similarity index 100% rename from config/contrib/gcp.py rename to src/config/contrib/gcp.py diff --git a/config/contrib/vault.py b/src/config/contrib/vault.py similarity index 100% rename from config/contrib/vault.py rename to src/config/contrib/vault.py diff --git a/config/helpers.py b/src/config/helpers.py similarity index 100% rename from config/helpers.py rename to src/config/helpers.py diff --git a/config/py.typed b/src/config/py.typed similarity index 100% rename from config/py.typed rename to src/config/py.typed diff --git a/tests/test_basic.py b/tests/test_basic.py index 9b4fc4c..eab1c30 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -34,6 +34,13 @@ } +def test_version_is_defined(): # type: ignore + from config import __version__, __version_tuple__ + + assert isinstance(__version__, str) + assert isinstance(__version_tuple__, tuple) + + def test_load_dict(): # type: ignore cfg = config_from_dict(DICT, lowercase_keys=True) assert cfg["a1.b1.c1"] == 1