priomesh-python 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.
priomesh/__init__.py ADDED
@@ -0,0 +1,26 @@
1
+ from .models import Job, Priority, SubQueue
2
+ from .policies import (
3
+ AbsolutePriorityPolicy,
4
+ AgingWeightedRandomSubQueuePolicy,
5
+ FifoSubQueuePolicy,
6
+ PriorityPolicy,
7
+ RandomPriorityPolicy,
8
+ RandomSubQueuePolicy,
9
+ SubQueuePolicy,
10
+ )
11
+ from .queue import PriorityFairQueue, QueueShutDown
12
+
13
+ __all__ = [
14
+ "AbsolutePriorityPolicy",
15
+ "AgingWeightedRandomSubQueuePolicy",
16
+ "FifoSubQueuePolicy",
17
+ "Job",
18
+ "Priority",
19
+ "PriorityFairQueue",
20
+ "QueueShutDown",
21
+ "PriorityPolicy",
22
+ "RandomPriorityPolicy",
23
+ "RandomSubQueuePolicy",
24
+ "SubQueue",
25
+ "SubQueuePolicy",
26
+ ]
priomesh/models.py ADDED
@@ -0,0 +1,24 @@
1
+ # Job, Priority, SubQueue
2
+ from dataclasses import dataclass
3
+ from enum import IntEnum
4
+
5
+
6
+ class Priority(IntEnum):
7
+ CRITICAL = 1
8
+ HIGH = 2
9
+ NORMAL = 3
10
+ LOW = 4
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class Job:
15
+ name: str
16
+ priority: Priority
17
+ subqueue: str = "default"
18
+
19
+
20
+ @dataclass(frozen=False)
21
+ class SubQueue:
22
+ name: str
23
+ waiting_rounds: int
24
+ pending_jobs: int = 1
@@ -0,0 +1,4 @@
1
+ from .priority import PriorityPolicy, RandomPriorityPolicy, AbsolutePriorityPolicy
2
+ from .subqueue import SubQueuePolicy, FifoSubQueuePolicy, AgingWeightedRandomSubQueuePolicy, RandomSubQueuePolicy
3
+
4
+ # 定义可导出
@@ -0,0 +1,30 @@
1
+ import random
2
+ from collections.abc import Sequence
3
+ from typing import Protocol
4
+
5
+ from ..models import Priority
6
+
7
+
8
+ class PriorityPolicy(Protocol):
9
+ def choose(self, active_priorities: Sequence[Priority]) -> Priority:
10
+ """Choose one currently active priority."""
11
+
12
+
13
+ class AbsolutePriorityPolicy:
14
+ """Always choose the highest-priority active level."""
15
+
16
+ def choose(self, active_priorities: Sequence[Priority]) -> Priority:
17
+ return min(active_priorities)
18
+
19
+
20
+ class RandomPriorityPolicy:
21
+ """Randomly choose an active priority, favoring higher priorities."""
22
+
23
+ def choose(self, active_priorities: Sequence[Priority]) -> Priority:
24
+ ordered_priorities = sorted(active_priorities)
25
+ priority_weights = {
26
+ priority: len(ordered_priorities) - index
27
+ for index, priority in enumerate(ordered_priorities)
28
+ }
29
+ weights = [priority_weights[priority] for priority in active_priorities]
30
+ return random.choices(active_priorities, weights=weights, k=1)[0]
@@ -0,0 +1,52 @@
1
+ import math
2
+ import random
3
+ from collections.abc import Sequence
4
+ from typing import Protocol
5
+ from ..models import SubQueue
6
+
7
+
8
+ class SubQueuePolicy(Protocol):
9
+ def choose(self, subqueues: Sequence[SubQueue]) -> SubQueue:
10
+ """Choose one currently active subqueue."""
11
+
12
+
13
+ class AgingWeightedRandomSubQueuePolicy:
14
+ """Favor larger backlogs while increasing chances for waiting subqueues.
15
+
16
+ Each active subqueue's weight is sqrt(pending jobs) * (waiting rounds + 1).
17
+ The square root gives deeper queues more service opportunities without
18
+ making a small queue's chance negligible. Aging raises the weight of a
19
+ subqueue each time it is not selected and resets it when it is selected.
20
+ Selection remains probabilistic; this policy does not impose a wait limit.
21
+ """
22
+
23
+ def choose(self, subqueues: Sequence[SubQueue]) -> SubQueue:
24
+ weights = [
25
+ math.sqrt(subqueue.pending_jobs) * (subqueue.waiting_rounds + 1)
26
+ for subqueue in subqueues
27
+ ]
28
+ selected = random.choices(
29
+ subqueues,
30
+ weights=weights,
31
+ k=1,
32
+ )[0]
33
+ for subqueue in subqueues:
34
+ if subqueue is selected:
35
+ subqueue.waiting_rounds = 0
36
+ else:
37
+ subqueue.waiting_rounds += 1
38
+ return selected
39
+
40
+
41
+ class RandomSubQueuePolicy:
42
+ """Choose an active subqueue uniformly at random."""
43
+
44
+ def choose(self, subqueues: Sequence[SubQueue]) -> SubQueue:
45
+ return random.choice(subqueues)
46
+
47
+
48
+ class FifoSubQueuePolicy:
49
+ """Choose the subqueue at the front of the rotation."""
50
+
51
+ def choose(self, subqueues: Sequence[SubQueue]) -> SubQueue:
52
+ return subqueues[0]
priomesh/queue.py ADDED
@@ -0,0 +1,88 @@
1
+ import asyncio
2
+ from collections import deque
3
+ from .models import Priority, SubQueue, Job
4
+
5
+ from .policies import (
6
+ PriorityPolicy,
7
+ SubQueuePolicy,
8
+ RandomPriorityPolicy,
9
+ AgingWeightedRandomSubQueuePolicy
10
+ )
11
+
12
+
13
+ class QueueShutDown(Exception):
14
+ """Raised when an operation is attempted on a shut-down queue."""
15
+
16
+
17
+ class PriorityFairQueue:
18
+ """Schedule jobs by priority and distribute service fairly among subqueues.
19
+
20
+ The aging-weighted-random policy increases a subqueue's selection weight
21
+ with each round it is not selected. This provides probabilistic fairness:
22
+ longer-waiting active subqueues become increasingly likely to be served.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ priority_policy: PriorityPolicy | None = None,
28
+ subqueue_policy: SubQueuePolicy | None = None,
29
+ ) -> None:
30
+ self._priority_policy = priority_policy or RandomPriorityPolicy()
31
+ self._subqueue_policy = subqueue_policy or AgingWeightedRandomSubQueuePolicy()
32
+ self._queues: dict[Priority, dict[str, deque[Job]]] = {}
33
+ self._rotation: dict[Priority, list[SubQueue]] = {}
34
+ self._condition = asyncio.Condition()
35
+ self._shutdown = False
36
+
37
+ def empty(self) -> bool:
38
+ return not self._rotation
39
+
40
+ async def put(self, job: Job) -> None:
41
+ async with self._condition:
42
+ if self._shutdown:
43
+ raise QueueShutDown("cannot put into a shut-down queue")
44
+ level_queues = self._queues.setdefault(job.priority, {})
45
+ level_queues.setdefault(job.subqueue, deque()).append(job)
46
+
47
+ rotation = self._rotation.setdefault(job.priority, [])
48
+ subqueue = next(
49
+ (entry for entry in rotation if entry.name == job.subqueue),
50
+ None,
51
+ )
52
+ if subqueue is None:
53
+ rotation.append(SubQueue(name=job.subqueue, waiting_rounds=0))
54
+ else:
55
+ subqueue.pending_jobs += 1
56
+ self._condition.notify()
57
+
58
+ async def get(self) -> Job:
59
+ async with self._condition:
60
+ while True:
61
+ active_priorities = [p for p, r in self._rotation.items() if r]
62
+ if active_priorities:
63
+ priority = self._priority_policy.choose(active_priorities)
64
+ r = self._rotation[priority]
65
+
66
+ subqueue = self._subqueue_policy.choose(r)
67
+ r.remove(subqueue)
68
+ jobs = self._queues[priority][subqueue.name]
69
+ job = jobs.popleft()
70
+ subqueue.pending_jobs -= 1
71
+ if jobs:
72
+ r.append(subqueue)
73
+ else:
74
+ del self._queues[priority][subqueue.name]
75
+ if not r:
76
+ del self._queues[priority]
77
+ del self._rotation[priority]
78
+ # print(f"Processing job: {job.name}, priority: {job.priority}, subqueue: {job.subqueue}")
79
+ return job
80
+
81
+ if self._shutdown:
82
+ raise QueueShutDown("queue is shut down and empty")
83
+ await self._condition.wait()
84
+
85
+ async def shutdown(self) -> None:
86
+ async with self._condition:
87
+ self._shutdown = True
88
+ self._condition.notify_all() # 唤醒所有等待的协程,促使其检查关闭态并退出
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.4
2
+ Name: priomesh-python
3
+ Version: 0.1.0
4
+ Summary: A pluggable priority and fair-queue scheduling library
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 PrioMesh contributors
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Classifier: Development Status :: 3 - Alpha
28
+ Classifier: License :: OSI Approved :: MIT License
29
+ Classifier: Operating System :: OS Independent
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: Programming Language :: Python :: 3 :: Only
32
+ Classifier: Programming Language :: Python :: 3.10
33
+ Classifier: Programming Language :: Python :: 3.11
34
+ Classifier: Programming Language :: Python :: 3.12
35
+ Classifier: Programming Language :: Python :: 3.13
36
+ Classifier: Topic :: System :: Distributed Computing
37
+ Requires-Python: >=3.10
38
+ Description-Content-Type: text/markdown
39
+ License-File: LICENSE
40
+ Dynamic: license-file
41
+
42
+ # PrioMesh
43
+
44
+ PrioMesh is an early-stage Python library for priority-based task scheduling
45
+ across named sub-queues. It provides pluggable priority and sub-queue policies.
46
+
47
+ The current implementation is an in-memory, single-process prototype. Durable
48
+ storage, workers, and distributed execution are not implemented yet.
49
+
50
+ ## Requirements
51
+
52
+ - Python 3.10 or newer
53
+
54
+ ## Install
55
+
56
+ For development from a local checkout:
57
+
58
+ ```bash
59
+ git clone https://github.com/PrioMesh/priomesh-python.git
60
+ cd priomesh-python
61
+ python -m pip install -e .
62
+ ```
63
+
64
+ Once the project is published to PyPI, end users will be able to install it
65
+ with:
66
+
67
+ ```bash
68
+ python -m pip install priomesh-python
69
+ ```
70
+
71
+ Until then, users can install directly from the GitHub repository:
72
+
73
+ ```bash
74
+ python -m pip install "git+https://github.com/PrioMesh/priomesh-python.git"
75
+ ```
76
+
77
+ ## Quick start
78
+
79
+ ```python
80
+ import asyncio
81
+
82
+ from priomesh import Job, Priority, PriorityFairQueue, QueueShutDown
83
+
84
+
85
+ async def main() -> None:
86
+ queue = PriorityFairQueue()
87
+
88
+ async def consume() -> None:
89
+ try:
90
+ while True:
91
+ job = await queue.get()
92
+ print(job)
93
+ except QueueShutDown:
94
+ pass
95
+
96
+ consumer = asyncio.create_task(consume())
97
+ await queue.put(Job("job-1", Priority.HIGH, "tenant-a"))
98
+ await queue.put(Job("job-2", Priority.HIGH, "tenant-b"))
99
+ await queue.shutdown()
100
+ await consumer
101
+
102
+
103
+ asyncio.run(main())
104
+ ```
105
+
106
+ `get()` first selects an active priority level, then selects an active
107
+ sub-queue within that level. Jobs within each priority/sub-queue pair are
108
+ returned in FIFO order. Calling `get()` on an empty queue waits until a job is
109
+ put; `empty()` checks whether the queue currently has any jobs.
110
+
111
+ Call `shutdown()` to stop accepting jobs. It performs a graceful shutdown:
112
+ `put()` raises `QueueShutDown`, while `get()` continues returning jobs that
113
+ were already queued. Once the queue is drained, waiting and future `get()`
114
+ calls raise `QueueShutDown`, allowing consumers to exit. The example above
115
+ shows a consumer draining the queue and stopping cleanly.
116
+
117
+ ## Scheduling policies
118
+
119
+ Priority and sub-queue policies are independent and can be combined:
120
+
121
+ | Policy | Behavior | Use when |
122
+ | --- | --- | --- |
123
+ | `RandomPriorityPolicy` (default) | Randomly selects an active priority, weighting higher priorities more heavily. With all four levels active, the weights are 4:3:2:1 from `CRITICAL` to `LOW`. | Higher priorities should usually run sooner, while lower priorities should still be eligible. |
124
+ | `AbsolutePriorityPolicy` | Always selects the highest-priority active level. | Priority order must be strict. A continuously active higher priority can indefinitely delay lower priorities. |
125
+ | `AgingWeightedRandomSubQueuePolicy` (default) | Randomly selects a sub-queue with weight `sqrt(pending_jobs) * (waiting_rounds + 1)`. | Larger backlogs should receive more opportunities, while sub-queues skipped in prior selections gain weight. |
126
+ | `FifoSubQueuePolicy` | Selects the sub-queue at the front of the rotation; a sub-queue with remaining work goes to the back after serving one job. | Predictable round-robin service among active sub-queues at the selected priority is preferred. |
127
+ | `RandomSubQueuePolicy` | Selects uniformly at random among active sub-queues. | Sub-queues should have equal selection probability, regardless of backlog. |
128
+
129
+ The aging policy is probabilistic: its weights affect relative selection
130
+ chances, but it does not guarantee a fixed service ratio or a maximum wait.
131
+ `waiting_rounds` counts selections of other sub-queues at the same priority,
132
+ not elapsed time. When a sub-queue is selected, its waiting count resets.
133
+
134
+ Policies can be replaced when constructing the queue:
135
+
136
+ ```python
137
+ from priomesh import (
138
+ AbsolutePriorityPolicy,
139
+ AgingWeightedRandomSubQueuePolicy,
140
+ FifoSubQueuePolicy,
141
+ PriorityFairQueue,
142
+ RandomPriorityPolicy,
143
+ RandomSubQueuePolicy,
144
+ )
145
+
146
+ # Strict priority order, with deterministic round-robin among sub-queues.
147
+ queue = PriorityFairQueue(
148
+ priority_policy=AbsolutePriorityPolicy(),
149
+ subqueue_policy=FifoSubQueuePolicy(),
150
+ )
151
+
152
+ # Probabilistic priority and uniform random sub-queue selection.
153
+ queue = PriorityFairQueue(
154
+ priority_policy=RandomPriorityPolicy(),
155
+ subqueue_policy=RandomSubQueuePolicy(),
156
+ )
157
+
158
+ # These are also the defaults; this is equivalent to PriorityFairQueue().
159
+ queue = PriorityFairQueue(
160
+ priority_policy=RandomPriorityPolicy(),
161
+ subqueue_policy=AgingWeightedRandomSubQueuePolicy(),
162
+ )
163
+ ```
164
+
165
+ ## Run the example from a checkout
166
+
167
+ ```bash
168
+ python -m pip install -e .
169
+ python examples/basic.py
170
+ ```
171
+
172
+ ## Project status
173
+
174
+ PrioMesh is under active early development. The current queue is in-memory and
175
+ does not provide persistence, worker execution, retries, or cross-process
176
+ scheduling. A Go implementation is planned after the scheduling semantics
177
+ stabilize.
178
+
179
+ ## License
180
+
181
+ PrioMesh is licensed under the MIT License. See [LICENSE](LICENSE).
@@ -0,0 +1,11 @@
1
+ priomesh/__init__.py,sha256=lxHKW2avoW1kHn3AaH9iRLSN2oiXLPNmAIOvxY3K3qI,605
2
+ priomesh/models.py,sha256=ifsb9z5nTzkFW1aYDYw6IZf0eotBjwla8IKMG1YzCyo,380
3
+ priomesh/queue.py,sha256=3uGobGydcIZAfP4N26rDsN4Dp2CpbvGvpJ4zjBZzPdo,3417
4
+ priomesh/policies/__init__.py,sha256=IGMFLCYv-dXdMwmPf1E66VW80UIRu9v_gQitvcSgHpI,215
5
+ priomesh/policies/priority.py,sha256=DRxO5QKEVw8wE23KuwqnEs_krh5jjS6JMwd1AxiPnic,1017
6
+ priomesh/policies/subqueue.py,sha256=XBTvfL3z9tEC80lKUGn0ZA7BHK9Tb2DEgHExealneXg,1692
7
+ priomesh_python-0.1.0.dist-info/licenses/LICENSE,sha256=f_fNKxIw-cXfGU3A-cJdTaQ_doqT1olySCAYT774wvY,1078
8
+ priomesh_python-0.1.0.dist-info/METADATA,sha256=SO_pJvVFWi1sGyVw2vQK32vREhEcLEESovFzO_E__JU,6928
9
+ priomesh_python-0.1.0.dist-info/WHEEL,sha256=a-udL2m4V_6Yn8b-uKzWb12RjRxwwMo6ohYFM24tpaY,91
10
+ priomesh_python-0.1.0.dist-info/top_level.txt,sha256=BnatVdwy2qyvmjYO1H9zAH2D8DFYPExzWyxxUw9g_Pg,9
11
+ priomesh_python-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PrioMesh contributors
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.
@@ -0,0 +1 @@
1
+ priomesh