fluidattacks_core_http 12.0.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.
@@ -0,0 +1 @@
1
+ use_flake ".#python311.devShell"
@@ -0,0 +1,5 @@
1
+ target/
2
+ .direnv/
3
+ .venv/
4
+ .vscode/settings.json
5
+ .zed/settings.json
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.5
2
+ Name: fluidattacks_core_http
3
+ Version: 12.0.0
4
+ Summary: Fluid Attacks Core HTTP Library
5
+ Author-email: Development <development@fluidattacks.com>
6
+ License: MPL-2.0
7
+ Classifier: Development Status :: 1 - Planning
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Topic :: Software Development :: Libraries
13
+ Requires-Python: >=3.11
14
+ Requires-Dist: aiohttp>=3.10.0
15
+ Requires-Dist: certifi>=2024.8.30
16
+ Requires-Dist: urllib3>=2.6.1
@@ -0,0 +1,20 @@
1
+ { src, workspace_root }:
2
+ {
3
+ inherit src workspace_root;
4
+ root_path = "develops/fluidattacks-core/python/http";
5
+ module_name = "fluidattacks_core";
6
+ pypi_token = {
7
+ method = "oidc";
8
+ };
9
+ override = {
10
+ checks = {
11
+ basedpyright = false;
12
+ pytest = false;
13
+ };
14
+ configs = {
15
+ import_linter = ../import-linter.cfg;
16
+ mypy = ../mypy.ini;
17
+ ruff = ../ruff.toml;
18
+ };
19
+ };
20
+ }
@@ -0,0 +1,12 @@
1
+ path_filter: src:
2
+ path_filter {
3
+ root = src;
4
+ include = [
5
+ "fluidattacks_core"
6
+ "import-linter.cfg"
7
+ "mypy.ini"
8
+ "pyproject.toml"
9
+ "ruff.toml"
10
+ "uv.lock"
11
+ ];
12
+ }
@@ -0,0 +1,303 @@
1
+ {
2
+ "nodes": {
3
+ "flake-parts": {
4
+ "inputs": {
5
+ "nixpkgs-lib": "nixpkgs-lib"
6
+ },
7
+ "locked": {
8
+ "lastModified": 1762810396,
9
+ "narHash": "sha256-dxFVgQPG+R72dkhXTtqUm7KpxElw3u6E+YlQ2WaDgt8=",
10
+ "owner": "hercules-ci",
11
+ "repo": "flake-parts",
12
+ "rev": "0bdadb1b265fb4143a75bd1ec7d8c915898a9923",
13
+ "type": "github"
14
+ },
15
+ "original": {
16
+ "owner": "hercules-ci",
17
+ "repo": "flake-parts",
18
+ "type": "github"
19
+ }
20
+ },
21
+ "nix_filter": {
22
+ "locked": {
23
+ "lastModified": 1757882181,
24
+ "narHash": "sha256-+cCxYIh2UNalTz364p+QYmWHs0P+6wDhiWR4jDIKQIU=",
25
+ "owner": "numtide",
26
+ "repo": "nix-filter",
27
+ "rev": "59c44d1909c72441144b93cf0f054be7fe764de5",
28
+ "type": "github"
29
+ },
30
+ "original": {
31
+ "owner": "numtide",
32
+ "repo": "nix-filter",
33
+ "type": "github"
34
+ }
35
+ },
36
+ "nixpkgs": {
37
+ "locked": {
38
+ "lastModified": 1758690382,
39
+ "narHash": "sha256-NY3kSorgqE5LMm1LqNwGne3ZLMF2/ILgLpFr1fS4X3o=",
40
+ "owner": "nixos",
41
+ "repo": "nixpkgs",
42
+ "rev": "e643668fd71b949c53f8626614b21ff71a07379d",
43
+ "type": "github"
44
+ },
45
+ "original": {
46
+ "owner": "nixos",
47
+ "ref": "nixos-unstable",
48
+ "repo": "nixpkgs",
49
+ "type": "github"
50
+ }
51
+ },
52
+ "nixpkgs-lib": {
53
+ "locked": {
54
+ "lastModified": 1761765539,
55
+ "narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=",
56
+ "owner": "nix-community",
57
+ "repo": "nixpkgs.lib",
58
+ "rev": "719359f4562934ae99f5443f20aa06c2ffff91fc",
59
+ "type": "github"
60
+ },
61
+ "original": {
62
+ "owner": "nix-community",
63
+ "repo": "nixpkgs.lib",
64
+ "type": "github"
65
+ }
66
+ },
67
+ "nixpkgs_2": {
68
+ "locked": {
69
+ "lastModified": 1758690382,
70
+ "narHash": "sha256-NY3kSorgqE5LMm1LqNwGne3ZLMF2/ILgLpFr1fS4X3o=",
71
+ "owner": "nixos",
72
+ "repo": "nixpkgs",
73
+ "rev": "e643668fd71b949c53f8626614b21ff71a07379d",
74
+ "type": "github"
75
+ },
76
+ "original": {
77
+ "owner": "nixos",
78
+ "ref": "nixos-unstable",
79
+ "repo": "nixpkgs",
80
+ "type": "github"
81
+ }
82
+ },
83
+ "nixpkgs_3": {
84
+ "locked": {
85
+ "lastModified": 1761373498,
86
+ "narHash": "sha256-Q/uhWNvd7V7k1H1ZPMy/vkx3F8C13ZcdrKjO7Jv7v0c=",
87
+ "owner": "nixos",
88
+ "repo": "nixpkgs",
89
+ "rev": "6a08e6bb4e46ff7fcbb53d409b253f6bad8a28ce",
90
+ "type": "github"
91
+ },
92
+ "original": {
93
+ "owner": "nixos",
94
+ "ref": "nixos-unstable",
95
+ "repo": "nixpkgs",
96
+ "type": "github"
97
+ }
98
+ },
99
+ "nixpkgs_flake": {
100
+ "locked": {
101
+ "lastModified": 1778253243,
102
+ "narHash": "sha256-KkH2QvU6iRLDTGGEwH5jnMqHWj4zMTuHxi1S1zdr01w=",
103
+ "owner": "nixos",
104
+ "repo": "nixpkgs",
105
+ "rev": "4fcda7103530b23c5ca44286344912082aad4220",
106
+ "type": "github"
107
+ },
108
+ "original": {
109
+ "owner": "nixos",
110
+ "repo": "nixpkgs",
111
+ "type": "github"
112
+ }
113
+ },
114
+ "pyproject-build-systems": {
115
+ "inputs": {
116
+ "nixpkgs": "nixpkgs",
117
+ "pyproject-nix": "pyproject-nix",
118
+ "uv2nix": "uv2nix"
119
+ },
120
+ "locked": {
121
+ "lastModified": 1761781027,
122
+ "narHash": "sha256-YDvxPAm2WnxrznRqWwHLjryBGG5Ey1ATEJXrON+TWt8=",
123
+ "owner": "pyproject-nix",
124
+ "repo": "build-system-pkgs",
125
+ "rev": "795a980d25301e5133eca37adae37283ec3c8e66",
126
+ "type": "github"
127
+ },
128
+ "original": {
129
+ "owner": "pyproject-nix",
130
+ "repo": "build-system-pkgs",
131
+ "type": "github"
132
+ }
133
+ },
134
+ "pyproject-nix": {
135
+ "inputs": {
136
+ "nixpkgs": [
137
+ "python_builder",
138
+ "pyproject-build-systems",
139
+ "nixpkgs"
140
+ ]
141
+ },
142
+ "locked": {
143
+ "lastModified": 1758265079,
144
+ "narHash": "sha256-amLaLNwKSZPShQHzfgmc/9o76dU8xzN0743dWgvYlr8=",
145
+ "owner": "nix-community",
146
+ "repo": "pyproject.nix",
147
+ "rev": "02e9418fd4af638447dca4b17b1280da95527fc9",
148
+ "type": "github"
149
+ },
150
+ "original": {
151
+ "owner": "nix-community",
152
+ "repo": "pyproject.nix",
153
+ "type": "github"
154
+ }
155
+ },
156
+ "pyproject-nix_2": {
157
+ "inputs": {
158
+ "nixpkgs": "nixpkgs_2"
159
+ },
160
+ "locked": {
161
+ "lastModified": 1762427963,
162
+ "narHash": "sha256-CkPlAbIQ87wmjy5qHibfzk4DmMGBNqFer+lLfXjpP5M=",
163
+ "owner": "pyproject-nix",
164
+ "repo": "pyproject.nix",
165
+ "rev": "4540ea004e04fcd12dd2738d51383d10f956f7b9",
166
+ "type": "github"
167
+ },
168
+ "original": {
169
+ "owner": "pyproject-nix",
170
+ "repo": "pyproject.nix",
171
+ "type": "github"
172
+ }
173
+ },
174
+ "pyproject-nix_3": {
175
+ "inputs": {
176
+ "nixpkgs": [
177
+ "python_builder",
178
+ "uv2nix",
179
+ "nixpkgs"
180
+ ]
181
+ },
182
+ "locked": {
183
+ "lastModified": 1760402624,
184
+ "narHash": "sha256-jF6UKLs2uGc2rtved8Vrt58oTWjTQoAssuYs/0578Z4=",
185
+ "owner": "pyproject-nix",
186
+ "repo": "pyproject.nix",
187
+ "rev": "84c4ea102127c77058ea1ed7be7300261fafc7d2",
188
+ "type": "github"
189
+ },
190
+ "original": {
191
+ "owner": "pyproject-nix",
192
+ "repo": "pyproject.nix",
193
+ "type": "github"
194
+ }
195
+ },
196
+ "python_builder": {
197
+ "inputs": {
198
+ "nix_filter": "nix_filter",
199
+ "nixpkgs_flake": "nixpkgs_flake",
200
+ "pyproject-build-systems": "pyproject-build-systems",
201
+ "pyproject-nix": "pyproject-nix_2",
202
+ "shell-helpers": "shell-helpers",
203
+ "uv2nix": "uv2nix_2"
204
+ },
205
+ "locked": {
206
+ "dir": "common/utils/python-builder",
207
+ "lastModified": 1787078362,
208
+ "narHash": "sha256-HYL7Rn6SuNjYxkt/PwFLksZjsauCiY+ENbOWryLsIlw=",
209
+ "ref": "refs/heads/trunk",
210
+ "rev": "09fd25a13309eb5e3b5b2a061bb340df3ae5d281",
211
+ "shallow": true,
212
+ "type": "git",
213
+ "url": "ssh://git@gitlab.com/fluidattacks/universe"
214
+ },
215
+ "original": {
216
+ "dir": "common/utils/python-builder",
217
+ "rev": "09fd25a13309eb5e3b5b2a061bb340df3ae5d281",
218
+ "shallow": true,
219
+ "type": "git",
220
+ "url": "ssh://git@gitlab.com/fluidattacks/universe"
221
+ }
222
+ },
223
+ "root": {
224
+ "inputs": {
225
+ "python_builder": "python_builder"
226
+ }
227
+ },
228
+ "shell-helpers": {
229
+ "inputs": {
230
+ "flake-parts": "flake-parts",
231
+ "nixpkgs": [
232
+ "python_builder",
233
+ "nixpkgs_flake"
234
+ ]
235
+ },
236
+ "locked": {
237
+ "dir": "common/utils/shell-helpers",
238
+ "lastModified": 1762806066,
239
+ "narHash": "sha256-Wz99Fl7SpPU1NkTHJ36XOo6DjOEAMXCw3ThRABOS2Hs=",
240
+ "ref": "refs/heads/trunk",
241
+ "rev": "d73cfdc7ae9e7f547e22a4481d4c2ccce38b26a8",
242
+ "shallow": true,
243
+ "type": "git",
244
+ "url": "ssh://git@gitlab.com/fluidattacks/universe"
245
+ },
246
+ "original": {
247
+ "dir": "common/utils/shell-helpers",
248
+ "rev": "d73cfdc7ae9e7f547e22a4481d4c2ccce38b26a8",
249
+ "shallow": true,
250
+ "type": "git",
251
+ "url": "ssh://git@gitlab.com/fluidattacks/universe"
252
+ }
253
+ },
254
+ "uv2nix": {
255
+ "inputs": {
256
+ "nixpkgs": [
257
+ "python_builder",
258
+ "pyproject-build-systems",
259
+ "nixpkgs"
260
+ ],
261
+ "pyproject-nix": [
262
+ "python_builder",
263
+ "pyproject-build-systems",
264
+ "pyproject-nix"
265
+ ]
266
+ },
267
+ "locked": {
268
+ "lastModified": 1758933732,
269
+ "narHash": "sha256-HAmm1GBS1myZCFuog0DC2ZLaynvZtiUI2Crmo+cdQI0=",
270
+ "owner": "pyproject-nix",
271
+ "repo": "uv2nix",
272
+ "rev": "273ce18f913d8559e0d04f820d724308966d7c4d",
273
+ "type": "github"
274
+ },
275
+ "original": {
276
+ "owner": "pyproject-nix",
277
+ "repo": "uv2nix",
278
+ "type": "github"
279
+ }
280
+ },
281
+ "uv2nix_2": {
282
+ "inputs": {
283
+ "nixpkgs": "nixpkgs_3",
284
+ "pyproject-nix": "pyproject-nix_3"
285
+ },
286
+ "locked": {
287
+ "lastModified": 1761872265,
288
+ "narHash": "sha256-i25GRgp2vUOebY70L3NTAgkd+Pr1hnn5xM3qHxH0ONU=",
289
+ "owner": "pyproject-nix",
290
+ "repo": "uv2nix",
291
+ "rev": "74dfb62871be152ad3673b143b0cc56105a4f3c5",
292
+ "type": "github"
293
+ },
294
+ "original": {
295
+ "owner": "pyproject-nix",
296
+ "repo": "uv2nix",
297
+ "type": "github"
298
+ }
299
+ }
300
+ },
301
+ "root": "root",
302
+ "version": 7
303
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ description = "Fluid Attacks Core http package.";
3
+
4
+ inputs = {
5
+ python_builder = {
6
+ url = "git+ssh://git@gitlab.com/fluidattacks/universe?shallow=1&rev=09fd25a13309eb5e3b5b2a061bb340df3ae5d281&dir=common/utils/python-builder";
7
+ };
8
+ };
9
+
10
+ outputs =
11
+ { self, ... }@inputs:
12
+ let
13
+ build_args =
14
+ {
15
+ system,
16
+ python_version,
17
+ nixpkgs,
18
+ builders,
19
+ scripts,
20
+ }:
21
+ import ./build {
22
+ src = import ./build/filter.nix nixpkgs.nix-filter self;
23
+ workspace_root = ./.;
24
+ };
25
+ in
26
+ {
27
+ packages = inputs.python_builder.outputs.build build_args;
28
+ };
29
+ }
@@ -0,0 +1,5 @@
1
+ from .client import (
2
+ request,
3
+ )
4
+
5
+ __all__ = ["request"]
@@ -0,0 +1,90 @@
1
+ import ipaddress
2
+ import ssl
3
+ from collections.abc import Sequence
4
+ from typing import (
5
+ Any,
6
+ Literal,
7
+ )
8
+
9
+ import aiohttp
10
+ import certifi
11
+
12
+ from .validations import (
13
+ validate_local_request,
14
+ validate_url,
15
+ )
16
+
17
+
18
+ def get_secure_connector(
19
+ *,
20
+ allow_local_network: bool,
21
+ allow_localhost: bool,
22
+ ) -> type[aiohttp.TCPConnector]:
23
+ class SecureTCPConnector(aiohttp.TCPConnector):
24
+ async def _resolve_host(
25
+ self,
26
+ host: str,
27
+ port: int,
28
+ traces: Sequence[aiohttp.tracing.Trace] | None = None,
29
+ ) -> list[aiohttp.abc.ResolveResult]:
30
+ hosts = await super()._resolve_host(host, port, traces)
31
+ resolved_ips = [ipaddress.ip_address(host["host"]) for host in hosts]
32
+ validate_local_request(
33
+ resolved_ips,
34
+ allow_local_network=allow_local_network,
35
+ allow_localhost=allow_localhost,
36
+ )
37
+ return hosts
38
+
39
+ return SecureTCPConnector
40
+
41
+
42
+ async def request( # noqa: PLR0913
43
+ url: str,
44
+ *,
45
+ method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"],
46
+ allow_local_network: bool = False,
47
+ allow_localhost: bool = False,
48
+ ascii_only: bool = False,
49
+ dns_rebind_protection: bool = True,
50
+ enforce_sanitization: bool = False,
51
+ headers: dict[str, str] | None = None,
52
+ json: Any | None = None, # noqa: ANN401
53
+ ports: list[int] | None = None,
54
+ schemes: list[str] | None = None,
55
+ timeout: int = 10, # noqa: ASYNC109
56
+ ) -> aiohttp.ClientResponse:
57
+ validate_url(
58
+ url,
59
+ ascii_only=ascii_only,
60
+ enforce_sanitization=enforce_sanitization,
61
+ ports=ports or [],
62
+ schemes=schemes or [],
63
+ )
64
+ connector = (
65
+ get_secure_connector(
66
+ allow_local_network=allow_local_network,
67
+ allow_localhost=allow_localhost,
68
+ )
69
+ if dns_rebind_protection
70
+ else aiohttp.TCPConnector
71
+ )
72
+ connection = connector(
73
+ ssl=ssl.create_default_context(cafile=certifi.where()),
74
+ )
75
+
76
+ async with (
77
+ aiohttp.ClientSession(
78
+ connector=connection,
79
+ headers=headers,
80
+ ) as session,
81
+ session.request(
82
+ method,
83
+ url,
84
+ allow_redirects=not dns_rebind_protection,
85
+ json=json,
86
+ timeout=aiohttp.ClientTimeout(total=timeout),
87
+ ) as response,
88
+ ):
89
+ await response.read()
90
+ return response
@@ -0,0 +1,133 @@
1
+ import html
2
+ import ipaddress
3
+
4
+ from urllib3.exceptions import LocationParseError
5
+ from urllib3.util import Url, parse_url
6
+
7
+
8
+ class HTTPValidationError(Exception):
9
+ pass
10
+
11
+
12
+ def validate_scheme(scheme: str | None, schemes: list[str]) -> None:
13
+ if scheme and scheme not in schemes:
14
+ msg = f"Only allowed schemes are {', '.join(schemes)}"
15
+ raise HTTPValidationError(msg)
16
+
17
+
18
+ def validate_port(port: int | None, ports: list[int]) -> None:
19
+ if port and port < 1024 and port not in ports:
20
+ msg = f"Only allowed ports are {', '.join(map(str, ports))}, and any over 1024"
21
+ raise HTTPValidationError(msg)
22
+
23
+
24
+ def validate_host(host: str | None) -> None:
25
+ if not host:
26
+ return
27
+ try:
28
+ ipaddress.ip_address(host)
29
+ except ValueError:
30
+ if host[0].isalnum():
31
+ return
32
+ else:
33
+ return
34
+ msg = "Hostname or IP address invalid"
35
+ raise HTTPValidationError(msg)
36
+
37
+
38
+ def validate_unicode_restriction(uri: Url) -> None:
39
+ uri_str = str(uri)
40
+ if not uri_str.isascii():
41
+ msg = f"URI must be ascii only {uri_str}"
42
+ raise HTTPValidationError(msg)
43
+
44
+
45
+ def validate_html_tags(uri: Url) -> None:
46
+ uri_str = str(uri)
47
+ sanitized_uri = html.escape(uri_str)
48
+ if sanitized_uri != uri_str:
49
+ msg = "HTML/CSS/JS tags are not allowed"
50
+ raise HTTPValidationError(msg)
51
+
52
+
53
+ def validate_url(
54
+ url: str,
55
+ *,
56
+ ascii_only: bool,
57
+ enforce_sanitization: bool,
58
+ ports: list[int],
59
+ schemes: list[str],
60
+ ) -> None:
61
+ try:
62
+ uri = parse_url(url)
63
+ except LocationParseError as exc:
64
+ msg = f"Invalid URL {url}"
65
+ raise HTTPValidationError(msg) from exc
66
+ validate_host(uri.host)
67
+
68
+ if ascii_only:
69
+ validate_unicode_restriction(uri)
70
+ if enforce_sanitization:
71
+ validate_html_tags(uri)
72
+ if ports:
73
+ validate_port(uri.port, ports)
74
+ if schemes:
75
+ validate_scheme(uri.scheme, schemes)
76
+
77
+
78
+ def validate_loopback(
79
+ ips: list[ipaddress.IPv4Address | ipaddress.IPv6Address],
80
+ ) -> None:
81
+ if any(ip.is_loopback for ip in ips):
82
+ msg = "Requests to loopback addresses are not allowed"
83
+ raise HTTPValidationError(msg)
84
+
85
+
86
+ def validate_local_network(
87
+ ips: list[ipaddress.IPv4Address | ipaddress.IPv6Address],
88
+ ) -> None:
89
+ if any(ip.is_private for ip in ips):
90
+ msg = "Requests to the local network are not allowed"
91
+ raise HTTPValidationError(msg)
92
+
93
+
94
+ def validate_link_local(
95
+ ips: list[ipaddress.IPv4Address | ipaddress.IPv6Address],
96
+ ) -> None:
97
+ if any(ip.is_link_local for ip in ips):
98
+ msg = "Requests to the link local network are not allowed"
99
+ raise HTTPValidationError(msg)
100
+
101
+
102
+ def validate_shared_address(
103
+ ips: list[ipaddress.IPv4Address | ipaddress.IPv6Address],
104
+ ) -> None:
105
+ shared_address_space = ipaddress.ip_network("100.64.0.0/10")
106
+ if any(ip in shared_address_space for ip in ips):
107
+ msg = "Requests to the shared address space are not allowed"
108
+ raise HTTPValidationError(msg)
109
+
110
+
111
+ def validate_limited_broadcast_address(
112
+ ips: list[ipaddress.IPv4Address | ipaddress.IPv6Address],
113
+ ) -> None:
114
+ limited_broadcast_address = ipaddress.ip_address("255.255.255.255")
115
+ if any(ip == limited_broadcast_address for ip in ips):
116
+ msg = "Requests to the limited broadcast address are not allowed"
117
+ raise HTTPValidationError(msg)
118
+
119
+
120
+ def validate_local_request(
121
+ ips: list[ipaddress.IPv4Address | ipaddress.IPv6Address],
122
+ *,
123
+ allow_local_network: bool,
124
+ allow_localhost: bool,
125
+ ) -> None:
126
+ if not allow_localhost:
127
+ validate_loopback(ips)
128
+
129
+ if not allow_local_network:
130
+ validate_local_network(ips)
131
+ validate_link_local(ips)
132
+ validate_shared_address(ips)
133
+ validate_limited_broadcast_address(ips)
@@ -0,0 +1,22 @@
1
+ [importlinter]
2
+ root_package = fluidattacks_core
3
+ include_external_packages = True
4
+
5
+ [importlinter:contract:portion]
6
+ name = Single Portion
7
+ type = layers
8
+ containers =
9
+ fluidattacks_core
10
+ exhaustive = true
11
+ layers =
12
+ http
13
+
14
+ [importlinter:contract:dag]
15
+ name = Direct Acyclic Graph
16
+ type = layers
17
+ containers =
18
+ fluidattacks_core.http
19
+ exhaustive = true
20
+ layers =
21
+ client
22
+ validations
@@ -0,0 +1,32 @@
1
+ [mypy]
2
+ disallow_any_decorated = True
3
+ disallow_any_expr = False
4
+ disallow_any_explicit = False
5
+ disallow_any_generics = True
6
+ disallow_any_unimported = True
7
+ disallow_incomplete_defs = True
8
+ disallow_subclassing_any = True
9
+ disallow_untyped_calls = True
10
+ disallow_untyped_decorators = True
11
+ disallow_untyped_defs = True
12
+
13
+ check_untyped_defs = True
14
+ no_implicit_optional = True
15
+ strict = True
16
+ strict_equality = True
17
+ strict_optional = True
18
+
19
+ warn_redundant_casts = True
20
+ warn_unused_ignores = True
21
+ warn_no_return = True
22
+ warn_return_any = False
23
+ warn_unreachable = True
24
+
25
+ explicit_package_bases = True
26
+
27
+ ignore_errors = False
28
+ ignore_missing_imports = False
29
+ allow_untyped_globals = False
30
+ allow_redefinition = False
31
+ local_partial_types = False
32
+ implicit_reexport = False