pyproject.toml
# SPDX-FileCopyrightText: Copyright 2020-2023, Contributors to pocketutils
# SPDX-PackageHomePage: https://github.com/dmyersturnbull/pocketutils
# SPDX-License-Identifier: Apache-2.0
###################
# build-system
###################
[build-system]
requires = ["hatchling~=1.21"]
build-backend = "hatchling.build"
###################
# Project
###################
[project]
name = "pocketutils"
version = "0.10.0"
requires-python = "~=3.11"
readme = {file = "README.md", content-type = "text/markdown"}
description = "Adorable little Python code for you to copy or import."
keywords = ["python", "snippets", "utils", "gists", "bioinformatics"]
maintainers = [
{name="Douglas Myers-Turnbull", email=" dmyersturnbull@gmail.com"}
]
authors = [
{name="Douglas Myers-Turnbull", email=" dmyersturnbull@gmail.com"}
]
license = "Apache-2.0"
homepage = "https://github.com/dmyersturnbull/pocketutils"
repository = "https://github.com/dmyersturnbull/pocketutils"
documentation = "https://dmyersturnbull.github.io/pocketutils"
classifiers = [
"Development Status :: 3 - Alpha",
"Natural Language :: English",
"Programming Language :: Python :: 3 :: Only",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"regex >=2023",
"natsort >=8.4",
"orjson >=3.8",
]
[project.optional-dependencies]
compression = [
"Brotli >=1.1",
"lz4 >=4.0",
"snappy >=3.0",
"zstandard >=0.20",
]
encoding = [
"base2048 >=0.1.0"
]
formats = [
"orjson >=3.8",
"tomlkit >=0.11",
"ruamel.yaml >=0.17"
]
units = [
"pint >=0.20"
]
misc = [
"psutil >=5",
"httpx >=0.20",
]
#===== URLs =====#
[project.urls]
# :tyranno: "https://github.com/${.frag}"
Homepage = "https://github.com/dmyersturnbull/pocketutils"
# :tyranno: "https://github.com/${.frag}"
Source = "https://github.com/dmyersturnbull/pocketutils"
# :tyranno: https://${.vendor}.github.io/${project.name}
Documentation = "https://dmyersturnbull.github.io/pocketutils"
# :tyranno: ${.home}/issues
Tracker = "https://github.com/dmyersturnbull/pocketutils/issues"
# :tyranno: https://pypi.org/project/${project.name}
Download = "https://pypi.org/project/pocketutils/"
# :tyranno: ${.home}/blob/main/CHANGELOG.md
Changelog = "https://github.com/dmyersturnbull/pocketutils/blob/main/CHANGELOG.md"
###################
# Hatch
###################
[tool.hatch.envs.default]
dependencies = [
"pre-commit >=3.6",
"pytest >=7.4",
"tzdata >=2023",
"coverage[toml] >=7.4",
"pytest-cov >=4.1",
"numpy >=1.26",
"loguru >=0.7",
"psutil >=5.9",
"pandas >=2.1",
"ruff >=0.1.13",
"mkdocs >=1.5",
"mike >=2.0",
"mkdocs-literate-nav >=0.6",
"mkdocs-material >=9.5",
"mkdocs-minify-plugin >=0.7",
"mkdocstrings[python] >=0.24",
"dramatiq[watch] >=1.15",
"mkdocs-table-reader-plugin >=2.0",
"mkdocs-include-markdown-plugin >=6.0"
]
#skip-install = true
[tool.hatch.envs.default.scripts]
commit = "cz commit {args}"
test = "pytest"
clean = "tyranno clean"
changelog = "cz changelog"
build-docs = "mkdocs build --clean --strict"
serve-docs = "mike serve --config-file mkdocs.yaml"
deploy-docs = "mike deploy --config-file mkdocs.yaml {args}"
deploy-docs-latest = "mike deploy --config-file mkdocs.yaml {args} latest --update-aliases"
obliterate-docs = [
"mike delete --config-file mkdocs.yaml --all"
]
set-default-docs = "mike set-default --config-file mkdocs.yaml latest"
fmt = "pre-commit run --all-files"
bandit = "ruff --select S {args:.}"
lint = "pre-commit run --all-files"
###################
# Ruff
###################
[tool.ruff]
line-length = 120 # 120 is hatch's default via 'hatch init'
include = [
"*.py", # Source
"*.pyi", # Compiled
"pyproject.toml", # This (RUF checks)
"*.ipynb" # Jupyter notebooks
]
select = [
"A", # flake8-builtins
"ANN", # flake8-annotations
"ASYNC", # flake8-async
"B", # flake8-bugbear
"BLE", # flake8-blind-exception
"C", # flake8-comprehensions
"COM", # flake8-commas
"DTZ", # flake8-datetimez
"EM", # flake8-errmsg
"F", # pyflakes
"FA", # flake8-future-annotations
"E", # flake8 errors (most fixed by black; rest are nitpicky)
"G", # flake8-logging-concat
"I", # isort
"INP", # flake8-no-pep420
"ISC", # flake8-implicit-str-concat
"N", # flake8-naming
"NPY", # numpy-specific rules
"PERF", # perflint
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PL", # pylint
"PTH", # flake8-use-pathlib
"Q", # flake8-quotes
"RUF", # Ruff-specific tests
"S", # bandit
"SIM", # flake8-simplify
"SLOT", # flake8-slots
"T10", # debugger
"TCH", # flake8-type-checking
"TID", # flake8-tidy-imports (mostly bans relative imports)
"UP", # pyupgrade
"W", # warnings (most fixed by Black, but W605 is invalid escape char)
"YTT", # flake8-2020 (unlikely problematic checks for Python version)
"RUF" # Ruff-specific rules
]
ignore = [
"ARG", # flake8-unused (unused parameters are almost always intentional, like when overriding)
"INP001", # missing __init__ -- false positives
"B027", # Allow non-abstract empty methods in abstract base classes
"COM812", # conflicts with formatter
"FBT003", # Allow boolean positional values in function calls, like `dict.get(... True)`
"FBT", # flake8-boolean-trap (debatable, and might not have a choice)
"ICN", # flake8-import-conventionals (isort does this)
"ISC001", # conflicts with formatter
"C901", # Ignore complexity:
"PLR0911",
"PLR0912",
"PLR0913",
"PLR0915",
"D107", # Missing docstring in __init__ (put in class docstring)
"D212", # Multi-line docstring start (contradictory)
"E203", # Colons with space before (sometimes useful expand)
"E225", # Missing whitespace around operator (sometimes useful to condense)
"E501", # Line > 79 chars (we use black)
]
unfixable = [
"RUF100", # Unused noqa (should fix manually)
]
flake8-bandit.check-typed-exception = true
pydocstyle.convention = "google"
# This probably isn't needed
# :tyranno: ["${project.name}"]
#isort.known-first-party = ["pocketutils"]
flake8-tidy-imports.ban-relative-imports = "all"
[tool.ruff.per-file-ignores]
"tests/**/*" = [
"INP001", # missing __init__
"PLR2004", # magic values
"S101", # assert
"TID252", # relative imports
"S105", # Ignore checks for possible passwords
"S106",
"S107",
"S108", # Harcoded temp file
]
###################
# pytest
###################
#[tool.pytest] # in a future version of pytest
[tool.pytest.ini_options]
pythonpath = "./src" # critical!
# coverage stops recursing after it finds one dir without an __init__.py
# so if it finds src/java-app before src/pyapp, it won't find pyapp
# So specify exactly which directories to test
# :tyranno: addopts = "--cov=src/${project.name} --cov-report xml:coverage.xml --cov-report term --cov-config=pyproject.toml"
addopts = "--cov=src/pocketutils --cov-report xml:coverage.xml --cov-report term --cov-config=pyproject.toml tests/"
# show log output from the tests
# in the tests/ code, name the logger {pkg}-TEST to differentiate
log_cli = true
log_cli_level = "INFO"
log_cli_format = "%(asctime)s [%(levelname)8s] %(name)s: %(message)s (%(filename)s:%(lineno)s)"
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
# create markers so we can disable integration, network-connected, or very slow tests if desired
markers = [
"integration: marks integration tests (deselect with '-m \"not integration\"')",
"network: marks tests that require network access (deselect with '-m \"not network\"')"
]
doctest_optionflags = [
"DONT_ACCEPT_TRUE_FOR_1", # 1 is not True
"NORMALIZE_WHITESPACE", # allows us to wrap expected output to 120 lines (ignores newlines)
]
###################
# coverage
###################
[tool.coverage.run]
data_file = "coverage.xml"
branch = true # quantify % coverage of execution branches
parallel = true # note that pytest-cov overrides this
# :tyranno: source_pkgs = ["src/${project.name}", "tests"]
source_pkgs = ["pocketutils"]
omit = [
"src/**/__main__.py"
]
[tool.coverage.paths]
source = ["src/"]
[tool.coverage.report]
fail_under = 20 # 20% coverage required
precision = 1 # n decimal points for coverage %
show_missing = true
exclude_lines = [
"nocov",
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]
###################
# Commitizen
###################
[tool.commitizen]
major_version_zero = true
annotated_tag = true
gpg_sign = true
version_scheme = "semver"
version_provider = "pep621"
bump_message = "chore: bump version $current_version → $new_version"
name = "cz_customize"
[tool.commitizen.customize]
example = "feat(i18n): add Japanese translation"
info = """
We use [Conventional Commits](https://www.conventionalcommits.org/) with the following types.
| Type | Label | Changelog section | semver | Description |
| ----------- | ------------------- | --------------------- | ------ | ----------------------------------- |
| `feat:` | `type: feature` | `✨ Features` | minor | Add or change a feature |
| `fix:` | `type: fix` | `🐛 Bug fixes` | patch | Fix a bug |
| `security:` | `type: security` | `🔒 Security` | minor | Security issue |
| `docs:` | `type: docs` | `📝 Documentation` | patch | Add or modify docs or examples |
| `build:` | `type: build` | `🔧 Build system` | minor | Modify build, including Docker |
| `perf:` | `type: performance` | `⚡️ Performance` | patch | Increase speed / decrease resources |
| `test:` | `type: test` | `🚨 Tests` | N/A | Add or modify tests |
| `refactor:` | `type: refactor` | ignored | N/A | Refactor source code |
| `ci:` | `type: ci` | ignored | N/A | Modify CI/CD |
| `style:` | `type: style` | ignored | N/A | Improve style of source code |
| `chore:` | `type: chore` | ignored | N/A | Change non-source code |
"""
commit_parser = """\
^\
(?P<change_type>feat|fix|security|perf|build|docs|test|refactor|ci|style|chore)\
(?:\\((?P<scope>[-a-z0-9]+)\\))?\
(?P<breaking>!)?\
: (?P<message>[^\n]+)\
.*\
"""
changelog_pattern = "^(feat|fix|security|perf|build|docs)?(!)?"
schema = """
<type>[(<scope>)][!]: <subject>
<body>
[BREAKING CHANGE: <breaking>]
[Closes: #<issue>]
[*: <author>]+
Signed-off-by: <author>
"""
# See https://lore.kernel.org/git/60ad75ac7ffca_2ae08208b@natae.notmuch/
schema_pattern = """\
(?s)"\
(feat|fix|security|perf|build|docs|test|refactor|ci|style|chore)\
(?:\\(\\([-a-z0-9]+)\\))?\
(!)?\
: ([^\n]+)\
\n?\
(?:\nBREAKING CHANGE: [^\n]+))?\
(?:\nCloses: (#\\d+))+\
(?:\n(Co-authored-by: [^\n]+))+\
(?:\n((?:Acked-by|Reviewed-by|Helped-by|Reported-by|Mentored-by|Suggested-by|CC|Noticed-by|Tested-by): [^\n]+))?\
(?:\nSigned-off-by: ([^\n]+))?\
"""
message_template = """\
{{change_type}}\
{% if scope %}{{scope | trim}}{% endif %}\
{% if breaking %}!{% endif %}\
: {{subject | trim}}\
\n{{body | trim}}\
{% if breaking != '' %}\nBREAKING CHANGE: {{breaking | trim}}{% endif %}\
{% if issues %}\
{% set issuelist = issues.split(',') %}\
{% for issue in issuelist %}\nCloses: #{{issue | trim}}{% endfor %}\
{% endif %}\
{% if trailers %}\
{% set trailerslist = trailers.split('||') %}\
{% for trailer in trailerslist %}\n{{trailer | trim}}{% endfor %}\
{% endif %}\
"""
bump_pattern = "^(feat|fix|security|perf|build|docs)"
change_type_order = [
"breaking",
"security",
"feat",
"fix",
"perf",
"build",
"docs",
"test",
"refactor",
"ci",
"style",
"chore"
]
[tool.commitizen.customize.bump_map]
breaking = "MAJOR"
feat = "MINOR"
fix = "PATCH"
security = "PATCH"
perf = "PATCH"
build = "PATCH"
docs = "PATCH"
refactor = "PATCH"
[tool.commitizen.customize.change_type_map]
security = "🔒 Security"
feat = "✨ Features"
fix = "🐛 Bug fixes"
perf = "⚡️ Performance"
build = "🔧 Build"
docs = "📚 Documentation"
refactor = "⛵ Miscellaneous"
test = "⛵ Miscellaneous"
[[tool.commitizen.customize.questions]]
type = "list"
name = "change_type"
message = "Select the type of change you are committing"
choices = [
{value = "feat", name = "feat: A new feature.", key="f"},
{value = "fix", name = "fix: A bug fix.", key="x"},
{value = "security", name = "security: An exploit fix.", key="v"},
{value = "perf", name = "perf: A performance improvement.", key="p"},
{value = "build", name = "build: A build system change.", key="b"},
{value = "test", name = "test: Add/change tests.", key="t"},
{value = "docs", name = "docs: A change to documentation.", key="d"},
{value = "refactor", name = "refactor: A code refactoring.", key="r"},
{value = "ci", name = "ci/cd: A change to CI/CD.", key="c"},
{value = "style", name = "code style: A change to code style.", key="s"},
{value = "chore", name = "chore: A change to something non-code.", key="z"},
]
[[tool.commitizen.customize.questions]]
type = "input"
name = "subject"
message = "A short, imperative summary: (lowercase and no period).\n"
[[tool.commitizen.customize.questions]]
type = "list"
name = "scope"
message = "Scope (press [enter] to skip).\n"
choices = [
{value = "", name = "[none]"},
{value = "i18n", name = "i18n: Internationalization."},
{value = "plugins", name = "plugins: Plugins."}
]
[[tool.commitizen.customize.questions]]
type = "input"
name = "body"
message = "Body. Additional information: (press [enter] to skip)\n"
[[tool.commitizen.customize.questions]]
type = "input"
name = "breaking"
message = "If a breaking change, provide details: (press [enter] to skip)\n"
[[tool.commitizen.customize.questions]]
type = "input"
name = "issues"
message = "Closed issues, separated by commas: (press [enter] to skip)\n"
[[tool.commitizen.customize.questions]]
type = "input"
name = "trailers"
message = "Git trailers, separated by '||'. Each must be in the form '<key>: <value>'; e.g., 'Reviewed-by: John Johnson <john@git.com>': (press [enter] to skip)\n"