zyncio 0.1.0__py3-none-any.whl
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.
- zyncio/__init__.py +243 -0
- zyncio/py.typed +0 -0
- zyncio-0.1.0.dist-info/METADATA +169 -0
- zyncio-0.1.0.dist-info/RECORD +6 -0
- zyncio-0.1.0.dist-info/WHEEL +4 -0
- zyncio-0.1.0.dist-info/licenses/LICENSE +21 -0
zyncio/__init__.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Write dual sync/async interfaces with minimal duplication."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Coroutine
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any, Concatenate, Final, Generic, ParamSpec, TypeVar, overload
|
|
6
|
+
from typing_extensions import Self
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'Mode',
|
|
11
|
+
'SYNC',
|
|
12
|
+
'ASYNC',
|
|
13
|
+
'zfunc',
|
|
14
|
+
'zmethod',
|
|
15
|
+
'zclassmethod',
|
|
16
|
+
'zproperty',
|
|
17
|
+
'SyncMixin',
|
|
18
|
+
'AsyncMixin',
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Mode(Enum):
|
|
23
|
+
"""`zyncio` execution mode."""
|
|
24
|
+
|
|
25
|
+
SYNC = 'sync'
|
|
26
|
+
ASYNC = 'async'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
SYNC: Final = Mode.SYNC
|
|
30
|
+
ASYNC: Final = Mode.ASYNC
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
P = ParamSpec('P')
|
|
34
|
+
ReturnT = TypeVar('ReturnT')
|
|
35
|
+
SelfT = TypeVar('SelfT')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Zyncable = Callable[Concatenate[Mode, P], Coroutine[Any, Any, ReturnT]]
|
|
39
|
+
ZyncableMethod = Callable[Concatenate[SelfT, Mode, P], Coroutine[Any, Any, ReturnT]]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _run_sync_coroutine(coro: Coroutine[Any, Any, ReturnT]) -> ReturnT:
|
|
43
|
+
try:
|
|
44
|
+
coro.send(None)
|
|
45
|
+
except StopIteration as e:
|
|
46
|
+
return e.value
|
|
47
|
+
else:
|
|
48
|
+
raise RuntimeError('zyncio functions must only await pure coroutines in sync mode')
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class zfunc(Generic[P, ReturnT]):
|
|
52
|
+
"""Wrap a function to run in both sync and async modes."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, func: Zyncable[P, ReturnT]) -> None:
|
|
55
|
+
"""..
|
|
56
|
+
|
|
57
|
+
:param func: The function to wrap.
|
|
58
|
+
"""
|
|
59
|
+
self.func: Final[Zyncable[P, ReturnT]] = func
|
|
60
|
+
self.__name__: str = func.__name__
|
|
61
|
+
self.__qualname__: str = getattr(func, '__qualname__', func.__name__)
|
|
62
|
+
self.__doc__: str | None = getattr(func, '__doc__', None)
|
|
63
|
+
|
|
64
|
+
def __repr__(self) -> str:
|
|
65
|
+
return f'<{self.__module__}.{type(self).__name__} {self.__qualname__}>'
|
|
66
|
+
|
|
67
|
+
def run_sync(self, *args: P.args, **kwargs: P.kwargs) -> ReturnT:
|
|
68
|
+
"""Run the function in sync mode."""
|
|
69
|
+
return _run_sync_coroutine(self.func(SYNC, *args, **kwargs))
|
|
70
|
+
|
|
71
|
+
async def run_async(self, *args: P.args, **kwargs: P.kwargs) -> ReturnT:
|
|
72
|
+
"""Run the function in async mode."""
|
|
73
|
+
return await self.func(ASYNC, *args, **kwargs)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SyncMixin:
|
|
77
|
+
"""Mixin that makes `zyncio.zmethod`s into sync callables."""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AsyncMixin:
|
|
81
|
+
"""Mixin that makes `zyncio.zmethod`s into async callables."""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class zmethod(Generic[SelfT, P, ReturnT]):
|
|
85
|
+
"""Wrap a method to run in both sync and async modes."""
|
|
86
|
+
|
|
87
|
+
def __init__(self, func: ZyncableMethod[SelfT, P, ReturnT]) -> None:
|
|
88
|
+
"""..
|
|
89
|
+
|
|
90
|
+
:param func: The method to wrap.
|
|
91
|
+
"""
|
|
92
|
+
self.func: Final[ZyncableMethod[SelfT, P, ReturnT]] = func
|
|
93
|
+
self.__name__: str = func.__name__
|
|
94
|
+
self.__qualname__: str = getattr(func, '__qualname__', func.__name__)
|
|
95
|
+
self.__doc__: str | None = getattr(func, '__doc__', None)
|
|
96
|
+
|
|
97
|
+
def __repr__(self) -> str:
|
|
98
|
+
return f'<{self.__module__}.{type(self).__name__} {self.__qualname__}>'
|
|
99
|
+
|
|
100
|
+
@overload
|
|
101
|
+
def __get__(self, instance: None, owner: type[SelfT]) -> Self: ...
|
|
102
|
+
@overload
|
|
103
|
+
def __get__(self, instance: SelfT, owner: type[SelfT] | None) -> 'BoundZyncMethod[SelfT, P, ReturnT]': ...
|
|
104
|
+
def __get__(self, instance: SelfT | None, owner: type[SelfT] | None) -> 'Self | BoundZyncMethod[SelfT, P, ReturnT]':
|
|
105
|
+
if instance is None:
|
|
106
|
+
return self
|
|
107
|
+
return BoundZyncMethod(self.func, instance)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class zclassmethod(Generic[SelfT, P, ReturnT]):
|
|
111
|
+
"""Wrap a method to run in both sync and async modes."""
|
|
112
|
+
|
|
113
|
+
def __init__(self, func: ZyncableMethod[type[SelfT], P, ReturnT]) -> None:
|
|
114
|
+
"""..
|
|
115
|
+
|
|
116
|
+
:param func: The method to wrap.
|
|
117
|
+
"""
|
|
118
|
+
self.func: Final[ZyncableMethod[type[SelfT], P, ReturnT]] = func.__func__ if isinstance(func, classmethod) else func
|
|
119
|
+
self.__name__: str = func.__name__
|
|
120
|
+
self.__qualname__: str = getattr(func, '__qualname__', func.__name__)
|
|
121
|
+
self.__doc__: str | None = getattr(func, '__doc__', None)
|
|
122
|
+
|
|
123
|
+
def __repr__(self) -> str:
|
|
124
|
+
return f'<{self.__module__}.{type(self).__name__} {self.__qualname__}>'
|
|
125
|
+
|
|
126
|
+
def __get__(self, instance: SelfT | None, owner: type[SelfT]) -> 'BoundZyncClassMethod[SelfT, P, ReturnT]':
|
|
127
|
+
return BoundZyncClassMethod(self.func, owner)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
SyncSelfT = TypeVar('SyncSelfT', bound=SyncMixin)
|
|
131
|
+
AsyncSelfT = TypeVar('AsyncSelfT', bound=AsyncMixin)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class BoundZyncMethod(Generic[SelfT, P, ReturnT]):
|
|
135
|
+
"""A bound `zyncio.zmethod`."""
|
|
136
|
+
|
|
137
|
+
def __init__(self, func: ZyncableMethod[SelfT, P, ReturnT], instance: SelfT) -> None:
|
|
138
|
+
"""..
|
|
139
|
+
|
|
140
|
+
:param func: The method to wrap.
|
|
141
|
+
:param instance: The instance to bind the method to.
|
|
142
|
+
"""
|
|
143
|
+
self.func: Final[ZyncableMethod[SelfT, P, ReturnT]] = func
|
|
144
|
+
self.instance: Final[SelfT] = instance
|
|
145
|
+
|
|
146
|
+
def __repr__(self) -> str:
|
|
147
|
+
return f'<{self.__module__}.{type(self).__name__} {self.func.__qualname__} of {self.instance!r}>'
|
|
148
|
+
|
|
149
|
+
def run_zync(self, mode: Mode, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, ReturnT]:
|
|
150
|
+
"""Run the method in the given mode."""
|
|
151
|
+
return self.func(self.instance, mode, *args, **kwargs)
|
|
152
|
+
|
|
153
|
+
def run_sync(self, *args: P.args, **kwargs: P.kwargs) -> ReturnT:
|
|
154
|
+
"""Run the method in sync mode."""
|
|
155
|
+
return _run_sync_coroutine(self.func(self.instance, SYNC, *args, **kwargs))
|
|
156
|
+
|
|
157
|
+
async def run_async(self, *args: P.args, **kwargs: P.kwargs) -> ReturnT:
|
|
158
|
+
"""Run the method in async mode."""
|
|
159
|
+
return await self.func(self.instance, ASYNC, *args, **kwargs)
|
|
160
|
+
|
|
161
|
+
@overload
|
|
162
|
+
def __call__(self: 'BoundZyncMethod[SyncSelfT, P, ReturnT]', *args: P.args, **kwargs: P.kwargs) -> ReturnT: ...
|
|
163
|
+
@overload
|
|
164
|
+
def __call__(self: 'BoundZyncMethod[AsyncSelfT, P, ReturnT]', *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, ReturnT]: ...
|
|
165
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> ReturnT | Coroutine[Any, Any, ReturnT]: # noqa: D102
|
|
166
|
+
if isinstance(self.instance, SyncMixin):
|
|
167
|
+
return self.run_sync(*args, **kwargs)
|
|
168
|
+
elif isinstance(self.instance, AsyncMixin):
|
|
169
|
+
return self.run_async(*args, **kwargs)
|
|
170
|
+
else:
|
|
171
|
+
raise TypeError(f'{type(self).__name__} is only callable when bound to instances of SyncMixin or AsyncMixin')
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class BoundZyncClassMethod(Generic[SelfT, P, ReturnT]):
|
|
175
|
+
"""A bound `zyncio.zclassmethod`."""
|
|
176
|
+
|
|
177
|
+
def __init__(self, func: ZyncableMethod[type[SelfT], P, ReturnT], cls: type[SelfT]) -> None:
|
|
178
|
+
"""..
|
|
179
|
+
|
|
180
|
+
:param func: The method to wrap.
|
|
181
|
+
:param cls: The class to bind the method to.
|
|
182
|
+
"""
|
|
183
|
+
self.func: Final[ZyncableMethod[type[SelfT], P, ReturnT]] = func
|
|
184
|
+
self.cls: Final[type[SelfT]] = cls
|
|
185
|
+
|
|
186
|
+
def __repr__(self) -> str:
|
|
187
|
+
return f'<{self.__module__}.{type(self).__name__} {self.func.__qualname__} of {self.cls!r}>'
|
|
188
|
+
|
|
189
|
+
def run_zync(self, mode: Mode, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, ReturnT]:
|
|
190
|
+
"""Run the method in the given mode."""
|
|
191
|
+
return self.func(self.cls, mode, *args, **kwargs)
|
|
192
|
+
|
|
193
|
+
def run_sync(self, *args: P.args, **kwargs: P.kwargs) -> ReturnT:
|
|
194
|
+
"""Run the method in sync mode."""
|
|
195
|
+
return _run_sync_coroutine(self.func(self.cls, SYNC, *args, **kwargs))
|
|
196
|
+
|
|
197
|
+
async def run_async(self, *args: P.args, **kwargs: P.kwargs) -> ReturnT:
|
|
198
|
+
"""Run the method in async mode."""
|
|
199
|
+
return await self.func(self.cls, ASYNC, *args, **kwargs)
|
|
200
|
+
|
|
201
|
+
@overload
|
|
202
|
+
def __call__(self: 'BoundZyncClassMethod[SyncSelfT, P, ReturnT]', *args: P.args, **kwargs: P.kwargs) -> ReturnT: ...
|
|
203
|
+
@overload
|
|
204
|
+
def __call__(self: 'BoundZyncClassMethod[AsyncSelfT, P, ReturnT]', *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, ReturnT]: ...
|
|
205
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> ReturnT | Coroutine[Any, Any, ReturnT]: # noqa: D102
|
|
206
|
+
if issubclass(self.cls, SyncMixin):
|
|
207
|
+
return self.run_sync(*args, **kwargs)
|
|
208
|
+
elif issubclass(self.cls, AsyncMixin):
|
|
209
|
+
return self.run_async(*args, **kwargs)
|
|
210
|
+
else:
|
|
211
|
+
raise TypeError(f'{type(self).__name__} is only callable when bound to subclasses of SyncMixin or AsyncMixin')
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class zproperty(Generic[SelfT, ReturnT]):
|
|
215
|
+
"""Wrap a method to act as a property in sync mode, and as a coroutine in async mode."""
|
|
216
|
+
|
|
217
|
+
def __init__(self, func: ZyncableMethod[SelfT, [], ReturnT]) -> None:
|
|
218
|
+
"""..
|
|
219
|
+
|
|
220
|
+
:param func: The method to wrap.
|
|
221
|
+
"""
|
|
222
|
+
self.func: Final[ZyncableMethod[SelfT, [], ReturnT]] = func
|
|
223
|
+
self.__name__: str = func.__name__
|
|
224
|
+
self.__qualname__: str = getattr(func, '__qualname__', func.__name__)
|
|
225
|
+
self.__doc__: str | None = getattr(func, '__doc__', None)
|
|
226
|
+
|
|
227
|
+
def __repr__(self) -> str:
|
|
228
|
+
return f'<{self.__module__}.{type(self).__name__} {self.__qualname__}>'
|
|
229
|
+
|
|
230
|
+
@overload
|
|
231
|
+
def __get__(self, instance: None, owner: type[SelfT]) -> Self: ...
|
|
232
|
+
@overload
|
|
233
|
+
def __get__(self: 'zproperty[SyncSelfT, ReturnT]', instance: SelfT, owner: type[SelfT] | None) -> ReturnT: ...
|
|
234
|
+
@overload
|
|
235
|
+
def __get__(self: 'zproperty[AsyncSelfT, ReturnT]', instance: SelfT, owner: type[SelfT] | None) -> 'BoundZyncMethod[SelfT, [], ReturnT]': ...
|
|
236
|
+
def __get__(self, instance: SelfT | None, owner: type[SelfT] | None) -> 'Self | ReturnT | BoundZyncMethod[SelfT, [], ReturnT]':
|
|
237
|
+
if instance is None:
|
|
238
|
+
return self
|
|
239
|
+
elif isinstance(instance, SyncMixin):
|
|
240
|
+
return BoundZyncMethod(self.func, instance).run_sync()
|
|
241
|
+
elif isinstance(instance, AsyncMixin):
|
|
242
|
+
return BoundZyncMethod(self.func, instance)
|
|
243
|
+
raise TypeError(f'{type(self).__name__} can only be accessed on instances of SyncMixin or AsyncMixin')
|
zyncio/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zyncio
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Write dual sync/async interfaces with minimal duplication.
|
|
5
|
+
Project-URL: Documentation, https://github.com/BenjyWiener/zyncio#readme
|
|
6
|
+
Project-URL: Issues, https://github.com/BenjyWiener/zyncio/issues
|
|
7
|
+
Project-URL: Source, https://github.com/BenjyWiener/zyncio
|
|
8
|
+
Author-email: Benjy Wiener <benjywiener@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: typing-extensions~=4.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: coverage; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# `zyncio`
|
|
29
|
+
|
|
30
|
+
## Write dual sync/async interfaces with minimal duplication.
|
|
31
|
+
|
|
32
|
+
> If I had a nickel for every almost identical interface I had to write,
|
|
33
|
+
> I'd have two nickels... which isn't a lot, but it's weird that I had to
|
|
34
|
+
> write it twice.
|
|
35
|
+
>
|
|
36
|
+
> – Dr. Doofenshmirtz, before discovering zyncio.
|
|
37
|
+
|
|
38
|
+
# What is `zyncio`?
|
|
39
|
+
|
|
40
|
+
`zyncio` allows you to write interfaces that can be used synchronously and asynchronously,
|
|
41
|
+
while avoiding the code duplication this usually entails.
|
|
42
|
+
|
|
43
|
+
# How does it work?
|
|
44
|
+
|
|
45
|
+
`zyncio` works due to the fact that in Python you can actually run a coroutine **without an event loop**,
|
|
46
|
+
as long as your chain of `await`s consists exclusively of other coroutines (i.e. no `Future`s or `Task`s):
|
|
47
|
+
|
|
48
|
+
> The behavior of `await coroutine` is effectively the same as invoking a regular, synchronous Python function.
|
|
49
|
+
>
|
|
50
|
+
> – [A Conceptual Overview of `asyncio`](https://docs.python.org/3/howto/a-conceptual-overview-of-asyncio.html#await)
|
|
51
|
+
|
|
52
|
+
To run such a coroutine, we simply call `send(None)`, catch the `StopIteration`, and extract its `value`:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
coro = pure_coroutine_func()
|
|
56
|
+
try:
|
|
57
|
+
coro.send(None)
|
|
58
|
+
except StopIteration as e:
|
|
59
|
+
ret = e.value
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This means that a single `async def` function can be made to run in both synchronous and asynchronous
|
|
63
|
+
contexts, as long as we have a way to determine which mode we're currently using:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
async def zync_sleep(zync_mode: zyncio.Mode, secs: float) -> None:
|
|
67
|
+
if zync_mode is zyncio.SYNC:
|
|
68
|
+
time.sleep(secs)
|
|
69
|
+
else:
|
|
70
|
+
await asyncio.sleep(secs)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
But this isn't very convenient; you need to pass an additional parameter, and running in
|
|
74
|
+
sync mode is pretty clunky. That's where `zyncio.zfunc` comes in:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
@zyncio.zfunc
|
|
78
|
+
async def zync_sleep(zync_mode: zyncio.Mode, secs: float) -> None:
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
zync_sleep.run_sync(3)
|
|
82
|
+
asyncio.run(zync_sleep.run_async(3))
|
|
83
|
+
|
|
84
|
+
@zyncio.zfunc
|
|
85
|
+
async def sleep_3(zync_mode: zyncio.Mode) -> None:
|
|
86
|
+
await zync_sleep.run_zync(zync_mode, 3)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## The real magic: `SyncMixin`/`AsyncMixin`, `zyncio.zmethod`, and `zyncio.zproperty`
|
|
90
|
+
|
|
91
|
+
The real power of `zyncio` comes out when implementing client interfaces:
|
|
92
|
+
|
|
93
|
+
1. Implement a single base client, using the `zyncio.zmethod` and `zyncio.zproperty`
|
|
94
|
+
decorators.
|
|
95
|
+
|
|
96
|
+
2. Create two subclasses a sync client and an async client, adding the `zyncio.SyncMixin`
|
|
97
|
+
and `zyncio.AsyncMixin` mixins respectively.
|
|
98
|
+
|
|
99
|
+
3. All of your `zyncio.zmethod`s magically become sync methods on the sync client and async
|
|
100
|
+
methods on the async client.
|
|
101
|
+
|
|
102
|
+
All of the `zyncio.zproperty`s magically become properties on the sync client, and async
|
|
103
|
+
methods on the async client.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
class BaseClient:
|
|
107
|
+
def __init__(self, sock: socket.socket) -> None:
|
|
108
|
+
self.sock: socket.socket = sock
|
|
109
|
+
|
|
110
|
+
@zyncio.zmethod
|
|
111
|
+
async def send_msg(self, zync_mode: zyncio.Mode, data: bytes) -> None:
|
|
112
|
+
if zync_mode is zyncio.SYNC:
|
|
113
|
+
self.sock.sendall(data)
|
|
114
|
+
else:
|
|
115
|
+
loop = asyncio.get_running_loop()
|
|
116
|
+
await loop.sock_sendall(self.sock, data)
|
|
117
|
+
|
|
118
|
+
@zyncio.zmethod
|
|
119
|
+
async def recv_msg(self, zync_mode: zyncio.Mode, n: int) -> bytes:
|
|
120
|
+
buf = b''
|
|
121
|
+
if zync_mode is zyncio.SYNC:
|
|
122
|
+
while len(buf) < n:
|
|
123
|
+
buf += self.sock.recv(n)
|
|
124
|
+
else:
|
|
125
|
+
loop = asyncio.get_running_loop()
|
|
126
|
+
while len(buf) < n:
|
|
127
|
+
buf += await loop.sock_recv(self.sock, n)
|
|
128
|
+
return buf
|
|
129
|
+
|
|
130
|
+
@zyncio.zmethod
|
|
131
|
+
async def do_handshake(self, zync_mode: zyncio.Mode) -> None:
|
|
132
|
+
await self.send_msg.run_zync(zync_mode, HANDSHAKE_REQ)
|
|
133
|
+
response = await self.recv_msg.run_zync(zync_mode, len(HANDSHAKE_RESP))
|
|
134
|
+
if response != HANDSHAKE_RESP:
|
|
135
|
+
raise RuntimeError('Handshake failed')
|
|
136
|
+
|
|
137
|
+
@zyncio.zproperty
|
|
138
|
+
async def status(self, zync_mode: zyncio.Mode) -> str:
|
|
139
|
+
await self.send_msg.run_zync(zync_mode, STATUS_REQ)
|
|
140
|
+
return (await self.recv_msg.run_zync(zync_mode, STATUS_RESP_LEN)).decode()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class SyncClient(BaseClient, zyncio.SyncMixin):
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class AsyncClient(BaseClient, zyncio.AsyncMixin):
|
|
148
|
+
def __init__(self, sock: socket.socket) -> None:
|
|
149
|
+
super().__init__(sock)
|
|
150
|
+
self.sock.setblocking(False)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
sync_client = SyncClient(sock)
|
|
154
|
+
sync_client.do_handshake() # Magically sync!
|
|
155
|
+
print('Status:', sync_client.status) # Sync property
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def use_async_client():
|
|
159
|
+
async_client = AsyncClient(sock)
|
|
160
|
+
await async_client.do_handshake() # Magically async!
|
|
161
|
+
print('Status:', await sync_client.status()) # Async func
|
|
162
|
+
|
|
163
|
+
asyncio.run(use_async_client())
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
# Typing
|
|
167
|
+
|
|
168
|
+
`zyncio` is fully typed, and built specifically for typed projects. If you're getting
|
|
169
|
+
unexepcted type checking errors, please [open an issue](https://github.com/BenjyWiener/zyncio/issues).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
zyncio/__init__.py,sha256=8n_oe7_cJxOQq8NF9JRakK8bw51yQkj0Pv1QSPIqddU,9504
|
|
2
|
+
zyncio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
zyncio-0.1.0.dist-info/METADATA,sha256=vVVl-g1XGniPrsI5qYqEVvvvdgjPqSvwtdUMg2MytO0,5877
|
|
4
|
+
zyncio-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
5
|
+
zyncio-0.1.0.dist-info/licenses/LICENSE,sha256=ZQlZ_loQOM5bqucZC4VC-ha3VmWsx39qjxnGF9yY9JU,1069
|
|
6
|
+
zyncio-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Benjy Wiener
|
|
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.
|