-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpyproject.toml
More file actions
243 lines (226 loc) · 8.9 KB
/
Copy pathpyproject.toml
File metadata and controls
243 lines (226 loc) · 8.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
[build-system]
requires = ["setuptools>=77", "setuptools_scm>=8"]
build-backend = "setuptools.build_meta"
[project]
name = "cronstable"
description = "A modern, distributed, fault-tolerant, highly available, leader-electing, container-friendly, highly configurable, precompiled, multi-architecture, portable, security-hardened, production-ready cron replacement."
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
license-files = ["LICENSE", "TRADEMARKS.md", "LICENSING.md"]
authors = [{ name = "Parker Loflin", email = "parker@cronstable.dev" }]
maintainers = [{ name = "Parker Loflin", email = "parker@cronstable.dev" }]
keywords = [
"cron",
"crontab",
"scheduler",
"job-scheduler",
"task-scheduler",
"scheduling",
"cron-replacement",
"cronjob",
"container",
"docker",
"kubernetes",
"k8s",
"daemon",
"yaml",
"devops",
"sre",
"sysadmin",
"sentry",
"statsd",
"prometheus",
"metrics",
"monitoring",
"retry",
"timezone",
"asyncio",
]
dynamic = ["version"]
dependencies = [
"strictyaml>=1.7,<2",
"aiohttp>=3.10,<4",
"sentry-sdk>=2,<3",
"aiosmtplib>=3,<6",
"jinja2>=3,<4",
# zoneinfo's tz database, so timezones resolve on minimal/slim images
# that don't ship the system tzdata.
"tzdata>=2024.1",
# per-run CPU/memory accounting (opt-in `monitorResources`; see
# cronstable.resources). psutil gives a portable, process-tree-aware read of
# a job's CPU time and resident memory on Linux, macOS and Windows alike --
# asyncio's own subprocess reaping only surfaces the exit code. The feature
# is off by default, but the import is unconditional so `cronstable --help`
# and the type checker resolve it everywhere; it ships wheels for the
# mainstream targets and builds from source elsewhere.
"psutil>=5.9",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: System Administrators",
"Natural Language :: English",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Environment :: Console",
"Framework :: AsyncIO",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS",
"Operating System :: Microsoft :: Windows",
"Topic :: System :: Systems Administration",
"Topic :: System :: Monitoring",
"Topic :: Office/Business :: Scheduling",
"Topic :: Utilities",
]
[project.urls]
Homepage = "https://github.com/ptweezy/cronstable"
Documentation = "https://github.com/ptweezy/cronstable#readme"
Source = "https://github.com/ptweezy/cronstable"
Changelog = "https://github.com/ptweezy/cronstable/releases"
Issues = "https://github.com/ptweezy/cronstable/issues"
Container = "https://github.com/ptweezy/cronstable/pkgs/container/cronstable"
[project.scripts]
cronstable = "cronstable.__main__:main"
[project.optional-dependencies]
# Optional: use the official Kubernetes client for the `kubernetes` leadership
# backend. Entirely optional -- without it (or on an architecture where its
# grpc/protobuf wheels are unavailable) the backend auto-falls back to its
# hand-rolled apiserver REST transport over the core aiohttp dependency, so the
# core install stays zero-new-dep and architecture-portable. The etcd backend
# needs no extra: it always uses etcd's v3 gRPC-gateway JSON/HTTP API directly.
kubernetes = ["kubernetes>=27"]
# Optional: swap asyncio's event loop for uvloop's faster libuv-based one
# (cluster HTTP, web dashboard, Prometheus scrape). Best-effort at runtime --
# cronstable falls back to stock asyncio when it is absent -- so it stays off the
# core install to keep the zero-new-dep, architecture-portable baseline intact.
# Gated off Windows, which has no uvloop build (and needs the Proactor loop for
# subprocesses anyway); pip skips it there rather than failing to resolve.
speedups = [
"uvloop>=0.19; sys_platform != 'win32'",
# orjson accelerates the durable-state persistence and cluster-gossip JSON
# paths (see cronstable._json, which falls back to the stdlib json when it is
# absent). Like uvloop it is best-effort: it ships no wheels for some of
# the leaner architectures we target (riscv64, armv6, musl ppc64le/s390x),
# so it stays optional and the zero-new-dep, architecture-portable core
# install runs on stock json without it. It does have Windows wheels, so
# (unlike uvloop) it needs no platform marker.
"orjson>=3.9",
]
# End-to-end encrypted push alerts (the `push` reporter; see cronstable.push).
# PyNaCl bundles libsodium (pure C, no Rust), ships abi3 wheels for every
# mainstream target incl. win-arm64, and source-builds with a plain C
# toolchain on the leaner arches. Optional so the zero-new-dep,
# architecture-portable core install holds; UNLIKE the speedups extras there
# is no silent runtime fallback: a config with a `push:` section refuses to
# start when this is absent (an alerting channel must fail closed, not
# self-disable). See config._validate_push_config.
push = ["pynacl>=1.5"]
# Zero-config LAN discovery (the opt-in `web.bonjour` mDNS advert; see
# cronstable.discovery). Same fail-closed contract: web.bonjour without this
# installed is a ConfigError, never a silent no-advert.
discovery = ["zeroconf>=0.132"]
dev = [
# only the cluster mTLS tests use it, and it has no win-arm64 wheel; see
# requirements_dev.txt for why it's gated to other platforms.
"cryptography; platform_machine != 'ARM64' or sys_platform != 'win32'",
# security linter; bandit[toml] reads [tool.bandit] from pyproject.toml.
"bandit[toml]",
"mypy",
"mypy-extensions",
# validates docs/openapi.yaml (.github/scripts/check_openapi.py, tox -e openapi).
"openapi-spec-validator",
"pytest",
"pytest-asyncio",
"pytest-cov",
"ruff",
"tox",
# tox-uv routes tox's env creation + installs through uv (see tox.ini).
"tox-uv",
]
[tool.setuptools]
# The kubernetes/etcd leadership backends talk to their store over plain HTTP
# via the core aiohttp dependency (no kubernetes/etcd3 client, no grpc), so
# they add no runtime dependency and need no optional extra -- which also keeps
# cronstable's wide architecture coverage intact.
packages = ["cronstable", "cronstable.web", "cronstable.backends"]
[tool.setuptools.package-data]
"cronstable.web" = ["*.html"]
# Third-party license notices ride inside every artifact (wheel, Docker,
# and the frozen binaries via the spec's collect_data_files), so the
# LGPL notice for the bundled python-zeroconf is never separated from
# the code it covers. Printed by `cronstable --third-party-licenses`.
"cronstable" = ["licenses/*.txt"]
[tool.setuptools_scm]
version_file = "cronstable/version.py"
[tool.black]
line-length = 79
target_version = ['py310', 'py311', 'py312', 'py313']
[tool.ruff]
target-version = "py310"
line-length = 79
[tool.ruff.lint]
select = [
# Bugbear warnings
"B",
"B9",
# mccabe complexity warnings
"C",
# Pycodestyle errors https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes
"E",
# Pyflakes violations https://flake8.pycqa.org/en/latest/user/error-codes.html
"F",
# Pycodestyle warnings
"W",
# pyupgrade
# "UP",
"I", # <= import sorting
]
[tool.ruff.lint.mccabe]
max-complexity = 20
[tool.mypy]
python_version = "3.10"
# Type-check against the POSIX API surface (grp/pwd/os.setuid/...), which is
# where those calls actually run; the Windows-specific branches are guarded at
# runtime (sys.platform checks / getattr) and covered by the test suite. CI
# runs mypy on Linux, so pinning this keeps local runs on any OS consistent.
platform = "linux"
no_implicit_optional = true
warn_no_return = true
warn_return_any = true
strict_optional = true
[[tool.mypy.overrides]]
# The optional-extra deps are imported behind try/except guards (orjson in
# cronstable._json, uvloop in cronstable.__main__, pynacl in cronstable.push,
# zeroconf in cronstable.discovery), so a checkout without the extras
# installed must still type-check cleanly.
module = [
"orjson",
"uvloop",
"nacl",
"nacl.*",
"zeroconf",
"zeroconf.*",
]
ignore_missing_imports = true
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
[tool.coverage.run]
source = ["cronstable"]
branch = true
omit = ["cronstable/version.py"]
[tool.coverage.report]
show_missing = true
[tool.bandit]
exclude_dirs = ["tests"]
# B104's only hits are the literal "0.0.0.0"/"::" strings in config.py's
# wildcard-listen match-sets (_WILDCARD_LISTEN_HOSTS and friends): data used to
# recognize a wildcard listen address, not a socket bind. The real listen host
# is user-configured and never a literal here, so B104 yields only false
# positives. Bandit still runs at medium+; every other check stays active.
skips = ["B104"]