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.
Files changed (42) hide show
  1. xrdcore-0.4.0/.github/workflows/ci.yml +60 -0
  2. xrdcore-0.4.0/.github/workflows/publish.yml +44 -0
  3. xrdcore-0.4.0/.gitignore +27 -0
  4. xrdcore-0.4.0/CHANGELOG.md +158 -0
  5. xrdcore-0.4.0/CONTRIBUTING.md +200 -0
  6. xrdcore-0.4.0/COPYING +674 -0
  7. xrdcore-0.4.0/LICENSE +165 -0
  8. xrdcore-0.4.0/PKG-INFO +95 -0
  9. xrdcore-0.4.0/README.md +57 -0
  10. xrdcore-0.4.0/SECURITY.md +82 -0
  11. xrdcore-0.4.0/pyproject.toml +179 -0
  12. xrdcore-0.4.0/src/xrdcore/__init__.py +87 -0
  13. xrdcore-0.4.0/src/xrdcore/_version.py +3 -0
  14. xrdcore-0.4.0/src/xrdcore/cancel.py +242 -0
  15. xrdcore-0.4.0/src/xrdcore/checksum.py +195 -0
  16. xrdcore-0.4.0/src/xrdcore/cli.py +234 -0
  17. xrdcore-0.4.0/src/xrdcore/crc32c.py +116 -0
  18. xrdcore-0.4.0/src/xrdcore/crc64.py +95 -0
  19. xrdcore-0.4.0/src/xrdcore/errors.py +153 -0
  20. xrdcore-0.4.0/src/xrdcore/fanout.py +254 -0
  21. xrdcore-0.4.0/src/xrdcore/log.py +299 -0
  22. xrdcore-0.4.0/src/xrdcore/pipeline.py +273 -0
  23. xrdcore-0.4.0/src/xrdcore/py.typed +0 -0
  24. xrdcore-0.4.0/src/xrdcore/report.py +748 -0
  25. xrdcore-0.4.0/src/xrdcore/retry.py +178 -0
  26. xrdcore-0.4.0/src/xrdcore/safexml.py +81 -0
  27. xrdcore-0.4.0/src/xrdcore/sockets.py +47 -0
  28. xrdcore-0.4.0/tests/test_cancel.py +106 -0
  29. xrdcore-0.4.0/tests/test_checksum.py +96 -0
  30. xrdcore-0.4.0/tests/test_cli.py +212 -0
  31. xrdcore-0.4.0/tests/test_crc.py +169 -0
  32. xrdcore-0.4.0/tests/test_fanout.py +246 -0
  33. xrdcore-0.4.0/tests/test_log.py +152 -0
  34. xrdcore-0.4.0/tests/test_package.py +104 -0
  35. xrdcore-0.4.0/tests/test_pipeline.py +167 -0
  36. xrdcore-0.4.0/tests/test_pipeline_shared.py +209 -0
  37. xrdcore-0.4.0/tests/test_release_metadata.py +20 -0
  38. xrdcore-0.4.0/tests/test_report.py +524 -0
  39. xrdcore-0.4.0/tests/test_retry.py +133 -0
  40. xrdcore-0.4.0/tests/test_safexml.py +222 -0
  41. xrdcore-0.4.0/tests/test_scorecard.py +18 -0
  42. 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
@@ -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.