diff --git a/.cruft.json b/.cruft.json new file mode 100644 index 0000000..301ccf8 --- /dev/null +++ b/.cruft.json @@ -0,0 +1,18 @@ +{ + "template": "https://github.com/reef-technologies/cookiecutter-rt-pkg", + "commit": "9098eed1a7c4e44e568f990cb111e9ccb6bf78ca", + "checkout": null, + "context": { + "cookiecutter": { + "package_name": "django_business_metrics", + "repository_github_url": "https://github.com/reef-technologies/django-business-metrics", + "is_django_package": "y", + "_jinja2_env_vars": { + "block_start_string": "# COOKIECUTTER{%", + "block_end_string": "%}" + }, + "_template": "https://github.com/reef-technologies/cookiecutter-rt-pkg" + } + }, + "directory": null +} diff --git a/.gitignore b/.gitignore index 714ac0d..d9603cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,140 +1,16 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ +*.pyc +*.sqlite3 +*~ *.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ -docs/site -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments +/.idea/ .env .venv -env/ -/venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ -.vscode/ -caches/ -.idea/ -__pypackages__ -.pdm.toml +venv +media/ +.backups/ +.envrc .pdm-python -temp.py - -# Pyannotate generated stubs -type_info.json -.pdm-build/ +.terraform.lock.hcl +.terraform/ +.nox/ +__pycache__ diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 0000000..f95210b --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1,2 @@ +# disable errors related to cookiecutter templating: +disable=SC1054,SC1056,SC1072,SC1073,SC1083,SC1009 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a67fc7..25a96b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the changes for the +This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the changes for the upcoming release can be found in [changelog.d](changelog.d). diff --git a/README.md b/README.md index 7922e90..622b560 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Django Prometheus business metrics + [![Continuous Integration](https://github.com/reef-technologies/django-business-metrics/workflows/Continuous%20Integration/badge.svg)](https://github.com/reef-technologies/django-business-metrics/actions?query=workflow%3A%22Continuous+Integration%22) [![License](https://img.shields.io/pypi/l/django_business_metrics.svg?label=License)](https://pypi.python.org/pypi/django_business_metrics) [![python versions](https://img.shields.io/pypi/pyversions/django_business_metrics.svg?label=python%20versions)](https://pypi.python.org/pypi/django_business_metrics) [![PyPI version](https://img.shields.io/pypi/v/django_business_metrics.svg?label=PyPI%20version)](https://pypi.python.org/pypi/django_business_metrics) This Django app provides a Prometheus metrics endpoint serving so-called business metrics. These are metrics that are calculated when Prometheus hits the metrics endpoint. ## Usage -> This project uses [ApiVer](https://www.youtube.com/watch?v=FgcoAKchPjk). -> Always import from `django_business_metrics.v0` namespace and not from `django_business_metrics`. - +> [!IMPORTANT] +> This package uses [ApiVer](#versioning), make sure to import `django_business_metrics.v0`. 1. Create a `BusinessMetricsManager` object and register some metrics: @@ -46,12 +46,24 @@ This Django app provides a Prometheus metrics endpoint serving so-called busines 3. Setup your Prometheus agent to scrape metrics from `/business-metrics` endpoint. +## Versioning + +This package uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +TL;DR you are safe to use [compatible release version specifier](https://packaging.python.org/en/latest/specifications/version-specifiers/#compatible-release) `~=MAJOR.MINOR` in your `pyproject.toml` or `requirements.txt`. + +Additionally, this package uses [ApiVer](https://www.youtube.com/watch?v=FgcoAKchPjk) to further reduce the risk of breaking changes. +This means, the public API of this package is explicitly versioned, e.g. `django_business_metrics.v1`, and will not change in a backwards-incompatible way even when `django_business_metrics.v2` is released. + +Internal packages, i.e. prefixed by `django_business_metrics._` do not share these guarantees and may change in a backwards-incompatible way at any time even in patch releases. + + ## Development + Pre-requisites: - [pdm](https://pdm.fming.dev/) - [nox](https://nox.thea.codes/en/stable/) -- [docker](https://www.docker.com/) and [docker composep plugin](https://docs.docker.com/compose/) +- [docker](https://www.docker.com/) and [docker compose plugin](https://docs.docker.com/compose/) Ideally, you should run `nox -t format lint` before every commit to ensure that the code is properly formatted and linted. diff --git a/noxfile.py b/noxfile.py index 09969de..ade2cde 100644 --- a/noxfile.py +++ b/noxfile.py @@ -19,27 +19,30 @@ MAIN_BRANCH_NAME = "master" PYTHON_VERSIONS = ["3.9", "3.11", "3.12"] PYTHON_DEFAULT_VERSION = PYTHON_VERSIONS[-1] - DJANGO_VERSIONS = ["3.2", "4.2"] DEMO_APP_DIR = ROOT / "demo" nox.options.default_venv_backend = "venv" nox.options.stop_on_first_error = True -nox.options.reuse_existing_virtualenvs = True +nox.options.reuse_existing_virtualenvs = not CI -# In CI, use Python interpreter provided by GitHub Actions if CI: + # In CI, use Python interpreter provided by GitHub Actions PYTHON_VERSIONS = [sys.executable] -def install(session: nox.Session, *args, no_self=False, no_default=False): +def install(session: nox.Session, *groups, dev: bool = True, editable: bool = False, no_self=False, no_default=False): other_args = [] + if not dev: + other_args.append("--prod") + if not editable: + other_args.append("--no-editable") if no_self: other_args.append("--no-self") if no_default: other_args.append("--no-default") - for group in args: + for group in groups: other_args.extend(["--group", group]) session.run("pdm", "install", "--check", *other_args, external=True) @@ -53,8 +56,7 @@ def _list_files() -> list[Path]: ): cmd_result = subprocess.run(cmd, check=True, text=True, capture_output=True) file_list.extend(cmd_result.stdout.splitlines()) - file_paths = [Path(p) for p in file_list] - return file_paths + return [Path(p) for p in file_list] def list_files(suffix: str | None = None) -> list[Path]: @@ -140,7 +142,8 @@ def format_(session): @nox.session(python=PYTHON_DEFAULT_VERSION, tags=["lint", "check"]) def lint(session): """Run linters in readonly mode.""" - install(session, "lint") + # "test" group is required for mypy to work against test files + install(session, "lint", "test") session.run("ruff", "check", "--diff", "--unsafe-fixes", ".") session.run("ruff", "format", "--diff", ".") session.run("mypy", ".") @@ -152,7 +155,7 @@ def lint(session): @nox.session(python=PYTHON_VERSIONS, tags=["test", "check"]) @nox.parametrize("django", DJANGO_VERSIONS) def test(session, django: str): - session.run("pdm", "install", "--dev") + install(session, "test") session.run("pip", "install", f"django~={django}.0") session.run("pytest", "-vv", "-n", "auto", *session.posargs) @@ -162,7 +165,7 @@ def make_release(session): parser = argparse.ArgumentParser() def version(value): - if not re.match(r"\d+\.\d+\.\d+", value): + if not re.match(r"\d+\.\d+\.\d+(?:(?:a|b|rc)\d+)?", value): raise argparse.ArgumentTypeError("Invalid version format") return value diff --git a/pdm.lock b/pdm.lock index 787c613..1328d31 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,18 +5,7 @@ groups = ["default", "lint", "release", "test"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:59a295fc8c815eecfd20019fa496a32e96ed9e403868652ca428d08bd43497b0" - -[[package]] -name = "argcomplete" -version = "3.3.0" -requires_python = ">=3.8" -summary = "Bash tab completion for argparse" -groups = ["test"] -files = [ - {file = "argcomplete-3.3.0-py3-none-any.whl", hash = "sha256:c168c3723482c031df3c207d4ba8fa702717ccb9fc0bfe4117166c1f537b4a54"}, - {file = "argcomplete-3.3.0.tar.gz", hash = "sha256:fd03ff4a5b9e6580569d34b273f741e85cd9e072f3feeeee3eba4891c70eda62"}, -] +content_hash = "sha256:da630db6a6fa07bedc5b0eb8f5961ea1ba5ef4086033c609e95dfa7b65ef114a" [[package]] name = "asgiref" @@ -85,30 +74,6 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "colorlog" -version = "6.8.2" -requires_python = ">=3.6" -summary = "Add colours to the output of Python's logging module." -groups = ["test"] -dependencies = [ - "colorama; sys_platform == \"win32\"", -] -files = [ - {file = "colorlog-6.8.2-py3-none-any.whl", hash = "sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33"}, - {file = "colorlog-6.8.2.tar.gz", hash = "sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44"}, -] - -[[package]] -name = "distlib" -version = "0.3.8" -summary = "Distribution utilities" -groups = ["test"] -files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, -] - [[package]] name = "django" version = "4.2.13" @@ -159,22 +124,6 @@ files = [ {file = "django_stubs_ext-5.0.2.tar.gz", hash = "sha256:409c62585d7f996cef5c760e6e27ea3ff29f961c943747e67519c837422cad32"}, ] -[[package]] -name = "django-stubs" -version = "5.0.2" -extras = ["compatible-mypy"] -requires_python = ">=3.8" -summary = "Mypy stubs for Django" -groups = ["lint"] -dependencies = [ - "django-stubs==5.0.2", - "mypy~=1.10.0", -] -files = [ - {file = "django_stubs-5.0.2-py3-none-any.whl", hash = "sha256:cb0c506cb5c54c64612e4a2ee8d6b913c6178560ec168009fe847c09747c304b"}, - {file = "django_stubs-5.0.2.tar.gz", hash = "sha256:236bc5606e5607cb968f92b648471f9edaa461a774bc013bf9e6bff8730f6bdf"}, -] - [[package]] name = "exceptiongroup" version = "1.2.1" @@ -198,17 +147,6 @@ files = [ {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, ] -[[package]] -name = "filelock" -version = "3.14.0" -requires_python = ">=3.8" -summary = "A platform independent file lock." -groups = ["test"] -files = [ - {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, - {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, -] - [[package]] name = "freezegun" version = "1.5.1" @@ -328,7 +266,7 @@ name = "mypy" version = "1.10.0" requires_python = ">=3.8" summary = "Optional static typing for Python" -groups = ["lint", "test"] +groups = ["lint"] dependencies = [ "mypy-extensions>=1.0.0", "tomli>=1.1.0; python_version < \"3.11\"", @@ -364,50 +302,21 @@ name = "mypy-extensions" version = "1.0.0" requires_python = ">=3.5" summary = "Type system extensions for programs checked with the mypy type checker." -groups = ["lint", "test"] +groups = ["lint"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -[[package]] -name = "nox" -version = "2024.4.15" -requires_python = ">=3.7" -summary = "Flexible test automation." -groups = ["test"] -dependencies = [ - "argcomplete<4.0,>=1.9.4", - "colorlog<7.0.0,>=2.6.1", - "packaging>=20.9", - "tomli>=1; python_version < \"3.11\"", - "virtualenv>=20.14.1", -] -files = [ - {file = "nox-2024.4.15-py3-none-any.whl", hash = "sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565"}, - {file = "nox-2024.4.15.tar.gz", hash = "sha256:ecf6700199cdfa9e5ea0a41ff5e6ef4641d09508eda6edb89d9987864115817f"}, -] - [[package]] name = "packaging" -version = "24.0" -requires_python = ">=3.7" -summary = "Core utilities for Python packages" -groups = ["test"] -files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, -] - -[[package]] -name = "platformdirs" -version = "4.2.2" +version = "24.1" requires_python = ">=3.8" -summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +summary = "Core utilities for Python packages" groups = ["test"] files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -451,6 +360,32 @@ files = [ {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, ] +[[package]] +name = "pytest-apiver" +version = "0.0.1" +requires_python = ">=3.9" +git = "https://github.com/reef-technologies/pytest_apiver.git" +revision = "9d77f421d8b9d21d3f7fb7dc6caf4f9f98fb71a9" +summary = "" +groups = ["test"] +dependencies = [ + "pytest", +] + +[[package]] +name = "pytest-asyncio" +version = "0.23.7" +requires_python = ">=3.8" +summary = "Pytest support for asyncio" +groups = ["test"] +dependencies = [ + "pytest<9,>=7.0.0", +] +files = [ + {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, + {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, +] + [[package]] name = "pytest-django" version = "4.8.0" @@ -496,28 +431,28 @@ files = [ [[package]] name = "ruff" -version = "0.4.8" +version = "0.4.10" requires_python = ">=3.7" summary = "An extremely fast Python linter and code formatter, written in Rust." -groups = ["lint", "test"] -files = [ - {file = "ruff-0.4.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7663a6d78f6adb0eab270fa9cf1ff2d28618ca3a652b60f2a234d92b9ec89066"}, - {file = "ruff-0.4.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eeceb78da8afb6de0ddada93112869852d04f1cd0f6b80fe464fd4e35c330913"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aad360893e92486662ef3be0a339c5ca3c1b109e0134fcd37d534d4be9fb8de3"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:284c2e3f3396fb05f5f803c9fffb53ebbe09a3ebe7dda2929ed8d73ded736deb"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7354f921e3fbe04d2a62d46707e569f9315e1a613307f7311a935743c51a764"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:72584676164e15a68a15778fd1b17c28a519e7a0622161eb2debdcdabdc71883"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9678d5c9b43315f323af2233a04d747409d1e3aa6789620083a82d1066a35199"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704977a658131651a22b5ebeb28b717ef42ac6ee3b11e91dc87b633b5d83142b"}, - {file = "ruff-0.4.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05f8d6f0c3cce5026cecd83b7a143dcad503045857bc49662f736437380ad45"}, - {file = "ruff-0.4.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ea874950daca5697309d976c9afba830d3bf0ed66887481d6bca1673fc5b66a"}, - {file = "ruff-0.4.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fc95aac2943ddf360376be9aa3107c8cf9640083940a8c5bd824be692d2216dc"}, - {file = "ruff-0.4.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:384154a1c3f4bf537bac69f33720957ee49ac8d484bfc91720cc94172026ceed"}, - {file = "ruff-0.4.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e9d5ce97cacc99878aa0d084c626a15cd21e6b3d53fd6f9112b7fc485918e1fa"}, - {file = "ruff-0.4.8-py3-none-win32.whl", hash = "sha256:6d795d7639212c2dfd01991259460101c22aabf420d9b943f153ab9d9706e6a9"}, - {file = "ruff-0.4.8-py3-none-win_amd64.whl", hash = "sha256:e14a3a095d07560a9d6769a72f781d73259655919d9b396c650fc98a8157555d"}, - {file = "ruff-0.4.8-py3-none-win_arm64.whl", hash = "sha256:14019a06dbe29b608f6b7cbcec300e3170a8d86efaddb7b23405cb7f7dcaf780"}, - {file = "ruff-0.4.8.tar.gz", hash = "sha256:16d717b1d57b2e2fd68bd0bf80fb43931b79d05a7131aa477d66fc40fbd86268"}, +groups = ["lint"] +files = [ + {file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"}, + {file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"}, + {file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"}, + {file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"}, + {file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"}, + {file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"}, + {file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"}, + {file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"}, + {file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"}, + {file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"}, ] [[package]] @@ -620,13 +555,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.12.1" +version = "4.12.2" requires_python = ">=3.8" summary = "Backported and Experimental Type Hints for Python 3.8+" -groups = ["default", "lint", "test"] +groups = ["default", "lint"] files = [ - {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, - {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -643,29 +578,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" requires_python = ">=3.8" summary = "HTTP library with thread-safe connection pooling, file post, and more." groups = ["lint"] files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, -] - -[[package]] -name = "virtualenv" -version = "20.26.2" -requires_python = ">=3.7" -summary = "Virtual Python Environment builder" -groups = ["test"] -dependencies = [ - "distlib<1,>=0.3.7", - "filelock<4,>=3.12.2", - "platformdirs<5,>=3.9.1", -] -files = [ - {file = "virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b"}, - {file = "virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 4b4bd4e..0d7e6b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,10 @@ [project] name = "django-business-metrics" version = "1.0.2" +requires-python = ">=3.9" description = "Django Prometheus business metrics" -license = {file = "LICENSE"} +keywords = [] +license = {text = "MIT"} readme = "README.md" authors = [ {name = "Reef Technologies", email = "opensource@reef.pl"}, @@ -17,7 +19,6 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] -requires-python = ">=3.9" dependencies = [ "Django>=3", "prometheus-client>=0.13.0", @@ -27,21 +28,31 @@ dependencies = [ "Source" = "https://github.com/reef-technologies/django-business-metrics" "Issue Tracker" = "https://github.com/reef-technologies/django-business-metrics/issues" +[build-system] +requires = ["pdm-backend"] +build-backend = "pdm.backend" + +[tool.pytest.ini_options] +pythonpath = ["."] # allow for `import tests` in test files +target_package_name = "django_business_metrics" # required by pytest-apiver +DJANGO_SETTINGS_MODULE="tests.settings" + [tool.pdm] +distribution = true + [tool.pdm.dev-dependencies] test = [ "freezegun", - "mypy>=0.971", - "nox>=2024", + "pytest", + "pytest-apiver @ git+https://github.com/reef-technologies/pytest_apiver.git", + "pytest-asyncio", "pytest-django", "pytest-xdist", - "pytest>=6", - "ruff>=0.4", ] lint = [ "codespell[toml]", - "django-stubs[compatible-mypy]", - "mypy", + "django-stubs>=1.12.0", + "mypy>=1.8", "ruff", "types-freezegun", "types-python-dateutil", @@ -51,13 +62,6 @@ release = [ "towncrier", ] -[build-system] -requires = ["pdm-backend"] -build-backend = "pdm.backend" - -[tool.pytest.ini_options] -DJANGO_SETTINGS_MODULE="tests.settings" - [tool.ruff] line-length = 120 @@ -133,5 +137,6 @@ exclude_dirs = ["tests"] module = [ "nox", "pytest", + "tests.*", ] ignore_missing_imports = true diff --git a/tests/conftest.py b/tests/conftest.py index f2aac03..172ef99 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1 @@ -import pytest -from django.contrib.auth import get_user_model -from django.test import RequestFactory - - -@pytest.fixture -def request_factory(): - return RequestFactory() - - -@pytest.fixture -def user_model(): - return get_user_model() +from .django_fixtures import * # noqa: F401, F403 diff --git a/tests/django_fixtures.py b/tests/django_fixtures.py new file mode 100644 index 0000000..7ff0043 --- /dev/null +++ b/tests/django_fixtures.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +import pytest +from django.contrib.auth import get_user_model +from django.test import RequestFactory + + +@pytest.fixture +def request_factory() -> RequestFactory: + return RequestFactory() + + +@pytest.fixture +def user_model(): + return get_user_model() diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..c2cf7c1 --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1,3 @@ +""" +Tests for internals +""" diff --git a/tests/unit/api/__init__.py b/tests/unit/api/__init__.py new file mode 100644 index 0000000..0cdb696 --- /dev/null +++ b/tests/unit/api/__init__.py @@ -0,0 +1,5 @@ +""" +Tests covering the package API versioned with ApiVer. + +These tests will be run per each version of the API. +""" diff --git a/tests/unit/api/test_setup.py b/tests/unit/api/test_setup.py new file mode 100644 index 0000000..0a77d41 --- /dev/null +++ b/tests/unit/api/test_setup.py @@ -0,0 +1,6 @@ +def test_apiver_exports(apiver_module): + assert sorted(name for name in dir(apiver_module) if not name.startswith("_")) == [ + "BusinessMetricsManager", + "active_users", + "users", + ] diff --git a/tests/unit/internal/__init__.py b/tests/unit/internal/__init__.py new file mode 100644 index 0000000..0cdb696 --- /dev/null +++ b/tests/unit/internal/__init__.py @@ -0,0 +1,5 @@ +""" +Tests covering the package API versioned with ApiVer. + +These tests will be run per each version of the API. +""" diff --git a/tests/unit/internal/test_django_setup.py b/tests/unit/internal/test_django_setup.py new file mode 100644 index 0000000..cc26614 --- /dev/null +++ b/tests/unit/internal/test_django_setup.py @@ -0,0 +1,5 @@ +# cookiecutter-rt-pkg macro: requires cookiecutter.is_django_package + + +def test__setup(db): + pass diff --git a/tests/unit/internal/test_setup.py b/tests/unit/internal/test_setup.py new file mode 100644 index 0000000..16a12ef --- /dev/null +++ b/tests/unit/internal/test_setup.py @@ -0,0 +1,17 @@ +""" +Basic test environment setup test. +It should always pass as long as all dependencies are properly installed. +""" + +from datetime import datetime, timedelta + +import pytest +from freezegun import freeze_time + + +def test__setup(): + with freeze_time(datetime.now() - timedelta(days=1)): + pass + + with pytest.raises(ZeroDivisionError): + 1 / 0