dmyersturnbull/pocketutils

View on GitHub
pyproject.toml

Summary

Maintainability
Test Coverage
# 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"