zloop 0.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.
zloop-0.0.0/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ .zig-cache/
2
+ zig-out/
3
+ build_err.txt
4
+ *.so
5
+ dist/
6
+ build/
7
+ __pycache__/
8
+ .pytest_cache/
9
+ .coverage
10
+ .coverage.*
11
+ htmlcov/
12
+ .venv/
13
+ .uvicorn-upstream/
14
+ .uvloop-upstream/
15
+ site/
16
+ CODEX_REVIEW.md
17
+ DOCS_REVIEW.md
zloop-0.0.0/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Marcelo Trylesinski
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
zloop-0.0.0/PKG-INFO ADDED
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.4
2
+ Name: zloop
3
+ Version: 0.0.0
4
+ Summary: An asyncio event loop with a Zig core.
5
+ Author-email: Marcelo Trylesinski <marcelotryle@gmail.com>
6
+ License-Expression: BSD-3-Clause
7
+ License-File: LICENSE
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Framework :: AsyncIO
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Zig
12
+ Requires-Python: >=3.12
13
+ Description-Content-Type: text/markdown
14
+
15
+ # zloop
16
+
17
+ > [!WARNING]
18
+ > zloop is experimental. The API and behaviour may change at any time, and it is not yet ready for production use.
19
+
20
+ A drop-in [asyncio](https://docs.python.org/3/library/asyncio.html) event loop
21
+ whose engine is written in [Zig](https://ziglang.org). It's to asyncio what
22
+ [uvloop](https://github.com/MagicStack/uvloop) is - a real
23
+ `asyncio.AbstractEventLoop` - except the engine is a hand-written kqueue/epoll
24
+ reactor in Zig rather than libuv wrapped in Cython.
25
+
26
+ ```python
27
+ import asyncio
28
+ import zloop
29
+
30
+ print(asyncio.run(asyncio.sleep(0, "hello from a Zig loop"), loop_factory=zloop.new_event_loop))
31
+ ```
32
+
33
+ With [uvicorn](https://www.uvicorn.org):
34
+
35
+ ```bash
36
+ uvicorn app:app --loop zloop:new_event_loop
37
+ ```
38
+
39
+ ## Why
40
+
41
+ - **Drop-in.** A genuine `AbstractEventLoop`, so the asyncio ecosystem -
42
+ uvicorn, FastAPI, AnyIO, HTTPX - runs on it unchanged.
43
+ - **Correct.** Passes [uvicorn](https://github.com/encode/uvicorn)'s **entire**
44
+ test suite (1048 tests), identical to stock asyncio, plus its own suite at
45
+ **100%** coverage.
46
+ - **Fast.** Faster than uvloop on the workloads measured so far - scheduling,
47
+ timers, and small/medium-message socket throughput (e.g. `call_soon` +46%,
48
+ 1 KiB echo +16% on CPython 3.14 / macOS arm64). `create_future` ties, because
49
+ all three loops reuse CPython's C-accelerated `_asyncio.Future`.
50
+
51
+ ## Benchmarks
52
+
53
+ The fairest comparison is uvloop's *own* echo benchmark, run unchanged except
54
+ for a `--zloop` server flag mirroring `--uvloop` (the client is byte-for-byte
55
+ uvloop's). Requests/sec, higher is better - macOS arm64, CPython 3.14, 3
56
+ workers, best of 3:
57
+
58
+ | Message | Server mode | asyncio | uvloop | zloop | zloop vs uvloop |
59
+ | --- | --- | ---: | ---: | ---: | ---: |
60
+ | 1 KiB | proto | 113k | 113k | **121k** | **+7%** |
61
+ | 1 KiB | buffered | 115k | 115k | **123k** | **+7%** |
62
+ | 1 KiB | streams | 83k | 90k | **103k** | **+14%** |
63
+ | 10 KiB | proto | 105k | 110k | **113k** | **+3%** |
64
+ | 10 KiB | buffered | 105k | 105k | **124k** | **+18%** |
65
+ | 10 KiB | streams | 81k | 86k | **95k** | **+11%** |
66
+
67
+ For the 1-10 KiB messages common in HTTP, WebSocket frames, and RPC, zloop leads
68
+ uvloop in every cell. The 100 KiB row is omitted: at that size the test measures
69
+ loopback bandwidth, not the loop, and all three swing wildly run-to-run.
70
+
71
+ Reproduce it with `scripts/bench` (or `bash bench_uvloop/run_matrix.sh` for the
72
+ full matrix); the **Benchmark** CI workflow runs it on Linux and posts the table
73
+ to the run summary.
74
+
75
+ ## How it works
76
+
77
+ The loop *engine* lives in Zig; CPython is reused only where reimplementing
78
+ would be reckless: driving coroutines (`asyncio.Future` / `asyncio.Task`) and
79
+ the TLS state machine (`asyncio.sslproto`). That's exactly uvloop's boundary.
80
+
81
+ ```
82
+ zloop/ Python edge - new_event_loop() factory, connection setup
83
+ src/python/*.zig CPython C-API adapter - Loop, Handle, Transport
84
+ src/core/*.zig pure-Zig domain - run-once engine, kqueue/epoll reactor, timer heap
85
+ ```
zloop-0.0.0/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # zloop
2
+
3
+ > [!WARNING]
4
+ > zloop is experimental. The API and behaviour may change at any time, and it is not yet ready for production use.
5
+
6
+ A drop-in [asyncio](https://docs.python.org/3/library/asyncio.html) event loop
7
+ whose engine is written in [Zig](https://ziglang.org). It's to asyncio what
8
+ [uvloop](https://github.com/MagicStack/uvloop) is - a real
9
+ `asyncio.AbstractEventLoop` - except the engine is a hand-written kqueue/epoll
10
+ reactor in Zig rather than libuv wrapped in Cython.
11
+
12
+ ```python
13
+ import asyncio
14
+ import zloop
15
+
16
+ print(asyncio.run(asyncio.sleep(0, "hello from a Zig loop"), loop_factory=zloop.new_event_loop))
17
+ ```
18
+
19
+ With [uvicorn](https://www.uvicorn.org):
20
+
21
+ ```bash
22
+ uvicorn app:app --loop zloop:new_event_loop
23
+ ```
24
+
25
+ ## Why
26
+
27
+ - **Drop-in.** A genuine `AbstractEventLoop`, so the asyncio ecosystem -
28
+ uvicorn, FastAPI, AnyIO, HTTPX - runs on it unchanged.
29
+ - **Correct.** Passes [uvicorn](https://github.com/encode/uvicorn)'s **entire**
30
+ test suite (1048 tests), identical to stock asyncio, plus its own suite at
31
+ **100%** coverage.
32
+ - **Fast.** Faster than uvloop on the workloads measured so far - scheduling,
33
+ timers, and small/medium-message socket throughput (e.g. `call_soon` +46%,
34
+ 1 KiB echo +16% on CPython 3.14 / macOS arm64). `create_future` ties, because
35
+ all three loops reuse CPython's C-accelerated `_asyncio.Future`.
36
+
37
+ ## Benchmarks
38
+
39
+ The fairest comparison is uvloop's *own* echo benchmark, run unchanged except
40
+ for a `--zloop` server flag mirroring `--uvloop` (the client is byte-for-byte
41
+ uvloop's). Requests/sec, higher is better - macOS arm64, CPython 3.14, 3
42
+ workers, best of 3:
43
+
44
+ | Message | Server mode | asyncio | uvloop | zloop | zloop vs uvloop |
45
+ | --- | --- | ---: | ---: | ---: | ---: |
46
+ | 1 KiB | proto | 113k | 113k | **121k** | **+7%** |
47
+ | 1 KiB | buffered | 115k | 115k | **123k** | **+7%** |
48
+ | 1 KiB | streams | 83k | 90k | **103k** | **+14%** |
49
+ | 10 KiB | proto | 105k | 110k | **113k** | **+3%** |
50
+ | 10 KiB | buffered | 105k | 105k | **124k** | **+18%** |
51
+ | 10 KiB | streams | 81k | 86k | **95k** | **+11%** |
52
+
53
+ For the 1-10 KiB messages common in HTTP, WebSocket frames, and RPC, zloop leads
54
+ uvloop in every cell. The 100 KiB row is omitted: at that size the test measures
55
+ loopback bandwidth, not the loop, and all three swing wildly run-to-run.
56
+
57
+ Reproduce it with `scripts/bench` (or `bash bench_uvloop/run_matrix.sh` for the
58
+ full matrix); the **Benchmark** CI workflow runs it on Linux and posts the table
59
+ to the run summary.
60
+
61
+ ## How it works
62
+
63
+ The loop *engine* lives in Zig; CPython is reused only where reimplementing
64
+ would be reckless: driving coroutines (`asyncio.Future` / `asyncio.Task`) and
65
+ the TLS state machine (`asyncio.sslproto`). That's exactly uvloop's boundary.
66
+
67
+ ```
68
+ zloop/ Python edge - new_event_loop() factory, connection setup
69
+ src/python/*.zig CPython C-API adapter - Loop, Handle, Transport
70
+ src/core/*.zig pure-Zig domain - run-once engine, kqueue/epoll reactor, timer heap
71
+ ```
zloop-0.0.0/build.zig ADDED
@@ -0,0 +1,65 @@
1
+ const std = @import("std");
2
+
3
+ pub fn build(b: *std.Build) void {
4
+ const target = b.standardTargetOptions(.{});
5
+ const optimize = b.standardOptimizeOption(.{});
6
+
7
+ // Pure-Zig unit tests for the core data structures. Defined first so
8
+ // `zig build test` works without any Python configuration.
9
+ const core_tests = b.addTest(.{
10
+ .root_module = b.createModule(.{
11
+ .root_source_file = b.path("src/core/root.zig"),
12
+ .target = target,
13
+ .optimize = optimize,
14
+ .link_libc = true,
15
+ }),
16
+ });
17
+ const run_core_tests = b.addRunArtifact(core_tests);
18
+ const test_step = b.step("test", "Run pure-Zig core unit tests");
19
+ test_step.dependOn(&run_core_tests.step);
20
+
21
+ // Python build configuration is discovered by build_ext.sh and passed in as
22
+ // -D options or environment variables. Resolved lazily so the test step
23
+ // above never requires a Python toolchain.
24
+ const py_include = b.option([]const u8, "python-include", "Path to the CPython include dir") orelse
25
+ (b.graph.environ_map.get("ZLOOP_PYTHON_INCLUDE") orelse return);
26
+ const ext_suffix = b.option([]const u8, "ext-suffix", "Extension module suffix") orelse
27
+ (b.graph.environ_map.get("ZLOOP_EXT_SUFFIX") orelse return);
28
+
29
+ const core_mod = b.createModule(.{
30
+ .root_source_file = b.path("src/core/root.zig"),
31
+ .target = target,
32
+ .optimize = optimize,
33
+ .link_libc = true,
34
+ });
35
+
36
+ const mod = b.createModule(.{
37
+ .root_source_file = b.path("src/python/module.zig"),
38
+ .target = target,
39
+ .optimize = optimize,
40
+ .link_libc = true,
41
+ });
42
+ mod.addIncludePath(.{ .cwd_relative = py_include });
43
+ mod.addImport("core", core_mod);
44
+ // Provide atomic helpers the free-threaded headers declare but Zig's
45
+ // translate-c can't inline; harmless (unreferenced) on non-free-threaded builds.
46
+ mod.addCSourceFile(.{ .file = b.path("src/python/ft_atomics.c") });
47
+
48
+ const lib = b.addLibrary(.{
49
+ .name = "_zloop",
50
+ .root_module = mod,
51
+ .linkage = .dynamic,
52
+ });
53
+
54
+ // CPython extensions resolve interpreter symbols at load time. On macOS this
55
+ // requires undefined symbols to be allowed; on Linux they are global.
56
+ if (target.result.os.tag == .macos) {
57
+ lib.linker_allow_shlib_undefined = true;
58
+ }
59
+
60
+ // Install the shared object into the python package directory under the name
61
+ // CPython expects so it imports as `zloop._zloop`.
62
+ const out_name = b.fmt("_zloop{s}", .{ext_suffix});
63
+ const install = b.addInstallFileWithDir(lib.getEmittedBin(), .{ .custom = "../zloop" }, out_name);
64
+ b.getInstallStep().dependOn(&install.step);
65
+ }
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+ # Build the zloop Zig extension against the interpreter that will import it.
3
+ # Usage: ./build_ext.sh [path-to-python] (defaults to the uvicorn venv python)
4
+ set -euo pipefail
5
+
6
+ PY="${1:-/Users/marcelotryle/dev/encode/zloop/.uvicorn-upstream/.venv/bin/python}"
7
+ cd "$(dirname "$0")"
8
+
9
+ read -r INCLUDE SUFFIX < <("$PY" -c \
10
+ 'import sysconfig as s; print(s.get_path("platinclude"), s.get_config_var("EXT_SUFFIX"))')
11
+
12
+ export ZLOOP_PYTHON_INCLUDE="$INCLUDE"
13
+ export ZLOOP_EXT_SUFFIX="$SUFFIX"
14
+
15
+ MODE="${ZLOOP_BUILD_MODE:-ReleaseFast}"
16
+ zig build "-Doptimize=$MODE" "$@" 2>/dev/null || zig build "-Doptimize=$MODE"
17
+ echo "built zloop/_zloop$SUFFIX against $("$PY" --version) ($INCLUDE)"
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import shutil
5
+ import subprocess
6
+ import sys
7
+ import sysconfig
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
12
+
13
+ ROOT = Path(__file__).parent
14
+
15
+ # cibuildwheel builds every macOS wheel on the arm64 runner and asks for a
16
+ # specific arch through ARCHFLAGS; translate it into a Zig cross-compile target
17
+ # so the produced `.so` matches the wheel tag delocate enforces.
18
+ _MACOS_ZIG_ARCH = {"arm64": "aarch64-macos", "x86_64": "x86_64-macos"}
19
+
20
+ # Pin the binary's minimum macOS to match MACOSX_DEPLOYMENT_TARGET (set by
21
+ # cibuildwheel), so the wheel tag and the `.so`'s required OS version agree and
22
+ # delocate accepts the repaired wheel.
23
+ _MACOS_DEFAULT_MIN = "11.0"
24
+
25
+
26
+ def _zig_target_args() -> list[str]:
27
+ archflags = os.environ.get("ARCHFLAGS", "")
28
+ arches = archflags.split()[1::2] # "-arch x86_64 -arch arm64" -> ["x86_64", "arm64"]
29
+ if len(arches) != 1 or sys.platform != "darwin":
30
+ return []
31
+ arch = _MACOS_ZIG_ARCH.get(arches[0])
32
+ if not arch:
33
+ return []
34
+ min_version = os.environ.get("MACOSX_DEPLOYMENT_TARGET", _MACOS_DEFAULT_MIN)
35
+ return [f"-Dtarget={arch}.{min_version}"]
36
+
37
+
38
+ def _zig_command() -> list[str]:
39
+ """Resolve how to invoke Zig: a `zig` on PATH, else the `ziglang` pip package.
40
+
41
+ The pip fallback (`python -m ziglang`) works identically on the host and inside
42
+ cibuildwheel's manylinux containers, where a host-installed `zig` isn't visible.
43
+ """
44
+ if shutil.which("zig"):
45
+ return ["zig"]
46
+ try:
47
+ import ziglang # noqa: F401
48
+ except ImportError:
49
+ raise RuntimeError(
50
+ "Zig toolchain not found: install Zig and put it on PATH, or `pip install ziglang`."
51
+ ) from None
52
+ return [sys.executable, "-m", "ziglang"]
53
+
54
+
55
+ class ZigBuildHook(BuildHookInterface):
56
+ """Compile the Zig extension against the building interpreter during the wheel build.
57
+
58
+ This makes `uv build` / `pip wheel` / cibuildwheel produce a correct, platform-tagged
59
+ wheel with no out-of-band step: the `.so` is built here, against `sys.executable`, and
60
+ `build.zig` installs it into the `zloop/` package as `_zloop<EXT_SUFFIX>`.
61
+ """
62
+
63
+ PLUGIN_NAME = "custom"
64
+
65
+ def initialize(self, version: str, build_data: dict[str, Any]) -> None:
66
+ if self.target_name != "wheel":
67
+ return
68
+
69
+ include = sysconfig.get_path("platinclude")
70
+ ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")
71
+ if not include or not ext_suffix:
72
+ raise RuntimeError("could not resolve platinclude / EXT_SUFFIX from the building interpreter")
73
+
74
+ mode = os.environ.get("ZLOOP_BUILD_MODE", "ReleaseFast")
75
+ env = {**os.environ, "ZLOOP_PYTHON_INCLUDE": include, "ZLOOP_EXT_SUFFIX": ext_suffix}
76
+ subprocess.run(
77
+ [*_zig_command(), "build", f"-Doptimize={mode}", *_zig_target_args()],
78
+ cwd=ROOT,
79
+ env=env,
80
+ check=True,
81
+ )
82
+
83
+ artifact = f"zloop/_zloop{ext_suffix}"
84
+ if not (ROOT / artifact).exists():
85
+ raise RuntimeError(f"zig build did not produce {artifact}")
86
+
87
+ # Tag the wheel for this interpreter + platform rather than py3-none-any.
88
+ build_data["pure_python"] = False
89
+ build_data["infer_tag"] = True
90
+ build_data["artifacts"].append(artifact)
91
+
92
+ def clean(self, versions: list[str]) -> None:
93
+ for path in ROOT.glob("zloop/_zloop*.so"):
94
+ path.unlink()
95
+ for path in ROOT.glob("zloop/_zloop*.pyd"):
96
+ path.unlink()
97
+ print(f"removed compiled extensions; building Zig core via {sys.executable}", file=sys.stderr)
@@ -0,0 +1,125 @@
1
+ [build-system]
2
+ requires = ["hatchling", "uv-dynamic-versioning>=0.8.0", "ziglang==0.16.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "zloop"
7
+ description = "An asyncio event loop with a Zig core."
8
+ readme = "README.md"
9
+ license = "BSD-3-Clause"
10
+ license-files = ["LICENSE"]
11
+ requires-python = ">=3.12"
12
+ authors = [{ name = "Marcelo Trylesinski", email = "marcelotryle@gmail.com" }]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Framework :: AsyncIO",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Zig",
18
+ ]
19
+ dynamic = ["version"]
20
+
21
+ [dependency-groups]
22
+ dev = [
23
+ "pytest>=8.0",
24
+ "pytest-asyncio>=0.24",
25
+ "coverage>=7.0",
26
+ "ruff",
27
+ "mypy",
28
+ "trustme",
29
+ ]
30
+ docs = [
31
+ "zensical",
32
+ "matplotlib", # docs/gen_bench_chart.py renders the benchmark chart
33
+ ]
34
+ bench = [
35
+ "uvloop", # the comparison baseline for bench_uvloop/bench_ci.py
36
+ ]
37
+
38
+ [tool.hatch.version]
39
+ source = "uv-dynamic-versioning"
40
+
41
+ [tool.uv-dynamic-versioning]
42
+ vcs = "git"
43
+ style = "pep440"
44
+ bump = true
45
+ metadata = false # drop the +<hash> local segment; PyPI rejects it on uploads
46
+ fallback-version = "0.0.0"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["zloop"]
50
+ artifacts = ["zloop/*.so"]
51
+
52
+ [tool.hatch.build.targets.wheel.hooks.custom]
53
+ path = "hatch_build.py"
54
+
55
+ [tool.hatch.build.targets.sdist]
56
+ # Ship the sources needed to compile the extension from an sdist.
57
+ include = ["zloop", "src", "build.zig", "build_ext.sh", "hatch_build.py"]
58
+
59
+ [tool.cibuildwheel]
60
+ # Zig comes from the `ziglang` build requirement (build isolation), so no
61
+ # system toolchain install is needed. Skip 32-bit, musl, and PyPy: the Zig core
62
+ # targets glibc/macOS and CPython only.
63
+ build = "cp312-* cp313-* cp314-*"
64
+ skip = "*-win* *_i686 *-musllinux_*"
65
+ build-frontend = "build[uv]"
66
+ test-command = 'python -c "import asyncio, zloop; assert asyncio.run(asyncio.sleep(0, 1), loop_factory=zloop.new_event_loop) == 1"'
67
+
68
+ [tool.cibuildwheel.linux]
69
+ # manylinux container has no Zig; the ziglang wheel in the isolated build env
70
+ # provides it. Nothing extra to install. Each runner builds its native arch
71
+ # (x86_64 on ubuntu-latest, aarch64 on ubuntu-24.04-arm) - running the foreign
72
+ # manylinux container without QEMU hangs on `exec format error`.
73
+ archs = ["auto"]
74
+
75
+ [tool.cibuildwheel.macos]
76
+ archs = ["arm64", "x86_64"]
77
+ # Pin the wheel tag's minimum macOS; hatch_build.py passes the same version to
78
+ # Zig so the compiled `.so` and the tag agree and delocate accepts the wheel.
79
+ environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" }
80
+
81
+ [tool.pytest.ini_options]
82
+ addopts = "-rxXs --strict-config --strict-markers"
83
+ asyncio_mode = "auto"
84
+ xfail_strict = true
85
+ testpaths = ["tests"]
86
+
87
+ [tool.coverage.run]
88
+ source_pkgs = ["zloop"]
89
+ branch = true
90
+ parallel = true
91
+ omit = ["zloop/_zloop*"]
92
+
93
+ [tool.coverage.report]
94
+ precision = 2
95
+ fail_under = 100
96
+ show_missing = true
97
+ skip_covered = true
98
+ exclude_lines = [
99
+ "pragma: no cover",
100
+ "if TYPE_CHECKING:",
101
+ "raise NotImplementedError",
102
+ "@overload",
103
+ ]
104
+
105
+ [tool.ruff]
106
+ line-length = 120
107
+
108
+ [tool.ruff.lint]
109
+ select = ["E", "F", "I", "FA", "UP", "RUF100"]
110
+
111
+ [tool.ruff.lint.isort]
112
+ combine-as-imports = true
113
+
114
+ [tool.mypy]
115
+ strict = true
116
+ warn_unused_ignores = true
117
+ show_error_codes = true
118
+ files = ["zloop"]
119
+
120
+ [[tool.mypy.overrides]]
121
+ # The connection-setup layer deliberately widens the asyncio loop method
122
+ # signatures (Any / **_) the same way uvloop's stubs do; those intentional
123
+ # overrides and Any-returns aren't real type errors.
124
+ module = "zloop._io"
125
+ disable_error_code = ["override", "no-any-return"]
@@ -0,0 +1,25 @@
1
+ //! Monotonic clock. asyncio's loop.time() is time.monotonic(); we read the same
2
+ //! CLOCK_MONOTONIC source so deadlines computed on the Python side and the Zig
3
+ //! side agree.
4
+
5
+ const std = @import("std");
6
+ const sys = @import("sys.zig");
7
+
8
+ /// Current monotonic time in nanoseconds.
9
+ pub fn nowNs() u64 {
10
+ return sys.monotonicNs();
11
+ }
12
+
13
+ /// Current monotonic time in seconds as f64, matching time.monotonic().
14
+ pub fn nowSeconds() f64 {
15
+ return @as(f64, @floatFromInt(nowNs())) / std.time.ns_per_s;
16
+ }
17
+
18
+ const testing = std.testing;
19
+
20
+ test "clock is monotonic non-decreasing" {
21
+ const a = nowNs();
22
+ const b = nowNs();
23
+ try testing.expect(b >= a);
24
+ try testing.expect(nowSeconds() > 0);
25
+ }