fast-priority 0.0.14__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Korbinian Schweiger
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,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: fast-priority
3
+ Version: 0.0.14
4
+ Summary: Simple and streigt forward priority queing gateway using fastapi
5
+ Keywords: queue,fastapi,priority,redis
6
+ Author-email: Korbinian Schweiger <korbinian.schweiger@gmail.com>
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: Topic :: Internet
16
+ License-File: LICENSE
17
+ Requires-Dist: fastapi[standard]>=0.115.8
18
+ Requires-Dist: httpx
19
+ Requires-Dist: python-dotenv>=1.0.1
20
+ Requires-Dist: rq>=2.1.0
21
+ Requires-Dist: click
22
+ Requires-Dist: uvicorn>=0.34.0
23
+
24
+ # 🚀 Fast Priority Queue 🔥
25
+
26
+ A minimal priority queuing gateway built with FastAPI using Redis.
27
+
28
+ It is designed to sit between your clients and a backend REST API, managing two priority levels—high and low—using the [`rq` package](https://python-rq.org/). Requests are enqueued based on the request path and processed synchronously by dedicated worker processes, so the overall throughput is limited by the number of workers.
29
+
30
+ ## Overview
31
+
32
+ - Intercepts incoming client requests and forwards them to a target REST API.
33
+ - Enqueues requests into either a high-priority or low-priority queue based on configurable path matching.
34
+ - Processes queued requests via worker processes running in a separate environment.
35
+ - Offers Dockerized deployment for both the gateway and worker processes.
36
+
37
+
38
+ ## Configuration ⚙️
39
+
40
+ Both the gateway and workers are fully configurable via the following environment variables.
41
+
42
+ ### Gateway
43
+
44
+ | ENV | Description | Required | Default |
45
+ |-------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------|
46
+ | FAST_PRIORITY_TARGET_BASE_URL | Base url of the target REST api which should run behind the gateway | x | |
47
+ | FAST_PRIORITY_HIGH_PRIO_PATHS | Switch between **listed** and **unlisted** modes. If **listed** (**unlisted**) the paths defined in the _PATH_ env variables are put on the **high** (**low**) queue | | unlisted |
48
+ | FAST_PRIORITY_PRIO_PATHS | Comma separated list of paths on the target API that should have low priority. Low priority for exact matches | | None |
49
+ | FAST_PRIORITY_PRIO_BASE_PATHS | Comma separated list of paths on the target API that should have low priority. Low priority if a request paths starts with the value. | | None |
50
+ | FAST_PRIORITY_PASS_THROUGH | Comma separated list of paths on the target API that should skip the queue. Request will be directly be passed on. | | health/ |
51
+ | FAST_PRIORITY_POLL_INTERVAL | How often should each request check if the job is finished | | 1.0 |
52
+ | FAST_PRIORITY_TTL | Time-to-live (in seconds) for jobs on the queues. | | 300 |
53
+ | FAST_PRIORITY_REDIS_HOST | Redis host | | localhost |
54
+ | FAST_PRIORITY_REDIS_PORT | Redis port | | 6379 |
55
+ | FAST_PRIORITY_REDIS_USER | Redis username | | None |
56
+ | FAST_PRIORITY_REDIS_PASSWORD | Redis password | | None |
57
+
58
+ ### Queue worker (docker)
59
+
60
+ | ENV | Description | Required | Default |
61
+ |-------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|----------|-----------|
62
+ | FAST_PRIORITY_WORKERS | Set (e.g., non-empty or a number) to run as a worker instead of the gateway. Controls the number of worker processes to launch. | x | |
63
+ | FAST_PRIORITY_REDIS_HOST | Redis host | | localhost |
64
+ | FAST_PRIORITY_REDIS_PORT | Redis port | | 6379 |
65
+ | FAST_PRIORITY_REDIS_USER | Redis username | | None |
66
+ | FAST_PRIORITY_REDIS_PASSWORD | Redis password | | None |
67
+
68
+ ## Usage
69
+
70
+ Fast Priority Queue is designed to run via Docker (or Docker Compose) or using the script installed with the package `run-fast-priority`. The configuration works the same for both approaches using the environment variables described in [Configuration section](#configuration). The script is mainly provide to give the user the flexibility to include the gateway in their own docker containers by installing the package via pypi.
71
+
72
+ For development (or more specialized application) the gateway can also be run using the underlying command like this:
73
+
74
+ ```bash
75
+ fastapi run fast_priority/app.py --host 0.0.0.0 --port 8001
76
+ rq worker high low
77
+ ```
78
+
79
+ ### Docker 🐳
80
+
81
+ You can build the Docker container using the provided Dockerfile. The container adapts its run mode based on the presence of the environment variable `FAST_PRIORITY_WORKER`:
82
+
83
+ - If `FAST_PRIORITY_WORKER` is set, the container starts the worker(s).
84
+ - If not, the gateway is started.
85
+
86
+ The same strategy can be used with you own container and installing the package in the Dockerfile from pypi.
87
+
88
+
89
+ #### Examples
90
+
91
+ ```bash
92
+ # API
93
+ docker run -p 8010:8000 -e FAST_PRIORITY_TARGET_BASE_URL=http://localhost:8011 -e FAST_PRIORITY_REDIS_HOST=localhost fast_priority:latest
94
+
95
+ # Workers
96
+ docker run -p 8010:8000 -e FAST_PRIORITY_WORKER=1 -e FAST_PRIORITY_REDIS_HOST=localhost fast_priority:latest
97
+ ```
98
+
99
+
100
+ #### Compose 🐳🐳🐳
101
+
102
+ The simplest way to run Fast Priority Queue and its dependencies is via Docker Compose. Below is an example configuration:
103
+
104
+ ```yml
105
+ services:
106
+ behind_gateway_api:
107
+ ...
108
+ priorityity-gateway:
109
+ image: fast_priority:latest
110
+ environment:
111
+ - FAST_PRIORITY_TARGET_BASE_URL=http://behind_gateway_api:8000
112
+ - FAST_PRIORITY_REDIS_HOST=queue
113
+ - FAST_PRIORITY_PRIO_PATHS=endpoint_1,endpoint_2
114
+ ports:
115
+ - 8066:8000
116
+
117
+ priority-gateway-worker:
118
+ image: fast_priority:latest
119
+ environment:
120
+ - FAST_PRIORITY_WORKERS=1
121
+ - FAST_PRIORITY_REDIS_HOST=queue
122
+ networks:
123
+ - default
124
+ queue:
125
+ image: redis
126
+ networks:
127
+ - default
128
+
129
+ networks:
130
+ default:
131
+ driver: bridge
132
+
133
+ ```
134
+
135
+
136
+ ## Contributing
137
+ Contributions to Fast Priority Queue are welcome! Feel free to open issues or submit pull requests with improvements, bug fixes, or feature suggestions.
138
+
139
+
@@ -0,0 +1,115 @@
1
+ # 🚀 Fast Priority Queue 🔥
2
+
3
+ A minimal priority queuing gateway built with FastAPI using Redis.
4
+
5
+ It is designed to sit between your clients and a backend REST API, managing two priority levels—high and low—using the [`rq` package](https://python-rq.org/). Requests are enqueued based on the request path and processed synchronously by dedicated worker processes, so the overall throughput is limited by the number of workers.
6
+
7
+ ## Overview
8
+
9
+ - Intercepts incoming client requests and forwards them to a target REST API.
10
+ - Enqueues requests into either a high-priority or low-priority queue based on configurable path matching.
11
+ - Processes queued requests via worker processes running in a separate environment.
12
+ - Offers Dockerized deployment for both the gateway and worker processes.
13
+
14
+
15
+ ## Configuration ⚙️
16
+
17
+ Both the gateway and workers are fully configurable via the following environment variables.
18
+
19
+ ### Gateway
20
+
21
+ | ENV | Description | Required | Default |
22
+ |-------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------|
23
+ | FAST_PRIORITY_TARGET_BASE_URL | Base url of the target REST api which should run behind the gateway | x | |
24
+ | FAST_PRIORITY_HIGH_PRIO_PATHS | Switch between **listed** and **unlisted** modes. If **listed** (**unlisted**) the paths defined in the _PATH_ env variables are put on the **high** (**low**) queue | | unlisted |
25
+ | FAST_PRIORITY_PRIO_PATHS | Comma separated list of paths on the target API that should have low priority. Low priority for exact matches | | None |
26
+ | FAST_PRIORITY_PRIO_BASE_PATHS | Comma separated list of paths on the target API that should have low priority. Low priority if a request paths starts with the value. | | None |
27
+ | FAST_PRIORITY_PASS_THROUGH | Comma separated list of paths on the target API that should skip the queue. Request will be directly be passed on. | | health/ |
28
+ | FAST_PRIORITY_POLL_INTERVAL | How often should each request check if the job is finished | | 1.0 |
29
+ | FAST_PRIORITY_TTL | Time-to-live (in seconds) for jobs on the queues. | | 300 |
30
+ | FAST_PRIORITY_REDIS_HOST | Redis host | | localhost |
31
+ | FAST_PRIORITY_REDIS_PORT | Redis port | | 6379 |
32
+ | FAST_PRIORITY_REDIS_USER | Redis username | | None |
33
+ | FAST_PRIORITY_REDIS_PASSWORD | Redis password | | None |
34
+
35
+ ### Queue worker (docker)
36
+
37
+ | ENV | Description | Required | Default |
38
+ |-------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|----------|-----------|
39
+ | FAST_PRIORITY_WORKERS | Set (e.g., non-empty or a number) to run as a worker instead of the gateway. Controls the number of worker processes to launch. | x | |
40
+ | FAST_PRIORITY_REDIS_HOST | Redis host | | localhost |
41
+ | FAST_PRIORITY_REDIS_PORT | Redis port | | 6379 |
42
+ | FAST_PRIORITY_REDIS_USER | Redis username | | None |
43
+ | FAST_PRIORITY_REDIS_PASSWORD | Redis password | | None |
44
+
45
+ ## Usage
46
+
47
+ Fast Priority Queue is designed to run via Docker (or Docker Compose) or using the script installed with the package `run-fast-priority`. The configuration works the same for both approaches using the environment variables described in [Configuration section](#configuration). The script is mainly provide to give the user the flexibility to include the gateway in their own docker containers by installing the package via pypi.
48
+
49
+ For development (or more specialized application) the gateway can also be run using the underlying command like this:
50
+
51
+ ```bash
52
+ fastapi run fast_priority/app.py --host 0.0.0.0 --port 8001
53
+ rq worker high low
54
+ ```
55
+
56
+ ### Docker 🐳
57
+
58
+ You can build the Docker container using the provided Dockerfile. The container adapts its run mode based on the presence of the environment variable `FAST_PRIORITY_WORKER`:
59
+
60
+ - If `FAST_PRIORITY_WORKER` is set, the container starts the worker(s).
61
+ - If not, the gateway is started.
62
+
63
+ The same strategy can be used with you own container and installing the package in the Dockerfile from pypi.
64
+
65
+
66
+ #### Examples
67
+
68
+ ```bash
69
+ # API
70
+ docker run -p 8010:8000 -e FAST_PRIORITY_TARGET_BASE_URL=http://localhost:8011 -e FAST_PRIORITY_REDIS_HOST=localhost fast_priority:latest
71
+
72
+ # Workers
73
+ docker run -p 8010:8000 -e FAST_PRIORITY_WORKER=1 -e FAST_PRIORITY_REDIS_HOST=localhost fast_priority:latest
74
+ ```
75
+
76
+
77
+ #### Compose 🐳🐳🐳
78
+
79
+ The simplest way to run Fast Priority Queue and its dependencies is via Docker Compose. Below is an example configuration:
80
+
81
+ ```yml
82
+ services:
83
+ behind_gateway_api:
84
+ ...
85
+ priorityity-gateway:
86
+ image: fast_priority:latest
87
+ environment:
88
+ - FAST_PRIORITY_TARGET_BASE_URL=http://behind_gateway_api:8000
89
+ - FAST_PRIORITY_REDIS_HOST=queue
90
+ - FAST_PRIORITY_PRIO_PATHS=endpoint_1,endpoint_2
91
+ ports:
92
+ - 8066:8000
93
+
94
+ priority-gateway-worker:
95
+ image: fast_priority:latest
96
+ environment:
97
+ - FAST_PRIORITY_WORKERS=1
98
+ - FAST_PRIORITY_REDIS_HOST=queue
99
+ networks:
100
+ - default
101
+ queue:
102
+ image: redis
103
+ networks:
104
+ - default
105
+
106
+ networks:
107
+ default:
108
+ driver: bridge
109
+
110
+ ```
111
+
112
+
113
+ ## Contributing
114
+ Contributions to Fast Priority Queue are welcome! Feel free to open issues or submit pull requests with improvements, bug fixes, or feature suggestions.
115
+
File without changes
@@ -0,0 +1,219 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ from contextlib import asynccontextmanager
5
+ from enum import StrEnum
6
+ from typing import Any
7
+
8
+ import httpx
9
+ from dotenv import load_dotenv
10
+ from fastapi import FastAPI, HTTPException, Request, Response
11
+ from redis import Redis
12
+ from redis.exceptions import ConnectionError as RedisConnectionError
13
+ from rq import Queue
14
+ from rq.job import JobStatus
15
+
16
+ from fast_priority.utils import generate_enpoint_list, run_request
17
+
18
+ load_dotenv()
19
+
20
+
21
+ class HighPrioPaths(StrEnum):
22
+ LISTED = "listed"
23
+ UNLISTED = "unlisted"
24
+
25
+
26
+ logger = logging.getLogger("uvicorn.error")
27
+ # logger.setLevel(logging.DEBUG)
28
+
29
+ redis_conn = Redis(
30
+ host=os.environ.get("FAST_PRIORITY_REDIS_HOST", "localhost"),
31
+ port=int(os.environ.get("FAST_PRIORITY_REDIS_PORT", 6379)),
32
+ username=os.environ.get("FAST_PRIORITY_REDIS_USER", None),
33
+ password=os.environ.get("FAST_PRIORITY_REDIS_PASSWORD", None),
34
+ )
35
+ low_queue = Queue("low", connection=redis_conn)
36
+ high_queue = Queue("high", connection=redis_conn)
37
+
38
+ target_base_url = os.environ["FAST_PRIORITY_TARGET_BASE_URL"]
39
+ priority_mode = HighPrioPaths(
40
+ os.environ.get("FAST_PRIORITY_HIGH_PRIO_PATHS", "unlisted")
41
+ )
42
+ job_poll_interval = float(os.environ.get("FAST_PRIORITY_POLL_INTERVAL", 1.0))
43
+ job_ttl = int(os.environ.get("FAST_PRIORITY_TTL", 60 * 5))
44
+
45
+ doc_path = os.environ.get("FAST_PRIORITY_DOC_PATH", "/gateway_docs")
46
+ redoc_path = os.environ.get("FAST_PRIORITY_DOC_PATH", "/gateway_redoc")
47
+ health_path = os.environ.get("FAST_PRIORITY_HEALTH_PATH", "/gateway_health")
48
+
49
+ prio_paths = None
50
+ prio_base_paths = None
51
+ pass_through_paths = None
52
+
53
+
54
+ @asynccontextmanager
55
+ async def lifespan(app: FastAPI):
56
+ global prio_paths
57
+ global prio_base_paths
58
+ global pass_through_paths
59
+
60
+ pass_through_paths = ["health"]
61
+ pass_through_env = os.environ.get("FAST_PRIORITY_PASS_THROUGH", None)
62
+ if pass_through_env:
63
+ pass_through_paths = generate_enpoint_list(pass_through_env)
64
+
65
+ prio_paths = generate_enpoint_list(os.environ.get("FAST_PRIORITY_PRIO_PATHS", None))
66
+ prio_base_paths = generate_enpoint_list(
67
+ os.environ.get("FAST_PRIORITY_PRIO_BASE_PATHS", None)
68
+ )
69
+
70
+ if not prio_base_paths and not prio_paths:
71
+ logger.warning("No low priority endpoints defined.")
72
+ else:
73
+ if priority_mode == HighPrioPaths.LISTED:
74
+ logger.info("The following path will be passed to the **HIGH** queue")
75
+ else:
76
+ logger.info("The following path will be passed to the **LOW** queue")
77
+ if prio_paths:
78
+ logger.info("Low priority path")
79
+ for path in prio_paths:
80
+ logger.info(f" {path}")
81
+ if prio_base_paths:
82
+ logger.info("Low priority base path")
83
+ for path in prio_base_paths:
84
+ logger.info(f" {path}")
85
+
86
+ if pass_through_paths:
87
+ logger.info("Pass through paths")
88
+ for path in pass_through_paths:
89
+ logger.info(f" {path}")
90
+ yield
91
+
92
+
93
+ app = FastAPI(
94
+ lifespan=lifespan,
95
+ docs_url=doc_path,
96
+ redoc_url=redoc_path,
97
+ )
98
+
99
+
100
+ @app.get(health_path)
101
+ async def heath_check() -> Any:
102
+ try:
103
+ async with httpx.AsyncClient() as client:
104
+ await client.get(f"{target_base_url}")
105
+ except httpx.ConnectError:
106
+ target_reachable = False
107
+ else:
108
+ target_reachable = True
109
+
110
+ try:
111
+ redis_conn.info()
112
+ except RedisConnectionError:
113
+ redis_reachable = False
114
+ else:
115
+ redis_reachable = True
116
+
117
+ n_jobs_queued_low = None
118
+ n_jobs_queued_high = None
119
+ if redis_reachable:
120
+ n_jobs_queued_high = len(high_queue.jobs)
121
+ n_jobs_queued_low = len(low_queue.jobs)
122
+
123
+ return {
124
+ "target_reachable": target_reachable,
125
+ "redis_reachable": redis_reachable,
126
+ "queue": {"high": n_jobs_queued_high, "low": n_jobs_queued_low},
127
+ }
128
+
129
+
130
+ @app.get("/docs", include_in_schema=False)
131
+ @app.get("/redoc", include_in_schema=False)
132
+ async def get_target_docs(request: Request) -> Any:
133
+ current_path = request.url.path
134
+ print(current_path)
135
+ async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
136
+ response = await client.get( # type: ignore
137
+ url=f"{target_base_url}{current_path}",
138
+ )
139
+ new_content = response.content.decode("utf-8").replace(
140
+ "/openapi.json", "/target_openapi.json"
141
+ )
142
+ new_bytes = new_content.encode("utf-8")
143
+ new_headers = dict(response.headers)
144
+ new_headers["content-length"] = str(len(new_bytes))
145
+ return Response(
146
+ content=new_bytes,
147
+ status_code=response.status_code,
148
+ headers=new_headers,
149
+ )
150
+
151
+
152
+ @app.get("/target_openapi.json")
153
+ async def get_target_openapi_spec() -> Any:
154
+ async with httpx.AsyncClient() as client:
155
+ response = await client.get(f"{target_base_url}/openapi.json")
156
+ return Response(
157
+ content=response.content,
158
+ status_code=response.status_code,
159
+ headers=dict(response.headers),
160
+ )
161
+
162
+
163
+ async def forward_request(request: Request, path: str) -> Response:
164
+ assert prio_paths is not None
165
+ assert prio_base_paths is not None
166
+ # Prepare request components
167
+ url = f"/{path}?{request.url.query}"
168
+ headers = dict(request.headers)
169
+ headers.pop("host", None) # Remove original host header
170
+
171
+ request_body = await request.body()
172
+
173
+ if pass_through_paths and path in pass_through_paths:
174
+ logger.debug("%s in pass through. Skipping queue", path)
175
+ async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
176
+ return await client.request( # type: ignore
177
+ method=request.method,
178
+ url=f"{target_base_url}{url}",
179
+ headers=headers,
180
+ content=request_body,
181
+ )
182
+
183
+ use_queue = high_queue if priority_mode == HighPrioPaths.UNLISTED else low_queue
184
+ if path in prio_paths or any(path.startswith(b) for b in prio_base_paths):
185
+ use_queue = low_queue if priority_mode == HighPrioPaths.UNLISTED else high_queue
186
+
187
+ job = use_queue.enqueue(
188
+ run_request,
189
+ ttl=job_ttl,
190
+ failure_ttl=60 * 60,
191
+ kwargs=dict(
192
+ method=request.method,
193
+ url=f"{target_base_url}{url}",
194
+ headers=headers,
195
+ content=request_body,
196
+ ),
197
+ )
198
+
199
+ while job.result is None:
200
+ status = job.get_status(refresh=True)
201
+ if status in [JobStatus.FAILED, JobStatus.STOPPED, JobStatus.CANCELED]:
202
+ raise HTTPException(status_code=500, detail="Could not run request")
203
+ logger.debug(
204
+ "Waiting. Current status: %s of %s", job.get_status(refresh=True), job.id
205
+ )
206
+ await asyncio.sleep(job_poll_interval)
207
+
208
+ return job.result
209
+
210
+
211
+ @app.api_route("/{path:path}", methods=["GET", "POST"], include_in_schema=False)
212
+ async def proxy_request(request: Request, path: str) -> Any:
213
+ response = await forward_request(request, path)
214
+ print(response)
215
+ return Response(
216
+ content=response.content,
217
+ status_code=response.status_code,
218
+ headers=response.headers,
219
+ )
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import sys
4
+ from urllib.parse import quote
5
+
6
+ import uvicorn
7
+
8
+
9
+ def urlencode(s: str) -> str:
10
+ safe = "-_.~"
11
+ return quote(s, safe=safe)
12
+
13
+
14
+ def main():
15
+ role = "worker" if os.getenv("FAST_PRIORITY_WORKERS") else "api"
16
+
17
+ print("======================================")
18
+ print("==> Fast Priority Queue RUN SCRIPT <==")
19
+ print("======================================")
20
+ print(f"--> MODE: {role}")
21
+ print("======================================")
22
+
23
+ if role == "api":
24
+ from fast_priority.app import app
25
+
26
+ uvicorn.run(
27
+ app,
28
+ host=os.getenv("FAST_PRIORITY_HOST", "0.0.0.0"),
29
+ port=int(os.getenv("FAST_PRIORITY_PORT", 8000)),
30
+ # log_level="debug",
31
+ reload=False, # Disabled for production
32
+ )
33
+ elif role == "worker":
34
+ from rq.cli.cli import main as rq_cli
35
+
36
+ print("Running queue worker")
37
+ workers = os.getenv("FAST_PRIORITY_WORKERS", "1")
38
+ print(f"Using {workers} workers")
39
+
40
+ redis_host = os.getenv("FAST_PRIORITY_REDIS_HOST", "localhost")
41
+ redis_user = os.getenv("FAST_PRIORITY_REDIS_USER", "")
42
+ redis_password = os.getenv("FAST_PRIORITY_REDIS_PASSWORD", "")
43
+ redis_port = os.getenv("FAST_PRIORITY_REDIS_PORT", "")
44
+
45
+ auth = ""
46
+ if redis_user and redis_password:
47
+ auth = f"{urlencode(redis_user)}:{urlencode(redis_password)}@"
48
+ elif redis_password:
49
+ auth = f":{urlencode(redis_password)}@"
50
+
51
+ uri = f"redis://{auth}{redis_host}"
52
+ if redis_port:
53
+ uri += f":{redis_port}"
54
+
55
+ print(f" Using redis uri: {uri}")
56
+
57
+ try:
58
+ worker_count = int(workers)
59
+ if worker_count < 1:
60
+ raise ValueError
61
+ except ValueError:
62
+ print("ERR:: Invalid value for worker")
63
+ sys.exit(1)
64
+
65
+ if worker_count == 1:
66
+ print("Running with one worker")
67
+ rq_cli(["worker", "high", "low", "--url", uri], standalone_mode=True)
68
+
69
+ else:
70
+ print("Running multiple workers")
71
+ rq_cli(
72
+ [
73
+ "worker-pool",
74
+ "high",
75
+ "low",
76
+ "-n",
77
+ str(worker_count),
78
+ "--url",
79
+ uri,
80
+ ],
81
+ standalone_mode=True,
82
+ )
83
+
84
+ else:
85
+ print("ERR:: Invalid run mode")
86
+ sys.exit(1)
87
+
88
+
89
+ if __name__ == "__main__":
90
+ import dotenv
91
+
92
+ dotenv.load_dotenv()
93
+ main()
@@ -0,0 +1,17 @@
1
+ import httpx
2
+
3
+
4
+ def generate_enpoint_list(raw_endpoint_str: str | None) -> list[str]:
5
+ ret = []
6
+ if raw_endpoint_str:
7
+ ret = [
8
+ p.strip() for p in raw_endpoint_str.strip().split(",") if p not in {"", " "}
9
+ ]
10
+
11
+ return ret
12
+
13
+
14
+ def run_request(method: str, url: str, headers: dict, content: dict):
15
+ # print(f"Got: {method=}, {url=}")
16
+ with httpx.Client(timeout=None, follow_redirects=True) as client:
17
+ return client.request(method=method, url=url, headers=headers, content=content)
@@ -0,0 +1,93 @@
1
+ [build-system]
2
+ requires = ["flit_core >=3.2,<4"]
3
+ build-backend = "flit_core.buildapi"
4
+
5
+ [project]
6
+ name = "fast-priority"
7
+ authors = [
8
+ { name = "Korbinian Schweiger", email = "korbinian.schweiger@gmail.com" },
9
+ ]
10
+ license = { file = "LICENSE" }
11
+ version = "0.0.14"
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Programming Language :: Python",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Framework :: FastAPI",
18
+ "Intended Audience :: System Administrators",
19
+ "Topic :: Internet",
20
+ ]
21
+ keywords = ["queue", "fastapi", "priority", "redis"]
22
+ description = "Simple and streigt forward priority queing gateway using fastapi"
23
+ readme = "README.md"
24
+ requires-python = ">=3.12"
25
+ dependencies = [
26
+ "fastapi[standard]>=0.115.8",
27
+ "httpx",
28
+ "python-dotenv>=1.0.1",
29
+ "rq>=2.1.0",
30
+ "click",
31
+ "uvicorn>=0.34.0"
32
+ ]
33
+
34
+ [project.scripts]
35
+ run-fast-priority = "fast_priority.cli:main"
36
+
37
+ [tool.uv]
38
+ default-groups = ["dev", "test"]
39
+
40
+ [dependency-groups]
41
+ test = [
42
+ "pytest",
43
+ "pytest-mock",
44
+ "pytest-cov"
45
+ ]
46
+ dev = [
47
+ "uv",
48
+ "ruff",
49
+ "pre-commit",
50
+ "bpython",
51
+ "debugpy",
52
+ "flit>=3.11.0",
53
+ ]
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests/unit/"]
57
+
58
+ [tool.ruff]
59
+ line-length = 88
60
+ target-version = "py312"
61
+
62
+ [tool.ruff.lint]
63
+ select = [
64
+ "E",
65
+ "F",
66
+ "I",
67
+ "PD",
68
+ "PT",
69
+ "NPY",
70
+ "N",
71
+ "W",
72
+ "RET",
73
+ "ANN",
74
+ "RUF",
75
+ "SIM",
76
+ "PERF",
77
+ ]
78
+ unfixable = ["F841", "RUF015"]
79
+ ignore = [
80
+ "ANN202",
81
+ "ANN205",
82
+ "ANN001",
83
+ "ANN002",
84
+ "ANN003",
85
+ "ANN401",
86
+ "RET502",
87
+ "RET503",
88
+ "RET504",
89
+ "RET505",
90
+ "RUF005",
91
+ "RUF009",
92
+ "E731",
93
+ ]