diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 7d38642f..76acb5a5 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,56 +1,98 @@
name: Python Tests
on:
- push:
+ push:
branches:
- '*'
jobs:
- test:
+ feature-branch-checks:
+ # these checks do not run this on master itself
+ if: github.ref != 'refs/heads/master'
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # we need history to diff properly
+
+ - &install-task # define a YAML anchor to re-use this task in other jobs
+ name: Install task to make use of Taskfile.yml
+ run: |
+ sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
+ task --version
+
+ - name: Verify CHANGELOG.md was updated
+ run: |
+ task check-changelog
+
+ - name: Verify that the package version was updated. This is needed because master is automatically deployed to pypi.org
+ run: |
+ task check-version
+
+# TODO
+# - name: Run code linter
+# run: |
+# pip install ruff
+# task lint
+
+ test:
+ needs: feature-branch-checks
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.11, 3.12, 3.13, 3.14]
+
steps:
- - uses: actions/checkout@v4
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python-version }}
- check-latest: true
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install -U coveralls
- pip install -e .[dev]
- - name: Run tests with pytest
- run: |
- pytest --doctest-modules --cov=pedantic --cov-branch --cov-report= --cov-report=term
- - name: Coveralls
- uses: coverallsapp/github-action@v2.3.6
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- file: .coverage
- deploy:
- needs: test
- runs-on: ubuntu-latest
- if: github.ref == 'refs/heads/master'
- environment:
- name: pypi-deployment
- steps:
- - uses: actions/checkout@v4
- - name: Set up Python
- uses: actions/setup-python@v5
- with:
- python-version: 3.12
- - name: Install Build Tools
- run: |
- pip install -U build twine
- - name: Set Twine Environment Variables
- run: |
- echo "TWINE_USERNAME=${{ secrets.PYPI_USERNAME }}" >> $GITHUB_ENV
- echo "TWINE_PASSWORD=${{ secrets.PYPI_PASSWORD }}" >> $GITHUB_ENV
- - name: Build and Upload to PyPI
- run: |
- python -m build
- twine upload dist/*
\ No newline at end of file
+ - uses: actions/checkout@v4
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+ check-latest: true
+
+ - *install-task # see above
+
+ - name: Install dependencies
+ run: |
+ task dependencies-with-pip
+ pip install -U coveralls
+
+ - name: Run tests with pytest
+ run: |
+ task tests-with-cov
+
+ - name: Coveralls
+ uses: coverallsapp/github-action@v2.3.6
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ file: .coverage
+
+ deploy:
+ needs: test
+ runs-on: ubuntu-latest
+ if: github.ref == 'refs/heads/master'
+ environment:
+ name: pypi-deployment
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: 3.12
+
+ - name: Install Build Tools
+ run: |
+ pip install -U build twine
+
+ - name: Set Twine Environment Variables
+ run: |
+ echo "TWINE_USERNAME=${{ secrets.PYPI_USERNAME }}" >> $GITHUB_ENV
+ echo "TWINE_PASSWORD=${{ secrets.PYPI_PASSWORD }}" >> $GITHUB_ENV
+
+ - name: Build and Upload to PyPI
+ run: |
+ python -m build
+ twine upload dist/*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0851406c..057e1df6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,23 @@
# Changelog
+## Pedantic 3.0.0
+- removed decorator `@count_calls`
+- removed decorator `@does_same_as_function`
+- removed decorator `@mock`
+- removed decorator `@rename_kwargs`
+- removed decorator `@require_kwargs`
+- removed decorator `@timer`
+- removed decorator `@timer_class`
+- removed decorator `@uminplemented`
+- removed decorator `@trace_if_returns`
+- removed `GenericMixin.type_var`. Use `GenericMixin.type_vars` instead.
+- added `Taskfile.yml` and use it in CI
+- removed `create_pdoc.sh`
+- moved `examples` out of the package
+- CI: added a check that `CHANGELOG.md` is modified on feature branches
+- CI: added a check that the poetry package version was updated
+- added `ruff` linter and apply lint rules to code
+- updated `README.md`
+
## Pedantic 2.4.0
- migrate from unittest to pytest
- exclude tests from package deployment
diff --git a/README.md b/README.md
index 41957d46..a62e428f 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,7 @@
-# pedantic-python-decorators [](https://travis-ci.com/LostInDarkMath/pedantic-python-decorators) [](https://coveralls.io/github/LostInDarkMath/pedantic-python-decorators?branch=master) [](https://badge.fury.io/py/pedantic) [](https://anaconda.org/conda-forge/pedantic) [](https://GitHub.com/LostInDarkMath/pedantic-python-decorators) [](https://GitHub.com/LostInDarkMath/pedantic-python-decorators) [](https://GitHub.com/LostInDarkMath/pedantic-python-decorators/issues) [](https://GitHub.com/LostInDarkMath/pedantic-python-decorators/pulls)
+# pedantic-python-decorators [](https://coveralls.io/github/LostInDarkMath/pedantic-python-decorators?branch=master) [](https://badge.fury.io/py/pedantic) [](https://anaconda.org/conda-forge/pedantic) [](https://GitHub.com/LostInDarkMath/pedantic-python-decorators) [](https://GitHub.com/LostInDarkMath/pedantic-python-decorators) [](https://GitHub.com/LostInDarkMath/pedantic-python-decorators/issues) [](https://GitHub.com/LostInDarkMath/pedantic-python-decorators/pulls)
This packages includes many decorators that will make you write cleaner Python code.
-## Getting Started
-This package requires Python 3.11 or later.
-There are multiple options for installing this package.
-
-### Option 1: Installing with pip from [Pypi](https://pypi.org/)
-Run `pip install pedantic`.
-
-### Option 2: Installing with conda from [conda-forge](conda-forge.org)
-Run `conda install -c conda-forge pedantic`
-
-### Option 3: Installing with pip and git
-1. Install [Git](https://git-scm.com/downloads) if you don't have it already.
-2. Run `pip install git+https://github.com/LostInDarkMath/pedantic-python-decorators.git@master`
-
-### Option 4: Offline installation using wheel
-1. Download the [latest release here](https://github.com/LostInDarkMath/PythonHelpers/releases/latest) by clicking on `pedantic-python-decorators-x.y.z-py-none-any.whl`.
-2. Execute `pip install pedantic-python-decorators-x.y.z-py3-none-any.whl`.
-
## The [@pedantic](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/method_decorators.html#pedantic.method_decorators.pedantic) decorator - Type checking at runtime
The `@pedantic` decorator does the following things:
- The decorated function can only be called by using keyword arguments. Positional arguments are not accepted.
@@ -50,120 +32,21 @@ get_sum_of(values=[0, 1.2, 3, 5.4]) # this raises the following runtime error:
## The [@validate]() decorator
As the name suggests, with `@validate` you are able to validate the values that are passed to the decorated function.
-That is done in a highly customizable way.
-But the highest benefit of this decorator is that it makes it extremely easy to write decoupled easy testable, maintainable and scalable code.
-The following example shows the decoupled implementation of a configurable algorithm with the help of `@validate`:
-```python
-import os
-from dataclasses import dataclass
-
-from pedantic import validate, ExternalParameter, overrides, Validator, Parameter, Min, ReturnAs
-
-
-@dataclass(frozen=True)
-class Configuration:
- iterations: int
- max_error: float
-
-
-class ConfigurationValidator(Validator):
- @overrides(Validator)
- def validate(self, value: Configuration) -> Configuration:
- if value.iterations < 1 or value.max_error < 0:
- self.raise_exception(msg=f'Invalid configuration: {value}', value=value)
-
- return value
-
-
-class ConfigFromEnvVar(ExternalParameter):
- """ Reads the configuration from environment variables. """
-
- @overrides(ExternalParameter)
- def has_value(self) -> bool:
- return 'iterations' in os.environ and 'max_error' in os.environ
-
- @overrides(ExternalParameter)
- def load_value(self) -> Configuration:
- return Configuration(
- iterations=int(os.environ['iterations']),
- max_error=float(os.environ['max_error']),
- )
-
-
-class ConfigFromFile(ExternalParameter):
- """ Reads the configuration from a config file. """
-
- @overrides(ExternalParameter)
- def has_value(self) -> bool:
- return os.path.isfile('config.csv')
-
- @overrides(ExternalParameter)
- def load_value(self) -> Configuration:
- with open(file='config.csv', mode='r') as file:
- content = file.readlines()
- return Configuration(
- iterations=int(content[0].strip('\n')),
- max_error=float(content[1]),
- )
-
-
-# choose your configuration source here:
-@validate(ConfigFromEnvVar(name='config', validators=[ConfigurationValidator()]), strict=False, return_as=ReturnAs.KWARGS_WITH_NONE)
-# @validate(ConfigFromFile(name='config', validators=[ConfigurationValidator()]), strict=False)
-
-# with strict_mode = True (which is the default)
-# you need to pass a Parameter for each parameter of the decorated function
-# @validate(
-# Parameter(name='value', validators=[Min(5, include_boundary=False)]),
-# ConfigFromFile(name='config', validators=[ConfigurationValidator()]),
-# )
-def my_algorithm(value: float, config: Configuration) -> float:
- """
- This method calculates something that depends on the given value with considering the configuration.
- Note how well this small piece of code is designed:
- - Fhe function my_algorithm() need a Configuration but has no knowledge where this come from.
- - Furthermore, it doesn't care about parameter validation.
- - The ConfigurationValidator doesn't know anything about the creation of the data.
- - The @validate decorator is the only you need to change, if you want a different configuration source.
- """
- print(value)
- print(config)
- return value
-
-
-if __name__ == '__main__':
- # we can call the function with a config like there is no decorator.
- # This makes testing extremely easy: no config files, no environment variables or stuff like that
- print(my_algorithm(value=2, config=Configuration(iterations=3, max_error=4.4)))
-
- os.environ['iterations'] = '12'
- os.environ['max_error'] = '3.1415'
-
- # but we also can omit the config and load it implicitly by our custom Parameters
- print(my_algorithm(value=42.0))
-```
## List of all decorators in this package
-- [@count_calls](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_count_calls.html)
- [@deprecated](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_deprecated.html)
-- [@does_same_as_function](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_does_same_as_function.html)
- [@frozen_dataclass](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/cls_deco_frozen_dataclass.html#pedantic.decorators.cls_deco_frozen_dataclass.frozen_dataclass)
- [@frozen_type_safe_dataclass](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/cls_deco_frozen_dataclass.html#pedantic.decorators.cls_deco_frozen_dataclass.frozen_type_safe_dataclass)
- [@for_all_methods](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/class_decorators.html#pedantic.decorators.class_decorators.for_all_methods)
- [@in_subprocess](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_in_subprocess.html)
-- [@mock](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_mock.html)
- [@overrides](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_overrides.html)
- [@pedantic](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_pedantic.html#pedantic.decorators.fn_deco_pedantic.pedantic)
- [@pedantic_class](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/class_decorators.html#pedantic.decorators.class_decorators.pedantic_class)
-- [@rename_kwargs](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_rename_kwargs.html)
- [@require_kwargs](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_require_kwargs.html)
- [@retry](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_retry.html)
-- [@timer](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_timer.html)
-- [@timer_class](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/class_decorators.html#pedantic.decorators.class_decorators.timer_class)
- [@trace](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_trace.html)
- [@trace_class](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/class_decorators.html#pedantic.decorators.class_decorators.trace_class)
- [@trace_if_returns](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_trace_if_returns.html)
-- [@unimplemented](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_unimplemented.html)
- [@validate](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic/decorators/fn_deco_validate/fn_deco_validate.html)
## List of all mixins in this package
@@ -183,12 +66,8 @@ There are no hard dependencies. But if you want to use some advanced features yo
- `GenericFlaskDeserializer`
## Contributing
-Feel free to contribute by submitting a pull request :)
-
-## Acknowledgments
-* [Rathaustreppe](https://github.com/rathaustreppe)
-* [Aran-Fey](https://stackoverflow.com/questions/55503673/how-do-i-check-if-a-value-matches-a-type-in-python/55504010#55504010)
-* [user395760](https://stackoverflow.com/questions/55503673/how-do-i-check-if-a-value-matches-a-type-in-python/55504010#55504010)
+This project is based on [poetry](https://python-poetry.org/) and [taskfile](https://taskfile.dev).
+**Tip:** Run `task validate` before making commits.
## Risks and side effects
The usage of decorators may affect the performance of your application.
@@ -207,6 +86,4 @@ enable_pedantic()
This package is **not** compatible with compiled source code (e.g. with [Nuitka](https://github.com/Nuitka/Nuitka)).
That's because it uses the `inspect` module from the standard library which will raise errors like `OSError: could not get source code` in case of compiled source code.
-
Don't forget to check out the [documentation](https://lostindarkmath.github.io/pedantic-python-decorators/pedantic).
-Happy coding!
diff --git a/Taskfile.yml b/Taskfile.yml
new file mode 100644
index 00000000..7de0acf5
--- /dev/null
+++ b/Taskfile.yml
@@ -0,0 +1,134 @@
+# yaml-language-server: $schema=https://taskfile.dev/schema.json
+
+version: '3'
+
+tasks:
+ dependencies:
+ desc: Updates the poetry.lock file and installs all dependencies
+ cmds:
+ - pip install --upgrade pip
+ - poetry lock
+ - poetry install --extras dev
+ silent: true
+
+ dependencies-with-pip:
+ desc: Installs all dependencies using pip only
+ cmds:
+ - pip install --upgrade pip
+ - pip install .[dev]
+ silent: true
+
+ tests:
+ desc: Runs the tests
+ cmds:
+ - pytest --doctest-modules
+
+ tests-with-cov:
+ desc: Runs the tests with coverage
+ cmds:
+ - pytest --doctest-modules --cov=pedantic --cov-branch --cov-report= --cov-report=term
+ silent: true
+
+ lint:
+ desc: Runs the linter (ruff)
+ silent: true
+ cmds:
+ - ruff check pedantic
+
+ lint-fix:
+ desc: Runs the linter and fixes all fixable errors automatically
+ silent: true
+ cmds:
+ - ruff check pedantic --fix
+
+ validate:
+ desc: Runs all checks and updates the docu. It is recommended to do this before making a commit.
+ cmds:
+ - task: check-changelog
+ - task: check-version
+ - task: tests
+ - task: lint
+ - task: generate-docs
+
+ docs:
+ desc: Creates the HTML documentation and opens is Chrome
+ cmds:
+ - task: generate-docs
+ - google-chrome ./docs/index.html
+ silent: true
+
+ generate-docs:
+ desc: Creates the HTML documentation
+ cmds:
+ - rm -rf ./docs
+ - pip install pdoc
+ - pdoc -o docs pedantic
+ silent: true
+
+ check-changelog:
+ desc: Fails if CHANGELOG.md was not modified compared to master
+ silent: true
+ cmds:
+ - |
+ set -e
+
+ echo "Checking that CHANGELOG.md was updated..."
+
+ # Ensure we have master available (works both locally and in CI)
+ if git show-ref --verify --quiet refs/remotes/origin/master; then
+ BASE=origin/master
+ elif git show-ref --verify --quiet refs/heads/master; then
+ BASE=master
+ else
+ echo "❌ Could not find master branch to diff against."
+ exit 1
+ fi
+
+ # Fetch latest master if we're in a repo with a remote
+ git fetch origin master >/dev/null 2>&1 || true
+
+ if git diff --name-only "$BASE"...HEAD | grep -qx "CHANGELOG.md"; then
+ echo "✅ CHANGELOG.md was updated."
+ else
+ echo "❌ CHANGELOG.md was NOT updated."
+ echo ""
+ echo "Please add an entry describing your change."
+ exit 1
+ fi
+
+ check-version:
+ desc: Fails if the version in pyproject.toml was not bumped compared to master
+ silent: true
+ cmds:
+ - |
+ set -e
+
+ echo "Checking that project version was updated..."
+
+ # Determine comparison base (works locally and in CI)
+ if git show-ref --verify --quiet refs/remotes/origin/master; then
+ BASE=origin/master
+ elif git show-ref --verify --quiet refs/heads/master; then
+ BASE=master
+ else
+ echo "❌ Could not find master branch to diff against."
+ exit 1
+ fi
+
+ # Try to fetch latest master (no-op if no remote available)
+ git fetch origin master >/dev/null 2>&1 || true
+
+ # Extract versions
+ CURRENT_VERSION=$(grep -E '^version\s*=' pyproject.toml | head -1 | sed -E 's/.*"(.*)".*/\1/')
+ BASE_VERSION=$(git show "$BASE:pyproject.toml" | grep -E '^version\s*=' | head -1 | sed -E 's/.*"(.*)".*/\1/')
+
+ echo "Base version: $BASE_VERSION"
+ echo "Current version: $CURRENT_VERSION"
+
+ if [ "$CURRENT_VERSION" = "$BASE_VERSION" ]; then
+ echo "❌ Version was not updated."
+ echo "Please bump the version in pyproject.toml."
+ exit 1
+ fi
+
+ echo "✅ Version was updated."
diff --git a/create_pdoc.sh b/create_pdoc.sh
deleted file mode 100755
index 0d883f02..00000000
--- a/create_pdoc.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-pip3 install pdoc3
-pdoc3 --html --output-dir docs pedantic --force
-"docs/pedantic/index.html"
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 00000000..3bec2c37
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/docs/pedantic.html b/docs/pedantic.html
new file mode 100644
index 00000000..f42a2ae1
--- /dev/null
+++ b/docs/pedantic.html
@@ -0,0 +1,261 @@
+
+
+
+
+
+
+ pedantic API documentation
+
+
+
+
+
+
+
+
+
+
+
+
+pedantic
+
+
+
+
+
+
+ 1from pedantic.decorators import overrides, trace, trace_if_returns, \
+ 2 deprecated, require_kwargs, pedantic, \
+ 3 pedantic_require_docstring, for_all_methods, trace_class, pedantic_class, \
+ 4 pedantic_class_require_docstring, frozen_dataclass, frozen_type_safe_dataclass, in_subprocess, \
+ 5 calculate_in_subprocess, retry
+ 6
+ 7from pedantic.mixins import GenericMixin, create_decorator, DecoratorType, WithDecoratedMethods
+ 8
+ 9from pedantic.type_checking_logic import assert_value_matches_type, resolve_forward_ref
+10
+11from pedantic.exceptions import NotImplementedException
+12
+13from pedantic.env_var_logic import disable_pedantic, enable_pedantic, is_enabled
+14
+15from pedantic.decorators.fn_deco_validate.fn_deco_validate import validate, ReturnAs
+16from pedantic.decorators.fn_deco_validate.exceptions import *
+17from pedantic.decorators.fn_deco_validate.parameters import *
+18from pedantic.decorators.fn_deco_validate.validators import *
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/pedantic/constants.html b/docs/pedantic/constants.html
index 34974f39..6874302e 100644
--- a/docs/pedantic/constants.html
+++ b/docs/pedantic/constants.html
@@ -1,66 +1,399 @@
-
-
-
-pedantic.constants API documentation
-
-
-
-
-
-
-
-
-
-
+
+
+
+ pedantic.constants API documentation
+
+
+
+
+
+
+
-
-
-
-Module pedantic.constants
-
-
-
-
-
-
-
-