jararaca 0.2.37a10__tar.gz → 0.2.37a11__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.
Potentially problematic release.
This version of jararaca might be problematic. Click here for more details.
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/PKG-INFO +1 -1
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/pyproject.toml +1 -1
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/__init__.py +7 -0
- jararaca-0.2.37a11/src/jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +131 -0
- jararaca-0.2.37a10/src/jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +0 -76
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/LICENSE +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/README.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/Architecture/Message Bus/Decorators.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/Architecture/Message Bus/Interceptors.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/Architecture/Observability/Decorators.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/Architecture/Observability/Interceptors.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/Architecture/Presentation/Decorators.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/Architecture/RPC/RestClients/Decorators.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/Architecture/RPC/RestClients/Middlewared.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/Architecture/Schedule/Decorators.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/Architecture/Websocket/Decorators.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/Architecture/Websocket/Interceptors.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/Concept/Adapters.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/Concept/Hexagonal Architecture.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/Concept/Ports.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.webp +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/assets/tracing_example.png +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/index.md +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/__main__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/cli.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/common/__init__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/core/__init__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/core/providers.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/core/uow.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/di.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/files/entity.py.mako +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/lifecycle.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/messagebus/__init__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/messagebus/bus_message_controller.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/messagebus/decorators.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/messagebus/interceptors/__init__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/messagebus/publisher.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/messagebus/types.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/messagebus/worker.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/microservice.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/observability/decorators.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/observability/interceptor.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/observability/providers/__init__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/observability/providers/otel.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/persistence/base.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/persistence/exports.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/persistence/interceptors/__init__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/persistence/interceptors/aiosqa_interceptor.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/persistence/session.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/persistence/sort_filter.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/persistence/utilities.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/presentation/__init__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/presentation/decorators.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/presentation/hooks.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/presentation/http_microservice.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/presentation/server.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/presentation/websocket/__init__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/presentation/websocket/base_types.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/presentation/websocket/context.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/presentation/websocket/decorators.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/presentation/websocket/redis.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/presentation/websocket/types.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/presentation/websocket/websocket_interceptor.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/py.typed +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/rpc/__init__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/rpc/http/__init__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/rpc/http/backends/__init__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/rpc/http/backends/httpx.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/rpc/http/backends/otel.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/rpc/http/decorators.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/rpc/http/httpx.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/scheduler/__init__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/scheduler/decorators.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/scheduler/scheduler.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/tools/app_config/__init__.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/tools/app_config/decorators.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/tools/app_config/interceptor.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/tools/metadata.py +0 -0
- {jararaca-0.2.37a10 → jararaca-0.2.37a11}/src/jararaca/tools/typescript/interface_parser.py +0 -0
|
@@ -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),
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
from typing import Any, AsyncContextManager, AsyncGenerator, Protocol
|
|
3
|
+
|
|
4
|
+
import aio_pika
|
|
5
|
+
from aio_pika.abc import AbstractConnection
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from jararaca.messagebus.publisher import MessagePublisher, provide_message_publisher
|
|
9
|
+
from jararaca.microservice import AppContext, AppInterceptor
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MessageBusConnectionFactory(Protocol):
|
|
13
|
+
|
|
14
|
+
def provide_connection(self) -> AsyncContextManager[MessagePublisher]: ...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MessageBusPublisherInterceptor(AppInterceptor):
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
connection_factory: MessageBusConnectionFactory,
|
|
22
|
+
connection_name: str = "default",
|
|
23
|
+
):
|
|
24
|
+
self.connection_factory = connection_factory
|
|
25
|
+
self.connection_name = connection_name
|
|
26
|
+
|
|
27
|
+
@asynccontextmanager
|
|
28
|
+
async def intercept(self, app_context: AppContext) -> AsyncGenerator[None, None]:
|
|
29
|
+
if app_context.context_type == "websocket":
|
|
30
|
+
yield
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
async with self.connection_factory.provide_connection() as connection:
|
|
34
|
+
with provide_message_publisher(self.connection_name, connection):
|
|
35
|
+
yield
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AIOPikaMessagePublisher(MessagePublisher):
|
|
39
|
+
|
|
40
|
+
def __init__(self, channel: aio_pika.abc.AbstractChannel, exchange_name: str):
|
|
41
|
+
self.channel = channel
|
|
42
|
+
self.exchange_name = exchange_name
|
|
43
|
+
|
|
44
|
+
async def publish(self, message: BaseModel, topic: str) -> None:
|
|
45
|
+
exchange = await self.channel.declare_exchange(
|
|
46
|
+
self.exchange_name,
|
|
47
|
+
type=aio_pika.ExchangeType.TOPIC,
|
|
48
|
+
)
|
|
49
|
+
routing_key = f"{self.exchange_name}.{topic}."
|
|
50
|
+
await exchange.publish(
|
|
51
|
+
aio_pika.Message(body=message.model_dump_json().encode()),
|
|
52
|
+
routing_key=routing_key,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class GenericPoolConfig(BaseModel):
|
|
57
|
+
max_size: int
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AIOPikaConnectionFactory(MessageBusConnectionFactory):
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
url: str,
|
|
65
|
+
exchange: str,
|
|
66
|
+
connection_pool_config: GenericPoolConfig | None = None,
|
|
67
|
+
channel_pool_config: GenericPoolConfig | None = None,
|
|
68
|
+
):
|
|
69
|
+
self.url = url
|
|
70
|
+
self.exchange = exchange
|
|
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
|
+
|
|
118
|
+
@asynccontextmanager
|
|
119
|
+
async def provide_connection(self) -> AsyncGenerator[AIOPikaMessagePublisher, Any]:
|
|
120
|
+
|
|
121
|
+
async with self.acquire_channel() as channel:
|
|
122
|
+
tx = channel.transaction()
|
|
123
|
+
|
|
124
|
+
await tx.select()
|
|
125
|
+
|
|
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
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
from contextlib import asynccontextmanager
|
|
2
|
-
from typing import Any, AsyncContextManager, AsyncGenerator, Protocol
|
|
3
|
-
|
|
4
|
-
import aio_pika
|
|
5
|
-
from pydantic import BaseModel
|
|
6
|
-
|
|
7
|
-
from jararaca.messagebus.publisher import MessagePublisher, provide_message_publisher
|
|
8
|
-
from jararaca.microservice import AppContext, AppInterceptor
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class MessageBusConnectionFactory(Protocol):
|
|
12
|
-
|
|
13
|
-
def provide_connection(self) -> AsyncContextManager[MessagePublisher]: ...
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class MessageBusPublisherInterceptor(AppInterceptor):
|
|
17
|
-
|
|
18
|
-
def __init__(
|
|
19
|
-
self,
|
|
20
|
-
connection_factory: MessageBusConnectionFactory,
|
|
21
|
-
connection_name: str = "default",
|
|
22
|
-
):
|
|
23
|
-
self.connection_factory = connection_factory
|
|
24
|
-
self.connection_name = connection_name
|
|
25
|
-
|
|
26
|
-
@asynccontextmanager
|
|
27
|
-
async def intercept(self, app_context: AppContext) -> AsyncGenerator[None, None]:
|
|
28
|
-
async with self.connection_factory.provide_connection() as connection:
|
|
29
|
-
with provide_message_publisher(self.connection_name, connection):
|
|
30
|
-
yield
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class AIOPikaMessagePublisher(MessagePublisher):
|
|
34
|
-
|
|
35
|
-
def __init__(self, channel: aio_pika.abc.AbstractChannel, exchange_name: str):
|
|
36
|
-
self.channel = channel
|
|
37
|
-
self.exchange_name = exchange_name
|
|
38
|
-
|
|
39
|
-
async def publish(self, message: BaseModel, topic: str) -> None:
|
|
40
|
-
exchange = await self.channel.declare_exchange(
|
|
41
|
-
self.exchange_name,
|
|
42
|
-
type=aio_pika.ExchangeType.TOPIC,
|
|
43
|
-
)
|
|
44
|
-
routing_key = f"{self.exchange_name}.{topic}."
|
|
45
|
-
await exchange.publish(
|
|
46
|
-
aio_pika.Message(body=message.model_dump_json().encode()),
|
|
47
|
-
routing_key=routing_key,
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class AIOPikaConnectionFactory(MessageBusConnectionFactory):
|
|
52
|
-
|
|
53
|
-
def __init__(
|
|
54
|
-
self,
|
|
55
|
-
url: str,
|
|
56
|
-
exchange: str,
|
|
57
|
-
):
|
|
58
|
-
self.url = url
|
|
59
|
-
self.exchange = exchange
|
|
60
|
-
|
|
61
|
-
@asynccontextmanager
|
|
62
|
-
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
|
-
|
|
67
|
-
tx = channel.transaction()
|
|
68
|
-
|
|
69
|
-
await tx.select()
|
|
70
|
-
|
|
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
|
|
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
|
{jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg
RENAMED
|
File without changes
|
{jararaca-0.2.37a10 → jararaca-0.2.37a11}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.webp
RENAMED
|
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
|
|
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
|