diff --git a/.cruft.json b/.cruft.json new file mode 100644 index 0000000..65848b5 --- /dev/null +++ b/.cruft.json @@ -0,0 +1,18 @@ +{ + "template": "https://github.com/reef-technologies/cookiecutter-rt-pkg", + "commit": "d9240e46239f3622c739043a5e6cbf605a6244d2", + "checkout": null, + "context": { + "cookiecutter": { + "package_name": "deterministic_ml", + "repository_github_url": "https://github.com/backend-developers-ltd/deterministic-ml", + "is_django_package": "n", + "_jinja2_env_vars": { + "block_start_string": "# COOKIECUTTER{%", + "block_end_string": "%}" + }, + "_template": "https://github.com/reef-technologies/cookiecutter-rt-pkg" + } + }, + "directory": null +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..41f5a4b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" # specify the directory to scan for dependency files, e.g., "/" + schedule: + interval: "daily" + open-pull-requests-limit: 0 # Only security updates will be opened as PRs + - package-ecosystem: "docker" + directory: "/" # specify the directory to scan for dependency files, e.g., "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 0 # Only security updates will be opened as PRs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..abb4f73 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +name: Continuous Integration + +on: + push: + branches: [master, main] + pull_request: + branches: [master, main] + +env: + PYTHON_DEFAULT_VERSION: "3.12" + +jobs: + linter: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python ${{ env.PYTHON_DEFAULT_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_DEFAULT_VERSION }} + cache: "pip" + cache-dependency-path: 'pdm.lock' + - uses: actions/cache@v4 + with: + path: ~/.cache/pdm + key: ${{ env.PYTHON_DEFAULT_VERSION }}-pdm-${{ hashFiles('pdm.lock') }} + restore-keys: ${{ env.PYTHON_DEFAULT_VERSION }}-pdm- + - name: Install dependencies + run: python -m pip install --upgrade nox 'pdm>=2.12,<3' + - name: Run linters + run: nox -vs lint + test: + timeout-minutes: 10 + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest"] + python-version: ["3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + cache-dependency-path: 'pdm.lock' + - uses: actions/cache@v4 + with: + path: ~/.cache/pdm + key: ${{ matrix.python-version }}-pdm-${{ hashFiles('pdm.lock') }} + restore-keys: ${{ matrix.python-version }}-pdm- + - name: Install dependencies + run: python -m pip install --upgrade 'nox==2024.3.2' 'pdm==2.13.2' + - name: Run unit tests + run: nox -vs test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..548a99c --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,93 @@ +name: "Continuous Deployment" + +# This workflow requires https://docs.pypi.org/trusted-publishers/ to be enabled for the repository. +# Follow instructions from this link to enable it. +# Use this workflow (`publish.yml`) in the configuration. +# Please note this process has to be repeated for Test PyPI and PyPI separately. + +on: + push: + tags: + - 'v*' # push events to matching v*, i.e. v1.0, v20.15.10 + - 'draft/v*' + +env: + PYTHON_DEFAULT_VERSION: "3.12" + +jobs: + publish: + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + permissions: + id-token: write # allows publishing to PyPI + contents: write # allows uploading a GitHub release + runs-on: ubuntu-latest + steps: + - name: Get version from tag + id: get-version + run: | + if [[ ${{ github.ref }} == refs/tags/v* ]]; then + echo "draft=false" >> "$GITHUB_OUTPUT" + echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" + else + echo "draft=true" >> "$GITHUB_OUTPUT" + echo "version=${GITHUB_REF#refs/tags/draft/v}" >> "$GITHUB_OUTPUT" + fi + export IS_PRERELEASE=$([[ ${{ github.ref }} =~ [^0-9]$ ]] && echo true || echo false) + echo "prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python ${{ env.PYTHON_DEFAULT_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_DEFAULT_VERSION }} + + - name: Install dependencies + run: python -m pip install --upgrade nox 'pdm>=2.12,<3' + + - name: Read the Changelog + id: read-changelog + uses: mindsers/changelog-reader-action@v2 + with: + version: ${{ steps.get-version.outputs.version }} + path: ./CHANGELOG.md + continue-on-error: ${{ fromJSON(steps.get-version.outputs.draft) }} + + - name: Build + run: pdm build + + - name: Sign distribution + uses: sigstore/gh-action-sigstore-python@v2.1.1 + with: + inputs: >- + dist/*.tar.gz + dist/*.whl + + - name: Create GitHub release + id: create-release + uses: softprops/action-gh-release@v2 + with: + name: ${{ steps.get-version.outputs.version }} + body: ${{ steps.read-changelog.outputs.changes }} + draft: ${{ fromJSON(steps.get-version.outputs.draft)}} + prerelease: ${{ fromJSON(steps.get-version.outputs.prerelease) }} + files: >- + dist/*.tar.gz + dist/*.whl + dist/*.sigstore + + - name: Remove signature files as pypa/gh-action-pypi-publish does not support them + run: rm -f dist/*.sigstore + + - name: Publish distribution 📦 to TestPyPI + if: ${{ steps.get-version.outputs.draft == 'true' }} + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + - name: Publish distribution 📦 to PyPI + if: ${{ steps.get-version.outputs.draft == 'false' }} + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d9603cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.pyc +*.sqlite3 +*~ +*.egg-info/ +/.idea/ +.env +.venv +venv +media/ +.backups/ +.envrc +.pdm-python +.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 new file mode 100644 index 0000000..e3f36bc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog +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 +upcoming release can be found in [changelog.d](changelog.d). + + diff --git a/README.md b/README.md index a83a049..44fd35a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,43 @@ # deterministic-ml -Deterministic ML Models execution using Python frameworks + [![Continuous Integration](https://github.com/backend-developers-ltd/deterministic-ml/workflows/Continuous%20Integration/badge.svg)](https://github.com/backend-developers-ltd/deterministic-ml/actions?query=workflow%3A%22Continuous+Integration%22) [![License](https://img.shields.io/pypi/l/deterministic_ml.svg?label=License)](https://pypi.python.org/pypi/deterministic_ml) [![python versions](https://img.shields.io/pypi/pyversions/deterministic_ml.svg?label=python%20versions)](https://pypi.python.org/pypi/deterministic_ml) [![PyPI version](https://img.shields.io/pypi/v/deterministic_ml.svg?label=PyPI%20version)](https://pypi.python.org/pypi/deterministic_ml) + +## Usage + +> [!IMPORTANT] +> This package uses [ApiVer](#versioning), make sure to import `deterministic_ml.v1`. + + +## 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. `deterministic_ml.v1`, and will not change in a backwards-incompatible way even when `deterministic_ml.v2` is released. + +Internal packages, i.e. prefixed by `deterministic_ml._` 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 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. +Before submitting a PR, make sure that tests pass as well, you can do so using: +``` +nox -t check # equivalent to `nox -t format lint test` +``` + +If you wish to install dependencies into `.venv` so your IDE can pick them up, you can do so using: +``` +pdm install --dev +``` + +### Release process + +Run `nox -s make_release -- X.Y.Z` where `X.Y.Z` is the version you're releasing and follow the printed instructions. diff --git a/docs/3rd_party/cookiecutter-rt-pkg/CHANGELOG.md b/docs/3rd_party/cookiecutter-rt-pkg/CHANGELOG.md new file mode 100644 index 0000000..f995542 --- /dev/null +++ b/docs/3rd_party/cookiecutter-rt-pkg/CHANGELOG.md @@ -0,0 +1,15 @@ +# cookiecutter-rt-pkg Changelog + +Main purpose of this file is to provide a changelog for the template itself. +It is not intended to be used as a changelog for the generated project. + +This changelog will document any know **BREAKING** changes between versions of the template. +Please review this new entries carefully after applying `cruft update` before committing the changes. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +Currently, `cookiecutter-rt-pkg` has no explicit versioning amd we purely rely on `cruft` diff. + +## [Unreleased] + +* **BREAKING** Introduced cookiecutter-rt-pkg template diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 0000000..49e49b2 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,206 @@ +from __future__ import annotations + +import argparse +import functools +import os +import re +import subprocess +import sys +import tempfile +from pathlib import Path + +import nox + +os.environ["PDM_IGNORE_SAVED_PYTHON"] = "1" + +CI = os.environ.get("CI") is not None + +ROOT = Path(".") +MAIN_BRANCH_NAME = "master" +PYTHON_VERSIONS = ["3.11", "3.12"] +PYTHON_DEFAULT_VERSION = PYTHON_VERSIONS[-1] + +nox.options.default_venv_backend = "venv" +nox.options.stop_on_first_error = True +nox.options.reuse_existing_virtualenvs = not CI + + +if CI: + # In CI, use Python interpreter provided by GitHub Actions + PYTHON_VERSIONS = [sys.executable] + + +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 groups: + other_args.extend(["--group", group]) + session.run("pdm", "install", "--check", *other_args, external=True) + + +@functools.lru_cache +def _list_files() -> list[Path]: + file_list = [] + for cmd in ( + ["git", "ls-files"], + ["git", "ls-files", "--others", "--exclude-standard"], + ): + cmd_result = subprocess.run(cmd, check=True, text=True, capture_output=True) + file_list.extend(cmd_result.stdout.splitlines()) + return [Path(p) for p in file_list] + + +def list_files(suffix: str | None = None) -> list[Path]: + """List all non-files not-ignored by git.""" + file_paths = _list_files() + if suffix is not None: + file_paths = [p for p in file_paths if p.suffix == suffix] + return file_paths + + +def run_readable(session, mode="check"): + session.run( + "docker", + "run", + "--platform", + "linux/amd64", + "--rm", + "-v", + f"{ROOT.absolute()}:/data", + "-w", + "/data", + "ghcr.io/bobheadxi/readable:v0.5.0@sha256:423c133e7e9ca0ac20b0ab298bd5dbfa3df09b515b34cbfbbe8944310cc8d9c9", + mode, + "![.]**/*.md", + external=True, + ) + + +def run_shellcheck(session, mode="check"): + shellcheck_cmd = [ + "docker", + "run", + "--platform", + "linux/amd64", # while this image is multi-arch, we cannot use digest with multi-arch images + "--rm", + "-v", + f"{ROOT.absolute()}:/mnt", + "-w", + "/mnt", + "-q", + "koalaman/shellcheck:0.9.0@sha256:a527e2077f11f28c1c1ad1dc784b5bc966baeb3e34ef304a0ffa72699b01ad9c", + ] + + files = list_files(suffix=".sh") + if not files: + session.log("No shell files found") + return + shellcheck_cmd.extend(files) + + if mode == "fmt": + with tempfile.NamedTemporaryFile(mode="w+") as diff_file: + session.run( + *shellcheck_cmd, + "--format=diff", + external=True, + stdout=diff_file, + success_codes=[0, 1], + ) + diff_file.seek(0) + diff = diff_file.read() + if len(diff.splitlines()) > 1: # ignore single-line message + session.log("Applying shellcheck patch:\n%s", diff) + subprocess.run( + ["patch", "-p1"], + input=diff, + text=True, + check=True, + ) + + session.run(*shellcheck_cmd, external=True) + + +@nox.session(name="format", python=PYTHON_DEFAULT_VERSION, tags=["format", "check"]) +def format_(session): + """Lint the code and apply fixes in-place whenever possible.""" + install(session, "lint", no_self=True, no_default=True) + session.run("ruff", "check", "--fix", ".") + run_shellcheck(session, mode="fmt") + run_readable(session, mode="fmt") + session.run("ruff", "format", ".") + + +@nox.session(python=PYTHON_DEFAULT_VERSION, tags=["lint", "check"]) +def lint(session): + """Run linters in readonly mode.""" + # "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", ".") + session.run("codespell", ".") + run_shellcheck(session, mode="check") + run_readable(session, mode="check") + + +@nox.session(python=PYTHON_VERSIONS, tags=["test", "check"]) +def test(session): + install(session, "test") + session.run("pytest", "-vv", "-n", "auto", *session.posargs) + + +@nox.session(python=PYTHON_DEFAULT_VERSION) +def make_release(session): + install(session, "release", no_self=True, no_default=True) + parser = argparse.ArgumentParser() + + def version(value): + if not re.match(r"\d+\.\d+\.\d+(?:(?:a|b|rc)\d+)?", value): + raise argparse.ArgumentTypeError("Invalid version format") + return value + + parser.add_argument( + "release_version", + help="Release version in semver format (e.g. 1.2.3)", + type=version, + ) + parser.add_argument( + "--draft", + action="store_true", + help="Create a draft release", + ) + parsed_args = parser.parse_args(session.posargs) + + local_changes = subprocess.check_output(["git", "diff", "--stat"]) + if local_changes: + session.error("Uncommitted changes detected") + + current_branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], text=True).strip() + if current_branch != MAIN_BRANCH_NAME: + session.warn(f"Releasing from a branch {current_branch!r}, while main branch is {MAIN_BRANCH_NAME!r}") + if not parsed_args.draft: + session.error("Only draft releases are allowed from non-main branch") + + session.run("towncrier", "build", "--yes", "--version", parsed_args.release_version) + + if parsed_args.draft: + tag = f"draft/v{parsed_args.release_version}" + message = f"Draft release {tag}" + else: + tag = f"v{parsed_args.release_version}" + message = f"release {tag}" + + session.log( + f"CHANGELOG updated, please review changes, and execute when ready:\n" + f" git commit -m {message!r}\n" + f" git push origin {current_branch}\n" + f" git tag {tag}\n" + f" git push origin {tag}\n" + ) diff --git a/pdm.lock b/pdm.lock new file mode 100644 index 0000000..ae5619a --- /dev/null +++ b/pdm.lock @@ -0,0 +1,382 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default", "lint", "release", "test"] +strategy = ["cross_platform", "inherit_metadata"] +lock_version = "4.4.2" +content_hash = "sha256:206e06a728b8f3ddd1c4fbc34f39cb1b0fbe268d1ef5a2fecc0275a2790a4bd2" + +[[package]] +name = "click" +version = "8.1.7" +requires_python = ">=3.7" +summary = "Composable command line interface toolkit" +groups = ["release"] +dependencies = [ + "colorama; platform_system == \"Windows\"", +] +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[[package]] +name = "codespell" +version = "2.3.0" +requires_python = ">=3.8" +summary = "Codespell" +groups = ["lint"] +files = [ + {file = "codespell-2.3.0-py3-none-any.whl", hash = "sha256:a9c7cef2501c9cfede2110fd6d4e5e62296920efe9abfb84648df866e47f58d1"}, + {file = "codespell-2.3.0.tar.gz", hash = "sha256:360c7d10f75e65f67bad720af7007e1060a5d395670ec11a7ed1fed9dd17471f"}, +] + +[[package]] +name = "codespell" +version = "2.3.0" +extras = ["toml"] +requires_python = ">=3.8" +summary = "Codespell" +groups = ["lint"] +dependencies = [ + "codespell==2.3.0", +] +files = [ + {file = "codespell-2.3.0-py3-none-any.whl", hash = "sha256:a9c7cef2501c9cfede2110fd6d4e5e62296920efe9abfb84648df866e47f58d1"}, + {file = "codespell-2.3.0.tar.gz", hash = "sha256:360c7d10f75e65f67bad720af7007e1060a5d395670ec11a7ed1fed9dd17471f"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." +groups = ["release", "test"] +marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "execnet" +version = "2.1.1" +requires_python = ">=3.8" +summary = "execnet: rapid multi-Python deployment" +groups = ["test"] +files = [ + {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, + {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, +] + +[[package]] +name = "freezegun" +version = "1.5.1" +requires_python = ">=3.7" +summary = "Let your Python tests travel through time" +groups = ["test"] +dependencies = [ + "python-dateutil>=2.7", +] +files = [ + {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, + {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +requires_python = ">=3.7" +summary = "brain-dead simple config-ini parsing" +groups = ["test"] +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +requires_python = ">=3.7" +summary = "A very fast and expressive template engine." +groups = ["release"] +dependencies = [ + "MarkupSafe>=2.0", +] +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[[package]] +name = "markupsafe" +version = "2.1.5" +requires_python = ">=3.7" +summary = "Safely add untrusted strings to HTML/XML markup." +groups = ["release"] +files = [ + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "mypy" +version = "1.11.1" +requires_python = ">=3.8" +summary = "Optional static typing for Python" +groups = ["lint"] +dependencies = [ + "mypy-extensions>=1.0.0", + "typing-extensions>=4.6.0", +] +files = [ + {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"}, + {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"}, + {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"}, + {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"}, + {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"}, + {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"}, + {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"}, + {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"}, + {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"}, + {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"}, +] + +[[package]] +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"] +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 = "packaging" +version = "24.1" +requires_python = ">=3.8" +summary = "Core utilities for Python packages" +groups = ["test"] +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +requires_python = ">=3.8" +summary = "plugin and hook calling mechanisms for python" +groups = ["test"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[[package]] +name = "pytest" +version = "8.3.2" +requires_python = ">=3.8" +summary = "pytest: simple powerful testing with Python" +groups = ["test"] +dependencies = [ + "colorama; sys_platform == \"win32\"", + "iniconfig", + "packaging", + "pluggy<2,>=1.5", +] +files = [ + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, +] + +[[package]] +name = "pytest-apiver" +version = "0.1.0" +requires_python = ">=3.9" +summary = "" +groups = ["test"] +dependencies = [ + "pytest", +] +files = [ + {file = "pytest_apiver-0.1.0-py3-none-any.whl", hash = "sha256:fc86ec3de77506a26aa232858f94c5bf397b1a0798ba7c0772061017119f3420"}, + {file = "pytest_apiver-0.1.0.tar.gz", hash = "sha256:105023e626a3e3e59c59efb3f4268245b09ddfb3d8e627f67e4f411d77642e67"}, +] + +[[package]] +name = "pytest-asyncio" +version = "0.23.8" +requires_python = ">=3.8" +summary = "Pytest support for asyncio" +groups = ["test"] +dependencies = [ + "pytest<9,>=7.0.0", +] +files = [ + {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, + {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, +] + +[[package]] +name = "pytest-xdist" +version = "3.6.1" +requires_python = ">=3.8" +summary = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +groups = ["test"] +dependencies = [ + "execnet>=2.1", + "pytest>=7.0.0", +] +files = [ + {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, + {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +summary = "Extensions to the standard Python datetime module" +groups = ["test"] +dependencies = [ + "six>=1.5", +] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[[package]] +name = "ruff" +version = "0.5.6" +requires_python = ">=3.7" +summary = "An extremely fast Python linter and code formatter, written in Rust." +groups = ["lint"] +files = [ + {file = "ruff-0.5.6-py3-none-linux_armv6l.whl", hash = "sha256:a0ef5930799a05522985b9cec8290b185952f3fcd86c1772c3bdbd732667fdcd"}, + {file = "ruff-0.5.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b652dc14f6ef5d1552821e006f747802cc32d98d5509349e168f6bf0ee9f8f42"}, + {file = "ruff-0.5.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:80521b88d26a45e871f31e4b88938fd87db7011bb961d8afd2664982dfc3641a"}, + {file = "ruff-0.5.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9bc8f328a9f1309ae80e4d392836e7dbc77303b38ed4a7112699e63d3b066ab"}, + {file = "ruff-0.5.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d394940f61f7720ad371ddedf14722ee1d6250fd8d020f5ea5a86e7be217daf"}, + {file = "ruff-0.5.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111a99cdb02f69ddb2571e2756e017a1496c2c3a2aeefe7b988ddab38b416d36"}, + {file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e395daba77a79f6dc0d07311f94cc0560375ca20c06f354c7c99af3bf4560c5d"}, + {file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c476acb43c3c51e3c614a2e878ee1589655fa02dab19fe2db0423a06d6a5b1b6"}, + {file = "ruff-0.5.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2ff8003f5252fd68425fd53d27c1f08b201d7ed714bb31a55c9ac1d4c13e2eb"}, + {file = "ruff-0.5.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c94e084ba3eaa80c2172918c2ca2eb2230c3f15925f4ed8b6297260c6ef179ad"}, + {file = "ruff-0.5.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f77c1c3aa0669fb230b06fb24ffa3e879391a3ba3f15e3d633a752da5a3e670"}, + {file = "ruff-0.5.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f908148c93c02873210a52cad75a6eda856b2cbb72250370ce3afef6fb99b1ed"}, + {file = "ruff-0.5.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:563a7ae61ad284187d3071d9041c08019975693ff655438d8d4be26e492760bd"}, + {file = "ruff-0.5.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:94fe60869bfbf0521e04fd62b74cbca21cbc5beb67cbb75ab33fe8c174f54414"}, + {file = "ruff-0.5.6-py3-none-win32.whl", hash = "sha256:e6a584c1de6f8591c2570e171cc7ce482bb983d49c70ddf014393cd39e9dfaed"}, + {file = "ruff-0.5.6-py3-none-win_amd64.whl", hash = "sha256:d7fe7dccb1a89dc66785d7aa0ac283b2269712d8ed19c63af908fdccca5ccc1a"}, + {file = "ruff-0.5.6-py3-none-win_arm64.whl", hash = "sha256:57c6c0dd997b31b536bff49b9eee5ed3194d60605a4427f735eeb1f9c1b8d264"}, + {file = "ruff-0.5.6.tar.gz", hash = "sha256:07c9e3c2a8e1fe377dd460371c3462671a728c981c3205a5217291422209f642"}, +] + +[[package]] +name = "six" +version = "1.16.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +summary = "Python 2 and 3 compatibility utilities" +groups = ["test"] +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "towncrier" +version = "24.7.1" +requires_python = ">=3.8" +summary = "Building newsfiles for your project." +groups = ["release"] +dependencies = [ + "click", + "jinja2", +] +files = [ + {file = "towncrier-24.7.1-py3-none-any.whl", hash = "sha256:685e2a94335b5dc47537b4d3b449a25b18571ea85b07dcf6e8df31ba40f692dd"}, + {file = "towncrier-24.7.1.tar.gz", hash = "sha256:57a057faedabcadf1a62f6f9bad726ae566c1f31a411338ddb8316993f583b3d"}, +] + +[[package]] +name = "types-freezegun" +version = "1.1.10" +summary = "Typing stubs for freezegun" +groups = ["lint"] +files = [ + {file = "types-freezegun-1.1.10.tar.gz", hash = "sha256:cb3a2d2eee950eacbaac0673ab50499823365ceb8c655babb1544a41446409ec"}, + {file = "types_freezegun-1.1.10-py3-none-any.whl", hash = "sha256:fadebe72213e0674036153366205038e1f95c8ca96deb4ef9b71ddc15413543e"}, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20240316" +requires_python = ">=3.8" +summary = "Typing stubs for python-dateutil" +groups = ["lint"] +files = [ + {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"}, + {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"}, +] + +[[package]] +name = "types-requests" +version = "2.32.0.20240712" +requires_python = ">=3.8" +summary = "Typing stubs for requests" +groups = ["lint"] +dependencies = [ + "urllib3>=2", +] +files = [ + {file = "types-requests-2.32.0.20240712.tar.gz", hash = "sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358"}, + {file = "types_requests-2.32.0.20240712-py3-none-any.whl", hash = "sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +requires_python = ">=3.8" +summary = "Backported and Experimental Type Hints for Python 3.8+" +groups = ["lint"] +files = [ + {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]] +name = "urllib3" +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.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1a8eed8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,135 @@ +[project] +name = "deterministic-ml" +requires-python = ">=3.11" +keywords = [] +license = {text = "MIT"} +readme = "README.md" +authors = [ + {name = "Reef Technologies", email = "opensource@reef.pl"}, +] +classifiers = [ + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dynamic = [ + "version", +] +dependencies = [ +] + +[project.urls] +"Source" = "https://github.com/backend-developers-ltd/deterministic-ml" +"Issue Tracker" = "https://github.com/backend-developers-ltd/deterministic-ml/issues" + +[build-system] +requires = ["pdm-backend"] +build-backend = "pdm.backend" + +[tool.pdm] +distribution = true + +[tool.pdm.version] +source = "scm" + +[tool.pdm.dev-dependencies] +test = [ + "freezegun", + "pytest", + "pytest-apiver", + "pytest-asyncio", + "pytest-xdist", +] +lint = [ + "codespell[toml]", + "mypy>=1.8", + "ruff", + "types-freezegun", + "types-python-dateutil", + "types-requests", +] +release = [ + "towncrier", +] + +[tool.pytest.ini_options] +pythonpath = ["."] # allow for `import tests` in test files +target_package_name = "deterministic_ml" # required by pytest-apiver + +[tool.ruff] +line-length = 120 + +[tool.ruff.lint] +select = [ + "D", + "E", "F", "I", "UP", + "TCH005", +] +ignore = [ + "D100", "D105", "D107", "D200", "D202", "D203", "D205", "D212", "D400", "D401", "D415", + "D101", "D102", "D103", "D104", # TODO remove once we have docstring for all public methods +] + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F401"] +"test/**" = ["D", "F403", "F405"] + +[tool.codespell] +skip = "*.min.js,pdm.lock" +ignore-words-list = "datas" + +[tool.towncrier] +directory = "changelog.d" +filename = "CHANGELOG.md" +start_string = "\n" +underlines = ["", "", ""] +title_format = "## [{version}](https://github.com/backend-developers-ltd/deterministic-ml/releases/tag/v{version}) - {project_date}" +issue_format = "[#{issue}](https://github.com/backend-developers-ltd/deterministic-ml/issues/{issue})" + +[[tool.towncrier.type]] +directory = "removed" +name = "Removed" +showcontent = true + +[[tool.towncrier.type]] +directory = "changed" +name = "Changed" +showcontent = true + +[[tool.towncrier.type]] +directory = "fixed" +name = "Fixed" +showcontent = true + +[[tool.towncrier.type]] +directory = "deprecated" +name = "Deprecated" +showcontent = true + +[[tool.towncrier.type]] +directory = "added" +name = "Added" +showcontent = true + +[[tool.towncrier.type]] +directory = "doc" +name = "Doc" +showcontent = true + +[[tool.towncrier.type]] +directory = "infrastructure" +name = "Infrastructure" +showcontent = true + +[tool.mypy] + +[[tool.mypy.overrides]] +module = [ + "nox", + "pytest", + "tests.*", +] +ignore_missing_imports = true diff --git a/src/deterministic_ml/__init__.py b/src/deterministic_ml/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/deterministic_ml/_internal/__init__.py b/src/deterministic_ml/_internal/__init__.py new file mode 100644 index 0000000..e5b9275 --- /dev/null +++ b/src/deterministic_ml/_internal/__init__.py @@ -0,0 +1,5 @@ +""" +Internal implementation of deterministic_ml. + +This project uses ApiVer, and as such, all imports should be done from v* submodules. +""" diff --git a/src/deterministic_ml/py.typed b/src/deterministic_ml/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/deterministic_ml/v1/__init__.py b/src/deterministic_ml/v1/__init__.py new file mode 100644 index 0000000..e366e76 --- /dev/null +++ b/src/deterministic_ml/v1/__init__.py @@ -0,0 +1,3 @@ +""" +Public interface of the deterministic_ml package. +""" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1 @@ + 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..4343b66 --- /dev/null +++ b/tests/unit/api/test_setup.py @@ -0,0 +1,2 @@ +def test_apiver_exports(apiver_module): + assert sorted(name for name in dir(apiver_module) if not name.startswith("_")) == [] 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_setup.py b/tests/unit/internal/test_setup.py new file mode 100644 index 0000000..ac554c7 --- /dev/null +++ b/tests/unit/internal/test_setup.py @@ -0,0 +1,19 @@ +""" +Test setup tests. + +This test file is here always to indicate that everything was installed and the CI was able to run tests. +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