xrdcore 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xrdcore-0.4.0/.github/workflows/ci.yml +60 -0
- xrdcore-0.4.0/.github/workflows/publish.yml +44 -0
- xrdcore-0.4.0/.gitignore +27 -0
- xrdcore-0.4.0/CHANGELOG.md +158 -0
- xrdcore-0.4.0/CONTRIBUTING.md +200 -0
- xrdcore-0.4.0/COPYING +674 -0
- xrdcore-0.4.0/LICENSE +165 -0
- xrdcore-0.4.0/PKG-INFO +95 -0
- xrdcore-0.4.0/README.md +57 -0
- xrdcore-0.4.0/SECURITY.md +82 -0
- xrdcore-0.4.0/pyproject.toml +179 -0
- xrdcore-0.4.0/src/xrdcore/__init__.py +87 -0
- xrdcore-0.4.0/src/xrdcore/_version.py +3 -0
- xrdcore-0.4.0/src/xrdcore/cancel.py +242 -0
- xrdcore-0.4.0/src/xrdcore/checksum.py +195 -0
- xrdcore-0.4.0/src/xrdcore/cli.py +234 -0
- xrdcore-0.4.0/src/xrdcore/crc32c.py +116 -0
- xrdcore-0.4.0/src/xrdcore/crc64.py +95 -0
- xrdcore-0.4.0/src/xrdcore/errors.py +153 -0
- xrdcore-0.4.0/src/xrdcore/fanout.py +254 -0
- xrdcore-0.4.0/src/xrdcore/log.py +299 -0
- xrdcore-0.4.0/src/xrdcore/pipeline.py +273 -0
- xrdcore-0.4.0/src/xrdcore/py.typed +0 -0
- xrdcore-0.4.0/src/xrdcore/report.py +748 -0
- xrdcore-0.4.0/src/xrdcore/retry.py +178 -0
- xrdcore-0.4.0/src/xrdcore/safexml.py +81 -0
- xrdcore-0.4.0/src/xrdcore/sockets.py +47 -0
- xrdcore-0.4.0/tests/test_cancel.py +106 -0
- xrdcore-0.4.0/tests/test_checksum.py +96 -0
- xrdcore-0.4.0/tests/test_cli.py +212 -0
- xrdcore-0.4.0/tests/test_crc.py +169 -0
- xrdcore-0.4.0/tests/test_fanout.py +246 -0
- xrdcore-0.4.0/tests/test_log.py +152 -0
- xrdcore-0.4.0/tests/test_package.py +104 -0
- xrdcore-0.4.0/tests/test_pipeline.py +167 -0
- xrdcore-0.4.0/tests/test_pipeline_shared.py +209 -0
- xrdcore-0.4.0/tests/test_release_metadata.py +20 -0
- xrdcore-0.4.0/tests/test_report.py +524 -0
- xrdcore-0.4.0/tests/test_retry.py +133 -0
- xrdcore-0.4.0/tests/test_safexml.py +222 -0
- xrdcore-0.4.0/tests/test_scorecard.py +18 -0
- xrdcore-0.4.0/tests/test_sockets.py +56 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# Every gate is one of xrd-devtools' reusable workflows (rob-c/xrd-devtools,
|
|
4
|
+
# .github/workflows): the same steps in every repository of the chain, each
|
|
5
|
+
# reading its configuration from this repository's pyproject.toml.
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
pull_request:
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
inputs:
|
|
12
|
+
devtools-ref:
|
|
13
|
+
description: "rob-c/xrd-devtools ref whose gates and tools run"
|
|
14
|
+
default: main
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
# ruff, mypy --strict, complexipy, the take-over scorecard and the
|
|
25
|
+
# configuration conformance check. ``fast`` too: google-crc32c ships its
|
|
26
|
+
# own types, and mypy checks the accelerated path against them.
|
|
27
|
+
static:
|
|
28
|
+
uses: rob-c/xrd-devtools/.github/workflows/static.yml@main
|
|
29
|
+
with:
|
|
30
|
+
devtools-ref: ${{ inputs.devtools-ref || 'main' }}
|
|
31
|
+
extras: dev,fast
|
|
32
|
+
|
|
33
|
+
# The CRC-32C accelerator is optional: the suite must pass, at the same
|
|
34
|
+
# 100 %, with and without it, on Linux and macOS, Python 3.11-3.14.
|
|
35
|
+
test:
|
|
36
|
+
uses: rob-c/xrd-devtools/.github/workflows/tests.yml@main
|
|
37
|
+
with:
|
|
38
|
+
devtools-ref: ${{ inputs.devtools-ref || 'main' }}
|
|
39
|
+
extras: '["dev", "dev,fast"]'
|
|
40
|
+
|
|
41
|
+
# The wheel holds only the package; installed into a clean environment it
|
|
42
|
+
# imports every public module, with nothing else installed (no
|
|
43
|
+
# dependencies), and the ``fast`` extra brings the accelerator.
|
|
44
|
+
package:
|
|
45
|
+
uses: rob-c/xrd-devtools/.github/workflows/package.yml@main
|
|
46
|
+
with:
|
|
47
|
+
package: xrdcore
|
|
48
|
+
devtools-ref: ${{ inputs.devtools-ref || 'main' }}
|
|
49
|
+
post-install: |
|
|
50
|
+
"$PY" -m pip install --only-binary=:all: "$(ls "$DIST"/*.whl)[fast]"
|
|
51
|
+
"$PY" -m pip check
|
|
52
|
+
"$PY" -I -c 'from xrdcore import crc32c; assert crc32c.IS_ACCELERATED and crc32c.crc32c(b"123456789") == 0xE3069283'
|
|
53
|
+
|
|
54
|
+
# No function duplicated across the chain; every repository's configuration
|
|
55
|
+
# is the standard. This commit stands in for xrdcore's main.
|
|
56
|
+
chain:
|
|
57
|
+
uses: rob-c/xrd-devtools/.github/workflows/chain.yml@main
|
|
58
|
+
with:
|
|
59
|
+
xrdcore-ref: ${{ github.sha }}
|
|
60
|
+
devtools-ref: ${{ inputs.devtools-ref || 'main' }}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Builds the sdist and wheel on a version tag and uploads them to PyPI through
|
|
2
|
+
# trusted publishing (no API token: PyPI trusts this workflow's OIDC identity,
|
|
3
|
+
# registered under the project's "Publishing" settings, with the "pypi"
|
|
4
|
+
# environment). xrdcore is the bottom of the chain and depends on nothing, so
|
|
5
|
+
# it is published first; xrdsec's and xrdhttp's publish jobs then install
|
|
6
|
+
# their wheels against this one from PyPI. The chain's runbook is xrdclient's
|
|
7
|
+
# docs/releasing.md ("Releasing the 0.4.0 chain").
|
|
8
|
+
name: Publish
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
tags: ["v*"]
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
# Every step but the upload is xrd-devtools' shared publish action: build,
|
|
19
|
+
# `twine check --strict`, the artifact contents (check_dist), then a clean
|
|
20
|
+
# install that imports every public module, has no default dependency, and
|
|
21
|
+
# whose tag, runtime version and dated changelog section agree.
|
|
22
|
+
build:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: rob-c/xrd-devtools/.github/actions/publish@main
|
|
26
|
+
with:
|
|
27
|
+
package: xrdcore
|
|
28
|
+
check-dist: true
|
|
29
|
+
no-dependencies: true
|
|
30
|
+
|
|
31
|
+
# Trusted publishing ties PyPI's trust to this job of this workflow, so the
|
|
32
|
+
# upload stays here, in a job that only downloads what the build staged.
|
|
33
|
+
publish:
|
|
34
|
+
needs: build
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
environment: pypi
|
|
37
|
+
permissions:
|
|
38
|
+
id-token: write
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/download-artifact@v4
|
|
41
|
+
with:
|
|
42
|
+
name: dist
|
|
43
|
+
path: dist/
|
|
44
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
xrdcore-0.4.0/.gitignore
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
|
|
4
|
+
# Build artefacts
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
site/
|
|
9
|
+
|
|
10
|
+
# Tooling caches and coverage data
|
|
11
|
+
.coverage
|
|
12
|
+
.coverage.*
|
|
13
|
+
htmlcov/
|
|
14
|
+
.hypothesis/
|
|
15
|
+
.mypy_cache/
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
.tox/
|
|
19
|
+
.venv/
|
|
20
|
+
venv/
|
|
21
|
+
|
|
22
|
+
# Generated datasets
|
|
23
|
+
/datasets/
|
|
24
|
+
|
|
25
|
+
# CI checkouts of sibling repositories (xrd-devtools, the library candidates)
|
|
26
|
+
/.dependencies/
|
|
27
|
+
.complexipy_cache/
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable user-visible changes are recorded here. This project follows
|
|
4
|
+
[Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [0.4.0] - 2026-10-19
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- First release: the shared core extracted from xrdclient 0.4.0 so that
|
|
11
|
+
xrdclient, xgfalclient and xrdarc stop importing each other's private
|
|
12
|
+
modules.
|
|
13
|
+
- `xrdcore.safexml` (was `xrdclient._xml`).
|
|
14
|
+
- `xrdcore.checksum`, `xrdcore.crc32c`, `xrdcore.crc64` (were
|
|
15
|
+
`xrdclient.crypto.checksum`, `.crc32c`, `.crc64`). The pure-Python CRC-32C
|
|
16
|
+
is public as `crc32c_py`; `Digest` names either kind of digest. XRootD's
|
|
17
|
+
4 KiB page framing (`pack_pages`, `unpack_pages`, `page_span`) stays with
|
|
18
|
+
the XRootD client.
|
|
19
|
+
- `xrdcore.checksum.ChecksumInfo`: the one `algorithm:value` type for a
|
|
20
|
+
reported checksum (was `xrdclient.types.ChecksumInfo` and a look-alike in
|
|
21
|
+
`xrdhttp.metalink`), so checksums from every protocol compare equal.
|
|
22
|
+
- `xrdcore.log` (was `xrdclient._log`). `get_logger()` keeps the caller's
|
|
23
|
+
logger name instead of forcing an `xrdclient.` prefix; the filter is public
|
|
24
|
+
as `RedactingFilter`/`FILTER`.
|
|
25
|
+
- `xrdcore.cancel` (was `xrdclient._cancel`); its context variable is now the
|
|
26
|
+
one every library looks at.
|
|
27
|
+
- `xrdcore.pipeline` (was `xrdclient.copy._pipeline`).
|
|
28
|
+
- `xrdcore.report` (was `xrdclient.cli._output`), with `dumps()` and
|
|
29
|
+
`register_text_type()` in place of its dependency on `XRootDURL`.
|
|
30
|
+
- `xrdcore.retry` (was `xrdclient.retry`, née `_retry`): `ceiling`, `delay`,
|
|
31
|
+
`pause`, `use_rng` and `retry_after`.
|
|
32
|
+
- `xrdcore.errors`: `XrdError`, `ProtocolError`, `MetalinkError`,
|
|
33
|
+
`ConnectionError`, `TransientError`, `TimeoutError`, `AuthenticationError`,
|
|
34
|
+
`CredentialError`, `TokenExpiredError`, `InsecureCredentialError`.
|
|
35
|
+
- Release readiness: the suite gates coverage at 100 % line and branch
|
|
36
|
+
(`fail_under = 100`), with the CRC-32C accelerator probe, the safe-XML
|
|
37
|
+
guard (every encoding, both ElementTree parsers, hostile documents) and the
|
|
38
|
+
report (both formats, captured streams, exit-code policy) tested here
|
|
39
|
+
rather than in xrdclient; the wheel ships `py.typed` (PEP 561).
|
|
40
|
+
- xrdclient 0.4.0 is built on this package: its old module names are aliases
|
|
41
|
+
of these modules, and its shared error names are these classes.
|
|
42
|
+
- `xrdcore.fanout` (was `xrdclient._fanout`): `run_all()` and `Stopped`, the
|
|
43
|
+
thread fan-out behind xrdclient's bulk download, parallel copy and
|
|
44
|
+
multi-replica reader, xgfalclient's parallel ranged HTTP download (which
|
|
45
|
+
had its own pool) and xrdarc's `get` (likewise). One exception object
|
|
46
|
+
raised by several jobs is now reported once, not noted on itself.
|
|
47
|
+
- `xrdcore.cli`: the exit statuses xrdarc, xrdwms and the storage clients'
|
|
48
|
+
tools share (`ExitCode`: 0, 1, 2, 70, 130, 141, from xrdarc's
|
|
49
|
+
`cli._common.ExitCode` and xrdwms's `cli.ExitCode`), `extends()` checking
|
|
50
|
+
a tool's own table keeps them, `run()` (Ctrl-C → 130, `BrokenPipeError` →
|
|
51
|
+
a quiet 141, everything else to the tool's handler) and `silence_stdout()`
|
|
52
|
+
(was xrdarc's `cli._common.silence_stdout` and xgfalclient's
|
|
53
|
+
`cli._base._silence_stdout`). `report.run_cli` takes its 130 from it.
|
|
54
|
+
- `xrdcore.errors.rebuild`, the unpickler the clients' own exceptions reduce
|
|
55
|
+
through, is public (it was `_rebuild`, which xrdclient imported anyway).
|
|
56
|
+
- `xrdcore.cli.Fatal`, the handler `run()` hands any other failure to (was
|
|
57
|
+
xrdarc's `cli._fatal` and xrdwms's `cli._fatal`, near copies): an `error`
|
|
58
|
+
record under `--json`/`--xml`, one redacted `prog: message` line on
|
|
59
|
+
stderr, the status from the tool's exception table (`exit_code()`, rows of
|
|
60
|
+
`ExitRule`: a class and a code, or a function of the exception), and a
|
|
61
|
+
bug's traceback logged at DEBUG instead of printed; `Fatal.interrupted`
|
|
62
|
+
says `prog: interrupted` for `run`'s Ctrl-C. xrdclient's `cli.fail` is one.
|
|
63
|
+
- `xrdcore.log.configure()`, `verbosity_level()`, `VERBOSITY` and
|
|
64
|
+
`Configured`: a command-line tool's `-v` handler (was built separately by
|
|
65
|
+
xrdarc's `cli._common`, xrdclient's `cli.configure_logging` and
|
|
66
|
+
xgfalclient's `cli._base._Logging`; xrdwms had none). One handler on the
|
|
67
|
+
loggers named, on a given stream or whatever `sys.stderr` is at each
|
|
68
|
+
record, with `FILTER`'s redaction and muting on every record it writes
|
|
69
|
+
(third-party loggers included), replacing its own earlier handler rather
|
|
70
|
+
than adding a second; `Configured.close()` undoes it. Its first argument
|
|
71
|
+
is a level, not a `-v` count, because the tools count on different scales
|
|
72
|
+
(`verbosity_level(count, scale)`) and xrdarc's `-d LEVEL` names one.
|
|
73
|
+
- `xrdcore.fanout.Pool`: the long-lived thread pool for work that is not a
|
|
74
|
+
fan-out (xrdclient's asyncio facade and pyxrootd callback dispatcher,
|
|
75
|
+
xrdwms's backend threads and dask executor). Made on first use, sized
|
|
76
|
+
then (the size may be a callable reading configuration), named, and made
|
|
77
|
+
again in a forked child, whose copy of the parent's executor has no
|
|
78
|
+
threads. With `run_all` it is where the chain makes its thread pools: the
|
|
79
|
+
clients' lint bans `ThreadPoolExecutor` and names these two.
|
|
80
|
+
- `xrdcore.report.parse(text, output_format)`: the format's reader, giving
|
|
81
|
+
back a finished document of either format as the one JSON tree (XML
|
|
82
|
+
through `safexml`, encoded strings and keys decoded). It replaces the
|
|
83
|
+
`xml_value`/`decode` helpers that xrdcore's, xrdclient's and xgfalclient's
|
|
84
|
+
report tests each carried.
|
|
85
|
+
- `xrdcore.sockets`: `recv_exact(sock, count)`, the exactly-N-bytes read
|
|
86
|
+
every framed stream protocol loops on, raising `ShortRead` (an
|
|
87
|
+
`xrdcore.errors.ConnectionError`, so an `OSError`) with how far it got.
|
|
88
|
+
It replaces the copies in xrdsec's KDC client and xgfalclient's dcap fake.
|
|
89
|
+
|
|
90
|
+
### Packaging
|
|
91
|
+
|
|
92
|
+
- CI (`.github/workflows/ci.yml`): ruff, `mypy --strict` and the family's
|
|
93
|
+
take-over scorecard (`tests/test_scorecard.py`, measured with
|
|
94
|
+
xrd-devtools' scorecard plugin; `complexipy` and `radon` joined the `dev`
|
|
95
|
+
extra); the suite at 100 % on Linux and macOS, Python 3.11-3.14, with and
|
|
96
|
+
without the `fast` extra; and a package job that builds the sdist and
|
|
97
|
+
wheel, checks their contents (xrd-devtools' `check_dist.py`: no caches,
|
|
98
|
+
coverage data or stray top-level files; `py.typed` present), installs the
|
|
99
|
+
wheel into a clean environment and imports every public module from it.
|
|
100
|
+
- Publishing (`.github/workflows/publish.yml`): a `v<version>` tag builds,
|
|
101
|
+
checks that tag, runtime version, wheel metadata and a dated changelog entry
|
|
102
|
+
agree and that the wheel has no default dependency, and uploads to PyPI by
|
|
103
|
+
trusted publishing. xrdcore is the first package of the 0.4.0 chain to be
|
|
104
|
+
published; xrdclient's `docs/releasing.md` is the chain's runbook.
|
|
105
|
+
- `CONTRIBUTING.md` and `SECURITY.md`.
|
|
106
|
+
- The chain's tooling moved to the new
|
|
107
|
+
[xrd-devtools](https://github.com/rob-c/xrd-devtools) repository, used from
|
|
108
|
+
a checkout (it is not on PyPI and cannot be a dependency):
|
|
109
|
+
`tests/test_scorecard.py` is now a few lines loading its scorecard plugin
|
|
110
|
+
(found through pytest's `pythonpath`: `../xrd-devtools/src` or CI's
|
|
111
|
+
`.dependencies/xrd-devtools/src`; it skips, saying so, without one), in
|
|
112
|
+
place of the copy that located xrdclient's `tools/scorecard.py` through
|
|
113
|
+
`XRDCLIENT_CHECKOUT`/`SCORECARD_REQUIRED`. `.github/scripts/check_dist.py`,
|
|
114
|
+
identical in the three libraries, is xrd-devtools' `check_dist.py`; ruff,
|
|
115
|
+
mypy and complexipy no longer list `.github/scripts`.
|
|
116
|
+
- CI (`.github/workflows/ci.yml`) calls xrd-devtools' reusable workflows -
|
|
117
|
+
`static.yml` (ruff, mypy, complexipy, the scorecard, made required, and
|
|
118
|
+
`xrddevtools conformance`), `tests.yml`, `package.yml` and `chain.yml` (the
|
|
119
|
+
duplicate and conformance gates over all seven repositories) - with the
|
|
120
|
+
candidates from its one candidates action; `publish.yml` checks
|
|
121
|
+
xrd-devtools out to run `check_dist.py`. CI therefore needs
|
|
122
|
+
`rob-c/xrd-devtools` to exist.
|
|
123
|
+
|
|
124
|
+
### Code health
|
|
125
|
+
|
|
126
|
+
- No grandfathered code: ruff's full rule set (`SIM` added) with nothing
|
|
127
|
+
ignored, `mypy --strict` over the tests and release scripts as well as
|
|
128
|
+
the package, complexipy at 15 with no snapshot, and every warning an
|
|
129
|
+
error under pytest. The suppressions are gone (6 `type: ignore`, 1 `noqa`)
|
|
130
|
+
and so is the untyped-import exemption for google-crc32c, which ships its
|
|
131
|
+
own types. `errors`' pickling helpers are typed; the private
|
|
132
|
+
`safexml._guard_expat` and `pipeline._abort` take any object, as they
|
|
133
|
+
always looked at it by attribute only.
|
|
134
|
+
- Banned APIs (ruff `TID251`; CONTRIBUTING.md explains the list): xrdcore
|
|
135
|
+
imports no third-party package, no protocol module (`http.client`,
|
|
136
|
+
`ssl`) and nothing above it in the chain (xrdsec, xrdhttp, the clients).
|
|
137
|
+
It owns `ThreadPoolExecutor` (`fanout`) and the XML parsers (`safexml`),
|
|
138
|
+
which the other repositories ban in its favour.
|
|
139
|
+
- Release metadata is xrd-devtools' release gate:
|
|
140
|
+
`tests/test_release_metadata.py` is `release_gate(__file__)` from
|
|
141
|
+
`xrddevtools.pytest_plugin` - one version source (`_version.py`, read by
|
|
142
|
+
hatch), a CHANGELOG led by exactly that version's well-formed section,
|
|
143
|
+
project metadata and README that name this repository, and a release
|
|
144
|
+
workflow built through the shared publish action.
|
|
145
|
+
- The publish workflow's build job is xrd-devtools' shared `publish`
|
|
146
|
+
composite action (build, `twine check --strict`, the artifact or
|
|
147
|
+
pre-flight checks, a clean install whose tag, runtime version and dated
|
|
148
|
+
changelog section agree, the artifact upload); only the trusted-publishing
|
|
149
|
+
upload to PyPI stays in this workflow's own job.
|
|
150
|
+
- The standard's settings in `pyproject.toml` (ruff rules, the banned-API
|
|
151
|
+
table, mypy's strictness and allowed overrides, complexipy, coverage,
|
|
152
|
+
pytest's warnings filter) and CONTRIBUTING's *The code standard*, *Banned
|
|
153
|
+
APIs* table and *xrd-devtools* sections are managed blocks, written by
|
|
154
|
+
`xrddevtools sync` from the standard kept once in xrd-devtools;
|
|
155
|
+
`xrddevtools conformance` fails a hand edit between the markers. The
|
|
156
|
+
repository's own keys and notes sit beside the blocks.
|
|
157
|
+
- `[project.urls]` (Homepage, Documentation, Repository, Issues,
|
|
158
|
+
Changelog), as every other package of the chain has.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Contributing to xrdcore
|
|
2
|
+
|
|
3
|
+
xrdcore is the dependency-free bottom layer of the storage-client family:
|
|
4
|
+
xrdsec, xrdhttp, xrdclient, xgfalclient and xrdarc all import it, and every
|
|
5
|
+
one of their `--help` screens loads it. Changes are welcome when they keep the
|
|
6
|
+
three properties it is built around: it needs nothing but the standard library
|
|
7
|
+
to import (the `fast` extra only accelerates CRC-32C); it holds only what more
|
|
8
|
+
than one package of the family needs and nothing protocol-specific; and it
|
|
9
|
+
depends on no other package of the family, ever.
|
|
10
|
+
|
|
11
|
+
## Development setup
|
|
12
|
+
|
|
13
|
+
Python 3.11 through 3.14 are supported. xrdcore has no dependencies, so a
|
|
14
|
+
checkout on its own is a complete development environment:
|
|
15
|
+
|
|
16
|
+
```console
|
|
17
|
+
$ python3 -m venv .venv
|
|
18
|
+
$ .venv/bin/python -m pip install -U pip
|
|
19
|
+
$ .venv/bin/python -m pip install -e '.[dev,fast]'
|
|
20
|
+
$ .venv/bin/pytest -q -n auto
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`pyproject.toml` puts `src` on the test path, so a suite run from the shared
|
|
24
|
+
workspace virtual environment needs nothing installed. The suite is hermetic.
|
|
25
|
+
Run it once without the `fast` extra as well: the CRC-32C tests then cover the
|
|
26
|
+
pure-Python path, and CI runs both.
|
|
27
|
+
`mypy` wants the `fast` extra installed: google-crc32c ships its own types,
|
|
28
|
+
and the accelerated path is checked against them.
|
|
29
|
+
|
|
30
|
+
## Definition of done
|
|
31
|
+
|
|
32
|
+
A change is ready when all of these pass, as CI runs them:
|
|
33
|
+
|
|
34
|
+
```console
|
|
35
|
+
$ ruff check src tests
|
|
36
|
+
$ ruff format --check src tests
|
|
37
|
+
$ mypy
|
|
38
|
+
$ complexipy
|
|
39
|
+
$ pytest -q -p no:cacheprovider -n auto --cov --cov-report=term-missing:skip-covered
|
|
40
|
+
$ python -m build && twine check --strict dist/*
|
|
41
|
+
$ python ../xrd-devtools/src/xrddevtools/check_dist.py contents xrdcore dist
|
|
42
|
+
$ xrddevtools conformance --repo .
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- **Coverage is 100 % of lines and branches** (`fail_under = 100`). Reach it
|
|
46
|
+
with a test that makes the case happen - a hostile document, a cancelled
|
|
47
|
+
copy, a missing accelerator - not with an exclusion.
|
|
48
|
+
- **The code standard** (below) holds for every line: nothing suppressed,
|
|
49
|
+
nothing ignored, no snapshot. Fix the code rather than silencing a finding.
|
|
50
|
+
- **Types are `mypy --strict`.** The package ships `py.typed`; its annotations
|
|
51
|
+
are part of the API of every package above it.
|
|
52
|
+
- **The take-over scorecard.** `tests/test_scorecard.py` loads the chain's
|
|
53
|
+
scorecard plugin (`xrddevtools.pytest_plugin`) from an
|
|
54
|
+
[xrd-devtools](https://github.com/rob-c/xrd-devtools) checkout and fails if
|
|
55
|
+
any of the twelve measurements of `src` is worse than the median of 39
|
|
56
|
+
widely used Python libraries. Do not lower the bar to land a change; split
|
|
57
|
+
the function, group its options, or explain the code.
|
|
58
|
+
- **The chain's gates** pass: the scorecard above, the release-metadata gate
|
|
59
|
+
(`tests/test_release_metadata.py`: one version source, a CHANGELOG led by
|
|
60
|
+
that version, metadata that agrees), `xrddevtools conformance` (the
|
|
61
|
+
standard, and every managed block as `xrddevtools sync` writes it) and
|
|
62
|
+
`xrddevtools dupes` (no function copied into another repository) - all
|
|
63
|
+
from [xrd-devtools](#xrd-devtools).
|
|
64
|
+
- **Documentation.** Every module, class and function has a docstring that
|
|
65
|
+
explains purpose, contract, failure modes and why - not a restatement of the
|
|
66
|
+
signature. The README's module table is the package's map; keep it current.
|
|
67
|
+
- **Changelog.** Add user-visible changes to [CHANGELOG.md](CHANGELOG.md) in
|
|
68
|
+
the same pull request, and say which package a moved name came from.
|
|
69
|
+
|
|
70
|
+
## The code standard
|
|
71
|
+
|
|
72
|
+
<!-- >>> xrddevtools sync: code-standard (generated from xrd-devtools/src/xrddevtools/standard) >>> -->
|
|
73
|
+
Every repository of the chain is held to one code standard, and every line
|
|
74
|
+
of it is held to the standard: old code passes the same checks as new code.
|
|
75
|
+
|
|
76
|
+
- **Ruff** selects `E`, `F`, `W`, `I`, `UP`, `B`, `C4`, `SIM`, `RUF`, the
|
|
77
|
+
high-confidence security checks `S102`, `S307`, `S506`, `S602`, `S608`,
|
|
78
|
+
`S609`, `S701` and the banned-API rule `TID251` (see *Banned APIs*), with
|
|
79
|
+
`ignore = []` and no per-file ignores, over `src`, `tests`; `ruff format`
|
|
80
|
+
formats it, 100 columns wide.
|
|
81
|
+
- **mypy** is strict (Python 3.11) over `src`, `tests`. A test function may
|
|
82
|
+
leave its fixtures unannotated where `pyproject.toml` allows it, but its
|
|
83
|
+
body is still checked; only a named third-party module that ships no types
|
|
84
|
+
may be imported untyped, with the reason beside it.
|
|
85
|
+
- **complexipy** allows no function above 15, over `src`, `tests`, with no
|
|
86
|
+
snapshot of old offenders.
|
|
87
|
+
- **Coverage** is 100 % of lines and branches, reached by a test that makes
|
|
88
|
+
each case happen. The only exclusions are `if TYPE_CHECKING:`,
|
|
89
|
+
`@overload`, `raise NotImplementedError`, `\.\.\.$`, `if __name__ ==
|
|
90
|
+
.__main__.:`; there is no `omit`.
|
|
91
|
+
- **Every warning is an error** (`filterwarnings = ["error"]`): ours are
|
|
92
|
+
fixed, not filtered.
|
|
93
|
+
- **No suppression comment**: no `# noqa`, `# pragma: no cover`, `# fmt:
|
|
94
|
+
off`, `# complexipy: ignore` or `# nosec`. A `# type: ignore[code]` only
|
|
95
|
+
where it cannot be avoided (a test passing a wrongly typed value on
|
|
96
|
+
purpose), with its error code and the reason beside it or just above.
|
|
97
|
+
- **No snapshot or baseline** of old findings, for any tool.
|
|
98
|
+
|
|
99
|
+
The settings behind these rules are the managed blocks of `pyproject.toml`
|
|
100
|
+
(between `# >>> xrddevtools sync` markers), and this section is one of the
|
|
101
|
+
managed blocks of this file (between `<!-- >>> xrddevtools sync` markers).
|
|
102
|
+
`xrddevtools sync` writes them all from the standard, kept once in
|
|
103
|
+
xrd-devtools (`src/xrddevtools/standard`); `xrddevtools conformance`, which
|
|
104
|
+
CI runs, fails if one is edited by hand. To change the standard, change it
|
|
105
|
+
there and run `xrddevtools sync --workspace ..` over every repository.
|
|
106
|
+
<!-- <<< xrddevtools sync: code-standard <<< -->
|
|
107
|
+
|
|
108
|
+
## Banned APIs
|
|
109
|
+
|
|
110
|
+
<!-- >>> xrddevtools sync: banned-apis (generated from xrd-devtools/src/xrddevtools/standard) >>> -->
|
|
111
|
+
Ruff's `TID251` rule refuses the APIs whose job another part of the chain
|
|
112
|
+
does, in every tree ruff checks (`src`, `tests`), with no per-file ignore
|
|
113
|
+
and no `noqa`; the list is `[tool.ruff.lint.flake8-tidy-imports.banned-api]`
|
|
114
|
+
in `pyproject.toml`, and each entry's message names what to use instead. A
|
|
115
|
+
need for a banned API is met by the library that owns it growing an API,
|
|
116
|
+
with its tests - never by an exception here.
|
|
117
|
+
|
|
118
|
+
| Banned | Use instead | Why |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `urllib3`, `requests`, `httpx` | the chain has no third-party HTTP stack: use xrdhttp.session.Session (pooling, redirects, retries; TLS from xrdsec.tls) | one HTTP stack for the whole chain, with its TLS, redirect and retry rules |
|
|
121
|
+
| `urllib.request` | use xrdhttp.session.Session: urllib's opener has none of the chain's TLS, redirect or retry rules | urllib's opener knows none of the grid's TLS (CAs, CRLs, proxies) and follows redirects with credentials attached |
|
|
122
|
+
| `gssapi` | Kerberos GSS-API is xrdsec.krb5.gss (the xrdsec[krb5] extra), one wrapper for every client | one GSS-API wrapper, behind the `xrdsec[krb5]` extra |
|
|
123
|
+
| `krb5` | pykrb5 is not a dependency: credential caches are xrdsec.krb5.caches / xrdsec.krb5.ccache, GSS-API is xrdsec.krb5.gss | the pure-Python ccache, KCM and keyring readers replaced it |
|
|
124
|
+
| `botocore`, `boto3` | S3 signing and the S3 codec are xrdhttp.s3 (the xrdhttp[s3] extra); only xrdhttp.s3 imports botocore | S3 signing and the S3 codec, behind the `xrdhttp[s3]` extra |
|
|
125
|
+
| `http.client` | xrdcore speaks no protocol: HTTP is xrdhttp's (xrdhttp.session) | - |
|
|
126
|
+
| `ssl` | xrdcore speaks no protocol: TLS contexts are xrdsec.tls's | - |
|
|
127
|
+
| `cryptography` | xrdcore has no third-party dependency: cryptography belongs in xrdsec | - |
|
|
128
|
+
| `asn1crypto` | xrdcore has no third-party dependency: ASN.1 belongs in xrdsec (xrdsec.der) | - |
|
|
129
|
+
| `xrdsec`, `xrdhttp`, `xrdclient`, `xgfalclient`, `xrdarc`, `xrdwms` | - | they are built on this repository; the dependency direction never points up |
|
|
130
|
+
<!-- <<< xrddevtools sync: banned-apis <<< -->
|
|
131
|
+
|
|
132
|
+
What xrdcore owns, it does not ban: `concurrent.futures.ThreadPoolExecutor`
|
|
133
|
+
(`fanout.run_all` and `fanout.Pool` are built on it) and the
|
|
134
|
+
`xml.etree.ElementTree` parsers (`safexml` is built on them, and its tests
|
|
135
|
+
compare against them). The other repositories ban both, naming these
|
|
136
|
+
modules instead - so a new need there (another kind of pool, another kind
|
|
137
|
+
of XML input) is met by an API here, with its tests, not by an exception
|
|
138
|
+
there.
|
|
139
|
+
|
|
140
|
+
## xrd-devtools
|
|
141
|
+
|
|
142
|
+
<!-- >>> xrddevtools sync: devtools (generated from xrd-devtools/src/xrddevtools/standard) >>> -->
|
|
143
|
+
The chain's gates and tools - the take-over scorecard and the
|
|
144
|
+
release-metadata and maintainability gates, `xrddevtools conformance`,
|
|
145
|
+
`dupes` and `sync`, the release pre-flight and the packaging tools - live in
|
|
146
|
+
[xrd-devtools](https://github.com/rob-c/xrd-devtools). It is never uploaded
|
|
147
|
+
to PyPI and cannot be a dependency (PyPI refuses direct-URL requirements),
|
|
148
|
+
so it is used from a checkout:
|
|
149
|
+
|
|
150
|
+
- **A developer** keeps it beside this repository (`../xrd-devtools`, the
|
|
151
|
+
workspace layout). pytest's `pythonpath` lists `../xrd-devtools/src`, so
|
|
152
|
+
the gate tests find it with nothing installed; `pip install -e
|
|
153
|
+
../xrd-devtools` once gives the `xrddevtools` command.
|
|
154
|
+
- **CI** checks it out under `.dependencies/xrd-devtools` (the shared
|
|
155
|
+
`candidates` action always does), which `pythonpath` lists too. It loads
|
|
156
|
+
the plugin with `pytest -p xrddevtools.pytest_plugin`, which fails at
|
|
157
|
+
start-up if it cannot be imported, and sets `XRD_DEVTOOLS_REQUIRED=1`, so
|
|
158
|
+
a missing radon or complexipy fails rather than skips. This repository's
|
|
159
|
+
workflows are xrd-devtools' reusable workflows and actions: CI runs only
|
|
160
|
+
once `rob-c/xrd-devtools` exists.
|
|
161
|
+
- **Anywhere else** - a wheel-only test environment, a distribution build -
|
|
162
|
+
`pip install -e` a checkout or put its `src` on `PYTHONPATH`. Without one,
|
|
163
|
+
the gate tests (`tests/test_scorecard.py`,
|
|
164
|
+
`tests/test_release_metadata.py`) skip, saying why.
|
|
165
|
+
<!-- <<< xrddevtools sync: devtools <<< -->
|
|
166
|
+
|
|
167
|
+
## Layering and compatibility
|
|
168
|
+
|
|
169
|
+
- **No dependencies.** `dependencies = []` in `pyproject.toml` is a contract:
|
|
170
|
+
the publish workflow refuses a wheel with a default requirement. An
|
|
171
|
+
accelerator goes behind an extra with a pure-Python fallback, as
|
|
172
|
+
`google-crc32c` does.
|
|
173
|
+
- **Imports.** xrdcore never imports xrdsec, xrdhttp or a client, and
|
|
174
|
+
`import xrdcore` imports no submodule; `tests/test_package.py` pins both.
|
|
175
|
+
- **Shared formats are API.** The `storage-client-report` document
|
|
176
|
+
(`xrdcore.report`) is read by scripts written against any of the family's
|
|
177
|
+
tools; a change that would make an existing consumer misread it bumps its
|
|
178
|
+
`VERSION`. Redaction patterns (`xrdcore.log`) only ever grow.
|
|
179
|
+
- **Exceptions.** The clients join `xrdcore.errors` by identity or multiple
|
|
180
|
+
inheritance (`xrdclient.XRootDError is XrdError`), so a rename, a new base
|
|
181
|
+
class or a changed attribute is a change to their public API too. Add,
|
|
182
|
+
deprecate, then remove; never change what an existing keyword means.
|
|
183
|
+
|
|
184
|
+
## Releasing
|
|
185
|
+
|
|
186
|
+
xrdcore is released first: everything else in the family requires it. The
|
|
187
|
+
whole chain's runbook is xrdclient's
|
|
188
|
+
[docs/releasing.md](https://github.com/rob-c/xrdclient/blob/main/docs/releasing.md)
|
|
189
|
+
("Releasing the 0.4.0 chain").
|
|
190
|
+
|
|
191
|
+
1. Set `__version__` in `src/xrdcore/_version.py` (the build reads it from
|
|
192
|
+
there) and date the version's [CHANGELOG.md](CHANGELOG.md) entry.
|
|
193
|
+
2. Wait for CI on the release commit, then push an annotated `v<version>` tag.
|
|
194
|
+
The publish workflow rebuilds the sdist and wheel, checks the artifact
|
|
195
|
+
contents, that the tag, the runtime version, the wheel metadata and a dated
|
|
196
|
+
changelog entry agree and that the wheel has no default dependency, and
|
|
197
|
+
uploads to PyPI through trusted publishing. Never upload a local build.
|
|
198
|
+
3. Wait until `pip download --only-binary=:all: xrdcore==<version>` succeeds
|
|
199
|
+
from a clean environment before tagging xrdsec or xrdhttp: their publish
|
|
200
|
+
jobs install against this release from PyPI.
|