jararaca 0.3.9__py3-none-any.whl → 0.3.11a0__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.

Potentially problematic release.


This version of jararaca might be problematic. Click here for more details.

@@ -15,6 +15,8 @@ from jararaca.messagebus.interceptors.publisher_interceptor import (
15
15
  from jararaca.messagebus.publisher import IMessage, MessagePublisher
16
16
  from jararaca.scheduler.types import DelayedMessageData
17
17
 
18
+ logger = logging.getLogger(__name__)
19
+
18
20
 
19
21
  class AIOPikaMessagePublisher(MessagePublisher):
20
22
 
@@ -28,8 +30,13 @@ class AIOPikaMessagePublisher(MessagePublisher):
28
30
  self.channel = channel
29
31
  self.exchange_name = exchange_name
30
32
  self.message_broker_backend = message_broker_backend
33
+ self.staged_delayed_messages: list[DelayedMessageData] = []
34
+ self.staged_messages: list[IMessage] = []
31
35
 
32
36
  async def publish(self, message: IMessage, topic: str) -> None:
37
+ self.staged_messages.append(message)
38
+
39
+ async def _publish(self, message: IMessage, topic: str) -> None:
33
40
  exchange = await self.channel.get_exchange(self.exchange_name, ensure=False)
34
41
  if not exchange:
35
42
  logging.warning(f"Exchange {self.exchange_name} not found")
@@ -45,7 +52,7 @@ class AIOPikaMessagePublisher(MessagePublisher):
45
52
  raise NotImplementedError(
46
53
  "Delay is not implemented for AIOPikaMessagePublisher"
47
54
  )
48
- await self.message_broker_backend.enqueue_delayed_message(
55
+ self.staged_delayed_messages.append(
49
56
  DelayedMessageData(
50
57
  message_topic=message.MESSAGE_TOPIC,
51
58
  payload=message.model_dump_json().encode(),
@@ -60,7 +67,7 @@ class AIOPikaMessagePublisher(MessagePublisher):
60
67
  raise NotImplementedError(
61
68
  "Schedule is not implemented for AIOPikaMessagePublisher"
62
69
  )
63
- await self.message_broker_backend.enqueue_delayed_message(
70
+ self.staged_delayed_messages.append(
64
71
  DelayedMessageData(
65
72
  message_topic=message.MESSAGE_TOPIC,
66
73
  payload=message.model_dump_json().encode(),
@@ -68,6 +75,28 @@ class AIOPikaMessagePublisher(MessagePublisher):
68
75
  )
69
76
  )
70
77
 
78
+ async def flush(self) -> None:
79
+ print("Flushing messages to AIOPika channel")
80
+ for message in self.staged_messages:
81
+ logger.debug(
82
+ f"Publishing message {message.MESSAGE_TOPIC} with payload: {message.model_dump_json()}"
83
+ )
84
+ await self._publish(message, message.MESSAGE_TOPIC)
85
+
86
+ if len(self.staged_delayed_messages) > 0:
87
+ if not self.message_broker_backend:
88
+ raise NotImplementedError(
89
+ "MessageBrokerBackend is required to publish delayed messages"
90
+ )
91
+
92
+ for delayed_message in self.staged_delayed_messages:
93
+ logger.debug(
94
+ f"Scheduling delayed message {delayed_message.message_topic} with payload: {delayed_message.payload.decode()}"
95
+ )
96
+ await self.message_broker_backend.enqueue_delayed_message(
97
+ delayed_message
98
+ )
99
+
71
100
 
72
101
  class GenericPoolConfig(BaseModel):
73
102
  max_size: int
@@ -32,3 +32,5 @@ class MessageBusPublisherInterceptor(AppInterceptor):
32
32
  async with self.connection_factory.provide_connection() as connection:
33
33
  with provide_message_publisher(self.connection_name, connection):
34
34
  yield
35
+
36
+ await connection.flush()
@@ -1,7 +1,8 @@
1
+ from abc import ABC, abstractmethod
1
2
  from contextlib import contextmanager, suppress
2
3
  from contextvars import ContextVar
3
4
  from datetime import datetime, tzinfo
4
- from typing import Any, ClassVar, Generator, Literal, Protocol
5
+ from typing import Any, ClassVar, Generator, Literal
5
6
 
6
7
  from pydantic import BaseModel
7
8
 
@@ -19,24 +20,31 @@ class IMessage(BaseModel):
19
20
  MESSAGE_TYPE: ClassVar[Literal["task", "event"]] = "task"
20
21
 
21
22
 
22
- class MessagePublisher(Protocol):
23
+ class MessagePublisher(ABC):
24
+ @abstractmethod
23
25
  async def publish(self, message: IMessage, topic: str) -> None:
24
- raise NotImplementedError()
26
+ pass
25
27
 
28
+ @abstractmethod
26
29
  async def delay(self, message: IMessage, seconds: int) -> None:
27
30
  """
28
31
  Delay the message for a given number of seconds.
29
32
  """
30
33
 
31
- raise NotImplementedError()
32
-
34
+ @abstractmethod
33
35
  async def schedule(
34
36
  self, message: IMessage, when: datetime, timezone: tzinfo
35
37
  ) -> None:
36
38
  """
37
39
  Schedule the message for a given datetime.
38
40
  """
39
- raise NotImplementedError()
41
+
42
+ @abstractmethod
43
+ async def flush(self) -> None:
44
+ """
45
+ Publish all messages that have been delayed or scheduled.
46
+ This is typically called at the end of a request or task processing.
47
+ """
40
48
 
41
49
 
42
50
  message_publishers_ctx = ContextVar[dict[str, MessagePublisher]](
@@ -58,6 +58,7 @@ class AIOSqlAlchemySessionInterceptor(AppInterceptor):
58
58
  with provide_session(self.config.connection_name, session):
59
59
  try:
60
60
  yield
61
+ print("Committing session")
61
62
  await session.commit()
62
63
  except Exception as e:
63
64
  await session.rollback()
@@ -213,7 +213,7 @@ class CRUDOperations(Generic[IDENTIFIABLE_T]):
213
213
  # region PaginatedFilter
214
214
  class PaginatedFilter(BaseModel):
215
215
  page: Annotated[int, Field(gt=-1)] = 0
216
- page_size: int = 10
216
+ page_size: Annotated[int, Field(gt=0)] = 10
217
217
  sort_models: list[SortModel] = []
218
218
  filter_models: list[FilterModel] = []
219
219
 
@@ -307,51 +307,63 @@ class QueryOperations(Generic[QUERY_FILTER_T, QUERY_ENTITY_T]):
307
307
  Callable[[Select[Tuple[QUERY_ENTITY_T]]], Select[Tuple[QUERY_ENTITY_T]]]
308
308
  ] = [],
309
309
  ) -> "Paginated[QUERY_ENTITY_T]":
310
+ """
311
+ Executes a query with the provided filter and interceptors.
312
+ Args:
313
+ filter: The filter to apply to the query.
314
+ interceptors: A list of functions that can modify the query before execution.
315
+ Returns:
316
+ Paginated[QUERY_ENTITY_T]: A paginated result containing the items and metadata.
317
+ """
318
+
319
+ tier_one_filtered_query = self.generate_filtered_query(
320
+ filter, select(self.entity_type)
321
+ )
310
322
 
311
- query = reduce(
323
+ tier_two_filtered_query = reduce(
312
324
  lambda query, interceptor: interceptor(query),
313
325
  interceptors,
314
- select(self.entity_type),
326
+ tier_one_filtered_query,
315
327
  )
316
328
 
317
329
  if self.sort_rule_applier:
318
- query = self.sort_rule_applier.create_query_for_sorting_list(
319
- query, filter.sort_models
330
+ tier_two_filtered_query = (
331
+ self.sort_rule_applier.create_query_for_sorting_list(
332
+ tier_two_filtered_query, filter.sort_models
333
+ )
320
334
  )
321
335
 
322
336
  if self.filter_rule_applier:
323
- query = self.filter_rule_applier.create_query_for_filter_list(
324
- query, filter.filter_models
337
+ tier_two_filtered_query = (
338
+ self.filter_rule_applier.create_query_for_filter_list(
339
+ tier_two_filtered_query, filter.filter_models
340
+ )
325
341
  )
326
342
 
327
343
  unpaginated_total = (
328
344
  await self.session.execute(
329
- select(func.count()).select_from(query.subquery())
345
+ select(func.count()).select_from(tier_two_filtered_query.subquery())
330
346
  )
331
347
  ).scalar_one()
332
348
 
333
- filtered_query = self.generate_filtered_query(filter, query)
334
-
335
- paginated_query = filtered_query.limit(filter.page_size).offset(
349
+ paginated_query = tier_two_filtered_query.limit(filter.page_size).offset(
336
350
  (filter.page) * filter.page_size
337
351
  )
338
352
 
339
- filtered_total = (
353
+ paginated_total = (
340
354
  await self.session.execute(
341
355
  select(func.count()).select_from(paginated_query.subquery())
342
356
  )
343
357
  ).scalar_one()
344
358
 
359
+ result = await self.session.execute(paginated_query)
360
+ result_scalars = list(self.judge_unique(result).scalars().all())
361
+
345
362
  return Paginated(
346
- items=[
347
- e
348
- for e in self.judge_unique(
349
- await self.session.execute(paginated_query)
350
- ).scalars()
351
- ],
352
- total=filtered_total,
363
+ items=result_scalars,
364
+ total=paginated_total,
353
365
  unpaginated_total=unpaginated_total,
354
- total_pages=int(filtered_total / filter.page_size) + 1,
366
+ total_pages=int(unpaginated_total / filter.page_size) + 1,
355
367
  )
356
368
 
357
369
  def judge_unique(
@@ -16,12 +16,12 @@ class WebSocketConnectionManager(Protocol):
16
16
  async def remove_websocket(self, websocket: WebSocket) -> None: ...
17
17
 
18
18
 
19
- _ws_manage_ctx = ContextVar[WebSocketConnectionManager]("ws_manage_ctx")
19
+ _ws_conn_manager_ctx = ContextVar[WebSocketConnectionManager]("ws_manage_ctx")
20
20
 
21
21
 
22
22
  def use_ws_manager() -> WebSocketConnectionManager:
23
23
  try:
24
- return _ws_manage_ctx.get()
24
+ return _ws_conn_manager_ctx.get()
25
25
  except LookupError:
26
26
  raise RuntimeError("No WebSocketConnectionManager found")
27
27
 
@@ -30,9 +30,35 @@ def use_ws_manager() -> WebSocketConnectionManager:
30
30
  def provide_ws_manager(
31
31
  ws_manager: WebSocketConnectionManager,
32
32
  ) -> Generator[None, None, None]:
33
- token = _ws_manage_ctx.set(ws_manager)
33
+ token = _ws_conn_manager_ctx.set(ws_manager)
34
34
  try:
35
35
  yield
36
36
  finally:
37
37
  with suppress(ValueError):
38
- _ws_manage_ctx.reset(token)
38
+ _ws_conn_manager_ctx.reset(token)
39
+
40
+
41
+ class WebSocketMessageSender(Protocol):
42
+ async def send(self, rooms: list[str], message: WebSocketMessageBase) -> None: ...
43
+
44
+
45
+ _ws_msg_sender_ctx = ContextVar[WebSocketMessageSender]("ws_msg_sender_ctx")
46
+
47
+
48
+ def use_ws_message_sender() -> WebSocketMessageSender:
49
+ try:
50
+ return _ws_msg_sender_ctx.get()
51
+ except LookupError:
52
+ raise RuntimeError("No WebSocketMessageSender found")
53
+
54
+
55
+ @contextmanager
56
+ def provide_ws_message_sender(
57
+ ws_message_sender: WebSocketMessageSender,
58
+ ) -> Generator[None, None, None]:
59
+ token = _ws_msg_sender_ctx.set(ws_message_sender)
60
+ try:
61
+ yield
62
+ finally:
63
+ with suppress(ValueError):
64
+ _ws_msg_sender_ctx.reset(token)
@@ -1,8 +1,8 @@
1
1
  from jararaca.presentation.websocket.base_types import WebSocketMessageBase
2
- from jararaca.presentation.websocket.context import use_ws_manager
2
+ from jararaca.presentation.websocket.context import use_ws_message_sender
3
3
 
4
4
 
5
5
  class WebSocketMessage(WebSocketMessageBase):
6
6
 
7
7
  async def send(self, *rooms: str) -> None:
8
- await use_ws_manager().send(list(rooms), self)
8
+ await use_ws_message_sender().send(list(rooms), self)
@@ -25,7 +25,9 @@ from jararaca.presentation.decorators import (
25
25
  )
26
26
  from jararaca.presentation.websocket.context import (
27
27
  WebSocketConnectionManager,
28
+ WebSocketMessageSender,
28
29
  provide_ws_manager,
30
+ provide_ws_message_sender,
29
31
  )
30
32
  from jararaca.presentation.websocket.decorators import (
31
33
  INHERITS_WS_MESSAGE,
@@ -127,9 +129,23 @@ class WebSocketConnectionManagerImpl(WebSocketConnectionManager):
127
129
  # async def setup_consumer(self, websocket: WebSocket) -> None: ...
128
130
 
129
131
 
132
+ class StagingWebSocketMessageSender(WebSocketMessageSender):
133
+
134
+ def __init__(self, connection: WebSocketConnectionManager) -> None:
135
+ self.connection = connection
136
+ self.staged_messages: list[tuple[list[str], WebSocketMessageBase]] = []
137
+
138
+ async def send(self, rooms: list[str], message: WebSocketMessageBase) -> None:
139
+ self.staged_messages.append((rooms, message))
140
+
141
+ async def flush(self) -> None:
142
+ for rooms, message in self.staged_messages:
143
+ await self.connection.send(rooms, message)
144
+ self.staged_messages.clear()
145
+
146
+
130
147
  class WebSocketInterceptor(AppInterceptor, AppInterceptorWithLifecycle):
131
148
  """
132
- @Deprecated
133
149
  WebSocketInterceptor is responsible for managing WebSocket connections and
134
150
  intercepting WebSocket requests within the application. It integrates with
135
151
  the application's lifecycle and provides a router for WebSocket endpoints.
@@ -171,8 +187,14 @@ class WebSocketInterceptor(AppInterceptor, AppInterceptorWithLifecycle):
171
187
  @asynccontextmanager
172
188
  async def intercept(self, app_context: AppContext) -> AsyncGenerator[None, None]:
173
189
 
174
- with provide_ws_manager(self.connection_manager):
190
+ staging_ws_messages_sender = StagingWebSocketMessageSender(
191
+ self.connection_manager
192
+ )
193
+ with provide_ws_manager(self.connection_manager), provide_ws_message_sender(
194
+ staging_ws_messages_sender
195
+ ):
175
196
  yield
197
+ await staging_ws_messages_sender.flush()
176
198
 
177
199
  # def __wrap_with_uow_context_provider(
178
200
  # self, uow: UnitOfWorkContextProvider, func: Callable[..., Any]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: jararaca
3
- Version: 0.3.9
3
+ Version: 0.3.11a0
4
4
  Summary: A simple and fast API framework for Python
5
5
  Author: Lucas S
6
6
  Author-email: me@luscasleo.dev
@@ -16,10 +16,10 @@ jararaca/messagebus/bus_message_controller.py,sha256=Xd_qwnX5jUvgBTCarHR36fvtol9
16
16
  jararaca/messagebus/consumers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  jararaca/messagebus/decorators.py,sha256=y-w4dWbP9ZW3ZJ4mE9iIaxw01ZC5snEbOuBY5NC-Bn0,5626
18
18
  jararaca/messagebus/interceptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py,sha256=Aex87wopB34sMNzXBgi4KucVHjL2wYog3-IH75DAfDk,5387
20
- jararaca/messagebus/interceptors/publisher_interceptor.py,sha256=fQFFW9hH6ZU3UOyR7kMPrNp9wA71qEy5XlgrBQdBMS4,1230
19
+ jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py,sha256=WBHf8DKcuCeH4kBZq9fx_qsryhU7yWplJNLF16LpqMk,6574
20
+ jararaca/messagebus/interceptors/publisher_interceptor.py,sha256=wkIDoa__Q7o74cv89PXdBJFbe-ijxH8Xc_-hBEgxYjM,1272
21
21
  jararaca/messagebus/message.py,sha256=U6cyd2XknX8mtm0333slz5fanky2PFLWCmokAO56vvU,819
22
- jararaca/messagebus/publisher.py,sha256=K7WsOMVTyLmdms3ZKEshqrQc_DhNreiFK-HnmOT9Ce0,1965
22
+ jararaca/messagebus/publisher.py,sha256=JTkxdKbvxvDWT8nK8PVEyyX061vYYbKQMxRHXrZtcEY,2173
23
23
  jararaca/messagebus/worker.py,sha256=2jReCepgMQktdLh4A3OqmOmx6KNgqUGpXIOXoBUtXSg,13778
24
24
  jararaca/messagebus/worker_v2.py,sha256=Ey9HPgVSuIYJUggJGPKmgymZHz7-2TUSYgDB52u9T10,20683
25
25
  jararaca/microservice.py,sha256=C_Txqm3xSmdHIghJigKk6TOycL5eTJv9PF6XP7TA8j4,6638
@@ -30,10 +30,10 @@ jararaca/observability/providers/otel.py,sha256=LgfoITdoQTCxKebfLcEfwMiG992wlWY_
30
30
  jararaca/persistence/base.py,sha256=Xfnpvj3yeLdpVBifH5W6AwPCLwL2ot0dpLzbPg1zwkQ,966
31
31
  jararaca/persistence/exports.py,sha256=Ghx4yoFaB4QVTb9WxrFYgmcSATXMNvrOvT8ybPNKXCA,62
32
32
  jararaca/persistence/interceptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- jararaca/persistence/interceptors/aiosqa_interceptor.py,sha256=H6ZjOdosYGCZUzKjugiXQwJkAbnsL4HnkZLOEQhULEc,1986
33
+ jararaca/persistence/interceptors/aiosqa_interceptor.py,sha256=EZz80PZVb80DrM1rOLRkKMDO-93by6xp97G7o_LzziE,2034
34
34
  jararaca/persistence/session.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
35
  jararaca/persistence/sort_filter.py,sha256=agggpN0YvNjUr6wJjy69NkaqxoDDW13ys9B3r85OujA,9226
36
- jararaca/persistence/utilities.py,sha256=0v1YrK-toTCgtw8aROtrDMNfjo3qWWNAb5qQcCOdSUU,13681
36
+ jararaca/persistence/utilities.py,sha256=imcV4Oi5kXNk6m9QF2-OsnFpcTRY4w5mBYLdEx5XTSQ,14296
37
37
  jararaca/presentation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  jararaca/presentation/decorators.py,sha256=EErRZtLZDoZZj1ZVlDkGQbvqpoQkBaoU70KAUwWIufs,9146
39
39
  jararaca/presentation/hooks.py,sha256=WBbU5DG3-MAm2Ro2YraQyYG_HENfizYfyShL2ktHi6k,1980
@@ -41,11 +41,11 @@ jararaca/presentation/http_microservice.py,sha256=g771JosV6jTY3hQtG-HkLOo-T0e-r3
41
41
  jararaca/presentation/server.py,sha256=JuEatLFhD_NFnszwXDSgtcCXNOwvQYyxoxxQ33hnAEc,3731
42
42
  jararaca/presentation/websocket/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
43
  jararaca/presentation/websocket/base_types.py,sha256=AvUeeZ1TFhSiRMcYqZU1HaQNqSrcgTkC5R0ArP5dGmA,146
44
- jararaca/presentation/websocket/context.py,sha256=090vJmO4bWt0Hr7MI-ZtRlVHTD2JW5JvZRJTFI9XGKs,1180
44
+ jararaca/presentation/websocket/context.py,sha256=A6K5W3kqo9Hgeh1m6JiI7Cdz5SfbXcaICSVX7u1ARZo,1903
45
45
  jararaca/presentation/websocket/decorators.py,sha256=ZNd5aoA9UkyfHOt1C8D2Ffy2gQUNDEsusVnQuTgExgs,2157
46
46
  jararaca/presentation/websocket/redis.py,sha256=6wD4zGHftJXNDW3VfS65WJt2cnOgTI0zmQOfjZ_CEXE,4726
47
- jararaca/presentation/websocket/types.py,sha256=Crz88c1670YLtIhDvbeuyBV1SN-z9RtEGZxaonGvapY,294
48
- jararaca/presentation/websocket/websocket_interceptor.py,sha256=R_DfIMhi1ngFfCGy33b6yv_6LxCM-Dipkr12z6evfyk,8297
47
+ jararaca/presentation/websocket/types.py,sha256=M8snAMSdaQlKrwEM2qOgF2qrefo5Meio_oOw620Joc8,308
48
+ jararaca/presentation/websocket/websocket_interceptor.py,sha256=OET9Dv8CVJn6bkjnmMXqT_yRWr9HTyd7E-dtN7zfeQ4,9155
49
49
  jararaca/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
50
  jararaca/rpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
51
  jararaca/rpc/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -66,8 +66,8 @@ jararaca/tools/metadata.py,sha256=7nlCDYgItNybentPSSCc2MLqN7IpBd0VyQzfjfQycVI,14
66
66
  jararaca/tools/typescript/interface_parser.py,sha256=35xbOrZDQDyTXdMrVZQ8nnFw79f28lJuLYNHAspIqi8,30492
67
67
  jararaca/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
68
  jararaca/utils/rabbitmq_utils.py,sha256=FPDP8ZVgvitZXV-oK73D7EIANsqUzXTW7HdpEKsIsyI,2811
69
- jararaca-0.3.9.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
70
- jararaca-0.3.9.dist-info/METADATA,sha256=QUd0KvFdDWQNVvIzaVfGRWXzttqzP_AqAo7HbjzZ0pc,4951
71
- jararaca-0.3.9.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
72
- jararaca-0.3.9.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
73
- jararaca-0.3.9.dist-info/RECORD,,
69
+ jararaca-0.3.11a0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
70
+ jararaca-0.3.11a0.dist-info/METADATA,sha256=XbMVjGqVYheva2VT-YGJoMx_DalQbkjx7t3PfkFACrI,4954
71
+ jararaca-0.3.11a0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
72
+ jararaca-0.3.11a0.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
73
+ jararaca-0.3.11a0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.2
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any