jararaca 0.2.37a10__py3-none-any.whl → 0.2.37a12__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.
jararaca/__init__.py CHANGED
@@ -59,6 +59,7 @@ if TYPE_CHECKING:
59
59
  from .messagebus.decorators import MessageBusController, MessageHandler
60
60
  from .messagebus.interceptors.aiopika_publisher_interceptor import (
61
61
  AIOPikaConnectionFactory,
62
+ GenericPoolConfig,
62
63
  MessageBusPublisherInterceptor,
63
64
  )
64
65
  from .messagebus.publisher import use_publisher
@@ -192,6 +193,7 @@ if TYPE_CHECKING:
192
193
  "Put",
193
194
  "Delete",
194
195
  "use_publisher",
196
+ "GenericPoolConfig",
195
197
  "AIOPikaConnectionFactory",
196
198
  "MessageBusPublisherInterceptor",
197
199
  "RedisWebSocketConnectionBackend",
@@ -221,6 +223,11 @@ _dynamic_imports: "dict[str, tuple[str, str, str | None]]" = {
221
223
  "messagebus.bus_message_controller",
222
224
  None,
223
225
  ),
226
+ "GenericPoolConfig": (
227
+ __SPEC_PARENT__,
228
+ "messagebus.interceptors.aiopika_publisher_interceptor",
229
+ None,
230
+ ),
224
231
  "ack": (__SPEC_PARENT__, "messagebus.bus_message_controller", None),
225
232
  "nack": (__SPEC_PARENT__, "messagebus.bus_message_controller", None),
226
233
  "reject": (__SPEC_PARENT__, "messagebus.bus_message_controller", None),
jararaca/cli.py CHANGED
@@ -1,5 +1,8 @@
1
1
  import importlib
2
2
  import importlib.resources
3
+ import os
4
+ import sys
5
+ import time
3
6
  from codecs import StreamWriter
4
7
  from typing import Any
5
8
  from urllib.parse import urlparse, urlunsplit
@@ -208,12 +211,83 @@ def scheduler(
208
211
  "file_path",
209
212
  type=click.File("w"),
210
213
  )
211
- def gen_tsi(app_path: str, file_path: StreamWriter) -> None:
212
- app = find_microservice_by_module_path(app_path)
214
+ @click.option(
215
+ "--watch",
216
+ is_flag=True,
217
+ help="Watch for file changes and regenerate TypeScript interfaces",
218
+ )
219
+ @click.option(
220
+ "--src-dir",
221
+ type=click.Path(exists=True, file_okay=False, dir_okay=True),
222
+ default="src",
223
+ help="Source directory to watch for changes (default: src)",
224
+ )
225
+ def gen_tsi(app_path: str, file_path: StreamWriter, watch: bool, src_dir: str) -> None:
226
+ """Generate TypeScript interfaces from a Python microservice."""
227
+
228
+ # Generate typescript interfaces
229
+ def generate_interfaces() -> None:
230
+ try:
231
+ app = find_microservice_by_module_path(app_path)
232
+ content = write_microservice_to_typescript_interface(app)
233
+
234
+ # Save current position
235
+ file_path.tell()
236
+
237
+ # Reset file to beginning
238
+ file_path.seek(0)
239
+ file_path.truncate()
240
+
241
+ # Write new content
242
+ file_path.write(content)
243
+ file_path.flush()
213
244
 
214
- content = write_microservice_to_typescript_interface(app)
245
+ print(f"Generated TypeScript interfaces at {time.strftime('%H:%M:%S')}")
246
+ except Exception as e:
247
+ print(f"Error generating TypeScript interfaces: {e}", file=sys.stderr)
215
248
 
216
- file_path.write(content)
249
+ # Initial generation
250
+ generate_interfaces()
251
+
252
+ # If watch mode is not enabled, exit
253
+ if not watch:
254
+ return
255
+
256
+ try:
257
+ from watchdog.events import FileSystemEvent, FileSystemEventHandler
258
+ from watchdog.observers import Observer
259
+ except ImportError:
260
+ print(
261
+ "Watchdog is required for watch mode. Install it with: pip install watchdog",
262
+ file=sys.stderr,
263
+ )
264
+ return
265
+
266
+ # Set up file system event handler
267
+ class PyFileChangeHandler(FileSystemEventHandler):
268
+ def on_modified(self, event: FileSystemEvent) -> None:
269
+ src_path = (
270
+ event.src_path
271
+ if isinstance(event.src_path, str)
272
+ else str(event.src_path)
273
+ )
274
+ if not event.is_directory and src_path.endswith(".py"):
275
+ print(f"File changed: {src_path}")
276
+ generate_interfaces()
277
+
278
+ # Set up observer
279
+ observer = Observer()
280
+ observer.schedule(PyFileChangeHandler(), src_dir, recursive=True)
281
+ observer.start()
282
+
283
+ print(f"Watching for changes in {os.path.abspath(src_dir)}...")
284
+ try:
285
+ while True:
286
+ time.sleep(1)
287
+ except KeyboardInterrupt:
288
+ observer.stop()
289
+ print("Watch mode stopped")
290
+ observer.join()
217
291
 
218
292
 
219
293
  def camel_case_to_snake_case(name: str) -> str:
@@ -2,6 +2,7 @@ from contextlib import asynccontextmanager
2
2
  from typing import Any, AsyncContextManager, AsyncGenerator, Protocol
3
3
 
4
4
  import aio_pika
5
+ from aio_pika.abc import AbstractConnection
5
6
  from pydantic import BaseModel
6
7
 
7
8
  from jararaca.messagebus.publisher import MessagePublisher, provide_message_publisher
@@ -25,6 +26,10 @@ class MessageBusPublisherInterceptor(AppInterceptor):
25
26
 
26
27
  @asynccontextmanager
27
28
  async def intercept(self, app_context: AppContext) -> AsyncGenerator[None, None]:
29
+ if app_context.context_type == "websocket":
30
+ yield
31
+ return
32
+
28
33
  async with self.connection_factory.provide_connection() as connection:
29
34
  with provide_message_publisher(self.connection_name, connection):
30
35
  yield
@@ -48,29 +53,79 @@ class AIOPikaMessagePublisher(MessagePublisher):
48
53
  )
49
54
 
50
55
 
56
+ class GenericPoolConfig(BaseModel):
57
+ max_size: int
58
+
59
+
51
60
  class AIOPikaConnectionFactory(MessageBusConnectionFactory):
52
61
 
53
62
  def __init__(
54
63
  self,
55
64
  url: str,
56
65
  exchange: str,
66
+ connection_pool_config: GenericPoolConfig | None = None,
67
+ channel_pool_config: GenericPoolConfig | None = None,
57
68
  ):
58
69
  self.url = url
59
70
  self.exchange = exchange
60
71
 
72
+ self.connection_pool: aio_pika.pool.Pool[AbstractConnection] | None = None
73
+ self.channel_pool: aio_pika.pool.Pool[aio_pika.abc.AbstractChannel] | None = (
74
+ None
75
+ )
76
+
77
+ if connection_pool_config:
78
+
79
+ async def get_connection() -> AbstractConnection:
80
+ return await aio_pika.connect_robust(self.url)
81
+
82
+ self.connection_pool = aio_pika.pool.Pool[AbstractConnection](
83
+ get_connection,
84
+ max_size=connection_pool_config.max_size,
85
+ )
86
+
87
+ if channel_pool_config:
88
+
89
+ async def get_channel() -> aio_pika.abc.AbstractChannel:
90
+ async with self.acquire_connection() as connection:
91
+ return await connection.channel(publisher_confirms=False)
92
+
93
+ self.channel_pool = aio_pika.pool.Pool[aio_pika.abc.AbstractChannel](
94
+ get_channel, max_size=channel_pool_config.max_size
95
+ )
96
+
97
+ @asynccontextmanager
98
+ async def acquire_connection(self) -> AsyncGenerator[AbstractConnection, Any]:
99
+ if not self.connection_pool:
100
+ async with await aio_pika.connect_robust(self.url) as connection:
101
+ yield connection
102
+ else:
103
+
104
+ async with self.connection_pool.acquire() as connection:
105
+ yield connection
106
+
107
+ @asynccontextmanager
108
+ async def acquire_channel(
109
+ self,
110
+ ) -> AsyncGenerator[aio_pika.abc.AbstractChannel, Any]:
111
+ if not self.channel_pool:
112
+ async with self.acquire_connection() as connection:
113
+ yield await connection.channel(publisher_confirms=False)
114
+ else:
115
+ async with self.channel_pool.acquire() as channel:
116
+ yield channel
117
+
61
118
  @asynccontextmanager
62
119
  async def provide_connection(self) -> AsyncGenerator[AIOPikaMessagePublisher, Any]:
63
- connection = await aio_pika.connect_robust(self.url)
64
- async with connection:
65
- async with connection.channel(publisher_confirms=False) as channel:
66
120
 
67
- tx = channel.transaction()
121
+ async with self.acquire_channel() as channel:
122
+ tx = channel.transaction()
68
123
 
69
- await tx.select()
124
+ await tx.select()
70
125
 
71
- try:
72
- yield AIOPikaMessagePublisher(channel, exchange_name=self.exchange)
73
- await tx.commit()
74
- except Exception as e:
75
- await tx.rollback()
76
- raise e
126
+ try:
127
+ yield AIOPikaMessagePublisher(channel, exchange_name=self.exchange)
128
+ await tx.commit()
129
+ except Exception as e:
130
+ await tx.rollback()
131
+ raise e
@@ -205,17 +205,17 @@ class MessageHandlerCallback:
205
205
  self, aio_pika_message: aio_pika.abc.AbstractIncomingMessage
206
206
  ) -> None:
207
207
 
208
- rounting_key = self.queue_name
208
+ routing_key = self.queue_name
209
209
 
210
- if rounting_key is None:
210
+ if routing_key is None:
211
211
  logger.warning("No topic found for message")
212
212
  await self.handle_reject_message(aio_pika_message)
213
213
  return
214
214
 
215
- handler_data = self.consumer.incoming_map.get(rounting_key)
215
+ handler_data = self.consumer.incoming_map.get(routing_key)
216
216
 
217
217
  if handler_data is None:
218
- logger.warning("No handler found for topic '%s'" % rounting_key)
218
+ logger.warning("No handler found for topic '%s'" % routing_key)
219
219
  await self.handle_reject_message(aio_pika_message)
220
220
 
221
221
  return
@@ -227,7 +227,7 @@ class MessageHandlerCallback:
227
227
  if len(sig.parameters) != 1:
228
228
  logger.warning(
229
229
  "Handler for topic '%s' must have exactly one parameter which is MessageOf[T extends Message]"
230
- % rounting_key
230
+ % routing_key
231
231
  )
232
232
  return
233
233
 
@@ -238,14 +238,14 @@ class MessageHandlerCallback:
238
238
  if param_origin is not MessageOf:
239
239
  logger.warning(
240
240
  "Handler for topic '%s' must have exactly one parameter of type Message"
241
- % rounting_key
241
+ % routing_key
242
242
  )
243
243
  return
244
244
 
245
245
  if len(parameter.annotation.__args__) != 1:
246
246
  logger.warning(
247
247
  "Handler for topic '%s' must have exactly one parameter of type Message"
248
- % rounting_key
248
+ % routing_key
249
249
  )
250
250
  return
251
251
 
@@ -253,8 +253,8 @@ class MessageHandlerCallback:
253
253
 
254
254
  if not issubclass(message_type, BaseModel):
255
255
  logger.warning(
256
- "Handler for topic '%s' must have exactly one parameter of type Message[BaseModel]"
257
- % rounting_key
256
+ "Handler for topic '%s' must have exactly one parameter of type MessageOf[BaseModel]"
257
+ % routing_key
258
258
  )
259
259
  return
260
260
 
@@ -266,7 +266,7 @@ class MessageHandlerCallback:
266
266
  async with self.consumer.uow_context_provider(
267
267
  MessageBusAppContext(
268
268
  message=builded_message,
269
- topic=rounting_key,
269
+ topic=routing_key,
270
270
  )
271
271
  ):
272
272
  ctx: AsyncContextManager[Any]
@@ -293,7 +293,7 @@ class MessageHandlerCallback:
293
293
  )
294
294
  else:
295
295
  logger.exception(
296
- f"Error processing message on topic {rounting_key}"
296
+ f"Error processing message on topic {routing_key}"
297
297
  )
298
298
  if incoming_message_spec.requeue_on_exception:
299
299
  await self.handle_reject_message(aio_pika_message, requeue=True)
@@ -0,0 +1,154 @@
1
+ Metadata-Version: 2.3
2
+ Name: jararaca
3
+ Version: 0.2.37a12
4
+ Summary: A simple and fast API framework for Python
5
+ Author: Lucas S
6
+ Author-email: me@luscasleo.dev
7
+ Requires-Python: >=3.11,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Provides-Extra: docs
13
+ Provides-Extra: http
14
+ Provides-Extra: opentelemetry
15
+ Requires-Dist: aio-pika (>=9.4.3,<10.0.0)
16
+ Requires-Dist: croniter (>=3.0.3,<4.0.0)
17
+ Requires-Dist: fastapi (>=0.113.0,<0.114.0)
18
+ Requires-Dist: mako (>=1.3.5,<2.0.0)
19
+ Requires-Dist: opentelemetry-api (>=1.27.0,<2.0.0) ; extra == "opentelemetry"
20
+ Requires-Dist: opentelemetry-distro (>=0.49b2,<0.50) ; extra == "opentelemetry"
21
+ Requires-Dist: opentelemetry-exporter-otlp (>=1.27.0,<2.0.0) ; extra == "opentelemetry"
22
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http (>=1.27.0,<2.0.0) ; extra == "opentelemetry"
23
+ Requires-Dist: opentelemetry-sdk (>=1.27.0,<2.0.0) ; extra == "opentelemetry"
24
+ Requires-Dist: redis (>=5.0.8,<6.0.0)
25
+ Requires-Dist: sqlalchemy (>=2.0.34,<3.0.0)
26
+ Requires-Dist: types-croniter (>=3.0.3.20240731,<4.0.0.0)
27
+ Requires-Dist: types-redis (>=4.6.0.20240903,<5.0.0.0)
28
+ Requires-Dist: uvicorn (>=0.30.6,<0.31.0)
29
+ Requires-Dist: uvloop (>=0.20.0,<0.21.0)
30
+ Requires-Dist: websockets (>=13.0.1,<14.0.0)
31
+ Project-URL: Repository, https://github.com/LuscasLeo/jararaca
32
+ Description-Content-Type: text/markdown
33
+
34
+ <img src="https://raw.githubusercontent.com/LuscasLeo/jararaca/main/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg" alt="Jararaca Logo" width="250" float="right">
35
+
36
+ # Jararaca Microservice Framework
37
+
38
+ ## Overview
39
+
40
+ Jararaca is an async-first microservice framework designed to simplify the development of distributed systems. It provides a comprehensive set of tools for building robust, scalable, and maintainable microservices with a focus on developer experience and type safety.
41
+
42
+ ## Key Features
43
+
44
+ ### REST API Development
45
+ - Easy-to-use interfaces for building REST APIs
46
+ - Automatic request/response validation
47
+ - Type-safe endpoints with FastAPI integration
48
+ - Automatic OpenAPI documentation generation
49
+
50
+ ### Message Bus Integration
51
+ - Topic-based message bus for event-driven architecture
52
+ - Support for both worker and publisher patterns
53
+ - Built-in message serialization and deserialization
54
+ - Easy integration with AIO Pika for RabbitMQ
55
+
56
+ ### Distributed WebSocket
57
+ - Room-based WebSocket communication
58
+ - Distributed broadcasting across multiple backend instances
59
+ - Automatic message synchronization between instances
60
+ - Built-in connection management and room handling
61
+
62
+ ### Task Scheduling
63
+ - Cron-based task scheduling
64
+ - Support for overlapping and non-overlapping tasks
65
+ - Distributed task execution
66
+ - Easy integration with message bus for task distribution
67
+
68
+ ### TypeScript Integration
69
+ - Automatic TypeScript interface generation
70
+ - Command-line tool for generating TypeScript types
71
+ - Support for REST endpoints, WebSocket events, and message bus payloads
72
+ - Type-safe frontend-backend communication
73
+
74
+ ### Hexagonal Architecture
75
+ - Clear separation of concerns
76
+ - Business logic isolation from infrastructure
77
+ - Easy testing and maintainability
78
+ - Dependency injection for flexible component management
79
+
80
+ ### Observability
81
+ - Built-in OpenTelemetry integration
82
+ - Distributed tracing support
83
+ - Logging and metrics collection
84
+ - Performance monitoring capabilities
85
+
86
+ ## Quick Start
87
+
88
+ ### Installation
89
+
90
+ ```bash
91
+ pip install jararaca
92
+ ```
93
+
94
+ ### Basic Usage
95
+
96
+ ```python
97
+ from jararaca import Microservice, create_http_server
98
+ from jararaca.presentation.http_microservice import HttpMicroservice
99
+
100
+ # Define your microservice
101
+ app = Microservice(
102
+ providers=[
103
+ # Add your providers here
104
+ ],
105
+ controllers=[
106
+ # Add your controllers here
107
+ ],
108
+ interceptors=[
109
+ # Add your interceptors here
110
+ ],
111
+ )
112
+
113
+ # Create HTTP server
114
+ http_app = HttpMicroservice(app)
115
+ web_app = create_http_server(app)
116
+ ```
117
+
118
+ ### Running the Service
119
+
120
+ ```bash
121
+ # Run as HTTP server
122
+ jararaca server app:http_app
123
+
124
+ # Run as message bus worker
125
+ jararaca worker app:app
126
+
127
+ # Run as scheduler
128
+ jararaca scheduler app:app
129
+
130
+ # Generate TypeScript interfaces
131
+ jararaca gen-tsi app.main:app app.ts
132
+ ```
133
+
134
+ ## Documentation
135
+
136
+ For detailed documentation, please visit our [documentation site](https://luscasleo.github.io/jararaca/).
137
+
138
+ ## Examples
139
+
140
+ Check out the [examples directory](examples/) for complete working examples of:
141
+ - REST API implementation
142
+ - WebSocket usage
143
+ - Message bus integration
144
+ - Task scheduling
145
+ - TypeScript interface generation
146
+
147
+ ## Contributing
148
+
149
+ Contributions are welcome! Please read our [contributing guidelines](.github/CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
150
+
151
+ ## License
152
+
153
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
154
+
@@ -1,9 +1,6 @@
1
- LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
2
- README.md,sha256=mte30I-ZEJJp-Oax-OganNgl6G9GaCZPL6JVFAvZGz4,7034
3
- pyproject.toml,sha256=Hz6Rk-E8INfd6U7oblRlcW8Qvm_aRmyY_EjWaGj6ukw,1840
4
- jararaca/__init__.py,sha256=gDCvrIhIu5_IkKjmZmhWZ2khOi8WGiw2AidFFe-BnWU,15118
1
+ jararaca/__init__.py,sha256=VBrN25GHJ3gDG95CcJWe3dmGcA-X2agzOCIBbjzc1Iw,15312
5
2
  jararaca/__main__.py,sha256=-O3vsB5lHdqNFjUtoELDF81IYFtR-DSiiFMzRaiSsv4,67
6
- jararaca/cli.py,sha256=JKk4xrRbtX2fM8yYw794lbxvJFH73bWw3GGIvrpAkeE,5706
3
+ jararaca/cli.py,sha256=K6df8fqEc4Qj-yGz5hmO3pSgFWxcLP4iciOP4LHida4,7979
7
4
  jararaca/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
5
  jararaca/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
6
  jararaca/core/providers.py,sha256=wktH84FK7c1s2wNq-fudf1uMfi3CQBR0neU2czJ_L0U,434
@@ -15,10 +12,10 @@ jararaca/messagebus/__init__.py,sha256=Zdl74HcS9K0FW6XUt7bVvaHEyxL8pWsqqakeRENIn
15
12
  jararaca/messagebus/bus_message_controller.py,sha256=Xd_qwnX5jUvgBTCarHR36fvtol9lPTsYp2IIGKyQQaE,1487
16
13
  jararaca/messagebus/decorators.py,sha256=GHlaXRuHtrz6R0HgcG2gJybpGYtdts9meDVSRPwN74I,4245
17
14
  jararaca/messagebus/interceptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py,sha256=rqAX644-VHarg6kPPsXGTmxYd79PpJXiJ1N1gIooJTo,2470
15
+ jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py,sha256=BPH5wOlj_CyHtJ7W4NWF2h0gYMwzOPNzFhGADk618N4,4373
19
16
  jararaca/messagebus/publisher.py,sha256=5ay9Znwybqt981OOykdWkFisSvGiTeTpPXDFLMnaiqg,1109
20
17
  jararaca/messagebus/types.py,sha256=iYLyLxWqOHkDadxyMqQPWy3itLNQfvD6oQe8jcq9nzo,887
21
- jararaca/messagebus/worker.py,sha256=hKACTyrIMHcuaySpmI3UhDCja6va1gGkFRoZJ7kYfoA,13613
18
+ jararaca/messagebus/worker.py,sha256=_X2Ctj7bI9XjCMvQMy8jM3dEE3CGnqgrU_B3lTGfmmQ,13605
22
19
  jararaca/microservice.py,sha256=1TvDKVMMREH27Ly8eTEheMmSfro4_Az_JKM_NdDvrgc,6636
23
20
  jararaca/observability/decorators.py,sha256=XffBinFXdiNkY6eo8_1nkr_GapM0RUGBg0aicBIelag,2220
24
21
  jararaca/observability/interceptor.py,sha256=GHkuGKFWftN7MDjvYeGFGEPnuJETNhtxRK6yuPrCrpU,1462
@@ -59,8 +56,8 @@ jararaca/tools/app_config/decorators.py,sha256=-ckkMZ1dswOmECdo1rFrZ15UAku--txaN
59
56
  jararaca/tools/app_config/interceptor.py,sha256=nfFZiS80hrbnL7-XEYrwmp2rwaVYBqxvqu3Y-6o_ov4,2575
60
57
  jararaca/tools/metadata.py,sha256=7nlCDYgItNybentPSSCc2MLqN7IpBd0VyQzfjfQycVI,1402
61
58
  jararaca/tools/typescript/interface_parser.py,sha256=4SHt094P-QawMFHSyMCiujQf8Niw7xACIO1RHBM8-w4,29192
62
- jararaca-0.2.37a10.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
63
- jararaca-0.2.37a10.dist-info/METADATA,sha256=d0angF0OwLZKGkRL3wKp8GkP79k0HTXlfRIUXRAoZOA,8555
64
- jararaca-0.2.37a10.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
65
- jararaca-0.2.37a10.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
66
- jararaca-0.2.37a10.dist-info/RECORD,,
59
+ jararaca-0.2.37a12.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
60
+ jararaca-0.2.37a12.dist-info/METADATA,sha256=W8aHUpqo5Q5iGBwX0BE4CS9MfEEMN97vM-YRuCJ2URM,4873
61
+ jararaca-0.2.37a12.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
62
+ jararaca-0.2.37a12.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
63
+ jararaca-0.2.37a12.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.1
2
+ Generator: poetry-core 2.1.2
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any