pyrock-core 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. pyrock_core-0.1.0/.gitignore +37 -0
  2. pyrock_core-0.1.0/CHANGELOG.md +113 -0
  3. pyrock_core-0.1.0/CONTRIBUTING.md +143 -0
  4. pyrock_core-0.1.0/DESIGN.md +1427 -0
  5. pyrock_core-0.1.0/LICENSE +21 -0
  6. pyrock_core-0.1.0/PKG-INFO +858 -0
  7. pyrock_core-0.1.0/README.md +828 -0
  8. pyrock_core-0.1.0/SECURITY.md +55 -0
  9. pyrock_core-0.1.0/examples/asgi_app.py +129 -0
  10. pyrock_core-0.1.0/examples/http_server.py +85 -0
  11. pyrock_core-0.1.0/examples/recipes/django_middleware.py +60 -0
  12. pyrock_core-0.1.0/examples/recipes/fastapi_app.py +61 -0
  13. pyrock_core-0.1.0/examples/recipes/flask_route.py +44 -0
  14. pyrock_core-0.1.0/examples/recipes/grpc_interceptors.py +114 -0
  15. pyrock_core-0.1.0/examples/recipes/httpx_hooks.py +43 -0
  16. pyrock_core-0.1.0/examples/recipes/requests_adapter.py +44 -0
  17. pyrock_core-0.1.0/examples/worker.py +68 -0
  18. pyrock_core-0.1.0/pyproject.toml +186 -0
  19. pyrock_core-0.1.0/scripts/tests/conftest.py +10 -0
  20. pyrock_core-0.1.0/scripts/tests/test_release.py +133 -0
  21. pyrock_core-0.1.0/scripts/tests/test_test_report.py +300 -0
  22. pyrock_core-0.1.0/src/pyrock/__init__.py +141 -0
  23. pyrock_core-0.1.0/src/pyrock/_bedrock.py +1147 -0
  24. pyrock_core-0.1.0/src/pyrock/_cloud.py +504 -0
  25. pyrock_core-0.1.0/src/pyrock/_compat.py +129 -0
  26. pyrock_core-0.1.0/src/pyrock/_context.py +171 -0
  27. pyrock_core-0.1.0/src/pyrock/_envdetect.py +412 -0
  28. pyrock_core-0.1.0/src/pyrock/_httpcore.py +268 -0
  29. pyrock_core-0.1.0/src/pyrock/_noop.py +98 -0
  30. pyrock_core-0.1.0/src/pyrock/_operation.py +1360 -0
  31. pyrock_core-0.1.0/src/pyrock/_push.py +254 -0
  32. pyrock_core-0.1.0/src/pyrock/_transport.py +77 -0
  33. pyrock_core-0.1.0/src/pyrock/_types.py +35 -0
  34. pyrock_core-0.1.0/src/pyrock/_version.py +10 -0
  35. pyrock_core-0.1.0/src/pyrock/asgi.py +269 -0
  36. pyrock_core-0.1.0/src/pyrock/attr.py +482 -0
  37. pyrock_core-0.1.0/src/pyrock/config.py +310 -0
  38. pyrock_core-0.1.0/src/pyrock/env.py +300 -0
  39. pyrock_core-0.1.0/src/pyrock/http.py +504 -0
  40. pyrock_core-0.1.0/src/pyrock/logs.py +669 -0
  41. pyrock_core-0.1.0/src/pyrock/metric/__init__.py +856 -0
  42. pyrock_core-0.1.0/src/pyrock/metric/prometheus.py +216 -0
  43. pyrock_core-0.1.0/src/pyrock/metric/runtime.py +358 -0
  44. pyrock_core-0.1.0/src/pyrock/privacy.py +582 -0
  45. pyrock_core-0.1.0/src/pyrock/profile/__init__.py +58 -0
  46. pyrock_core-0.1.0/src/pyrock/profile/_config.py +164 -0
  47. pyrock_core-0.1.0/src/pyrock/profile/_export.py +279 -0
  48. pyrock_core-0.1.0/src/pyrock/profile/_frames.py +91 -0
  49. pyrock_core-0.1.0/src/pyrock/profile/_heap.py +261 -0
  50. pyrock_core-0.1.0/src/pyrock/profile/_locks.py +337 -0
  51. pyrock_core-0.1.0/src/pyrock/profile/_otlp.py +270 -0
  52. pyrock_core-0.1.0/src/pyrock/profile/_pprof.py +208 -0
  53. pyrock_core-0.1.0/src/pyrock/profile/_sampler.py +345 -0
  54. pyrock_core-0.1.0/src/pyrock/profile/_stacks.py +85 -0
  55. pyrock_core-0.1.0/src/pyrock/profile/debug.py +149 -0
  56. pyrock_core-0.1.0/src/pyrock/py.typed +0 -0
  57. pyrock_core-0.1.0/src/pyrock/server.py +824 -0
  58. pyrock_core-0.1.0/src/pyrock/testing.py +397 -0
  59. pyrock_core-0.1.0/src/pyrock/trace/__init__.py +704 -0
  60. pyrock_core-0.1.0/src/pyrock/trace/http.py +155 -0
  61. pyrock_core-0.1.0/src/pyrock/trace/otlp/__init__.py +21 -0
  62. pyrock_core-0.1.0/src/pyrock/trace/otlp/_h2.py +1078 -0
  63. pyrock_core-0.1.0/src/pyrock/trace/otlp/_pb.py +308 -0
  64. pyrock_core-0.1.0/src/pyrock/trace/otlp/batch.py +272 -0
  65. pyrock_core-0.1.0/src/pyrock/trace/otlp/grpc.py +398 -0
  66. pyrock_core-0.1.0/src/pyrock/trace/otlp/http.py +228 -0
  67. pyrock_core-0.1.0/src/pyrock/trace/propagator.py +50 -0
  68. pyrock_core-0.1.0/src/pyrock/trace/sampler.py +156 -0
  69. pyrock_core-0.1.0/src/pyrock/trace/w3c.py +147 -0
  70. pyrock_core-0.1.0/src/pyrock/wsgi.py +247 -0
  71. pyrock_core-0.1.0/tests/__init__.py +0 -0
  72. pyrock_core-0.1.0/tests/conftest.py +103 -0
  73. pyrock_core-0.1.0/tests/e2e/__init__.py +1 -0
  74. pyrock_core-0.1.0/tests/e2e/_core_helpers.py +68 -0
  75. pyrock_core-0.1.0/tests/e2e/_export_helpers.py +130 -0
  76. pyrock_core-0.1.0/tests/e2e/_http_helpers.py +664 -0
  77. pyrock_core-0.1.0/tests/e2e/test_asgi.py +319 -0
  78. pyrock_core-0.1.0/tests/e2e/test_client.py +422 -0
  79. pyrock_core-0.1.0/tests/e2e/test_cloud.py +453 -0
  80. pyrock_core-0.1.0/tests/e2e/test_config_env.py +565 -0
  81. pyrock_core-0.1.0/tests/e2e/test_context.py +324 -0
  82. pyrock_core-0.1.0/tests/e2e/test_envdetect.py +231 -0
  83. pyrock_core-0.1.0/tests/e2e/test_fork.py +190 -0
  84. pyrock_core-0.1.0/tests/e2e/test_http_hardening.py +335 -0
  85. pyrock_core-0.1.0/tests/e2e/test_lifecycle.py +360 -0
  86. pyrock_core-0.1.0/tests/e2e/test_logging.py +425 -0
  87. pyrock_core-0.1.0/tests/e2e/test_metrics.py +535 -0
  88. pyrock_core-0.1.0/tests/e2e/test_noop.py +296 -0
  89. pyrock_core-0.1.0/tests/e2e/test_operations.py +804 -0
  90. pyrock_core-0.1.0/tests/e2e/test_privacy.py +475 -0
  91. pyrock_core-0.1.0/tests/e2e/test_profiles.py +504 -0
  92. pyrock_core-0.1.0/tests/e2e/test_propagation.py +475 -0
  93. pyrock_core-0.1.0/tests/e2e/test_server.py +434 -0
  94. pyrock_core-0.1.0/tests/e2e/test_steps_sources.py +315 -0
  95. pyrock_core-0.1.0/tests/e2e/test_trace_export.py +488 -0
  96. pyrock_core-0.1.0/tests/e2e/test_wsgi.py +290 -0
  97. pyrock_core-0.1.0/tests/structure/__init__.py +0 -0
  98. pyrock_core-0.1.0/tests/structure/test_import_cost.py +72 -0
  99. pyrock_core-0.1.0/tests/structure/test_structure.py +303 -0
  100. pyrock_core-0.1.0/tests/support/README.md +173 -0
  101. pyrock_core-0.1.0/tests/support/__init__.py +0 -0
  102. pyrock_core-0.1.0/tests/support/asgi_driver.py +295 -0
  103. pyrock_core-0.1.0/tests/support/collectors.py +606 -0
  104. pyrock_core-0.1.0/tests/support/logs.py +107 -0
  105. pyrock_core-0.1.0/tests/support/otlp_decode.py +181 -0
  106. pyrock_core-0.1.0/tests/support/prom.py +153 -0
  107. pyrock_core-0.1.0/tests/support/test_support.py +552 -0
  108. pyrock_core-0.1.0/tests/support/wsgi_driver.py +190 -0
  109. pyrock_core-0.1.0/tests/unit/__init__.py +0 -0
  110. pyrock_core-0.1.0/tests/unit/attr/__init__.py +0 -0
  111. pyrock_core-0.1.0/tests/unit/attr/test_attrs_event.py +194 -0
  112. pyrock_core-0.1.0/tests/unit/attr/test_errors.py +422 -0
  113. pyrock_core-0.1.0/tests/unit/attr/test_values.py +281 -0
  114. pyrock_core-0.1.0/tests/unit/cloud/__init__.py +0 -0
  115. pyrock_core-0.1.0/tests/unit/cloud/test_cloud_components.py +348 -0
  116. pyrock_core-0.1.0/tests/unit/cloud/test_review_fixes.py +224 -0
  117. pyrock_core-0.1.0/tests/unit/config/__init__.py +0 -0
  118. pyrock_core-0.1.0/tests/unit/config/test_config.py +391 -0
  119. pyrock_core-0.1.0/tests/unit/config/test_from_env.py +202 -0
  120. pyrock_core-0.1.0/tests/unit/core/__init__.py +0 -0
  121. pyrock_core-0.1.0/tests/unit/core/test_review_fixes.py +288 -0
  122. pyrock_core-0.1.0/tests/unit/env/__init__.py +0 -0
  123. pyrock_core-0.1.0/tests/unit/env/test_helpers.py +176 -0
  124. pyrock_core-0.1.0/tests/unit/env/test_parse.py +441 -0
  125. pyrock_core-0.1.0/tests/unit/logs/__init__.py +1 -0
  126. pyrock_core-0.1.0/tests/unit/logs/_helpers.py +105 -0
  127. pyrock_core-0.1.0/tests/unit/logs/test_canonical.py +146 -0
  128. pyrock_core-0.1.0/tests/unit/logs/test_handler.py +182 -0
  129. pyrock_core-0.1.0/tests/unit/logs/test_install.py +177 -0
  130. pyrock_core-0.1.0/tests/unit/logs/test_json_formatter.py +429 -0
  131. pyrock_core-0.1.0/tests/unit/logs/test_levels.py +46 -0
  132. pyrock_core-0.1.0/tests/unit/logs/test_text_formatter.py +79 -0
  133. pyrock_core-0.1.0/tests/unit/metric/__init__.py +1 -0
  134. pyrock_core-0.1.0/tests/unit/metric/_helpers.py +34 -0
  135. pyrock_core-0.1.0/tests/unit/metric/test_concurrency.py +183 -0
  136. pyrock_core-0.1.0/tests/unit/metric/test_prometheus.py +376 -0
  137. pyrock_core-0.1.0/tests/unit/metric/test_registry.py +452 -0
  138. pyrock_core-0.1.0/tests/unit/metric/test_runtime.py +404 -0
  139. pyrock_core-0.1.0/tests/unit/metric/test_types.py +349 -0
  140. pyrock_core-0.1.0/tests/unit/perf/__init__.py +0 -0
  141. pyrock_core-0.1.0/tests/unit/perf/test_policy_fast_path.py +210 -0
  142. pyrock_core-0.1.0/tests/unit/perf/test_span_finish.py +188 -0
  143. pyrock_core-0.1.0/tests/unit/privacy/__init__.py +0 -0
  144. pyrock_core-0.1.0/tests/unit/privacy/_helpers.py +68 -0
  145. pyrock_core-0.1.0/tests/unit/privacy/test_apply.py +492 -0
  146. pyrock_core-0.1.0/tests/unit/privacy/test_policy_type.py +111 -0
  147. pyrock_core-0.1.0/tests/unit/privacy/test_surfaces.py +270 -0
  148. pyrock_core-0.1.0/tests/unit/privacy/test_walker.py +355 -0
  149. pyrock_core-0.1.0/tests/unit/profile/__init__.py +1 -0
  150. pyrock_core-0.1.0/tests/unit/profile/_helpers.py +105 -0
  151. pyrock_core-0.1.0/tests/unit/profile/_proto.py +456 -0
  152. pyrock_core-0.1.0/tests/unit/profile/conftest.py +50 -0
  153. pyrock_core-0.1.0/tests/unit/profile/test_debug.py +154 -0
  154. pyrock_core-0.1.0/tests/unit/profile/test_export.py +507 -0
  155. pyrock_core-0.1.0/tests/unit/profile/test_fork_reset.py +282 -0
  156. pyrock_core-0.1.0/tests/unit/profile/test_heap.py +195 -0
  157. pyrock_core-0.1.0/tests/unit/profile/test_locks.py +479 -0
  158. pyrock_core-0.1.0/tests/unit/profile/test_otlp.py +299 -0
  159. pyrock_core-0.1.0/tests/unit/profile/test_pprof.py +209 -0
  160. pyrock_core-0.1.0/tests/unit/profile/test_sampler.py +440 -0
  161. pyrock_core-0.1.0/tests/unit/profile/test_stacks.py +164 -0
  162. pyrock_core-0.1.0/tests/unit/server/__init__.py +1 -0
  163. pyrock_core-0.1.0/tests/unit/server/test_apps.py +117 -0
  164. pyrock_core-0.1.0/tests/unit/server/test_config.py +107 -0
  165. pyrock_core-0.1.0/tests/unit/server/test_read_head.py +176 -0
  166. pyrock_core-0.1.0/tests/unit/server/test_review_fixes.py +211 -0
  167. pyrock_core-0.1.0/tests/unit/server/test_server.py +627 -0
  168. pyrock_core-0.1.0/tests/unit/trace/__init__.py +0 -0
  169. pyrock_core-0.1.0/tests/unit/trace/test_propagators.py +332 -0
  170. pyrock_core-0.1.0/tests/unit/trace/test_sampler.py +233 -0
  171. pyrock_core-0.1.0/tests/unit/trace/test_span_context.py +234 -0
  172. pyrock_core-0.1.0/tests/unit/trace/test_span_tracer.py +561 -0
  173. pyrock_core-0.1.0/tests/unit/trace/test_w3c.py +367 -0
  174. pyrock_core-0.1.0/tests/unit/trace_otlp/__init__.py +0 -0
  175. pyrock_core-0.1.0/tests/unit/trace_otlp/_h2server.py +609 -0
  176. pyrock_core-0.1.0/tests/unit/trace_otlp/_pbref.py +110 -0
  177. pyrock_core-0.1.0/tests/unit/trace_otlp/test_batch.py +541 -0
  178. pyrock_core-0.1.0/tests/unit/trace_otlp/test_batch_logging.py +154 -0
  179. pyrock_core-0.1.0/tests/unit/trace_otlp/test_grpc_exporter.py +375 -0
  180. pyrock_core-0.1.0/tests/unit/trace_otlp/test_h2_connection.py +452 -0
  181. pyrock_core-0.1.0/tests/unit/trace_otlp/test_h2_frames.py +315 -0
  182. pyrock_core-0.1.0/tests/unit/trace_otlp/test_hpack.py +555 -0
  183. pyrock_core-0.1.0/tests/unit/trace_otlp/test_http_exporter.py +395 -0
  184. pyrock_core-0.1.0/tests/unit/trace_otlp/test_parse_target.py +214 -0
  185. pyrock_core-0.1.0/tests/unit/trace_otlp/test_pb.py +472 -0
@@ -0,0 +1,37 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.so
5
+ *.egg-info/
6
+ .eggs/
7
+ build/
8
+ dist/
9
+ wheels/
10
+
11
+ # Environments and tools
12
+ .venv/
13
+ venv/
14
+ .python-version
15
+ .uv/
16
+ .mypy_cache/
17
+ .ruff_cache/
18
+ .pytest_cache/
19
+ .hypothesis/
20
+ .dmypy.json
21
+
22
+ # Coverage
23
+ .coverage
24
+ .coverage.*
25
+ coverage.xml
26
+ htmlcov/
27
+
28
+ # Profiles and editors
29
+ *.prof
30
+ *.pprof
31
+ .idea/
32
+ .vscode/
33
+ .DS_Store
34
+
35
+ # Profiler output
36
+ *.prof
37
+ prof.out
@@ -0,0 +1,113 @@
1
+ # Changelog
2
+
3
+ Notable changes to pyrock are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and releases use
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Because pyrock is
6
+ pre-1.0, minor releases may include intentional API or behavior changes; each
7
+ one is listed with a migration note.
8
+
9
+ ## Unreleased
10
+
11
+ No changes yet.
12
+
13
+ ## [0.1.0] - 2026-11-08
14
+
15
+ The initial release: a zero-dependency Python port of
16
+ [Bedrock](https://github.com/kzs0/bedrock) for CPython 3.11+, including the
17
+ free-threaded build.
18
+
19
+ Published on PyPI as `pyrock-core` (`pip install pyrock-core`); the import
20
+ package is `pyrock`. The name `pyrock` is taken on PyPI.
21
+
22
+ ### Added
23
+
24
+ - Core API: `pyrock.init()`, `pyrock.Bedrock`, and `operation`, `step` and
25
+ `source` as sync/async context managers and as decorators for functions,
26
+ coroutines, generators and async generators. Also `start_operation` for a
27
+ manual lifecycle, `register`, `current`, `current_operation`,
28
+ `current_span` and `current_span_context`.
29
+ - Operation metrics with the same names as Go (`<name>_count`, `_successes`,
30
+ `_failures`, `_duration_ms`), labels declared up front with a `"_"` default,
31
+ static labels, and series and family limits.
32
+ - Success by default: an escaping exception marks failure and is re-raised;
33
+ `expected=` exceptions do not. Error attributes (`error`, `error.type`,
34
+ `error.fingerprint`, `error.chain`, `error.group.count`, `error.types`) come
35
+ from `attr.error`.
36
+ - `no_trace`, numbered duplicate span names, and preallocated noop objects
37
+ when nothing is active.
38
+ - Context helpers for threads: `bind`, `ContextThreadPoolExecutor` and
39
+ `pyrock.Thread` (with `context=`).
40
+ - Tracing: `Tracer`/`Span`/`SpanContext`, W3C Trace Context (`trace.w3c`), an
41
+ HTTP propagator, a `Propagator` protocol with `CompositePropagator`, and the
42
+ `AlwaysOn`, `AlwaysOff`, `Ratio` and `ParentBased` samplers.
43
+ - OTLP trace export over HTTP/JSON (`urllib`) and gRPC, through a minimal
44
+ in-house HTTP/2 client (TLS with ALPN or h2c) and a hand-written protobuf
45
+ encoder; a bounded batch processor with export statistics.
46
+ - Metrics: a thread-safe registry of counters, gauges and histograms with
47
+ `.labels()`, histogram timers, runtime metrics (`python_*`, `process_*`), and
48
+ validated Prometheus text exposition.
49
+ - Logging on stdlib `logging`: a JSON or text handler, `CorrelationFilter`,
50
+ the `debug`/`info`/`warning`/`warn`/`error`/`log` helpers, canonical
51
+ `operation.complete` records, and `log_install` modes (`root`, `pyrock`,
52
+ `none`).
53
+ - HTTP: WSGI and ASGI 3 middleware (opt-in websocket operations), stdlib
54
+ `http.server` instrumentation (`http.instrument_http_server`), and an
55
+ instrumented `urllib` client (`http.get`/`post`/`request`, `build_opener`,
56
+ `TracingHandler`) plus `inject_headers` for third-party clients.
57
+ - The observability server (`/metrics`, `/health`, `/ready`, optional
58
+ `/debug/pprof/*`) with Go's security defaults and a connection cap.
59
+ - Profiles: CPU, WALL, HEAP, THREADS (`GOROUTINE`), MUTEX and BLOCK, encoded
60
+ as OTLP Profiles (v0.2.0) and pprof, exported over HTTP
61
+ (`ProfileExportConfig`) and served by the optional `profile.debug` handler,
62
+ which works with `go tool pprof`. Instrumented `Lock`, `RLock`, `Condition`
63
+ and `Semaphore` feed MUTEX and BLOCK.
64
+ - The cloud backend (`pyrock.cloud`, `CloudConfig`): gRPC traces with
65
+ `x-bedrock-key`, retry with configurable `retry_backoff`, fan-out to a local
66
+ `trace_url`, metrics push and profile push.
67
+ - `privacy.Policy` enforced on spans, events, logs, metric labels and profile
68
+ labels.
69
+ - Environment detection (process, git, Kubernetes, container, AWS, GCP) under
70
+ a 500 ms budget.
71
+ - `Config` with `BEDROCK_*` environment variables in Go's syntax, plus the
72
+ Python-only `BEDROCK_LOG_INSTALL` and `BEDROCK_SERVER_MAX_CONNECTIONS`;
73
+ `ConfigError` lists every problem. `env.parse` reads typed dataclasses.
74
+ - `pyrock.testing.capture()` and the `pyrock_capture` pytest fixture
75
+ (registered by entry point; pyrock never imports pytest).
76
+ - Fork safety: an `os.register_at_fork` hook resets state and restarts
77
+ background threads in the child.
78
+ - Runnable stdlib examples (`examples/http_server.py`, `asgi_app.py`,
79
+ `worker.py`) and copy-in recipes for httpx, requests, grpcio, Flask, Django
80
+ and FastAPI.
81
+ - `README.md`, `CONTRIBUTING.md` and `SECURITY.md`; CI on Python 3.11–3.15
82
+ and 3.14t (`PYTHON_GIL=0`), a 3.14t stress job, macOS and Windows runs, the
83
+ compat suite (official OTLP protos, a real grpcio collector) and a
84
+ pure-wheel build check.
85
+
86
+ ### Behavior decisions
87
+
88
+ These differ from Go Bedrock, or settle points the design left open:
89
+
90
+ - `Ratio` samples deterministically on the trace ID, and `ParentBased` drops
91
+ spans whose parent is unsampled.
92
+ - JSON log levels use Go's names (`DEBUG`, `INFO`, `WARN`, `ERROR`);
93
+ `CRITICAL` stays `CRITICAL`.
94
+ - Counters raise `ValueError` on negative or NaN increments. A different
95
+ `help` on re-registration is not a conflict (the first registration wins,
96
+ as in Go).
97
+ - Decorators always wrap and resolve the active instance at call time, so
98
+ functions decorated before `init()` are instrumented.
99
+ - Numbering applies only to siblings under the same operation or step; root
100
+ operations and direct children of a source are never numbered.
101
+ - There is no process-wide default instance and no monkey-patching of
102
+ `threading`. Middleware captures the instance at construction and activates
103
+ it per request. On interpreters that inherit context into new threads
104
+ (free-threaded 3.14), threads see the active instance.
105
+ - `http.user_agent`, `http.target` and `http.url` are emitted only when the
106
+ privacy policy allowlists them.
107
+ - `Config.log_level` takes level names only; the log helpers also take ints.
108
+ - `/debug/pprof` without a trailing slash is 404, and a CPU profile longer
109
+ than 30 s is rejected with 400, as in Go.
110
+ - CPU samples carry no asyncio task label; the profile exporter collects the
111
+ first profiles immediately on start.
112
+ - `Activation`, `MetricConflictError` and `MetricLimitError` can be imported
113
+ from `pyrock` but are not part of `__all__`.
@@ -0,0 +1,143 @@
1
+ # Contributing to pyrock
2
+
3
+ Thanks for helping improve pyrock. Small, focused changes with tests and clear
4
+ compatibility notes are easiest to review.
5
+
6
+ ## Before starting
7
+
8
+ Search the [issue tracker](https://github.com/kzs0/pyrock/issues) for related
9
+ work. Open an issue before a large feature, public API change or behavior
10
+ change, so the scope can be agreed before implementation. Security reports
11
+ must follow [SECURITY.md](SECURITY.md), not the public issue tracker.
12
+
13
+ You need [uv](https://docs.astral.sh/uv/). `uv sync` creates `.venv` with the
14
+ dev tools (pytest, coverage, ruff, mypy); uv installs any interpreter the
15
+ targets ask for. Clone your fork, create a topic branch, and keep unrelated
16
+ changes in separate pull requests.
17
+
18
+ ## Rules for every change
19
+
20
+ - **Zero runtime dependencies.** Everything under `src/` imports only the
21
+ standard library; `[project.dependencies]` stays empty. A structural test
22
+ enforces this. Test-only packages belong in a dependency group (`compat`),
23
+ and their tests must skip cleanly when the packages are missing. Do not add
24
+ or update tool dependencies unless the change requires it and the pull
25
+ request explains the tradeoff.
26
+ - **Python 3.11 baseline.** Code must stay valid on 3.11: use
27
+ `from __future__ import annotations`, PEP 604 unions, `typing.Self` and
28
+ `dataclass(slots=True)`, and no 3.12+ syntax (`type X = ...`, PEP 695
29
+ generics). Newer stdlib APIs (for example `sys._is_gil_enabled`) are allowed
30
+ only behind feature detection in `pyrock._compat`.
31
+ - **Free-threaded safety.** Never rely on the GIL. Shared mutable state is
32
+ guarded by a `threading.Lock`; CI runs the suite on 3.14t with
33
+ `PYTHON_GIL=0` and repeats the concurrency-sensitive tests (`make stress`).
34
+ - **Keep the design.** No process-wide globals (state lives in
35
+ `contextvars`), no monkey-patching, controlled metric cardinality, and the
36
+ dependency boundaries in `tests/structure/test_structure.py`. Heavy modules
37
+ stay lazy, so `import pyrock` remains cheap.
38
+ - **Tests are the contract.** Test observable behavior (OTLP bytes, exposition
39
+ text, log JSON, HTTP responses), not internals. Do not weaken or rewrite an
40
+ existing test to make a change pass; if you believe a test is wrong, say so
41
+ in the pull request and change it deliberately, with a changelog entry.
42
+ Cover success, failure, boundary, cancellation and concurrency where
43
+ relevant.
44
+ - **Deterministic tests.** Wait on events, barriers and test servers, not
45
+ `time.sleep`. Bind every test server to `127.0.0.1:0`. Use the fakes in
46
+ `tests/support/`.
47
+ - **Docs.** Update `README.md` (and `DESIGN.md` when the design changes) and
48
+ the `Unreleased` section of [CHANGELOG.md](CHANGELOG.md) when behavior,
49
+ configuration or API changes.
50
+
51
+ ## Validate
52
+
53
+ ```bash
54
+ make fmt # ruff format + ruff check --fix
55
+ make ci # what CI's lint and coverage jobs run
56
+ ```
57
+
58
+ `make ci` runs `ruff format --check`, `ruff check`, `mypy --strict src`, and
59
+ the full test suite under coverage with an 80% branch-coverage gate. Also run,
60
+ as relevant:
61
+
62
+ - `make matrix` for every supported interpreter (3.11 to 3.15 and 3.14t with
63
+ the GIL disabled);
64
+ - `make stress` after touching threads, fork handling, the batch processor or
65
+ the server;
66
+ - `make compat` after touching OTLP encoding or gRPC (official protos, a real
67
+ grpcio collector);
68
+ - `make build` after packaging changes (the wheel must stay pure Python with no
69
+ `Requires-Dist`).
70
+
71
+ CI runs the same commands on Linux for every interpreter, a 3.14t stress job,
72
+ 3.14 on macOS and Windows, the compat suite and a wheel check.
73
+
74
+ ## Compatibility
75
+
76
+ pyrock is below 1.0. Minor releases may contain intentional API or behavior
77
+ changes, but changes should still minimize disruption. A breaking change
78
+ needs:
79
+
80
+ - a clear reason and migration path;
81
+ - tests for the new contract;
82
+ - documentation updates; and
83
+ - an explicit changelog entry under `Changed`.
84
+
85
+ Wire compatibility with Go Bedrock (metric names, span attributes, log shape,
86
+ `BEDROCK_*` variables) is a feature. A deliberate difference must be listed in
87
+ the README's "Differences from Go Bedrock" section.
88
+
89
+ Bug fixes may reject input that was previously accepted incorrectly, or make
90
+ shutdown and network behavior stricter. Call these effects out so operators
91
+ can test their configuration before upgrading.
92
+
93
+ ## Pull requests
94
+
95
+ Describe the problem, the chosen behavior, compatibility or security impact,
96
+ and the commands used to validate the change. Link related issues. Keep
97
+ commits reviewable; maintainers may squash or rebase when merging.
98
+
99
+ ## Release process
100
+
101
+ Releases are maintainer-driven tags, published by the `Release` workflow
102
+ (`.github/workflows/release.yml`). For a release, a maintainer:
103
+
104
+ 1. sets the version in `src/pyrock/_version.py` (`X.Y.Z`, or `X.Y.ZaN`,
105
+ `X.Y.ZbN` or `X.Y.ZrcN` for a prerelease) and moves the relevant
106
+ `Unreleased` entries to a dated `## [X.Y.Z] - YYYY-MM-DD` section;
107
+ 2. verifies `make ci`, `make matrix` and `make compat` on the release commit
108
+ (`python3 scripts/release.py check vX.Y.Z` checks the version and
109
+ changelog locally);
110
+ 3. merges the release change to `main`;
111
+ 4. creates and pushes the tag `vX.Y.Z` (exactly `v` plus the version) on that
112
+ commit of `main`:
113
+
114
+ ```bash
115
+ git tag -a v0.1.0 -m "pyrock 0.1.0" <commit-on-main>
116
+ git push origin v0.1.0
117
+ ```
118
+
119
+ 5. lets the workflow run. It
120
+ - checks the tag matches the package version, the changelog has a dated
121
+ section for it, and the commit is on `main`;
122
+ - runs the full CI workflow as a gate;
123
+ - publishes the exact wheel and sdist CI built and checked to PyPI, using
124
+ trusted publishing (no API token is stored); and
125
+ - creates the GitHub Release with the changelog section as notes and the
126
+ distribution files attached, marked as a prerelease for `a`/`b`/`rc`
127
+ versions; and
128
+ 6. starts a fresh `Unreleased` section and a `.dev0` version on `main`.
129
+
130
+ Release tags must point to `main`. Never move or reuse an existing tag;
131
+ publish a new patch version for corrections. A failed run can be re-run: each
132
+ publishing step is safe to repeat once its predecessor succeeded.
133
+
134
+ ### One-time PyPI setup
135
+
136
+ PyPI trusted publishing has to be configured once, by a maintainer with a PyPI
137
+ account:
138
+
139
+ 1. On PyPI, add a *pending* trusted publisher for the project `pyrock-core`
140
+ (Account settings → Publishing): owner `kzs0`, repository `pyrock`,
141
+ workflow `release.yml`, environment `pypi`.
142
+ 2. On GitHub, create the `pypi` environment (Settings → Environments). Adding
143
+ required reviewers there makes every PyPI upload wait for an approval.