tina4-python 0.2.159__tar.gz → 0.2.161__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.
- {tina4_python-0.2.159 → tina4_python-0.2.161}/PKG-INFO +5 -1
- {tina4_python-0.2.159 → tina4_python-0.2.161}/README.md +4 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/pyproject.toml +1 -1
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Queue.py +71 -39
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Webserver.py +2 -2
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Websocket.py +2 -2
- {tina4_python-0.2.159 → tina4_python-0.2.161}/.gitignore +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Api.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Auth.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/CRUD.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Constant.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Database.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/DatabaseResult.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/DatabaseTypes.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Debug.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Env.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/FieldTypes.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Localization.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Messages.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/MiddleWare.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Migration.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/ORM.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Request.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Response.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Router.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Session.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/ShellColors.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Swagger.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Template.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/Testing.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/WSDL.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/__init__.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/cli.py +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/messages.pot +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/css/readme.md +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/images/403.png +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/images/404.png +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/images/500.png +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/images/logo.png +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/images/readme.md +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/js/readme.md +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/js/reconnecting-websocket.js +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/js/tina4helper.js +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/templates/readme.md +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tina4-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.161
|
|
4
4
|
Summary: Tina4Python - This is not another framework for Python
|
|
5
5
|
Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
|
|
6
6
|
Requires-Python: <4.0,>=3.12
|
|
@@ -107,6 +107,10 @@ https://tina4.com/
|
|
|
107
107
|
MIT © 2007 – 2025 Tina4 Stack
|
|
108
108
|
https://opensource.org/licenses/MIT
|
|
109
109
|
|
|
110
|
+
## Testing
|
|
111
|
+
|
|
112
|
+
uv run pytest --verbose
|
|
113
|
+
|
|
110
114
|
---
|
|
111
115
|
|
|
112
116
|
**Tina4** – The framework that keeps out of the way of your coding.
|
|
@@ -55,7 +55,7 @@ class Message:
|
|
|
55
55
|
delivery_tag: str
|
|
56
56
|
|
|
57
57
|
class Queue:
|
|
58
|
-
def __init__(self, config=None, topic="default-queue", callback=None):
|
|
58
|
+
def __init__(self, config=None, topic="default-queue", callback=None, batch_size=1):
|
|
59
59
|
if config is None:
|
|
60
60
|
config = Config()
|
|
61
61
|
self.config = config
|
|
@@ -63,6 +63,7 @@ class Queue:
|
|
|
63
63
|
self.callback = callback
|
|
64
64
|
self.producer = None
|
|
65
65
|
self.consumer = None
|
|
66
|
+
self.batch_size = batch_size
|
|
66
67
|
init_method = f"init_{config.queue_type.replace('-', '_')}"
|
|
67
68
|
getattr(self, init_method)()
|
|
68
69
|
|
|
@@ -101,53 +102,81 @@ class Queue:
|
|
|
101
102
|
delivery_callback(self.producer, e, None)
|
|
102
103
|
return e
|
|
103
104
|
|
|
104
|
-
def consume(self, acknowledge: bool = True) -> Generator[Message, None, None]:
|
|
105
|
+
def consume(self, acknowledge: bool = True) -> Generator[Message | List[Message], None, None]:
|
|
105
106
|
"""
|
|
106
|
-
Generator that yields messages
|
|
107
|
+
Generator that continuously yields messages from the queue as they arrive.
|
|
107
108
|
Use like:
|
|
108
109
|
for msg in queue.consume():
|
|
109
110
|
print(msg.data)
|
|
111
|
+
If a callback was provided in __init__, it will also be called for each message.
|
|
110
112
|
"""
|
|
111
113
|
prefix = self.get_prefix()
|
|
114
|
+
is_batch = self.batch_size > 1
|
|
115
|
+
count_messages = 0
|
|
112
116
|
try:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if acknowledge:
|
|
119
|
-
self.consumer.done(msg.message_id)
|
|
120
|
-
msg = self.consumer.get(msg.message_id)
|
|
121
|
-
response.status = int(msg.status)
|
|
122
|
-
yield response
|
|
117
|
+
message_found = True
|
|
118
|
+
batch = []
|
|
119
|
+
while message_found and count_messages < self.batch_size:
|
|
120
|
+
response = None
|
|
121
|
+
message_found = False
|
|
123
122
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
123
|
+
if self.config.queue_type == "litequeue":
|
|
124
|
+
msg = self.consumer.pop()
|
|
125
|
+
if msg:
|
|
126
|
+
message_found = True
|
|
127
|
+
data = json.loads(msg.data)
|
|
128
|
+
response = Message(msg.message_id, data["msg"], data["user_id"], msg.status, msg.in_time, "0")
|
|
129
|
+
if acknowledge:
|
|
130
|
+
self.consumer.done(msg.message_id)
|
|
131
|
+
updated = self.consumer.get(msg.message_id)
|
|
132
|
+
if updated:
|
|
133
|
+
response.status = int(updated.status)
|
|
132
134
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
elif self.config.queue_type == "mongo-queue-service":
|
|
136
|
+
msg = self.consumer.next(channel=prefix + self.topic)
|
|
137
|
+
if msg:
|
|
138
|
+
message_found = True
|
|
139
|
+
data = msg.payload
|
|
140
|
+
response = Message(data["message_id"], data["msg"], data["user_id"], 2 if acknowledge else 1, msg.queued_at, "0")
|
|
141
|
+
if acknowledge:
|
|
142
|
+
msg.complete()
|
|
139
143
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
elif self.config.queue_type == "rabbitmq":
|
|
145
|
+
method, _, body = self.consumer.basic_get(queue=prefix + self.topic, auto_ack=acknowledge)
|
|
146
|
+
if method:
|
|
147
|
+
message_found = True
|
|
148
|
+
data = json.loads(body)
|
|
149
|
+
response = Message(data["message_id"], data["msg"], data["user_id"], 2 if acknowledge else 1, data["in_time"], method.delivery_tag)
|
|
150
|
+
|
|
151
|
+
elif self.config.queue_type == "kafka":
|
|
152
|
+
msg = self.consumer.poll(0.1)
|
|
153
|
+
if msg and not msg.error():
|
|
154
|
+
message_found = True
|
|
155
|
+
data = json.loads(msg.value().decode('utf-8'))
|
|
156
|
+
response = Message(data["message_id"], data["msg"], data["user_id"], 2 if acknowledge else 1, data["in_time"], str(msg.offset()))
|
|
157
|
+
if acknowledge:
|
|
158
|
+
self.consumer.commit()
|
|
159
|
+
|
|
160
|
+
if message_found:
|
|
161
|
+
count_messages += 1
|
|
162
|
+
batch.append(response)
|
|
163
|
+
if self.callback:
|
|
164
|
+
try:
|
|
165
|
+
self.callback(response)
|
|
166
|
+
except Exception as e:
|
|
167
|
+
Debug.error("Failed to run queue callback", str(e))
|
|
168
|
+
else:
|
|
169
|
+
# No message available right now — brief sleep to avoid busy loop
|
|
170
|
+
time.sleep(0.05)
|
|
148
171
|
|
|
172
|
+
if len(batch) > 0:
|
|
173
|
+
if not is_batch:
|
|
174
|
+
yield batch[0]
|
|
175
|
+
else:
|
|
176
|
+
yield batch
|
|
149
177
|
except Exception as e:
|
|
150
178
|
Debug.error(f"Error consuming {self.topic}: {e}")
|
|
179
|
+
raise # Re-raise to stop consumption on fatal error
|
|
151
180
|
|
|
152
181
|
# init methods remain unchanged
|
|
153
182
|
def init_litequeue(self):
|
|
@@ -238,18 +267,21 @@ class Consumer:
|
|
|
238
267
|
self.poll_interval = poll_interval
|
|
239
268
|
|
|
240
269
|
def messages(self) -> Generator[Message, None, None]:
|
|
241
|
-
"""Generator that yields messages from all configured queues forever"""
|
|
242
270
|
Debug.debug("Consuming from queues", [q.topic for q in self.queues])
|
|
243
271
|
while True:
|
|
272
|
+
emptied_count = 0
|
|
244
273
|
for queue in self.queues:
|
|
274
|
+
drained = False
|
|
245
275
|
for message in queue.consume(self.acknowledge):
|
|
246
276
|
yield message
|
|
247
|
-
|
|
277
|
+
drained = True
|
|
278
|
+
if not drained:
|
|
279
|
+
emptied_count += 1
|
|
280
|
+
if emptied_count == len(self.queues):
|
|
281
|
+
time.sleep(self.poll_interval)
|
|
248
282
|
|
|
249
283
|
def run_forever(self):
|
|
250
284
|
"""Simple blocking runner — easy to use"""
|
|
251
285
|
for message in self.messages():
|
|
252
286
|
Debug.info(f"Received message: {message.message_id} -> {message.data}")
|
|
253
|
-
# Do something with message here
|
|
254
|
-
# Or just pass to a callback if needed
|
|
255
287
|
|
|
@@ -534,9 +534,9 @@ class Webserver:
|
|
|
534
534
|
# Route or WebSocket
|
|
535
535
|
# ------------------------------------------------------------------
|
|
536
536
|
if "sec-websocket-key" in lowercase_headers:
|
|
537
|
-
await self.get_response(self.method, reader, writer)
|
|
537
|
+
await self.get_response(self.method, scope={'type': 'websocket'}, reader=reader, writer=writer)
|
|
538
538
|
else:
|
|
539
|
-
response_bytes = await self.get_response(self.method, reader, writer)
|
|
539
|
+
response_bytes = await self.get_response(self.method, scope={'type': 'http'}, reader=reader, writer=writer)
|
|
540
540
|
if response_bytes:
|
|
541
541
|
writer.write(response_bytes)
|
|
542
542
|
await writer.drain()
|
|
@@ -32,12 +32,12 @@ class Websocket:
|
|
|
32
32
|
else:
|
|
33
33
|
if os.name == "nt":
|
|
34
34
|
connection = await self.server.AioServer.accept(
|
|
35
|
-
sock=TransportSocket(self.request.
|
|
35
|
+
sock=TransportSocket(self.request.asgi_writer.transport._sock), # not working properly
|
|
36
36
|
headers=self.request.headers
|
|
37
37
|
)
|
|
38
38
|
else:
|
|
39
39
|
connection = await self.server.AioServer.accept(
|
|
40
|
-
sock=self.request.
|
|
40
|
+
sock=self.request.asgi_writer.get_extra_info('socket').dup(),
|
|
41
41
|
headers=self.request.headers
|
|
42
42
|
)
|
|
43
43
|
return connection
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/js/reconnecting-websocket.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-0.2.159 → tina4_python-0.2.161}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|