wsbuilder 1.0.0__tar.gz → 1.0.2__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.
- {wsbuilder-1.0.0/src/wsbuilder.egg-info → wsbuilder-1.0.2}/PKG-INFO +1 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/getting-started.md +1 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/install.md +1 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/pyproject.toml +21 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/__init__.py +54 -54
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/app.py +2 -2
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/cookies.py +0 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/db_replicas.py +38 -39
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/dns.py +1 -2
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/headers.py +0 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/http.py +1 -2
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/http3_server.py +2 -3
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/http3_websocket.py +1 -2
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/mdns.py +1 -2
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/orm.py +5 -5
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/pki.py +1 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/predicts.py +0 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/proxyi.py +1 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/quic/address.py +1 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/quic/connection.py +2 -5
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/server.py +5 -5
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/tasks.py +0 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/ws.py +1 -2
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/ws_demo.py +9 -9
- {wsbuilder-1.0.0 → wsbuilder-1.0.2/src/wsbuilder.egg-info}/PKG-INFO +1 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_contributing_policy.py +0 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_dns.py +1 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_docs.py +10 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_documentation_files.py +0 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_hpack.py +0 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_http2.py +0 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_http3.py +0 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_http3_server.py +3 -4
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_http3_streaming_udp.py +2 -4
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_ia.py +8 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_orm_replicas.py +45 -45
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_pki.py +3 -3
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_pr_identity_policy.py +1 -2
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_quic_crypto.py +4 -2
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_tasks.py +9 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_ws.py +5 -6
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/LICENSE +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/MANIFEST.in +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/README.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/404.html +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/CNAME +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/advanced.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/ai-predict.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/api-map.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/app-http.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/architecture.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/assets/wsbuilder-mark.svg +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/beginners.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/cache-security.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/cache.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/capabilities.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/data-orm.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/dns.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/full-project.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/help/index.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/http-versions.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/http.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/ia.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/index.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/intermediate.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/mdns.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/network-edge.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/observability-tasks.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/observability.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/persistence.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/proxy.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/reference/index.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/security.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/stylesheets/extra.css +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/tasks.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/tls-certificates.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/docs/websocket.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/examples/full-project/.gitignore +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/examples/full-project/README.md +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/examples/full-project/app.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/examples/full-project/pyproject.toml +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/examples/full-project/upstream.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/mkdocs.yml +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/setup.cfg +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/__main__.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/cache.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/caches.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/constants.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/framework.py +24 -24
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/hpack.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/http1.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/http2.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/http3.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/ia.py +1 -1
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/logs.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/metrics.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/qpack.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/quic/__init__.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/quic/crypto.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/quic/frames.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/quic/packet.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/quic/recovery.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/quic/tls.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/quic/varint.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder/security.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder.egg-info/SOURCES.txt +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder.egg-info/dependency_links.txt +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder.egg-info/entry_points.txt +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder.egg-info/requires.txt +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/src/wsbuilder.egg-info/top_level.txt +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_app_threads.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_cache.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_caches.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_demo_monitor.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_headers_cookies.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_http.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_http1.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_http3_streaming.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_logs.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_mdns.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_metrics.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_orm.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_predicts.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_proxyi.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_quic_address.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_quic_protocol.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_quic_recovery.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_quic_tls.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_security.py +0 -0
- {wsbuilder-1.0.0 → wsbuilder-1.0.2}/tests/test_server.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wsbuilder"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.2"
|
|
8
8
|
description = "Lightweight HTTP + WebSocket framework for Python."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -49,3 +49,23 @@ package-dir = {"" = "src"}
|
|
|
49
49
|
[tool.setuptools.packages.find]
|
|
50
50
|
where = ["src"]
|
|
51
51
|
include = ["wsbuilder*"]
|
|
52
|
+
|
|
53
|
+
[tool.ruff]
|
|
54
|
+
target-version = "py311"
|
|
55
|
+
line-length = 100
|
|
56
|
+
src = ["src", "tests"]
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
select = ["E", "F", "W", "I", "B", "UP"]
|
|
60
|
+
ignore = [
|
|
61
|
+
"E501", # long literals and protocol tables read better unwrapped
|
|
62
|
+
"B905", # zip() over equal-length vectors; strict= would change behaviour
|
|
63
|
+
"UP031", # printf-style formatting is used deliberately in log templates
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint.per-file-ignores]
|
|
67
|
+
# Test closures defined in loops run and are joined within the same iteration.
|
|
68
|
+
"tests/*" = ["B023"]
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint.isort]
|
|
71
|
+
known-first-party = ["wsbuilder"]
|
|
@@ -1,19 +1,48 @@
|
|
|
1
1
|
"""wsbuilder: mini framework HTTP + WebSocket."""
|
|
2
2
|
|
|
3
|
+
from .cache import Cache, SQLiteMemoryCache, install_cache
|
|
4
|
+
from .caches import GlobalCacheRule, ViewResponseCache, install_caches
|
|
5
|
+
from .cookies import build_set_cookie, get_cookie, parse_cookie_header
|
|
6
|
+
from .db_replicas import (
|
|
7
|
+
DatabaseReplica,
|
|
8
|
+
DatabaseReplicaPool,
|
|
9
|
+
OptimizedDatabase,
|
|
10
|
+
SQLite3OptimizationConfig,
|
|
11
|
+
)
|
|
12
|
+
from .dns import LocalDNSServer
|
|
3
13
|
from .framework import (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
BALANCING_BEST,
|
|
15
|
+
BALANCING_CONSISTENT_HASH,
|
|
16
|
+
BALANCING_FIRST_AVAILABLE,
|
|
17
|
+
BALANCING_IP_HASH,
|
|
18
|
+
BALANCING_LEAST_BYTES_IN,
|
|
19
|
+
BALANCING_LEAST_BYTES_OUT,
|
|
20
|
+
BALANCING_LEAST_CONNECTIONS,
|
|
21
|
+
BALANCING_LEAST_REQUESTS,
|
|
22
|
+
BALANCING_LEAST_RESPONSE_TIME,
|
|
23
|
+
BALANCING_POWER_OF_TWO_CHOICES,
|
|
24
|
+
BALANCING_RANDOM,
|
|
25
|
+
BALANCING_ROUND_ROBIN,
|
|
26
|
+
BALANCING_WEIGHTED_ROUND_ROBIN,
|
|
27
|
+
SUPPORTED_BALANCING_STRATEGIES,
|
|
11
28
|
TASK_CANCELLED,
|
|
12
29
|
TASK_COMPLETED,
|
|
13
30
|
TASK_FAILED,
|
|
14
31
|
TASK_PENDING,
|
|
15
32
|
TASK_REJECTED,
|
|
16
33
|
TASK_RUNNING,
|
|
34
|
+
App,
|
|
35
|
+
HTTPServer,
|
|
36
|
+
ProxyI,
|
|
37
|
+
ProxyMetricsBucket,
|
|
38
|
+
ProxyRouteBuilder,
|
|
39
|
+
ProxyRule,
|
|
40
|
+
ProxyTarget,
|
|
41
|
+
Request,
|
|
42
|
+
Response,
|
|
43
|
+
Route,
|
|
44
|
+
Router,
|
|
45
|
+
RunningStats,
|
|
17
46
|
TaskCancelledError,
|
|
18
47
|
TaskClosedError,
|
|
19
48
|
TaskContext,
|
|
@@ -21,44 +50,31 @@ from .framework import (
|
|
|
21
50
|
TaskHandle,
|
|
22
51
|
TaskManager,
|
|
23
52
|
TaskRejectedError,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
ProxyRule,
|
|
27
|
-
ProxyRouteBuilder,
|
|
28
|
-
ProxyTarget,
|
|
29
|
-
ProxyMetricsBucket,
|
|
30
|
-
RunningStats,
|
|
53
|
+
WebSocket,
|
|
54
|
+
handshake_websocket_with_options,
|
|
31
55
|
install_proxyi,
|
|
32
56
|
normalize_balance_mode,
|
|
33
57
|
normalize_target,
|
|
34
|
-
SUPPORTED_BALANCING_STRATEGIES,
|
|
35
|
-
BALANCING_ROUND_ROBIN,
|
|
36
|
-
BALANCING_WEIGHTED_ROUND_ROBIN,
|
|
37
|
-
BALANCING_RANDOM,
|
|
38
|
-
BALANCING_LEAST_CONNECTIONS,
|
|
39
|
-
BALANCING_LEAST_RESPONSE_TIME,
|
|
40
|
-
BALANCING_LEAST_REQUESTS,
|
|
41
|
-
BALANCING_LEAST_BYTES_IN,
|
|
42
|
-
BALANCING_LEAST_BYTES_OUT,
|
|
43
|
-
BALANCING_IP_HASH,
|
|
44
|
-
BALANCING_CONSISTENT_HASH,
|
|
45
|
-
BALANCING_FIRST_AVAILABLE,
|
|
46
|
-
BALANCING_POWER_OF_TWO_CHOICES,
|
|
47
|
-
BALANCING_BEST,
|
|
48
58
|
parse_close_payload,
|
|
49
59
|
parse_query_string,
|
|
50
|
-
|
|
60
|
+
proxyi,
|
|
61
|
+
)
|
|
62
|
+
from .headers import get_header, has_header, normalize_header_name, set_header
|
|
63
|
+
from .ia import (
|
|
64
|
+
DataSet,
|
|
65
|
+
DataSummary,
|
|
66
|
+
DenseLayer,
|
|
67
|
+
ErrorSummary,
|
|
68
|
+
NeuralNetwork,
|
|
69
|
+
describe_data,
|
|
70
|
+
evaluate_errors,
|
|
71
|
+
submit_training_task,
|
|
51
72
|
)
|
|
52
|
-
from .metrics import AppMetrics, install_metrics
|
|
53
73
|
from .logs import NDJSONLog, install_logs
|
|
54
|
-
from .cache import Cache, SQLiteMemoryCache, install_cache
|
|
55
|
-
from .caches import GlobalCacheRule, ViewResponseCache, install_caches
|
|
56
|
-
from .security import ACLRule, SecurityDecision, SecurityPolicy, install_security
|
|
57
|
-
from .dns import LocalDNSServer
|
|
58
74
|
from .mdns import LocalMDNSServer, MDNSService, discover_mdns_services
|
|
59
|
-
from .
|
|
60
|
-
from .headers import get_header, has_header, normalize_header_name, set_header
|
|
75
|
+
from .metrics import AppMetrics, install_metrics
|
|
61
76
|
from .orm import (
|
|
77
|
+
SQL,
|
|
62
78
|
BlobField,
|
|
63
79
|
BooleanField,
|
|
64
80
|
Database,
|
|
@@ -69,7 +85,6 @@ from .orm import (
|
|
|
69
85
|
Model,
|
|
70
86
|
QuerySet,
|
|
71
87
|
RealField,
|
|
72
|
-
SQL,
|
|
73
88
|
TextField,
|
|
74
89
|
Transaction,
|
|
75
90
|
create_tables,
|
|
@@ -77,23 +92,8 @@ from .orm import (
|
|
|
77
92
|
quote_identifier,
|
|
78
93
|
validate_identifier,
|
|
79
94
|
)
|
|
80
|
-
from .db_replicas import (
|
|
81
|
-
DatabaseReplica,
|
|
82
|
-
DatabaseReplicaPool,
|
|
83
|
-
OptimizedDatabase,
|
|
84
|
-
SQLite3OptimizationConfig,
|
|
85
|
-
)
|
|
86
|
-
from .ia import (
|
|
87
|
-
DataSet,
|
|
88
|
-
DataSummary,
|
|
89
|
-
DenseLayer,
|
|
90
|
-
ErrorSummary,
|
|
91
|
-
NeuralNetwork,
|
|
92
|
-
describe_data,
|
|
93
|
-
evaluate_errors,
|
|
94
|
-
submit_training_task,
|
|
95
|
-
)
|
|
96
95
|
from .predicts import Predictor
|
|
96
|
+
from .security import ACLRule, SecurityDecision, SecurityPolicy, install_security
|
|
97
97
|
|
|
98
98
|
#: Names served from wsbuilder.pki, which needs the optional `tls` extra.
|
|
99
99
|
_PKI_EXPORTS = frozenset(
|
|
@@ -126,7 +126,7 @@ def __getattr__(name):
|
|
|
126
126
|
def __dir__():
|
|
127
127
|
return sorted(set(globals()) | _PKI_EXPORTS)
|
|
128
128
|
|
|
129
|
-
__version__ = "1.0.
|
|
129
|
+
__version__ = "1.0.2"
|
|
130
130
|
|
|
131
131
|
__all__ = [
|
|
132
132
|
"App",
|
|
@@ -431,7 +431,7 @@ class App:
|
|
|
431
431
|
|
|
432
432
|
def _sign_thread_cookie(self, route_path, thread_id, issued_at=None):
|
|
433
433
|
issued = int(time.time() if issued_at is None else issued_at)
|
|
434
|
-
payload = f"{route_path}|{thread_id}|{issued}".encode(
|
|
434
|
+
payload = f"{route_path}|{thread_id}|{issued}".encode()
|
|
435
435
|
sig = hmac.new(
|
|
436
436
|
self._thread_cookie_secret,
|
|
437
437
|
payload,
|
|
@@ -467,7 +467,7 @@ class App:
|
|
|
467
467
|
ttl = max(0.0, float(ttl_seconds or 0.0))
|
|
468
468
|
if issued > now + 60 or (ttl > 0 and now - issued > ttl):
|
|
469
469
|
return ""
|
|
470
|
-
payload = f"{route_path}|{normalized}|{issued}".encode(
|
|
470
|
+
payload = f"{route_path}|{normalized}|{issued}".encode()
|
|
471
471
|
expected = hmac.new(
|
|
472
472
|
self._thread_cookie_secret,
|
|
473
473
|
payload,
|
|
@@ -4,47 +4,46 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import sqlite3
|
|
6
6
|
import threading
|
|
7
|
-
from typing import Optional
|
|
8
7
|
from dataclasses import dataclass
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
@dataclass
|
|
12
11
|
class SQLite3OptimizationConfig:
|
|
13
12
|
"""Configuration for SQLite3 optimizations."""
|
|
14
|
-
|
|
13
|
+
|
|
15
14
|
# WAL mode - enables multiple readers
|
|
16
15
|
journal_mode: str = "WAL"
|
|
17
|
-
|
|
16
|
+
|
|
18
17
|
# Memory budget in KiB used with PRAGMA cache_size < 0.
|
|
19
18
|
cache_size: int = 10000
|
|
20
|
-
|
|
19
|
+
|
|
21
20
|
# Synchronization level (FULL=safer, NORMAL=faster)
|
|
22
21
|
synchronous: str = "NORMAL"
|
|
23
|
-
|
|
22
|
+
|
|
24
23
|
# Temp store location
|
|
25
24
|
temp_store: str = "MEMORY"
|
|
26
|
-
|
|
25
|
+
|
|
27
26
|
# Query timeout
|
|
28
27
|
busy_timeout: float = 10.0
|
|
29
|
-
|
|
28
|
+
|
|
30
29
|
# Enable memory-mapped I/O
|
|
31
30
|
mmap_size: int = 30000000 # 30MB
|
|
32
|
-
|
|
31
|
+
|
|
33
32
|
# Optimization settings
|
|
34
33
|
optimize: bool = True
|
|
35
34
|
automatic_indexing: bool = True
|
|
36
35
|
query_only: bool = False
|
|
37
|
-
|
|
36
|
+
|
|
38
37
|
# Foreign keys enforcement
|
|
39
38
|
foreign_keys: bool = True
|
|
40
|
-
|
|
39
|
+
|
|
41
40
|
# Checkpoint settings for WAL
|
|
42
41
|
wal_autocheckpoint: int = 1000 # Pages before auto-checkpoint
|
|
43
42
|
|
|
44
43
|
|
|
45
44
|
class DatabaseReplica:
|
|
46
45
|
"""Represents a read-only replica of the database."""
|
|
47
|
-
|
|
46
|
+
|
|
48
47
|
def __init__(
|
|
49
48
|
self,
|
|
50
49
|
db_path: str,
|
|
@@ -55,9 +54,9 @@ class DatabaseReplica:
|
|
|
55
54
|
self.db_path = db_path
|
|
56
55
|
self.timeout = timeout
|
|
57
56
|
self.detect_types = detect_types
|
|
58
|
-
self._conn:
|
|
57
|
+
self._conn: sqlite3.Connection | None = None
|
|
59
58
|
self._lock = threading.RLock()
|
|
60
|
-
|
|
59
|
+
|
|
61
60
|
def connect(self) -> sqlite3.Connection:
|
|
62
61
|
"""Get or create read-only connection."""
|
|
63
62
|
with self._lock:
|
|
@@ -73,14 +72,14 @@ class DatabaseReplica:
|
|
|
73
72
|
# Read-only optimization
|
|
74
73
|
self._conn.execute("PRAGMA query_only = ON")
|
|
75
74
|
return self._conn
|
|
76
|
-
|
|
75
|
+
|
|
77
76
|
def close(self):
|
|
78
77
|
"""Close the replica connection."""
|
|
79
78
|
with self._lock:
|
|
80
79
|
if self._conn:
|
|
81
80
|
self._conn.close()
|
|
82
81
|
self._conn = None
|
|
83
|
-
|
|
82
|
+
|
|
84
83
|
def execute(self, sql: str, params=None):
|
|
85
84
|
"""Execute a read-only query."""
|
|
86
85
|
conn = self.connect()
|
|
@@ -88,17 +87,17 @@ class DatabaseReplica:
|
|
|
88
87
|
params = ()
|
|
89
88
|
with self._lock:
|
|
90
89
|
return conn.execute(sql, tuple(params))
|
|
91
|
-
|
|
90
|
+
|
|
92
91
|
def fetchone(self, sql: str, params=None):
|
|
93
92
|
"""Fetch one result."""
|
|
94
93
|
with self._lock:
|
|
95
94
|
return self.execute(sql, params).fetchone()
|
|
96
|
-
|
|
95
|
+
|
|
97
96
|
def fetchall(self, sql: str, params=None):
|
|
98
97
|
"""Fetch all results."""
|
|
99
98
|
with self._lock:
|
|
100
99
|
return self.execute(sql, params).fetchall()
|
|
101
|
-
|
|
100
|
+
|
|
102
101
|
def scalar(self, sql: str, params=None, default=None):
|
|
103
102
|
"""Fetch a scalar value."""
|
|
104
103
|
with self._lock:
|
|
@@ -110,7 +109,7 @@ class DatabaseReplica:
|
|
|
110
109
|
|
|
111
110
|
class DatabaseReplicaPool:
|
|
112
111
|
"""Pool of read-only database replicas with round-robin distribution."""
|
|
113
|
-
|
|
112
|
+
|
|
114
113
|
def __init__(
|
|
115
114
|
self,
|
|
116
115
|
db_path: str,
|
|
@@ -125,7 +124,7 @@ class DatabaseReplicaPool:
|
|
|
125
124
|
self.replica_count = replica_count
|
|
126
125
|
self.timeout = timeout
|
|
127
126
|
self.detect_types = detect_types
|
|
128
|
-
|
|
127
|
+
|
|
129
128
|
self._replicas = [
|
|
130
129
|
DatabaseReplica(
|
|
131
130
|
db_path,
|
|
@@ -136,34 +135,34 @@ class DatabaseReplicaPool:
|
|
|
136
135
|
]
|
|
137
136
|
self._current_index = 0
|
|
138
137
|
self._lock = threading.RLock()
|
|
139
|
-
|
|
138
|
+
|
|
140
139
|
def get_replica(self) -> DatabaseReplica:
|
|
141
140
|
"""Get the next replica in round-robin fashion."""
|
|
142
141
|
with self._lock:
|
|
143
142
|
replica = self._replicas[self._current_index]
|
|
144
143
|
self._current_index = (self._current_index + 1) % len(self._replicas)
|
|
145
144
|
return replica
|
|
146
|
-
|
|
145
|
+
|
|
147
146
|
def execute(self, sql: str, params=None):
|
|
148
147
|
"""Execute query on a replica."""
|
|
149
148
|
replica = self.get_replica()
|
|
150
149
|
return replica.execute(sql, params)
|
|
151
|
-
|
|
150
|
+
|
|
152
151
|
def fetchone(self, sql: str, params=None):
|
|
153
152
|
"""Fetch one result from a replica."""
|
|
154
153
|
replica = self.get_replica()
|
|
155
154
|
return replica.fetchone(sql, params)
|
|
156
|
-
|
|
155
|
+
|
|
157
156
|
def fetchall(self, sql: str, params=None):
|
|
158
157
|
"""Fetch all results from a replica."""
|
|
159
158
|
replica = self.get_replica()
|
|
160
159
|
return replica.fetchall(sql, params)
|
|
161
|
-
|
|
160
|
+
|
|
162
161
|
def scalar(self, sql: str, params=None, default=None):
|
|
163
162
|
"""Fetch scalar from a replica."""
|
|
164
163
|
replica = self.get_replica()
|
|
165
164
|
return replica.scalar(sql, params, default)
|
|
166
|
-
|
|
165
|
+
|
|
167
166
|
def close_all(self):
|
|
168
167
|
"""Close all replica connections."""
|
|
169
168
|
for replica in self._replicas:
|
|
@@ -179,7 +178,7 @@ def _pragma_cache_size_from_kib(size_kib: int) -> int:
|
|
|
179
178
|
|
|
180
179
|
class OptimizedDatabase:
|
|
181
180
|
"""SQLite3 database with built-in optimizations."""
|
|
182
|
-
|
|
181
|
+
|
|
183
182
|
def __init__(
|
|
184
183
|
self,
|
|
185
184
|
path: str = ":memory:",
|
|
@@ -198,7 +197,7 @@ class OptimizedDatabase:
|
|
|
198
197
|
self._tx_depth = threading.local()
|
|
199
198
|
self._savepoint_sequence = 0
|
|
200
199
|
self.optimization_config = optimization_config or SQLite3OptimizationConfig()
|
|
201
|
-
|
|
200
|
+
|
|
202
201
|
# Connection setup
|
|
203
202
|
if shared_cache:
|
|
204
203
|
dsn = "file::memory:?cache=shared" if path == ":memory:" else path
|
|
@@ -216,14 +215,14 @@ class OptimizedDatabase:
|
|
|
216
215
|
timeout=timeout,
|
|
217
216
|
detect_types=detect_types,
|
|
218
217
|
)
|
|
219
|
-
|
|
218
|
+
|
|
220
219
|
self._conn.row_factory = sqlite3.Row
|
|
221
|
-
|
|
220
|
+
|
|
222
221
|
# Apply SQLite3 optimizations
|
|
223
222
|
self._apply_optimizations(timeout, pragmas)
|
|
224
|
-
|
|
223
|
+
|
|
225
224
|
# Setup read replicas if enabled
|
|
226
|
-
self._replicas:
|
|
225
|
+
self._replicas: DatabaseReplicaPool | None = None
|
|
227
226
|
if enable_replicas and path != ":memory:":
|
|
228
227
|
self._replicas = DatabaseReplicaPool(
|
|
229
228
|
path,
|
|
@@ -231,11 +230,11 @@ class OptimizedDatabase:
|
|
|
231
230
|
timeout=timeout,
|
|
232
231
|
detect_types=detect_types,
|
|
233
232
|
)
|
|
234
|
-
|
|
233
|
+
|
|
235
234
|
def _apply_optimizations(self, timeout: float, custom_pragmas: dict | None):
|
|
236
235
|
"""Apply SQLite3 optimization pragmas."""
|
|
237
236
|
config = self.optimization_config
|
|
238
|
-
|
|
237
|
+
|
|
239
238
|
base_pragmas = {
|
|
240
239
|
"journal_mode": config.journal_mode,
|
|
241
240
|
"cache_size": _pragma_cache_size_from_kib(config.cache_size),
|
|
@@ -247,25 +246,25 @@ class OptimizedDatabase:
|
|
|
247
246
|
"query_only": 1 if config.query_only else 0,
|
|
248
247
|
"automatic_index": 1 if config.automatic_indexing else 0,
|
|
249
248
|
}
|
|
250
|
-
|
|
249
|
+
|
|
251
250
|
if custom_pragmas:
|
|
252
251
|
base_pragmas.update(custom_pragmas)
|
|
253
|
-
|
|
252
|
+
|
|
254
253
|
with self._lock:
|
|
255
254
|
for key, value in base_pragmas.items():
|
|
256
255
|
self._log(f"PRAGMA {key}={value}")
|
|
257
256
|
self._conn.execute(f"PRAGMA {key}={value}")
|
|
258
|
-
|
|
257
|
+
|
|
259
258
|
# WAL checkpoint settings
|
|
260
259
|
if config.journal_mode.upper() == "WAL":
|
|
261
260
|
self._log(f"PRAGMA wal_autocheckpoint={config.wal_autocheckpoint}")
|
|
262
261
|
self._conn.execute(f"PRAGMA wal_autocheckpoint={config.wal_autocheckpoint}")
|
|
263
|
-
|
|
262
|
+
|
|
264
263
|
# Run optimization if enabled
|
|
265
264
|
if config.optimize:
|
|
266
265
|
self._log("PRAGMA optimize")
|
|
267
266
|
self._conn.execute("PRAGMA optimize")
|
|
268
|
-
|
|
267
|
+
|
|
269
268
|
def _log(self, *parts):
|
|
270
269
|
if self.debug:
|
|
271
270
|
print("[DB]", *parts)
|
|
@@ -2,7 +2,6 @@ import base64
|
|
|
2
2
|
import socket
|
|
3
3
|
import threading
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
QTYPE_A = 1
|
|
7
6
|
QTYPE_NS = 2
|
|
8
7
|
QTYPE_CNAME = 5
|
|
@@ -1028,7 +1027,7 @@ class LocalDNSServer:
|
|
|
1028
1027
|
while self._running.is_set():
|
|
1029
1028
|
try:
|
|
1030
1029
|
data, addr = self._sock.recvfrom(4096)
|
|
1031
|
-
except
|
|
1030
|
+
except TimeoutError:
|
|
1032
1031
|
continue
|
|
1033
1032
|
except OSError:
|
|
1034
1033
|
if self._running.is_set():
|
|
@@ -5,7 +5,6 @@ from urllib.parse import parse_qsl
|
|
|
5
5
|
from .constants import STATUS_MESSAGES
|
|
6
6
|
from .headers import validate_header_name, validate_header_value
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
MAX_QUERY_FIELDS = 1024
|
|
10
9
|
HTTP_0_9 = "HTTP/0.9"
|
|
11
10
|
HTTP_1_1 = "HTTP/1.1"
|
|
@@ -281,7 +280,7 @@ def send_http_response(conn, response, *, send_body=True, keep_alive=False, vers
|
|
|
281
280
|
use_chunked = "chunked" in lowermap.get("transfer-encoding", "").lower()
|
|
282
281
|
for chunk in _iter_stream_chunks(response.stream):
|
|
283
282
|
if use_chunked:
|
|
284
|
-
conn.sendall(f"{len(chunk):X}\r\n".encode(
|
|
283
|
+
conn.sendall(f"{len(chunk):X}\r\n".encode())
|
|
285
284
|
conn.sendall(chunk)
|
|
286
285
|
conn.sendall(b"\r\n")
|
|
287
286
|
else:
|
|
@@ -19,7 +19,7 @@ from cryptography.hazmat.primitives import serialization
|
|
|
19
19
|
from . import http3
|
|
20
20
|
from .http3_websocket import Http3WebSocketStream
|
|
21
21
|
from .quic.address import AddressValidator
|
|
22
|
-
from .quic.connection import
|
|
22
|
+
from .quic.connection import QuicConnection
|
|
23
23
|
from .quic.crypto import retry_integrity_tag
|
|
24
24
|
from .quic.packet import (
|
|
25
25
|
PACKET_INITIAL,
|
|
@@ -28,7 +28,6 @@ from .quic.packet import (
|
|
|
28
28
|
new_connection_id,
|
|
29
29
|
parse_long_header,
|
|
30
30
|
)
|
|
31
|
-
from .quic.varint import decode_varint
|
|
32
31
|
|
|
33
32
|
DEFAULT_MAX_CONNECTIONS = 256
|
|
34
33
|
|
|
@@ -106,7 +105,7 @@ class Http3Server:
|
|
|
106
105
|
while not self._stop.is_set():
|
|
107
106
|
try:
|
|
108
107
|
datagram, address = sock.recvfrom(65535)
|
|
109
|
-
except
|
|
108
|
+
except TimeoutError:
|
|
110
109
|
# The idle gap is where loss timers get a chance to fire;
|
|
111
110
|
# without this a probe would wait for the next datagram,
|
|
112
111
|
# which on a lossy path may never come.
|
|
@@ -8,7 +8,6 @@ those. Bytes the peer sends are pushed in by the thread that owns the UDP
|
|
|
8
8
|
socket; bytes this side sends are queued for that same thread to packetize.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
import socket
|
|
12
11
|
import threading
|
|
13
12
|
import time
|
|
14
13
|
|
|
@@ -60,7 +59,7 @@ class Http3WebSocketStream:
|
|
|
60
59
|
if deadline is not None:
|
|
61
60
|
remaining = deadline - time.monotonic()
|
|
62
61
|
if remaining <= 0:
|
|
63
|
-
raise
|
|
62
|
+
raise TimeoutError("timed out")
|
|
64
63
|
self._cond.wait(remaining)
|
|
65
64
|
if self._buffer:
|
|
66
65
|
chunk = bytes(self._buffer[:n])
|
|
@@ -7,10 +7,9 @@ import re
|
|
|
7
7
|
import socket
|
|
8
8
|
import threading
|
|
9
9
|
import time
|
|
10
|
+
from collections.abc import Mapping
|
|
10
11
|
from dataclasses import dataclass
|
|
11
12
|
from types import MappingProxyType
|
|
12
|
-
from typing import Mapping
|
|
13
|
-
|
|
14
13
|
|
|
15
14
|
DEFAULT_SERVICE_TYPE = "_http._tcp.local."
|
|
16
15
|
|
|
@@ -316,29 +316,29 @@ class Database:
|
|
|
316
316
|
)
|
|
317
317
|
|
|
318
318
|
self._conn.row_factory = sqlite3.Row
|
|
319
|
-
|
|
319
|
+
|
|
320
320
|
# Default optimized pragmas
|
|
321
321
|
base_pragmas = {
|
|
322
322
|
"foreign_keys": 1,
|
|
323
323
|
"busy_timeout": int(timeout * 1000),
|
|
324
324
|
}
|
|
325
|
-
|
|
325
|
+
|
|
326
326
|
# Add SQLite3 optimizations
|
|
327
327
|
if enable_wal:
|
|
328
328
|
base_pragmas["journal_mode"] = "WAL"
|
|
329
329
|
base_pragmas["wal_autocheckpoint"] = 1000
|
|
330
|
-
|
|
330
|
+
|
|
331
331
|
base_pragmas["cache_size"] = _pragma_cache_size_from_mb(cache_size_mb)
|
|
332
332
|
base_pragmas["synchronous"] = "NORMAL"
|
|
333
333
|
base_pragmas["temp_store"] = "MEMORY"
|
|
334
334
|
base_pragmas["mmap_size"] = 30000000
|
|
335
335
|
base_pragmas["automatic_index"] = 1
|
|
336
|
-
|
|
336
|
+
|
|
337
337
|
if pragmas:
|
|
338
338
|
base_pragmas.update(pragmas)
|
|
339
339
|
for key, value in base_pragmas.items():
|
|
340
340
|
self.set_pragma(key, value)
|
|
341
|
-
|
|
341
|
+
|
|
342
342
|
# Enable read replicas if needed
|
|
343
343
|
if enable_replicas and path != ":memory:":
|
|
344
344
|
from . import db_replicas
|
|
@@ -32,7 +32,7 @@ from cryptography.hazmat.primitives import hashes, serialization
|
|
|
32
32
|
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
|
33
33
|
from cryptography.x509.oid import ExtendedKeyUsageOID, NameOID
|
|
34
34
|
|
|
35
|
-
UTC = _datetime.
|
|
35
|
+
UTC = _datetime.UTC
|
|
36
36
|
|
|
37
37
|
DEFAULT_CA_VALID_DAYS = 3650
|
|
38
38
|
DEFAULT_LEAF_VALID_DAYS = 397
|
|
@@ -839,7 +839,7 @@ class ProxyRule:
|
|
|
839
839
|
return rows[0][4]
|
|
840
840
|
|
|
841
841
|
def _hash_score(self, key, target):
|
|
842
|
-
raw = f"{key}|{target.url}|{target.name}".encode(
|
|
842
|
+
raw = f"{key}|{target.url}|{target.name}".encode()
|
|
843
843
|
digest = hashlib.sha256(raw).digest()
|
|
844
844
|
value = int.from_bytes(digest[:8], byteorder="big", signed=False)
|
|
845
845
|
weight = max(1, int(getattr(target, "weight", 1) or 1))
|
|
@@ -44,7 +44,7 @@ class AddressValidator:
|
|
|
44
44
|
@staticmethod
|
|
45
45
|
def _address_bytes(address):
|
|
46
46
|
host, port = address
|
|
47
|
-
return f"{host}|{int(port)}".encode(
|
|
47
|
+
return f"{host}|{int(port)}".encode()
|
|
48
48
|
|
|
49
49
|
def issue(self, address, original_destination_cid):
|
|
50
50
|
"""Mint a token tied to this address and the client's first id."""
|