btclib-node 2026.8.27__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 (143) hide show
  1. btclib_node-2026.8.27/.markdownlint.jsonc +60 -0
  2. btclib_node-2026.8.27/.pre-commit-config.yaml +469 -0
  3. btclib_node-2026.8.27/.python-version +22 -0
  4. btclib_node-2026.8.27/.readthedocs.yaml +58 -0
  5. btclib_node-2026.8.27/.secrets.baseline +160 -0
  6. btclib_node-2026.8.27/.taplo.toml +27 -0
  7. btclib_node-2026.8.27/.yamllint.yaml +76 -0
  8. btclib_node-2026.8.27/AUTHORS.md +5 -0
  9. btclib_node-2026.8.27/CHANGELOG.md +4978 -0
  10. btclib_node-2026.8.27/CLAUDE.md +339 -0
  11. btclib_node-2026.8.27/CONTRIBUTING.md +392 -0
  12. btclib_node-2026.8.27/LICENSE +21 -0
  13. btclib_node-2026.8.27/PKG-INFO +114 -0
  14. btclib_node-2026.8.27/README.md +81 -0
  15. btclib_node-2026.8.27/RELEASE_NOTES.md +47 -0
  16. btclib_node-2026.8.27/RELEASING.md +518 -0
  17. btclib_node-2026.8.27/REPOSITORY.md +423 -0
  18. btclib_node-2026.8.27/REVIEWING.md +507 -0
  19. btclib_node-2026.8.27/SECURITY.md +85 -0
  20. btclib_node-2026.8.27/docs/source/btclib_node.block_db.rst +9 -0
  21. btclib_node-2026.8.27/docs/source/btclib_node.chainstate.rst +40 -0
  22. btclib_node-2026.8.27/docs/source/btclib_node.p2p.messages.rst +19 -0
  23. btclib_node-2026.8.27/docs/source/btclib_node.p2p.rst +62 -0
  24. btclib_node-2026.8.27/docs/source/btclib_node.rpc.rst +47 -0
  25. btclib_node-2026.8.27/docs/source/btclib_node.rst +93 -0
  26. btclib_node-2026.8.27/docs/source/changelog_link.md +4 -0
  27. btclib_node-2026.8.27/docs/source/conf.py +296 -0
  28. btclib_node-2026.8.27/docs/source/contributing_link.md +6 -0
  29. btclib_node-2026.8.27/docs/source/index.rst +22 -0
  30. btclib_node-2026.8.27/docs/source/modules.rst +7 -0
  31. btclib_node-2026.8.27/docs/source/readme_link.md +6 -0
  32. btclib_node-2026.8.27/docs/source/release_notes_link.md +6 -0
  33. btclib_node-2026.8.27/docs/source/reviewing_link.md +6 -0
  34. btclib_node-2026.8.27/docs/source/security_link.md +6 -0
  35. btclib_node-2026.8.27/pyproject.toml +282 -0
  36. btclib_node-2026.8.27/pyproject.toml.orig +960 -0
  37. btclib_node-2026.8.27/scripts/chains/mainnet.py +35 -0
  38. btclib_node-2026.8.27/scripts/chains/signet.py +30 -0
  39. btclib_node-2026.8.27/scripts/chains/testnet.py +30 -0
  40. btclib_node-2026.8.27/scripts/prune.py +27 -0
  41. btclib_node-2026.8.27/scripts/reset_chainstate.py +37 -0
  42. btclib_node-2026.8.27/scripts/reset_download.py +46 -0
  43. btclib_node-2026.8.27/scripts/test_errors.py +59 -0
  44. btclib_node-2026.8.27/scripts/testnet_test.py +26 -0
  45. btclib_node-2026.8.27/src/btclib_node/__init__.py +555 -0
  46. btclib_node-2026.8.27/src/btclib_node/block_db/__init__.py +385 -0
  47. btclib_node-2026.8.27/src/btclib_node/chains.py +269 -0
  48. btclib_node-2026.8.27/src/btclib_node/chainstate/__init__.py +59 -0
  49. btclib_node-2026.8.27/src/btclib_node/chainstate/block_index.py +722 -0
  50. btclib_node-2026.8.27/src/btclib_node/chainstate/contextual.py +190 -0
  51. btclib_node-2026.8.27/src/btclib_node/chainstate/filter_index.py +199 -0
  52. btclib_node-2026.8.27/src/btclib_node/chainstate/utxo_index.py +142 -0
  53. btclib_node-2026.8.27/src/btclib_node/config.py +147 -0
  54. btclib_node-2026.8.27/src/btclib_node/constants.py +40 -0
  55. btclib_node-2026.8.27/src/btclib_node/db.py +190 -0
  56. btclib_node-2026.8.27/src/btclib_node/download.py +695 -0
  57. btclib_node-2026.8.27/src/btclib_node/exceptions.py +219 -0
  58. btclib_node-2026.8.27/src/btclib_node/interpreter.py +164 -0
  59. btclib_node-2026.8.27/src/btclib_node/log.py +48 -0
  60. btclib_node-2026.8.27/src/btclib_node/main.py +387 -0
  61. btclib_node-2026.8.27/src/btclib_node/mempool.py +574 -0
  62. btclib_node-2026.8.27/src/btclib_node/p2p/__init__.py +15 -0
  63. btclib_node-2026.8.27/src/btclib_node/p2p/address.py +599 -0
  64. btclib_node-2026.8.27/src/btclib_node/p2p/callbacks.py +1178 -0
  65. btclib_node-2026.8.27/src/btclib_node/p2p/connection.py +870 -0
  66. btclib_node-2026.8.27/src/btclib_node/p2p/filter_size.py +41 -0
  67. btclib_node-2026.8.27/src/btclib_node/p2p/main.py +242 -0
  68. btclib_node-2026.8.27/src/btclib_node/p2p/manager.py +833 -0
  69. btclib_node-2026.8.27/src/btclib_node/p2p/messages/__init__.py +18 -0
  70. btclib_node-2026.8.27/src/btclib_node/p2p/messages/errors.py +143 -0
  71. btclib_node-2026.8.27/src/btclib_node/py.typed +0 -0
  72. btclib_node-2026.8.27/src/btclib_node/rpc/__init__.py +15 -0
  73. btclib_node-2026.8.27/src/btclib_node/rpc/callbacks.py +837 -0
  74. btclib_node-2026.8.27/src/btclib_node/rpc/connection.py +382 -0
  75. btclib_node-2026.8.27/src/btclib_node/rpc/errors.py +166 -0
  76. btclib_node-2026.8.27/src/btclib_node/rpc/main.py +121 -0
  77. btclib_node-2026.8.27/src/btclib_node/rpc/manager.py +347 -0
  78. btclib_node-2026.8.27/tests/README.md +26 -0
  79. btclib_node-2026.8.27/tests/__init__.py +444 -0
  80. btclib_node-2026.8.27/tests/_data/README.md +99 -0
  81. btclib_node-2026.8.27/tests/conftest.py +212 -0
  82. btclib_node-2026.8.27/tests/functional/__init__.py +12 -0
  83. btclib_node-2026.8.27/tests/functional/p2p/__init__.py +11 -0
  84. btclib_node-2026.8.27/tests/functional/p2p/backpressure_test.py +330 -0
  85. btclib_node-2026.8.27/tests/functional/p2p/block_filters_test.py +335 -0
  86. btclib_node-2026.8.27/tests/functional/p2p/download_test.py +110 -0
  87. btclib_node-2026.8.27/tests/functional/p2p/handshake_test.py +96 -0
  88. btclib_node-2026.8.27/tests/functional/p2p/init_test.py +50 -0
  89. btclib_node-2026.8.27/tests/functional/p2p/ping_test.py +137 -0
  90. btclib_node-2026.8.27/tests/functional/p2p/tx_test.py +115 -0
  91. btclib_node-2026.8.27/tests/functional/rpc/__init__.py +10 -0
  92. btclib_node-2026.8.27/tests/functional/rpc/chain_test.py +268 -0
  93. btclib_node-2026.8.27/tests/functional/rpc/connections_test.py +74 -0
  94. btclib_node-2026.8.27/tests/functional/rpc/errors_test.py +234 -0
  95. btclib_node-2026.8.27/tests/functional/rpc/init_test.py +129 -0
  96. btclib_node-2026.8.27/tests/functional/rpc/peers_test.py +199 -0
  97. btclib_node-2026.8.27/tests/functional/rpc/tx_test.py +246 -0
  98. btclib_node-2026.8.27/tests/integration/__init__.py +18 -0
  99. btclib_node-2026.8.27/tests/integration/backpressure_test.py +227 -0
  100. btclib_node-2026.8.27/tests/integration/bitcoind_test.py +68 -0
  101. btclib_node-2026.8.27/tests/integration/conftest.py +150 -0
  102. btclib_node-2026.8.27/tests/integration/reorg_test.py +330 -0
  103. btclib_node-2026.8.27/tests/interpreters_test.py +178 -0
  104. btclib_node-2026.8.27/tests/unit/__init__.py +11 -0
  105. btclib_node-2026.8.27/tests/unit/all_test.py +216 -0
  106. btclib_node-2026.8.27/tests/unit/block_db_test.py +448 -0
  107. btclib_node-2026.8.27/tests/unit/chains_test.py +117 -0
  108. btclib_node-2026.8.27/tests/unit/chainstate/__init__.py +10 -0
  109. btclib_node-2026.8.27/tests/unit/chainstate/_data/blockfilters.json +13 -0
  110. btclib_node-2026.8.27/tests/unit/chainstate/_data/testnet_bip158_vectors.json +5 -0
  111. btclib_node-2026.8.27/tests/unit/chainstate/block_index_test.py +1090 -0
  112. btclib_node-2026.8.27/tests/unit/chainstate/contextual_test.py +325 -0
  113. btclib_node-2026.8.27/tests/unit/chainstate/filter_index_test.py +715 -0
  114. btclib_node-2026.8.27/tests/unit/chainstate/init_test.py +19 -0
  115. btclib_node-2026.8.27/tests/unit/chainstate/utxo_index_test.py +232 -0
  116. btclib_node-2026.8.27/tests/unit/config_test.py +84 -0
  117. btclib_node-2026.8.27/tests/unit/conventions_test.py +183 -0
  118. btclib_node-2026.8.27/tests/unit/coverage_floor_test.py +227 -0
  119. btclib_node-2026.8.27/tests/unit/db_test.py +348 -0
  120. btclib_node-2026.8.27/tests/unit/docs_test.py +209 -0
  121. btclib_node-2026.8.27/tests/unit/download_test.py +1021 -0
  122. btclib_node-2026.8.27/tests/unit/harness_test.py +34 -0
  123. btclib_node-2026.8.27/tests/unit/helpers_test.py +220 -0
  124. btclib_node-2026.8.27/tests/unit/init_test.py +978 -0
  125. btclib_node-2026.8.27/tests/unit/interpreter_test.py +354 -0
  126. btclib_node-2026.8.27/tests/unit/log_test.py +45 -0
  127. btclib_node-2026.8.27/tests/unit/main_test.py +746 -0
  128. btclib_node-2026.8.27/tests/unit/mempool_test.py +694 -0
  129. btclib_node-2026.8.27/tests/unit/p2p/__init__.py +11 -0
  130. btclib_node-2026.8.27/tests/unit/p2p/address_test.py +1092 -0
  131. btclib_node-2026.8.27/tests/unit/p2p/callbacks_test.py +2605 -0
  132. btclib_node-2026.8.27/tests/unit/p2p/connection_test.py +1115 -0
  133. btclib_node-2026.8.27/tests/unit/p2p/main_test.py +765 -0
  134. btclib_node-2026.8.27/tests/unit/p2p/manager_test.py +1747 -0
  135. btclib_node-2026.8.27/tests/unit/p2p/messages/__init__.py +12 -0
  136. btclib_node-2026.8.27/tests/unit/p2p/messages/errors_test.py +128 -0
  137. btclib_node-2026.8.27/tests/unit/p2p/messages/init_test.py +401 -0
  138. btclib_node-2026.8.27/tests/unit/rpc/__init__.py +10 -0
  139. btclib_node-2026.8.27/tests/unit/rpc/callbacks_test.py +1548 -0
  140. btclib_node-2026.8.27/tests/unit/rpc/connection_test.py +477 -0
  141. btclib_node-2026.8.27/tests/unit/rpc/main_test.py +253 -0
  142. btclib_node-2026.8.27/tests/unit/rpc/manager_test.py +560 -0
  143. btclib_node-2026.8.27/uv.lock +2131 -0
@@ -0,0 +1,60 @@
1
+ {
2
+ // rules https://github.com/DavidAnson/markdownlint
3
+
4
+ // See https://github.com/DavidAnson/markdownlint-cli2 and
5
+ // https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.jsonc
6
+
7
+ // To locally disable a single error, e.g. MD013:
8
+ // <!-- markdownlint-disable MD013 -->
9
+ // and after the offending block
10
+ // <!-- markdownlint-enable MD013 -->
11
+
12
+ // This file is the same in btclib, btclib-secp256k1 and
13
+ // bitcoin-core-rpc, deliberately: prose moves between the three, and a
14
+ // paragraph that lints in one has to lint in the others. No rule is
15
+ // disabled here -- what follows only names a style where markdownlint's
16
+ // default is "consistent", which asks each file to agree with itself and
17
+ // therefore lets two files disagree. That is what let an indented block
18
+ // pass in one repository and fail in another.
19
+ //
20
+ // A rule that only one file needs is not here: bitcoin-core-rpc's
21
+ // CHANGELOG.md repeats "### Added" under every release, which is what
22
+ // MD024 exists to catch and what its own `siblings_only` setting is for
23
+ // -- but the other files, in every repository, have no such repeat, and
24
+ // a per-file exception belongs in that file's own
25
+ // `markdownlint-configure-file` comment rather than in a shared config
26
+ // read by files that never trip the rule it relaxes.
27
+
28
+ // MD003/heading-style - ATX, the "## Heading" spelling, everywhere
29
+ "MD003": { "style": "atx" },
30
+
31
+ // MD004/ul-style - a dash, which is what every list here already uses
32
+ "MD004": { "style": "dash" },
33
+
34
+ // MD007/ul-indent - Unordered list indentation
35
+ "MD007": {
36
+ // Spaces for indent
37
+ "indent": 4,
38
+ },
39
+
40
+ // MD029/ol-prefix - every item numbered "1.", the renderer numbering
41
+ // them: inserting a step into a release procedure then touches one line
42
+ // rather than renumbering every line below it
43
+ "MD029": { "style": "one" },
44
+
45
+ // MD035/hr-style - one spelling of the thematic break
46
+ "MD035": { "style": "---" },
47
+
48
+ // MD046/code-block-style - fenced, so a block can say which language it
49
+ // is in and the renderer can highlight it; an indented block cannot
50
+ "MD046": { "style": "fenced" },
51
+
52
+ // MD048/code-fence-style - backticks, tildes being for fencing a block
53
+ // that itself contains backticks, which nothing here does
54
+ "MD048": { "style": "backtick" },
55
+
56
+ // MD049/MD050 - asterisks for both, so that *emphasis* and **strong**
57
+ // are told apart by how many rather than by which character
58
+ "MD049": { "style": "asterisk" },
59
+ "MD050": { "style": "asterisk" },
60
+ }
@@ -0,0 +1,469 @@
1
+ ---
2
+ # Skip execution of one or more hooks using the SKIP environment variable:
3
+ # $ SKIP=mypy git commit -m "foo"
4
+ # $ SKIP=mypy,ruff-check pre-commit run --all-files
5
+ #
6
+ # If want to disable all hooks while committing, use the --no-verify/-n option:
7
+ # $ git commit -n -m "foo"
8
+
9
+ # these hooks are the lint gate: the lint workflow will run this very
10
+ # file once btclib-org/.github#9's workflow step lands, so what CI
11
+ # enforces will be exactly what a commit enforces, with no second list
12
+ # of tools and versions to keep in step. What pre-commit.ci provides in
13
+ # the meantime is the periodic pull request bumping the revisions
14
+ # pinned below
15
+ ci:
16
+ # a bot committing to a branch is at odds with a setup where a
17
+ # failing hook is fixed by its author
18
+ autofix_prs: false
19
+ # no autoupdate_branch, as dependabot declares no target-branch: the
20
+ # default is the default branch, which is the only branch a change
21
+ # lands on
22
+ autoupdate_commit_msg: "Update the pinned pre-commit hook revisions"
23
+ autoupdate_schedule: weekly
24
+ # the hook pre-commit.ci cannot run: it shells out to uv, which is not
25
+ # installed there. Not a gap in the gate, and only because
26
+ # .github/workflows/lint.yml now exists to run this very file with the
27
+ # same command a developer runs -- before it, a skipped hook here was a
28
+ # hook nothing ran at all, this repository having had no workflow but
29
+ # the review one (issue #33). What pre-commit.ci is kept for is the
30
+ # pull request above, bumping the revisions pinned below, and a local
31
+ # hook has no revision to bump: uv.lock moves its mypy instead
32
+ skip: [mypy]
33
+
34
+ repos:
35
+ # this file checking itself, and the only two hooks here with no
36
+ # environment to install. A `files` or `exclude` pattern that stops
37
+ # matching anything is a hook that has quietly stopped running: the
38
+ # rule is still written down and no longer enforced, which is the one
39
+ # defect a green run cannot report on itself
40
+ - repo: meta
41
+ hooks:
42
+ - id: check-hooks-apply
43
+ - id: check-useless-excludes
44
+ # this file checking itself further, on the two moves `autoupdate`
45
+ # makes that it must not take. A rev naming only a major version
46
+ # follows every future release of that major, which is the one thing
47
+ # a rev is here to prevent -- and a prerelease is not a release.
48
+ # A commit SHA stays acceptable, should a hook ever want one: the
49
+ # pattern requires the prerelease marker and its digits to end the
50
+ # value, which forty hex characters do not
51
+ - repo: local
52
+ hooks:
53
+ - id: pinned-rev
54
+ name: every pre-commit rev names a released version
55
+ language: pygrep
56
+ entry: '^ *rev: "?v?([0-9]+|[0-9][0-9.]*(a|b|rc|dev)[0-9]+)"?$'
57
+ files: ^\.pre-commit-config\.yaml$
58
+ - repo: https://github.com/pre-commit/pre-commit-hooks
59
+ rev: v6.0.0
60
+ hooks:
61
+ - id: trailing-whitespace
62
+ - id: end-of-file-fixer
63
+ - id: mixed-line-ending
64
+ # not the default --fix=auto, which normalizes a file to its own
65
+ # most frequent ending: a file entirely in CRLF is consistent by
66
+ # that measure, so it stays, and the LF lines added to it are
67
+ # converted the wrong way. This repository is LF throughout
68
+ args: [--fix=lf]
69
+ - id: check-case-conflict
70
+ # scripts/ has no shebang or executable bit today, and this hook
71
+ # is the pair that keeps it that way: it matches every text file,
72
+ # so the day one of them gains a shebang without chmod +x, or the
73
+ # reverse, one of the two catches it
74
+ - id: check-shebang-scripts-are-executable
75
+ - id: fix-byte-order-marker
76
+ - id: check-yaml
77
+ - id: check-json
78
+ # `.vscode` is jsonc and not json: VSCode reads a comment in its
79
+ # own settings.json and extensions.json, and what those two files
80
+ # carry besides the settings is the reason for each of them. A json
81
+ # parser rejects the file at the first one. It is not a gap:
82
+ # prettier below is given that directory and fails on what it
83
+ # cannot parse, which is the question this hook asks
84
+ exclude: ^\.vscode/
85
+ - id: pretty-format-json
86
+ name: pretty-format-json (in place fixes)
87
+ args: [--autofix]
88
+ # Two directories, for two different reasons.
89
+ # `.vscode`, for the reason check-json above is given the same
90
+ # exclude: those files are jsonc, this hook parses json, and
91
+ # prettier below is what formats them instead -- at its own indent,
92
+ # which is not this hook's and does not have to be, no other tool
93
+ # reading them.
94
+ # tests/**/_data/ is the vendored directory, as tests/_data/
95
+ # README.md declares it: what is in there is upstream's file byte
96
+ # for byte, pinned by a git blob sha, and a reformat voids the pin
97
+ # and the verdict together -- silently, this hook correcting
98
+ # rather than reporting. Excluded by directory rather than by name
99
+ # so that the next file vendored into it is safe before anyone
100
+ # remembers this line; check-json above still asks the question
101
+ # worth asking of them, which is whether they parse.
102
+ exclude: ^(\.vscode/|tests/(.*/)?_data/)
103
+ - id: check-merge-conflict
104
+ - id: check-vcs-permalinks
105
+ - id: detect-private-key
106
+ - id: debug-statements
107
+ - id: check-docstring-first
108
+ # default args, so the pattern it enforces is `*_test.py`, which
109
+ # every test module under tests/ is named. The hook's own
110
+ # exception list is `__init__.py` and `conftest.py`, which is
111
+ # where this suite's shared code lives -- section 7 of the
112
+ # organization standard puts it in a package `__init__.py` rather
113
+ # than in a module whose name says "test" and holds none, so the
114
+ # hook reads every file under tests/ with no exclude of its own
115
+ - id: name-tests-test
116
+ - id: check-toml
117
+ # only files being *added* are measured, the hook intersecting its
118
+ # arguments with git's added set
119
+ - id: check-added-large-files
120
+ # there are none, and a submodule is the one dependency that would
121
+ # not be in uv.lock, not be pinned by dependabot, and not be
122
+ # visible in an sdist
123
+ - id: forbid-submodules
124
+ # the credentials of other systems, which is the half of secret
125
+ # scanning GitHub's free plan does not cover. detect-private-key above
126
+ # covers the PEM case.
127
+ # Not gitleaks: all of its hook ids invoke it with --staged, so under
128
+ # "pre-commit run --all-files" it scans an empty staged set and passes
129
+ - repo: https://github.com/Yelp/detect-secrets
130
+ rev: v1.5.0
131
+ hooks:
132
+ - id: detect-secrets
133
+ name: detect-secrets (credentials of other systems)
134
+ # findings are recorded in .secrets.baseline as already reviewed
135
+ # rather than excluded from the scan: an exclusion makes a file
136
+ # blind for good, where a baseline entry is a finding somebody
137
+ # has read.
138
+ # The baseline was generated with the two entropy plugins off
139
+ # (--disable-plugin Base64HighEntropyString --disable-plugin
140
+ # HexHighEntropyString): with them on, the scan's only findings
141
+ # are the four hex genesis-block constants in chains.py and its
142
+ # test, and a new protocol constant is exactly what a legitimate
143
+ # addition looks like here -- the plugins would flag every one
144
+ # of them and nothing else, forever
145
+ args: [--baseline, .secrets.baseline]
146
+ # a typo in a comment is cosmetic; the same typo in the README is the
147
+ # shop window, and in an exception message it is an interface.
148
+ # Configured in pyproject.toml ([tool.codespell]) rather than in args
149
+ # here: the skip list needs a paragraph of justification, and a hook
150
+ # invocation is the wrong place for one
151
+ - repo: https://github.com/codespell-project/codespell
152
+ rev: v2.4.3
153
+ hooks:
154
+ - id: codespell
155
+ name: codespell (prose and comments, in place fixes)
156
+ # codespell only reads [tool.codespell] when told where to look;
157
+ # without this it neither applies `builtin = "clear"` nor
158
+ # `ignore-words-list`, and reports on its own unconfigured
159
+ # default instead
160
+ args: [--toml, pyproject.toml, --write-changes]
161
+ # --toml is read through tomllib, which is 3.11 and later; the
162
+ # interpreter pre-commit builds this hook's environment with is
163
+ # its own choice and not this project's requires-python, so the
164
+ # backport is declared unconditionally rather than reasoned about
165
+ additional_dependencies: [tomli]
166
+ # the second spell checker, and not a replacement for the one above:
167
+ # codespell matches whole words and keeps the hyphen inside the token,
168
+ # where typos splits hyphens, snake_case and camelCase
169
+ # crate-ci/typos re-tags a moving `v1` alias onto the same commit
170
+ # as each release, and `git describe --tags` breaks the tie between
171
+ # two tags on one commit by creation date, which names the alias.
172
+ # `autoupdate` proposes that alias on every run and `pinned-rev` above
173
+ # refuses it on every run, so the pin stays stuck rather than moving.
174
+ # `autoupdate` walks every `repo:` entry except `local` and `meta`, so
175
+ # a local hook is the one shape it cannot reach; `additional_dependencies`
176
+ # below carries the version pin in its place, and `language`, `entry`,
177
+ # `args` and `types` are upstream's own typos hook definition, copied
178
+ # in rather than fetched
179
+ - repo: local
180
+ hooks:
181
+ - id: typos
182
+ name: typos
183
+ language: python
184
+ additional_dependencies: [typos==1.49.0]
185
+ entry: typos
186
+ args: [--write-changes, --force-exclude]
187
+ types: [text]
188
+ stages: [pre-commit, pre-merge-commit, pre-push, manual]
189
+ - repo: https://github.com/DavidAnson/markdownlint-cli2
190
+ rev: v0.23.2
191
+ hooks:
192
+ - id: markdownlint-cli2
193
+ name: markdownlint-cli2 (in place fixes)
194
+ args: [--fix]
195
+ # how a link destination is spelled, which markdownlint has no rule for
196
+ # and the documentation build depends on. docs.yml greps the built html
197
+ # for `href="#./`: that is what myst renders in place of a link the
198
+ # RootFileLinks transform in docs/source/conf.py cannot resolve -- an
199
+ # anchor to an id no page has, which -W reports today and would stop
200
+ # reporting the day suppress_warnings took myst.xref_missing back. A
201
+ # grep can key on one spelling, so the destinations are spelled one way
202
+ # and this hook is what keeps them that way, in the commit that writes
203
+ # the link rather than in the artifact built from it.
204
+ #
205
+ # The rule is the prefix and not the extension, and the table in
206
+ # btclib-org/btclib#1175 is what decides that: "DOES_NOT_EXIST.txt",
207
+ # "sub/DOES_NOT_EXIST.md", "DOES_NOT_EXIST" and "../DOES_NOT_EXIST.md"
208
+ # each reach myst's fallback, and each is missed by the union of both
209
+ # greps a repository ran. An ".md"-scoped rule leaves all four
210
+ # writable. Measured on this tree by building docs/source with each of
211
+ # the four appended to CONTRIBUTING.md (already included by
212
+ # contributing_link.md): every one renders "#" followed by the bare
213
+ # destination -- "#DOES_NOT_EXIST.md", "#sub/DOES_NOT_EXIST.md",
214
+ # "#DOES_NOT_EXIST", "#../DOES_NOT_EXIST.md" -- none of them the
215
+ # "#./" prefix docs.yml's own grep looks for, and none reachable by
216
+ # widening that grep either: an autodoc anchor renders the identical
217
+ # shape, "#btclib_node.chains.Chain" among them.
218
+ #
219
+ # This tree carries no badge link today (`grep -rn '!\[' *.md` answers
220
+ # nothing), so the badge case the pattern below also has to survive is
221
+ # untested evidence here rather than measured; the pattern itself is
222
+ # the organization's own (btclib-org/.github#20), read from a sibling
223
+ # that does carry one.
224
+ #
225
+ # "../" is the row with nothing downstream behind it at all, which is
226
+ # why it is refused here rather than allowed as an ordinary relative
227
+ # path: conf.py's transform declines a target that normalizes above
228
+ # the repository root -- nothing above the root is a document this
229
+ # build can answer for -- so that shape reaches myst's fallback by
230
+ # design rather than by a gap in the transform, and renders
231
+ # href="#../page.md", which the grep in docs.yml does not match and
232
+ # should not be widened to. This hook is the only place it can be
233
+ # caught.
234
+ #
235
+ # pygrep, and the same hook in every repository of this organization
236
+ # (btclib-org/.github#20): there is no shared hook repository to put
237
+ # it in, and pinned-rev above is that arrangement already. The
238
+ # pattern asks for a whole `[text](destination)`, so grep output of
239
+ # the form `FILE.md:21:](x.md)` carries no "[" and is not matched.
240
+ # The lookbehind refuses a "[" preceded by a backtick, which is what
241
+ # lets prose quote the refused shape in a code span -- and refuses
242
+ # one preceded by "!" or "[" as well, so that a *quoted* badge is
243
+ # escaped whole: without those two the scan would re-enter at the
244
+ # image inside it and report the src. The leading "!?" is what keeps
245
+ # a standalone image's own src checked despite that.
246
+ #
247
+ # A reference definition is a link too, and the second branch is for
248
+ # it: "[label]: page.md" renders the same fallback as the inline
249
+ # form, carries no "(", so the first branch cannot see it. That
250
+ # branch is anchored at the start of the line, which is what keeps it
251
+ # off a *use* site such as "[label][ref]: the" appearing in prose. At
252
+ # most three leading spaces, four making the line an indented code
253
+ # block rather than a definition.
254
+ #
255
+ # What pygrep cannot see is a fenced block, being line based -- an
256
+ # example of the refused shape inside one fails this hook, and the
257
+ # code span is the way round it
258
+ - repo: local
259
+ hooks:
260
+ - id: local-link-prefix
261
+ name: a local markdown link destination begins with ./
262
+ language: pygrep
263
+ # one line, and no literal space in it -- "\x20" is the space the
264
+ # reference branch allows -- so yamllint reads the value as a
265
+ # single unbreakable word and its 100-column rule exempts it. A
266
+ # block scalar rather than a quoted one for the same reason a
267
+ # folded scalar was rejected: folding joins lines with a space,
268
+ # which would land inside the regex
269
+ entry: |-
270
+ (?<![`!\[])(?:!?\[(?:[^]]|\]\([^)]*\))*\]\(|^\x20{0,3}\[[^]]+\]:\s*)(?!\./|#|[A-Za-z][A-Za-z0-9+.-]*:)[^)\s]
271
+ types: [markdown]
272
+ # a line that ends inside a word, at that word's own hyphen.
273
+ # Section 4 of the organization standard is the rule: what such a
274
+ # line renders as, why nothing else here catches it, what this
275
+ # pattern cannot see, and what the measurement was before it was
276
+ # proposed
277
+ - id: no-hyphen-at-end-of-line
278
+ name: a word is not wrapped at its own hyphen
279
+ language: pygrep
280
+ entry: "[A-Za-z0-9]-$"
281
+ types: [markdown]
282
+ # what ruff-format is for Python, on the structured files ruff does
283
+ # not read: yaml and the jsonc that carries a comment. Before
284
+ # yamllint below, so the linter judges the formatted file
285
+ - repo: https://github.com/rbubley/mirrors-prettier
286
+ rev: v3.9.6
287
+ hooks:
288
+ - id: prettier
289
+ name: prettier (yaml and jsonc, in place fixes)
290
+ files: ^(.*\.ya?ml|\.vscode/.*\.json|\.markdownlint\.jsonc)$
291
+ # the same for toml, which here is pyproject.toml: .taplo.toml holds
292
+ # the formatting choices and the reasoning for them. uv.lock is toml
293
+ # to identify and therefore matched by the hook's own types, and it
294
+ # is uv's file to write
295
+ - repo: https://github.com/ComPWA/taplo-pre-commit
296
+ rev: v0.9.3
297
+ hooks:
298
+ - id: taplo-format
299
+ name: taplo (toml, in place fixes)
300
+ exclude: ^uv\.lock$
301
+ # the width of a yaml line, which nothing else here measures.
302
+ # Configured in .yamllint.yaml rather than in args here
303
+ # check-only: yamllint has no fix mode, and its own CLI names none
304
+ - repo: https://github.com/adrienverge/yamllint
305
+ rev: v1.38.0
306
+ hooks:
307
+ - id: yamllint
308
+ name: yamllint (line width and document start)
309
+ # .github/dependabot.yml is validated by nothing else here: check-yaml
310
+ # above asks whether it is yaml, and actionlint below reads workflows
311
+ # and not this. A typo in a package-ecosystem, a directory or a day is
312
+ # not an error to GitHub -- it silently updates nothing, and the
313
+ # evidence is a pull request that never arrives, so "it stopped running
314
+ # and said nothing" is exactly the failure to guard against.
315
+ # check-github-workflows is not added beside it: actionlint already
316
+ # reads every workflow and goes further than the schema does, into
317
+ # expressions, contexts and runner labels.
318
+ # .readthedocs.yaml is the same argument again: yaml that check-yaml
319
+ # reads as yaml and nothing reads as a build definition, on a service
320
+ # whose failure mode is a docs build that stops happening
321
+ - repo: https://github.com/python-jsonschema/check-jsonschema
322
+ rev: 0.38.0
323
+ hooks:
324
+ - id: check-dependabot
325
+ - id: check-readthedocs
326
+ # the workflows are yaml with expressions in it, and nothing else here
327
+ # validates them: an unknown key, a typo in a context reference, an
328
+ # action input that does not exist, a job depending on a job that is
329
+ # not there are all things a run would otherwise discover by failing --
330
+ # and these run weekly or on a merge, where a failure is read late.
331
+ # The Python packaging of actionlint rather than the upstream hook,
332
+ # whose only non-docker id is language: golang, i.e. a go toolchain on
333
+ # every machine that commits and on pre-commit.ci
334
+ - repo: https://github.com/Mateusz-Grzelinski/actionlint-py
335
+ rev: v1.7.12.24
336
+ hooks:
337
+ - id: actionlint
338
+ name: actionlint (github workflows)
339
+ # actionlint delegates the body of a run: step to shellcheck, and
340
+ # of a "shell: python" step to pyflakes, if it finds them on the
341
+ # PATH: without them it validates the yaml around a shell script
342
+ # and not the script -- and the changes job in test.yml is a
343
+ # shell script. Declared here rather than left to the machine, so
344
+ # that a commit checks what CI checks: the runner image carries
345
+ # shellcheck, so leaving it to chance would mean CI checking
346
+ # more.
347
+ # Pinned, unlike the revs above, because nothing bumps an
348
+ # additional dependency: pre-commit.ci moves rev, and dependabot
349
+ # has no pre-commit ecosystem. That is the point rather than the
350
+ # cost -- a shellcheck release adding a check would otherwise
351
+ # turn a pull request red for having changed nothing
352
+ additional_dependencies:
353
+ - shellcheck-py==0.11.0.1
354
+ - pyflakes==3.4.0
355
+ # actionlint asks whether a workflow is correct, zizmor whether it is
356
+ # safe: a token persisted where a later step can read it, a cache
357
+ # restored into a job that writes, an expression interpolated into a
358
+ # shell script, a trigger that runs a fork's code with write access.
359
+ # Every invariant the workflows here promise in a comment -- the SHA
360
+ # pins, the permissions block, persist-credentials: false -- is a
361
+ # comment until this hook reads it. Both this and actionlint report
362
+ # nothing on this tree today, which is the state they are here to keep
363
+ - repo: https://github.com/zizmorcore/zizmor-pre-commit
364
+ rev: v1.29.0
365
+ hooks:
366
+ - id: zizmor
367
+ # offline is what it does by default, said out loud: it warns
368
+ # about the implicit choice on every run, and the choice is the
369
+ # right one. Two audits can query GitHub for the actions a
370
+ # workflow pins -- impostor-commit and known-vulnerable-actions
371
+ # -- which would make the hook want a token and report
372
+ # differently on a machine that has none.
373
+ # --no-progress is repeated rather than added: args replaces what
374
+ # the hook declares, and that flag is the whole of what it
375
+ # declares, so passing --offline alone would drop it
376
+ args: [--offline, --no-progress]
377
+ # the width of a toml comment, the one prose left with no limit:
378
+ # taplo formats values and leaves comments alone by design.
379
+ # `.{80}\S*[ \t]` reports a line only when whitespace is left past
380
+ # column 80, the amnesty MD013 and ruff give a comment ending in a
381
+ # link that cannot be broken
382
+ - repo: local
383
+ hooks:
384
+ - id: toml-comment-width
385
+ name: toml comment width (80 columns, unbreakable links exempt)
386
+ language: pygrep
387
+ entry: '^(?=[ \t]*#).{80}\S*[ \t]'
388
+ files: \.toml$
389
+ # ruff replaces autoflake, bandit, black, copyright_notice_precommit,
390
+ # flake8, isort, and pyupgrade -- and fixes the copyright hook's own
391
+ # defect along the way: CPY001 checks whatever files it is given the
392
+ # same way regardless of who asked, where copyright-notice-precommit
393
+ # checked only staged files unless given --enforce-all, so under
394
+ # "pre-commit run --all-files" it checked nothing
395
+ - repo: https://github.com/astral-sh/ruff-pre-commit
396
+ rev: v0.16.4
397
+ hooks:
398
+ - id: ruff-check
399
+ name: ruff check (in place fixes)
400
+ args: [--fix]
401
+ - id: ruff-format
402
+ name: ruff format (in place fixes)
403
+ # the other half of pyproject.toml's unspecified-encoding, which reads
404
+ # `open` and the pathlib spellings and stops there. A child process
405
+ # decoded through `text=True` takes the locale's encoding by the same
406
+ # rule, and no linter here has an opinion on that keyword.
407
+ # `encoding=` is what the call wanted anyway, so the fix is one
408
+ # keyword in place of another and never a mode change
409
+ - repo: local
410
+ hooks:
411
+ - id: decoded-subprocess-encoding
412
+ name: a decoded subprocess names its encoding, not text=True
413
+ language: pygrep
414
+ entry: '\b(text|universal_newlines)=True'
415
+ files: \.py$
416
+ # a local hook running mypy from the project environment: every
417
+ # import the tree makes resolves under it, and there is no unstubbed
418
+ # dependency left to declare an override for -- pyproject.toml's
419
+ # [dependency-groups].lint is where that environment (mypy and
420
+ # types-requests) is declared.
421
+ # --locked and not --frozen, for the reason the uv-lock hook below
422
+ # cannot cover: a dependency added without a re-lock would leave mypy
423
+ # checking a stale environment and saying nothing, and this hook
424
+ # produces its verdict before that one runs.
425
+ # No paths on the command line, either: mypy reads [tool.mypy]'s
426
+ # `files` only when it is given none, so an argument list here would
427
+ # replace that key rather than agree with it -- two lists free to
428
+ # drift apart, and the gate silently running on the shorter one
429
+ - repo: local
430
+ hooks:
431
+ - id: mypy
432
+ name: mypy (in the project environment)
433
+ entry: uv run --locked --no-default-groups --group lint --group test mypy
434
+ language: system
435
+ types: [python]
436
+ pass_filenames: false
437
+ require_serial: true
438
+ - repo: https://github.com/astral-sh/uv-pre-commit
439
+ rev: 0.12.5
440
+ hooks:
441
+ # keep uv.lock in sync with pyproject.toml
442
+ - id: uv-lock
443
+ # what is tracked and not in the sdist, and what is in the sdist and not
444
+ # tracked, both ways round, against the patterns of
445
+ # [tool.uv.build-backend] -- which is also where what nothing asked
446
+ # before this hook existed is written down. Nothing is configured here:
447
+ # the hook's own manifest passes --no-isolation and --inject-junk and
448
+ # installs uv, which is the whole of what this needs. --no-isolation is
449
+ # what pre-commit.ci requires -- an isolated build environment cannot be
450
+ # created there -- and it costs nothing to build without one, the
451
+ # backend being the copy bundled in that uv. --inject-junk plants the
452
+ # leftovers a working directory collects, for the length of the run, so
453
+ # the check is made against a tree in the state it is usually in rather
454
+ # than a clean one; which of them an include pattern reaches, and which
455
+ # the backend's own default excludes already drop, is answered beside
456
+ # [tool.uv.build-backend] source-exclude
457
+ - repo: https://github.com/henryiii/check-sdist
458
+ rev: v1.6.0
459
+ hooks:
460
+ - id: check-sdist
461
+ # --installer=pip is what makes the environment decide the
462
+ # backend instead of the outer uv's own bundled copy: it runs
463
+ # `build --no-isolation`, which refuses one that does not
464
+ # satisfy [build-system] requires above. --inject-junk is
465
+ # repeated because args: replaces the manifest's own rather
466
+ # than extending it; --no-isolation stays in the manifest's
467
+ # entry:, which args: does not touch
468
+ args: [--inject-junk, --installer=pip]
469
+ additional_dependencies: ["uv_build>=0.12.5,<0.13"]
@@ -0,0 +1,22 @@
1
+ # The newest interpreter every dependency supports, and the version
2
+ # requires-python names. Not 3.14t, and not for the reason this comment
3
+ # used to give: plyvel's Cython source would not compile against the
4
+ # free-threaded build, and plyvel is gone. This tree's runtime closure
5
+ # does carry a compiled extension now: btclib depends on the
6
+ # `secp256k1` extra, pulling in `btclib-secp256k1` and, under it,
7
+ # `cffi`. Neither re-enables the GIL -- btclib-org/btclib-node#361
8
+ # measured `sys._is_gil_enabled()` still False after importing both
9
+ # under 3.14.6+freethreaded, and both ship paired cp314/cp314t wheels.
10
+ # `librt` is a dependency of mypy, a development-only package and not
11
+ # part of the runtime closure at all, and every compiled package in
12
+ # the lock, `librt` included, ships a cp314t wheel too.
13
+ # The pin holds anyway: Node.worker_pool builds a ThreadPool rather
14
+ # than a process Pool where sys._is_gil_enabled() is False
15
+ # (btclib-org/btclib-node#388, building on btclib-org/btclib-node#385),
16
+ # but choosing that at runtime is not the same as switching this
17
+ # interpreter's own default over to 3.14t, which nothing here asks for.
18
+ # btclib-org/btclib-node#112, which asked whether 3.14t was worth it, is
19
+ # answered and closed by this comment.
20
+ # A trailing comment on the version line below would make uv ignore this
21
+ # file, so the explanation stays up here instead.
22
+ 3.14
@@ -0,0 +1,58 @@
1
+ ---
2
+ # Read the Docs configuration file
3
+ # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4
+ #
5
+ # The dotted name is the canonical one; readthedocs.yml is honored too,
6
+ # but documented as deprecated.
7
+ #
8
+ # The build has to install the project itself, not sphinx alone: the
9
+ # autodoc directives in docs/source/*.rst import the module, a failed
10
+ # import is a sphinx warning rather than an error, and a "requirements:
11
+ # docs/requirements.txt" install therefore publishes a bare module title
12
+ # with no API beneath it, silently.
13
+ #
14
+ # What this file cannot say is *which* versions run it. This repository
15
+ # is not on Read the Docs yet -- REPOSITORY.md's own "No Pages and no
16
+ # Read the Docs" is what #264 leaves this file to answer for the day
17
+ # that changes, since a build config has to exist before a project can
18
+ # be connected to one.
19
+
20
+ # Required
21
+ version: 2
22
+
23
+ # the interpreter is declared rather than left to whatever read the docs
24
+ # defaults to: conf.py reads the version and the project name out of
25
+ # pyproject.toml with tomllib, which is standard library from 3.11 on. Which
26
+ # version above that floor is `.python-version`'s to say, 3.14 -- these
27
+ # docs are built twice, here and by docs.yml, and one interpreter is what
28
+ # keeps the build read on the website and the build that gates a merge from
29
+ # being two different questions
30
+ build:
31
+ os: ubuntu-24.04
32
+ tools:
33
+ python: "3.14"
34
+ # build.commands rather than the sphinx: and python: keys, which cannot
35
+ # express this and are rejected when it is present.
36
+ # autodoc has to import the module, so the project has to be installed,
37
+ # and what read the docs can express natively is "pip install ." plus a
38
+ # requirements file. uv installs the docs dependency group instead,
39
+ # from uv.lock, which is why there is no docs/requirements.txt: the
40
+ # group in pyproject.toml is the single declaration and the lock pins
41
+ # it, where a requirements file would be unpinned and kept in step by a
42
+ # header asking a human to do it.
43
+ # uv itself is not pinned, deliberately: no dependabot ecosystem watches
44
+ # this file, so a pin here is a version nobody bumps, and --locked makes
45
+ # the uv version irrelevant to what gets installed -- uv.lock decides
46
+ # that, and a uv too old to read the lock fails loudly rather than
47
+ # resolving something else.
48
+ # -W turns a failed import into a failed build, which is the point of
49
+ # the whole file; --keep-going reports every problem in the same run
50
+ # instead of one per push, and -n turns an unresolved cross-reference
51
+ # into a warning for -W to catch -- conf.py's own intersphinx_mapping
52
+ # and nitpick_ignore are what -n is checked against
53
+ commands:
54
+ - pip install uv
55
+ - uv sync --locked --no-default-groups --group docs
56
+ - >-
57
+ uv run --locked --no-default-groups --group docs
58
+ sphinx-build -W -n --keep-going -b html docs/source $READTHEDOCS_OUTPUT/html