noio 0.1__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,63 @@
1
+ name: Build
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_call:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ timeout-minutes: 5
15
+ strategy:
16
+ matrix:
17
+ python: ["3.15", "3.15t"]
18
+ env:
19
+ UV_PYTHON: ${{ matrix.python }}
20
+ steps:
21
+ - uses: actions/checkout@v7.0.1
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v10.3.0
24
+ - name: Set up Python
25
+ run: uv python install "${{ matrix.python }}"
26
+ - name: Install dependencies
27
+ run: uv sync --all-groups
28
+ - name: Check formatting
29
+ run: uv run ruff format --check
30
+ - name: Run ruff linting
31
+ run: uv run ruff check
32
+ - name: Run basedpyright
33
+ run: uv run basedpyright
34
+ - name: Verify free threading
35
+ if: matrix.python == '3.15t'
36
+ run: uv run python -c "import noio, sys; assert not sys._is_gil_enabled()"
37
+ - name: Run tests
38
+ run: uv run pytest --cov --cov-report=term-missing
39
+ timeout-minutes: 2
40
+ - name: Verify built package
41
+ run: |
42
+ uv build
43
+ uv run --isolated --no-project --with dist/noio-*.whl python -I -c "import noio"
44
+ - name: Verify release version
45
+ if: github.event_name == 'release'
46
+ env:
47
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
48
+ run: |
49
+ uv run python - <<'PY'
50
+ import os
51
+ import tomllib
52
+ from pathlib import Path
53
+
54
+ version = tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]
55
+ assert os.environ["RELEASE_TAG"] == version, "Release tag must match package version"
56
+ PY
57
+ - name: Upload distributions
58
+ if: github.event_name == 'release' && matrix.python == '3.15'
59
+ uses: actions/upload-artifact@v7.0.2
60
+ with:
61
+ name: distributions
62
+ path: dist/
63
+ if-no-files-found: error
@@ -0,0 +1,29 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ build:
12
+ uses: ./.github/workflows/build.yml
13
+
14
+ publish:
15
+ needs: build
16
+ runs-on: ubuntu-latest
17
+ timeout-minutes: 5
18
+ environment:
19
+ name: pypi
20
+ url: https://pypi.org/project/noio/
21
+ permissions:
22
+ id-token: write
23
+ steps:
24
+ - uses: actions/download-artifact@v8.0.2
25
+ with:
26
+ name: distributions
27
+ path: dist/
28
+ - name: Publish to PyPI
29
+ uses: pypa/gh-action-pypi-publish@v1.14.2
noio-0.1/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+ .coverage*
9
+ htmlcov/
10
+
11
+ # Virtual environments
12
+ .venv
13
+
14
+ # uv
15
+ uv.lock
16
+ .python-version
17
+ .ruff_cache/
18
+ .ropeproject/
19
+
20
+ # MkDocs
21
+ site/
noio-0.1/AGENTS.md ADDED
@@ -0,0 +1 @@
1
+ README.md
noio-0.1/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ Changelog
2
+ =========
3
+
4
+ All notable changes to this project will be documented in this file.
5
+
6
+ The format is loosely based on [Keep a Changelog](https://keepachangelog.com).
7
+
8
+ [0.1] - 2026-10-17
9
+ -----------------
10
+
11
+ Initial experimental release.
12
+ Requires Python 3.15 or later.
13
+
14
+ ### Added
15
+
16
+ - `@noio.routine` to support ordinary calls and awaited `.a()` calls
17
+ from a single coroutine implementation.
18
+ - A guard against ordinary routine calls inside routine implementations.
19
+
20
+ [0.1]: https://github.com/ryanhiebert/noio/releases/tag/0.1
@@ -0,0 +1,44 @@
1
+ Contributing
2
+ ============
3
+
4
+ Choose APIs that best serve noio's goals.
5
+ Compatibility with existing libraries is valuable,
6
+ but we are free to make different choices when they improve the API.
7
+ Trio has a history of thoughtful API design
8
+ and is a useful reference when evaluating those choices.
9
+ Consider the reasoning behind its decisions,
10
+ without treating them as requirements for noio.
11
+
12
+ Do not attempt to retain backward compatibility between versions,
13
+ even for features that have already been released.
14
+ Document incompatible changes under Breaking Changes in the changelog.
15
+
16
+ Small releases are welcome as long as the included features are ready for people to use,
17
+ and we have reasonable confidence that any breaking changes are documented in the changelog.
18
+
19
+ Before adding a dependency, check that its license is compatible with
20
+ distributing noio under the MIT license.
21
+ Check any attribution or license notice requirements as well.
22
+
23
+ Design primitives so the same instance can be shared across supported runtimes,
24
+ including runtimes running concurrently in different threads.
25
+ Where applicable, test interaction between runtimes,
26
+ not just each runtime independently.
27
+ Treat compatibility across supported runtimes as the default.
28
+ Individual primitive docs only need to describe compatibility limitations.
29
+
30
+ Do not rely on any kind of monkeypatching being in place.
31
+ When monkeypatching is predictable, such as with gevent,
32
+ make sure the implementation will work whether it is patched or not.
33
+
34
+ Every bug fix must include a regression test
35
+ that fails without the fix and passes with it.
36
+
37
+ Record user-facing changes in `CHANGELOG.md` under an Unreleased section,
38
+ creating that section when needed.
39
+ At release time, turn it into a dated version entry.
40
+
41
+ Use semantic line breaks in Markdown prose.
42
+ Start each sentence on a new line,
43
+ and break longer sentences at natural clause or phrase boundaries
44
+ rather than wrapping to a fixed width.
noio-0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ryan Hiebert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
noio-0.1/PKG-INFO ADDED
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.5
2
+ Name: noio
3
+ Version: 0.1
4
+ Summary: Experimental coroutine libraries for ordinary-function applications
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.15
8
+ Requires-Dist: aiologic>=0.17.1
9
+ Description-Content-Type: text/markdown
10
+
11
+ noio
12
+ ====
13
+
14
+ There are many opinions on the right way to do IO.
15
+ There are so many libraries.
16
+ Some use regular functions, while some use coroutines.
17
+ Some use threads.
18
+ Some use green threads.
19
+ Some existing abstractions only work with coroutines on single threads.
20
+ You just want people to be able to use your library.
21
+
22
+ `noio` is a minimal IO interface
23
+ that provides primitives that work no matter what IO the user chooses,
24
+ with clear documentation of any compatibility limitations.
25
+
26
+ Getting Started
27
+ ---------------
28
+
29
+ `noio` believes that coroutines are an advanced feature
30
+ that should be avoided wherever reasonable.
31
+ That includes getting started writing a library with `noio`.
32
+ When writing a new library, start with regular functions,
33
+ which are able to support threads and green threads.
34
+ It might look something like this:
35
+
36
+ ```python
37
+ def greeting(name: str) -> str:
38
+ return f"Hello, {name}!"
39
+
40
+ assert greeting("world") == "Hello, world!"
41
+ ```
42
+
43
+ When it's important for your function to also support coroutine libraries
44
+ like `asyncio`, `trio` and `anyio`,
45
+ just update it to look like something like this:
46
+
47
+ ```python
48
+ import asyncio
49
+ import noio
50
+
51
+ @noio.routine
52
+ async def greeting(name: str) -> str:
53
+ return f"Hello, {name}!"
54
+
55
+ @noio.routine
56
+ async def introduction(name: str) -> str:
57
+ return await greeting.a(name)
58
+
59
+ assert greeting("world") == "Hello, world!"
60
+ assert introduction("world") == "Hello, world!"
61
+ assert asyncio.run(introduction.a("world")) == "Hello, world!"
62
+ ```
63
+
64
+ When existing callers call your function the same way they used to,
65
+ it will work without modification
66
+ because it will drive the coroutine implementation to completion directly.
67
+ Other routines will await a new `.a()` version
68
+ that allows the run loop to handle scheduling.
69
+
70
+ Finding all the places to convert can be tricky,
71
+ so `noio` will raise an error
72
+ if a routine implementation calls another routine with a regular function call.
73
+
74
+ Contributing
75
+ ------------
76
+
77
+ Before making changes, read [CONTRIBUTING.md](CONTRIBUTING.md).
noio-0.1/README.md ADDED
@@ -0,0 +1,67 @@
1
+ noio
2
+ ====
3
+
4
+ There are many opinions on the right way to do IO.
5
+ There are so many libraries.
6
+ Some use regular functions, while some use coroutines.
7
+ Some use threads.
8
+ Some use green threads.
9
+ Some existing abstractions only work with coroutines on single threads.
10
+ You just want people to be able to use your library.
11
+
12
+ `noio` is a minimal IO interface
13
+ that provides primitives that work no matter what IO the user chooses,
14
+ with clear documentation of any compatibility limitations.
15
+
16
+ Getting Started
17
+ ---------------
18
+
19
+ `noio` believes that coroutines are an advanced feature
20
+ that should be avoided wherever reasonable.
21
+ That includes getting started writing a library with `noio`.
22
+ When writing a new library, start with regular functions,
23
+ which are able to support threads and green threads.
24
+ It might look something like this:
25
+
26
+ ```python
27
+ def greeting(name: str) -> str:
28
+ return f"Hello, {name}!"
29
+
30
+ assert greeting("world") == "Hello, world!"
31
+ ```
32
+
33
+ When it's important for your function to also support coroutine libraries
34
+ like `asyncio`, `trio` and `anyio`,
35
+ just update it to look like something like this:
36
+
37
+ ```python
38
+ import asyncio
39
+ import noio
40
+
41
+ @noio.routine
42
+ async def greeting(name: str) -> str:
43
+ return f"Hello, {name}!"
44
+
45
+ @noio.routine
46
+ async def introduction(name: str) -> str:
47
+ return await greeting.a(name)
48
+
49
+ assert greeting("world") == "Hello, world!"
50
+ assert introduction("world") == "Hello, world!"
51
+ assert asyncio.run(introduction.a("world")) == "Hello, world!"
52
+ ```
53
+
54
+ When existing callers call your function the same way they used to,
55
+ it will work without modification
56
+ because it will drive the coroutine implementation to completion directly.
57
+ Other routines will await a new `.a()` version
58
+ that allows the run loop to handle scheduling.
59
+
60
+ Finding all the places to convert can be tricky,
61
+ so `noio` will raise an error
62
+ if a routine implementation calls another routine with a regular function call.
63
+
64
+ Contributing
65
+ ------------
66
+
67
+ Before making changes, read [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -0,0 +1,3 @@
1
+ from .routine import routine as routine
2
+
3
+ __all__ = ["routine"]
noio-0.1/noio/_lock.py ADDED
@@ -0,0 +1,49 @@
1
+ from types import TracebackType
2
+ from typing import Self
3
+
4
+ import aiologic
5
+ from aiologic.lowlevel import current_async_library
6
+
7
+ from .routine import _sync_call
8
+ from .routine import routine
9
+
10
+
11
+ class Lock:
12
+ """An owned, non-reentrant lock shared by threads, greenlets, and async tasks."""
13
+
14
+ def __init__(self) -> None:
15
+ self.__lock = aiologic.Lock()
16
+
17
+ @routine
18
+ async def __enter__(self) -> Self:
19
+ """Wait for ownership; reacquiring raises RuntimeError."""
20
+ if _sync_call.get():
21
+ self.__lock.green_acquire()
22
+ else:
23
+ await self.__lock.async_acquire()
24
+ return self
25
+
26
+ @routine
27
+ async def __exit__(
28
+ self,
29
+ exc_type: type[BaseException] | None,
30
+ exc_value: BaseException | None,
31
+ traceback: TracebackType | None,
32
+ ) -> None:
33
+ """Release from the owning thread, greenlet, or async task."""
34
+ # An ordinary acquisition inside an async function still has green ownership.
35
+ if self.__lock.green_owned() or current_async_library(failsafe=True) is None:
36
+ self.__lock.green_release()
37
+ else:
38
+ self.__lock.async_release()
39
+
40
+ async def __aenter__(self) -> Self:
41
+ return await self.__enter__.a()
42
+
43
+ async def __aexit__(
44
+ self,
45
+ exc_type: type[BaseException] | None,
46
+ exc_value: BaseException | None,
47
+ traceback: TracebackType | None,
48
+ ) -> None:
49
+ await self.__exit__.a(exc_type, exc_value, traceback)