-
Notifications
You must be signed in to change notification settings - Fork 463
Expand file tree
/
Copy pathpyproject.toml
More file actions
251 lines (224 loc) · 8.35 KB
/
Copy pathpyproject.toml
File metadata and controls
251 lines (224 loc) · 8.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
[project]
name = 'exabgp'
version = '6.0.0'
description = 'BGP swiss army knife'
authors = [
{ name = "Thomas Mangin", email = "thomas.mangin@exa-networks.co.uk" },
]
license = 'BSD-3-Clause'
license-files = ['LICENCE.txt']
requires-python = ">=3.12"
keywords = [
'bgp',
'ddos-protection',
'health-check',
'flowspec',
'bgp-ls',
'mpls',
'vpls',
'ipv4',
'ipv6',
'sdn',
'high-availability',
]
classifiers = [
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: System Administrators',
'Intended Audience :: Telecommunications Industry',
'Operating System :: POSIX',
'Operating System :: MacOS :: MacOS X',
'Programming Language :: Python',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3.14',
'Topic :: Internet',
]
readme = 'README.md'
dependencies = []
[project.urls]
Repository = 'https://github.com/Exa-Networks/exabgp'
Homepage = 'https://github.com/Exa-Networks/exabgp'
Documentation = 'https://github.com/Exa-Networks/exabgp/wiki'
Issues = 'https://github.com/Exa-Networks/exabgp/issues'
Changelog = 'https://github.com/Exa-Networks/exabgp/blob/main/doc/CHANGELOG.rst'
[project.scripts]
exabgp = 'exabgp.application.main:main'
exabgp-cli = 'exabgp.application.cli:main'
exabgp-healthcheck = 'exabgp.application.healthcheck:main'
[dependency-groups]
dev = [
"ruff",
"mypy",
"pytest>=9.0.3",
"pytest-asyncio",
"pytest-cov",
"coveralls",
"psutil",
"hypothesis>=6.0",
"pytest-benchmark>=4.0",
"pytest-xdist>=3.0",
"pytest-timeout>=2.0",
"mutmut>=3.0",
"setuptools",
"pyinstaller",
]
[tool.uv]
environments = ["sys_platform == 'darwin'", "sys_platform == 'linux'"]
[build-system]
requires = ["setuptools", "wheel"]
# include = ["*.txt", "*.rst"]
[tool.pytest.ini_options]
# Bare `pytest` and `pytest ./tests` have to mean the same thing, or the pipeline and a
# developer at a prompt are running different suites and only one of them is right.
#
# They did not. Bare pytest collected 5198 with 8 errors against 5193 clean, because it
# walked mutants/, mutmut's 33M working copy of the tree, which is gitignored and holds a
# stale duplicate of every test. Anyone typing pytest in this repository after a mutation
# round got errors out of an artefact.
#
# Session 5.0 found the same setting missing on their branch, where the two agreed only
# because nothing outside tests/ happened to be named test_*.py. That is a fact about a
# layout rather than a setting, which is the divergence we spent today's rounds on, waiting
# rather than arrived.
testpaths = ["tests"]
asyncio_default_fixture_loop_scope = "function"
markers = [
"fuzz: Fuzzing tests using Hypothesis (deselect with '-m \"not fuzz\"')",
"asyncio: Async tests using pytest-asyncio",
"timeout: Test timeout in seconds (requires pytest-timeout plugin)",
"registry_floor: asserts the registry this file sweeps or parametrises from is whole",
"rfc: names the RFC requirements a test proves, joined to qa/rfc/ by ./qa/bin/check_rfc_compliance",
]
[tool.ruff]
line-length = 120
exclude = ["dev", "lib/exabgp/vendoring", "build", "site-packages"]
[tool.ruff.lint]
# Select same critical error codes as flake8 configuration:
# E9: Runtime/syntax errors
# F63: Invalid print statements
# F7: Syntax errors in docstrings and type comments
# F82: Undefined names in __all__
# select = ["E9", "F63", "F7", "F82"]
# Ignore specific rules:
# ERA001: Commented-out code (intentional in this codebase)
ignore = ["ERA001"]
[tool.ruff.lint.flake8-quotes]
inline-quotes = "single"
docstring-quotes = "double"
[tool.ruff.format]
quote-style = "single"
indent-style = "space"
docstring-code-format = true
# E131 continuation line unaligned for hanging indent
# E203 whitespace before ':'
# E222 multiple spaces after operator
# E231 missing whitespace after ','
# E241 multiple spaces after ':'
# E266
# E501 line too long
# W191
# W503
# F401 imported but unused
# F403
# the last , is required otherwise the last error ignore is not applied
[tool.mypy]
# Type annotation project started: 2025-11-12
# Strict mode enabled: 2025-12-17 (no regressions allowed)
# Note: Using 3.12 for mypy to match minimum supported version
python_version = "3.12"
# Strict mode - prevent any type regressions
strict = true
warn_unused_configs = true
warn_return_any = true
warn_redundant_casts = true
warn_unused_ignores = true
# Strict enforcement - no untyped code allowed
disallow_untyped_defs = true
disallow_untyped_calls = true
disallow_incomplete_defs = true
disallow_any_generics = true
disallow_subclassing_any = true
disallow_untyped_decorators = true
check_untyped_defs = true
no_implicit_reexport = true
strict_equality = true
extra_checks = true
# Import handling
follow_imports = "normal"
ignore_missing_imports = true
# Custom stubs for PEP 688 Buffer protocol (mypy doesn't fully support it yet)
mypy_path = "stubs"
# All modules use the global strict settings (2025-12-17), vendored code included since it
# was typed for the compiled build (2026-09-30)
[tool.mutmut]
# mutation testing: does the test suite notice when the code changes meaning?
# scoped to the code which validates what a peer or an operator sends, mutating all of
# src/exabgp would take hours and say little about the parts under test
#
# mutmut does not mutate the methods of a DECORATED class. Every decoder here used to carry
# its register decorator directly, so the code which parses what a peer sends was the one
# part of this tree mutation testing could not see: inet, ipvpn, label, rtc, vpls and aigp
# each produced zero mutants while sitting in this list as though they were covered.
#
# Each now keeps its body in an undecorated <Name>Base and registers an empty subclass, so
# all of them are reachable. Anything added here should be checked the same way: run it and
# count the mutants, because a module which produces none is a claim of coverage this
# configuration cannot deliver.
paths_to_mutate = [
"src/exabgp/util/psk.py",
"src/exabgp/bgp/message/update/nlri/cidr.py",
"src/exabgp/bgp/message/update/nlri/inet.py",
"src/exabgp/bgp/message/update/nlri/ipvpn.py",
"src/exabgp/bgp/message/update/nlri/label.py",
"src/exabgp/bgp/message/update/nlri/rtc.py",
"src/exabgp/bgp/message/update/nlri/vpls.py",
"src/exabgp/bgp/message/update/nlri/flow.py",
"src/exabgp/bgp/message/update/attribute/aigp.py",
"src/exabgp/reactor/api/command/group.py",
# the reactor's bookkeeping: which peers are held and which sockets are bound. Issue
# #1425 lived here and nothing tested it, so tests/unit/test_reload_invariants.py and
# test_removed_neighbor_cleanup.py are the only things defending these two files and
# both were written the same day as the fix. Mutation testing is what says whether they
# defend them or merely run them.
"src/exabgp/reactor/listener.py",
"src/exabgp/reactor/loop.py",
# what the peer is actually sent. PATHS-LIMIT decides how many paths per prefix leave
# this box and how many the peer may send us, so these two hold an operator's promise
# to a peer rather than a decoder's reading of one, and the same question applies:
# do the tests defend the selection, or only walk it.
"src/exabgp/rib/outgoing.py",
"src/exabgp/rib/incoming.py",
]
tests_dir = ["tests/unit/", "tests/fuzz/"]
do_not_mutate = ["*/vendoring/*"]
# the tests import the whole package, so the modules which are not mutated have to be
# there too, copied before the mutants are written over the files being mutated
also_copy = ["src/", "sbin/", "etc/", "qa/"]
# The modules mypyc compiles: all of them, but for the exclude below (plan/wip-mypyc.md,
# doc/user/compiled-build.md). Relative to
# the directory holding the exabgp package: a directory is every module under it, a
# pattern what it matches. A package's __init__.py is never compiled. Read by
# qa/bin/build_mypyc and by setup.py when EXABGP_MYPYC=1.
[tool.exabgp.mypyc]
modules = [
'exabgp/*.py',
'exabgp/application',
'exabgp/bgp',
'exabgp/cli',
'exabgp/configuration',
'exabgp/data',
'exabgp/debug',
'exabgp/environment',
'exabgp/logger',
'exabgp/protocol',
'exabgp/reactor',
'exabgp/rib',
'exabgp/util',
'exabgp/vendoring',
]
# Left interpreted, relative to the same directory. util/mypyc.py exists to replace
# mypy_extensions' trait and mypyc_attr at run time when it is not installed: compiled, the
# fallback branch is unreachable to mypy and would be compiled as a crash, and the module
# runs once at import.
exclude = ['exabgp/util/mypyc.py']