miniasyncio 0.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,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: miniasyncio
3
+ Version: 0.0.1
4
+ Summary: A small async package
5
+ Author: Benjamin
6
+ Author-email: Benjamin <author@example.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Dist: build>=1.5.0
12
+ Requires-Python: >=3.13
13
+ Project-URL: Homepage, https://github.com/pypa/sampleproject
14
+ Project-URL: Issues, https://github.com/pypa/sampleproject/issues
15
+ Description-Content-Type: text/markdown
16
+
File without changes
@@ -0,0 +1,31 @@
1
+ [project]
2
+ name = "miniasyncio"
3
+ version = "0.0.1"
4
+ authors = [
5
+ { name="Benjamin", email="author@example.com" },
6
+ ]
7
+ description = "A small async package"
8
+ readme = "README.md"
9
+ requires-python = ">=3.13"
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Operating System :: OS Independent",
13
+ ]
14
+ license = "MIT"
15
+ license-files = ["LICEN[CS]E*"]
16
+ dependencies = [
17
+ "build>=1.5.0",
18
+ ]
19
+
20
+ [project.urls]
21
+ Homepage = "https://github.com/pypa/sampleproject"
22
+ Issues = "https://github.com/pypa/sampleproject/issues"
23
+
24
+ [build-system]
25
+ requires = ["uv_build >= 0.11.7, <0.12.0"]
26
+ build-backend = "uv_build"
27
+
28
+ [dependency-groups]
29
+ dev = [
30
+ "pytest>=9.1.1",
31
+ ]
@@ -0,0 +1,47 @@
1
+ from collections.abc import Awaitable, Callable, Generator
2
+ from functools import wraps
3
+ import time
4
+ from typing import Any, override
5
+
6
+ from .common import Coroutine, EventLoop, FutureState
7
+
8
+ EL = EventLoop()
9
+
10
+ def run(coro):
11
+ EL.create_task(coro)
12
+ EL.run_loop()
13
+
14
+ @wraps(EventLoop.run_loop)
15
+ def run_loop():
16
+ EL.run_loop()
17
+
18
+ @wraps(EventLoop.create_task)
19
+ def create_task(coroutine):
20
+ EL.create_task(coroutine)
21
+
22
+ class gather(Awaitable[Any]):
23
+ def __init__(self, *coros):
24
+ self.gathered_tasks = EL.create_tasks(*coros)
25
+
26
+ @override
27
+ def __await__(self):
28
+ while not all([task.state == FutureState.FINISHED for task in self.gathered_tasks]):
29
+ yield
30
+ return [task.result for task in self.gathered_tasks]
31
+
32
+
33
+
34
+ def __async__[Y, S, R](gen_func: Callable[..., Generator[Y, S, R]]) -> Callable[..., Coroutine[Y, S, R]]:
35
+ def coroutine_factory(*args: Any, **kwargs: Any) -> Coroutine[Y, S, R]:
36
+ return Coroutine(gen_func, *args, **kwargs)
37
+ return coroutine_factory
38
+
39
+ class sleep:
40
+ def __init__(self, seconds: float):
41
+ self._start: float = time.monotonic()
42
+ self._expected_end: float = self._start + seconds
43
+
44
+ def __await__(self) -> Generator[None, None, float]:
45
+ while time.monotonic() < self._expected_end:
46
+ yield
47
+ return time.monotonic() - self._start
@@ -0,0 +1,88 @@
1
+ from collections.abc import Generator
2
+ from enum import auto, Enum
3
+ from typing import Any, Callable, override
4
+ from .linkedlist import CircularDoubleLL
5
+ import inspect
6
+
7
+ class FutureState(Enum):
8
+ PENDING = auto()
9
+ CANCELED = auto()
10
+ FINISHED = auto()
11
+
12
+ class Future:
13
+ """ Resolves sometime in the future """
14
+ def __init__(self):
15
+ self._result: Any|None = None
16
+ self.state: FutureState = FutureState.PENDING
17
+
18
+ @property
19
+ def result(self):
20
+ return self._result
21
+
22
+ @result.setter
23
+ def result(self, result: Any):
24
+ self._result = result
25
+ self.state = FutureState.FINISHED
26
+
27
+ class Task[Y,S,R](Future):
28
+ def __init__(self, coroutine: Coroutine[Y,S,R]):
29
+ super().__init__()
30
+ self.coroutine: Coroutine[Y,S,R] = coroutine
31
+
32
+ @override
33
+ def __str__(self):
34
+ return f"{self.state}{self.result}{self.coroutine}"
35
+
36
+ class TaskQueue(CircularDoubleLL[Task[Any, Any, Any]]):
37
+ def __init__(self):
38
+ super().__init__()
39
+
40
+ def enqueue(self, task: Task[Any, Any, Any]):
41
+ self.add(task)
42
+
43
+ def dequeue(self):
44
+ it = iter(self)
45
+ return next(it)
46
+
47
+ class Coroutine[Y, S, R]:
48
+ """ Awaitable object """
49
+ def __init__(self, gen_func: Callable[..., Generator[Y, S, R]], *args: Any, **kwargs: Any) -> None:
50
+ if not inspect.isgeneratorfunction(gen_func):
51
+ raise Exception("Expect a generator function.")
52
+
53
+ self.generator: Generator[Y, S, R] = gen_func(*args, **kwargs)
54
+
55
+ def __await__(self) -> Generator[Y, S, R]:
56
+ """ Should return a generator object """
57
+ return self.generator
58
+
59
+ class EventLoop:
60
+ """Main orchestrator"""
61
+ unique_instance: EventLoop|None = None
62
+
63
+ def __init__(self):
64
+ if self.unique_instance is not None:
65
+ return self.unique_instance
66
+
67
+ self.taskQueue: TaskQueue = TaskQueue()
68
+
69
+ def run_loop(self):
70
+ """ Runs until loop is empty """
71
+ for task in self.taskQueue.iter_forever():
72
+
73
+ try:
74
+ task.coroutine.__await__().send(None)
75
+ except StopIteration as e:
76
+ task.state = FutureState.FINISHED
77
+ task.result = e.value
78
+ self.taskQueue.delete(task)
79
+
80
+ def create_task(self, coroutine: Coroutine[Any, Any, Any]):
81
+ """ Wraps a Coroutine in a task and adds it to the event loop. """
82
+ task = Task(coroutine)
83
+ self.taskQueue.add(task)
84
+ return task
85
+
86
+ def create_tasks(self, *coros: Coroutine[Any, Any, Any]) -> list[Task[Any, Any, Any]]:
87
+ tasks = [self.create_task(coro) for coro in coros]
88
+ return tasks
@@ -0,0 +1,103 @@
1
+ from collections import defaultdict
2
+ from collections.abc import Generator
3
+ from typing import override
4
+
5
+
6
+ class ListNode[NodeType]:
7
+ def __init__(self, val: NodeType):
8
+ self.val: NodeType = val
9
+ self.next: ListNode[NodeType] = self
10
+ self.prev: ListNode[NodeType] = self
11
+
12
+ @override
13
+ def __str__(self):
14
+ return f"[{self.val}]"
15
+
16
+
17
+ class CircularDoubleLL[NodeType]:
18
+ def __init__(self):
19
+ self._head: ListNode[NodeType]|None = None
20
+ self._count: int = 0
21
+ self._node_table: dict[NodeType,list[ListNode[NodeType]]] = defaultdict(list)
22
+
23
+ def add(self, val: ListNode[NodeType]|NodeType):
24
+ if isinstance(val, ListNode):
25
+ newNode: ListNode[NodeType] = val
26
+ else:
27
+ newNode = ListNode(val)
28
+
29
+ if self._head is None:
30
+ self._head = newNode
31
+ newNode.next = newNode
32
+ newNode.prev = newNode
33
+ else:
34
+ newNode.next = self._head
35
+ newNode.prev = self._head.prev
36
+ newNode.prev.next = newNode
37
+ newNode.next.prev = newNode
38
+ self._head = newNode.next
39
+ self._count += 1
40
+ self._node_table[newNode.val].append(newNode)
41
+
42
+ def _delete(self, val: ListNode[NodeType]|NodeType):
43
+ node: ListNode[NodeType]
44
+ if isinstance(val, ListNode):
45
+ node = val
46
+ else:
47
+ if val not in self._node_table or len(self._node_table[val]) == 0:
48
+ raise KeyError
49
+ node = self._node_table[val].pop()
50
+
51
+ node.prev.next = node.next
52
+ node.next.prev = node.prev
53
+ if self._head is not None and self._head == node:
54
+ self._head = self._head.next
55
+ self._count -= 1
56
+ if self._count == 0:
57
+ self._head = None
58
+ # no more refs to node? => garbage collected
59
+
60
+ def delete(self, val: list[ListNode[NodeType]|NodeType]|ListNode[NodeType]|NodeType):
61
+ if isinstance(val, list):
62
+ errors = []
63
+ for item in val:
64
+ try:
65
+ self._delete(item)
66
+ except Exception as e:
67
+ errors.append(e)
68
+
69
+ for e in errors:
70
+ raise e
71
+ else:
72
+ self._delete(val)
73
+
74
+ def __iter__(self):
75
+ if self._head is None:
76
+ return None
77
+
78
+ current_node: ListNode[NodeType] = self._head
79
+ yield current_node
80
+
81
+ while current_node.next != self._head:
82
+ current_node = current_node.next
83
+ yield current_node
84
+
85
+ def iter_forever(self) -> Generator[NodeType, None, None]:
86
+ if self._head is None:
87
+ return None
88
+
89
+ current_node: ListNode[NodeType] = self._head
90
+ while self._count > 0:
91
+ yield current_node.val
92
+ current_node = current_node.next
93
+
94
+ def __len__(self):
95
+ return self._count
96
+
97
+ @override
98
+ def __str__(self):
99
+ out = "LinkedList: "
100
+ for node in self:
101
+ out += f'- {str(node)} -'
102
+ return out
103
+