async-batcher 0.1.0__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.
@@ -0,0 +1 @@
1
+
File without changes
File without changes
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import aioboto3
7
+
8
+ from async_batcher.batcher import AsyncBatcher
9
+
10
+ if TYPE_CHECKING:
11
+ from aiobotocore.config import AioConfig
12
+ from types_aiobotocore_dynamodb import DynamoDBServiceResource
13
+ from types_aiobotocore_dynamodb.type_defs import TableAttributeValueTypeDef
14
+
15
+
16
+ @dataclass(kw_only=True)
17
+ class GetItem:
18
+ table_name: str
19
+ key: dict[str, TableAttributeValueTypeDef]
20
+
21
+
22
+ class AsyncDynamoDbGetBatcher(AsyncBatcher[GetItem, dict[str, Any]]):
23
+ """Batcher for DynamoDB GetItem operation. It uses aioboto3 to interact with DynamoDB.
24
+
25
+ Args:
26
+ region_name: The region to use.
27
+ use_ssl: Whether to use SSL/TLS.
28
+ verify: Whether to verify SSL certificates.
29
+ endpoint_url: The complete URL to use for the constructed client. This is useful for local testing.
30
+ config: The configuration for the session.
31
+ aioboto3_session: The aioboto3 session to use. If not provided, a new session is created.
32
+ batch_size: The maximum number of items to process in a single batch. The default is 100 items,
33
+ which is the maximum number of items that can be processed in a single batch.
34
+ sleep_time (float, optional): The time to sleep between checking if the result is ready in seconds.
35
+ Defaults to 0.01. Set it to a value close to the expected time to process a batch
36
+ buffering_time (float, optional): The time to sleep after processing a batch or checking the buffer
37
+ in seconds. Defaults to 0.001.
38
+ You can increase this value if you don't need a low latency, but want to reduce the number of
39
+ processed batches.
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ *,
45
+ region_name: str | None = None,
46
+ use_ssl: bool | None = None,
47
+ verify: bool | None = None,
48
+ endpoint_url: str | None = None,
49
+ config: AioConfig | None = None,
50
+ aioboto3_session: aioboto3.Session | None = None,
51
+ batch_size: int = 100,
52
+ sleep_time: float = 0.01,
53
+ buffering_time: float = 0.001,
54
+ ):
55
+ super().__init__(batch_size=batch_size, sleep_time=sleep_time, buffering_time=buffering_time)
56
+ self.region_name = region_name
57
+ self.use_ssl = use_ssl
58
+ self.verify = verify
59
+ self.endpoint_url = endpoint_url
60
+ self.config = config
61
+ self.aioboto3_session = aioboto3_session or aioboto3.Session()
62
+
63
+ async def process_batch(self, batch: list[GetItem]) -> list[dict[str, TableAttributeValueTypeDef]]:
64
+ indexed_items: dict[tuple, int] = {}
65
+ request_items = {}
66
+ tables_keys = {}
67
+ for ind, item in enumerate(batch):
68
+ if item.table_name not in request_items:
69
+ request_items[item.table_name] = {"Keys": []}
70
+ tables_keys[item.table_name] = sorted(item.key.keys())
71
+ # TODO: support ProjectionExpression and ConsistentRead
72
+ request_items[item.table_name]["Keys"].append(item.key)
73
+ indexed_items[(item.table_name, *[item.key[key] for key in tables_keys[item.table_name]])] = ind
74
+
75
+ dynamodb: DynamoDBServiceResource
76
+ async with self.aioboto3_session.resource(
77
+ "dynamodb",
78
+ region_name=self.region_name,
79
+ use_ssl=self.use_ssl,
80
+ verify=self.verify,
81
+ endpoint_url=self.endpoint_url,
82
+ config=self.config,
83
+ ) as dynamodb:
84
+ response = await dynamodb.batch_get_item(
85
+ RequestItems=request_items,
86
+ ReturnConsumedCapacity="NONE",
87
+ )
88
+ result: list[None | dict[str, TableAttributeValueTypeDef]] = [None] * len(batch)
89
+ # TODO: handle UnprocessedKeys
90
+ for table in response["Responses"]:
91
+ for item in response["Responses"][table]:
92
+ index = indexed_items[(table, *[item[key] for key in tables_keys[table]])]
93
+ result[index] = item
94
+ return result
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Literal
5
+
6
+ import aioboto3
7
+
8
+ from async_batcher.batcher import AsyncBatcher
9
+
10
+ if TYPE_CHECKING:
11
+ from aiobotocore.config import AioConfig
12
+ from types_aiobotocore_dynamodb import DynamoDBServiceResource
13
+ from types_aiobotocore_dynamodb.type_defs import TableAttributeValueTypeDef
14
+
15
+
16
+ @dataclass(kw_only=True)
17
+ class WriteOperation:
18
+ operation: Literal["PUT", "DELETE"]
19
+ table_name: str
20
+ data: dict[str, TableAttributeValueTypeDef]
21
+
22
+
23
+ class AsyncDynamoDbWriteBatcher(AsyncBatcher[WriteOperation, None]):
24
+ """Batcher for DynamoDB WriteOperation. It uses aioboto3 to interact with DynamoDB.
25
+
26
+ Args:
27
+ region_name: The region to use.
28
+ use_ssl: Whether to use SSL/TLS.
29
+ verify: Whether to verify SSL certificates.
30
+ endpoint_url: The complete URL to use for the constructed client. This is useful for local testing.
31
+ config: The configuration for the session.
32
+ aioboto3_session: The aioboto3 session to use. If not provided, a new session is created.
33
+ batch_size: The maximum number of items to process in a single batch. The default is 25 items,
34
+ which is the maximum number of items that can be processed in a single batch.
35
+ sleep_time (float, optional): The time to sleep between checking if the result is ready in seconds.
36
+ Defaults to 0.01. Set it to a value close to the expected time to process a batch
37
+ buffering_time (float, optional): The time to sleep after processing a batch or checking the buffer
38
+ in seconds. Defaults to 0.001.
39
+ You can increase this value if you don't need a low latency, but want to reduce the number of
40
+ processed batches.
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ *,
46
+ region_name: str | None = None,
47
+ use_ssl: bool | None = None,
48
+ verify: bool | None = None,
49
+ endpoint_url: str | None = None,
50
+ config: AioConfig | None = None,
51
+ aioboto3_session: aioboto3.Session | None = None,
52
+ batch_size: int = 25,
53
+ sleep_time: float = 0.01,
54
+ buffering_time: float = 0.001,
55
+ ):
56
+ super().__init__(batch_size=batch_size, sleep_time=sleep_time, buffering_time=buffering_time)
57
+ self.region_name = region_name
58
+ self.use_ssl = use_ssl
59
+ self.verify = verify
60
+ self.endpoint_url = endpoint_url
61
+ self.config = config
62
+ self.aioboto3_session = aioboto3_session or aioboto3.Session()
63
+
64
+ async def process_batch(self, batch: list[WriteOperation]) -> None:
65
+ request_items = {}
66
+ for operation in batch:
67
+ if operation.table_name not in request_items:
68
+ request_items[operation.table_name] = []
69
+ request = {}
70
+ if operation.operation == "PUT":
71
+ request["PutRequest"] = {"Item": operation.data}
72
+ elif operation.operation == "DELETE":
73
+ request["DeleteRequest"] = {"Key": operation.data}
74
+ request_items[operation.table_name].append(request)
75
+
76
+ dynamodb: DynamoDBServiceResource
77
+ async with self.aioboto3_session.resource(
78
+ "dynamodb",
79
+ region_name=self.region_name,
80
+ use_ssl=self.use_ssl,
81
+ verify=self.verify,
82
+ endpoint_url=self.endpoint_url,
83
+ config=self.config,
84
+ ) as dynamodb:
85
+ # TODO: return something useful
86
+ await dynamodb.batch_write_item(
87
+ RequestItems=request_items,
88
+ ReturnConsumedCapacity="NONE",
89
+ ReturnItemCollectionMetrics="NONE",
90
+ )
@@ -0,0 +1,147 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ import asyncio
5
+ import logging
6
+ import uuid
7
+ from collections import deque
8
+ from threading import Thread
9
+ from typing import Generic, TypeVar
10
+
11
+ T = TypeVar("T")
12
+ S = TypeVar("S")
13
+
14
+
15
+ class AsyncBatcher(Generic[T, S], abc.ABC, Thread):
16
+ """A generic class for batching and processing items asynchronously.
17
+
18
+ Args:
19
+ batch_size (int, optional): The max number of items to process in a batch. Defaults to -1 (no limit).
20
+ sleep_time (float, optional): The time to sleep between checking if the result is ready in seconds.
21
+ Defaults to 0.01. Set it to a value close to the expected time to process a batch
22
+ buffering_time (float, optional): The time to sleep after processing a batch or checking the buffer
23
+ in seconds. Defaults to 0.001.
24
+ You can increase this value if you don't need a low latency, but want to reduce the number of
25
+ processed batches.
26
+ """
27
+
28
+ _buffer = deque[tuple[uuid.UUID, T]]
29
+ _results = dict[uuid.UUID, S]
30
+ logger = logging.getLogger(__name__)
31
+
32
+ def __init__(
33
+ self,
34
+ *,
35
+ batch_size: int = -1,
36
+ sleep_time: float = 0.01,
37
+ buffering_time: float = 0.001,
38
+ ):
39
+ super().__init__()
40
+ self.batch_size = batch_size
41
+ self.sleep_time = sleep_time
42
+ self.buffering_time = buffering_time
43
+ self._buffer = deque()
44
+ self._results = {}
45
+ self._current_batch = 0
46
+ self._should_stop = False
47
+ self._force_stop = False
48
+ self._is_running = False
49
+
50
+ @abc.abstractmethod
51
+ async def process_batch(self, batch: list[T]) -> list[S] | None:
52
+ """Process a batch of items.
53
+
54
+ This method should be overridden by the user to define how to process a batch of items.
55
+ """
56
+
57
+ async def _process_single(self, *, item: T) -> S:
58
+ """Process a single item.
59
+
60
+ This method adds the item to the buffer and waits for the result to be ready.
61
+ It's used by the `process` method to add an item to the buffer and get the result when it's ready.
62
+
63
+ Args:
64
+ item (T): The item to process.
65
+
66
+ Returns:
67
+ S: The result of processing the item.
68
+ """
69
+ started_at = asyncio.get_event_loop().time()
70
+ query_id = uuid.uuid4()
71
+ last_checked_batch = self._current_batch
72
+ self.logger.debug(f"Adding item {query_id} to the buffer.")
73
+ self._buffer.append((query_id, item))
74
+ while True:
75
+ if self._current_batch > last_checked_batch:
76
+ last_checked_batch = self._current_batch
77
+ if query_id in self._results:
78
+ elapsed_time = asyncio.get_event_loop().time() - started_at
79
+ self.logger.debug(f"Item {query_id} is ready after {elapsed_time} seconds.")
80
+ return self._results.pop(query_id)
81
+ await asyncio.sleep(self.sleep_time)
82
+
83
+ async def process(self, item: T) -> S:
84
+ """Add an item to the buffer and get the result when it's ready.
85
+
86
+ Args:
87
+ item (T): The item to process.
88
+
89
+ Returns:
90
+ S: The result of processing the item.
91
+ """
92
+ if not self._is_running:
93
+ raise RuntimeError("The batcher is not running.")
94
+ result = await asyncio.get_event_loop().create_task(self._process_single(item=item))
95
+ if isinstance(result, Exception):
96
+ raise result
97
+ return result
98
+
99
+ async def _arun(self):
100
+ """Run the batcher asynchronously."""
101
+ self._is_running = True
102
+ while not self._should_stop or (not self._force_stop and len(self._buffer) > 0):
103
+ ids = []
104
+ batch = []
105
+ while self._buffer and (len(batch) < self.batch_size or self.batch_size == -1):
106
+ query_id, item = self._buffer.popleft()
107
+ batch.append(item)
108
+ ids.append(query_id)
109
+ if batch:
110
+ started_at = asyncio.get_event_loop().time()
111
+ try:
112
+ results = await asyncio.get_event_loop().create_task(self.process_batch(batch=batch))
113
+ if results is None:
114
+ results = [None] * len(batch)
115
+ if len(results) != len(batch):
116
+ raise ValueError(f"Expected to get {len(batch)} results, but got {len(results)}.")
117
+ except Exception as e:
118
+ self.logger.error("Error processing batch", exc_info=True)
119
+ results = [e] * len(batch)
120
+ for query_id, result in zip(ids, results):
121
+ self._results[query_id] = result
122
+ elapsed_time = asyncio.get_event_loop().time() - started_at
123
+ self.logger.debug(
124
+ f"Processed batch {self._current_batch} of {len(batch)} elements"
125
+ f" in {elapsed_time} seconds."
126
+ )
127
+ self._current_batch += 1
128
+ await asyncio.sleep(max(self.buffering_time - elapsed_time, 0))
129
+ else:
130
+ self.logger.debug("No items to process. Sleeping.")
131
+ await asyncio.sleep(self.buffering_time)
132
+ self._is_running = False
133
+
134
+ def run(self):
135
+ """Run the batcher thread."""
136
+ asyncio.run(self._arun())
137
+
138
+ def stop(self, force: bool = False):
139
+ """Stop the batcher thread.
140
+
141
+ Args:
142
+ force (bool, optional): Whether to force stop the batcher without waiting for processing
143
+ the remaining buffer items. Defaults to False.
144
+ """
145
+ if force:
146
+ self._force_stop = True
147
+ self._should_stop = True
File without changes
File without changes
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from typing import TYPE_CHECKING
5
+
6
+ from async_batcher.batcher import AsyncBatcher
7
+
8
+ if TYPE_CHECKING:
9
+ from concurrent.futures import Executor
10
+
11
+ from keras import Model
12
+
13
+
14
+ class KerasAsyncBatcher(AsyncBatcher):
15
+ """Batcher for Keras models.
16
+
17
+ Args:
18
+ model: The Keras model to use for prediction.
19
+ executor: The executor to use for running the prediction.
20
+ batch_size (int, optional): The max number of items to process in a batch. Defaults to -1 (no limit).
21
+ sleep_time (float, optional): The time to sleep between checking if the result is ready in seconds.
22
+ Defaults to 0.01. Set it to a value close to the expected time to process a batch
23
+ buffering_time (float, optional): The time to sleep after processing a batch or checking the buffer
24
+ in seconds. Defaults to 0.001.
25
+ You can increase this value if you don't need a low latency, but want to reduce the number of
26
+ processed batches.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ *,
32
+ model: Model,
33
+ executor: Executor | None = None,
34
+ batch_size: int = -1,
35
+ sleep_time: float = 0.01,
36
+ buffering_time: float = 0.001,
37
+ ):
38
+ super().__init__(batch_size=batch_size, sleep_time=sleep_time, buffering_time=buffering_time)
39
+ self.model = model
40
+ self.executor = executor
41
+
42
+ async def process_batch(self, batch):
43
+ result = await asyncio.get_event_loop().run_in_executor(
44
+ self.executor, lambda _batch: self.model.predict(_batch, batch_size=len(_batch)), batch
45
+ )
46
+ return result
File without changes
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from typing import TYPE_CHECKING
5
+
6
+ from async_batcher.batcher import AsyncBatcher
7
+
8
+ if TYPE_CHECKING:
9
+ from concurrent.futures import Executor
10
+
11
+ from sklearn.base import BaseEstimator
12
+
13
+
14
+ class SklearnAsyncBatcher(AsyncBatcher):
15
+ """Batcher for scikit-learn models.
16
+
17
+ Args:
18
+ model: The scikit-learn model to use for prediction.
19
+ executor: The executor to use for running the prediction.
20
+ batch_size (int, optional): The max number of items to process in a batch. Defaults to -1 (no limit).
21
+ sleep_time (float, optional): The time to sleep between checking if the result is ready in seconds.
22
+ Defaults to 0.01. Set it to a value close to the expected time to process a batch
23
+ buffering_time (float, optional): The time to sleep after processing a batch or checking the buffer
24
+ in seconds. Defaults to 0.001.
25
+ You can increase this value if you don't need a low latency, but want to reduce the number of
26
+ processed batches.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ *,
32
+ model: BaseEstimator,
33
+ executor: Executor | None = None,
34
+ batch_size: int = -1,
35
+ sleep_time: float = 0.01,
36
+ buffering_time: float = 0.001,
37
+ ):
38
+ super().__init__(batch_size=batch_size, sleep_time=sleep_time, buffering_time=buffering_time)
39
+ self.model = model
40
+ self.executor = executor
41
+
42
+ async def process_batch(self, batch):
43
+ if hasattr(self.model, "predict"):
44
+ result = await asyncio.get_event_loop().run_in_executor(self.executor, self.model.predict, batch)
45
+ return result
46
+ else:
47
+ raise AttributeError("Model does not have a predict method")
File without changes
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Any, Literal
5
+
6
+ from cassandra.cqlengine.query import BatchQuery
7
+
8
+ from async_batcher.batcher import AsyncBatcher
9
+
10
+ if TYPE_CHECKING:
11
+ from cassandra.cqlengine.models import Model
12
+
13
+
14
+ @dataclass(kw_only=True)
15
+ class WriteOperation:
16
+ operation: Literal["INSERT", "UPDATE", "DELETE"]
17
+ model: Model
18
+ key: dict[str, Any] | None = None
19
+ data: dict[str, Any] | None = None
20
+
21
+
22
+ class AsyncScyllaDbWriteBatcher(AsyncBatcher[WriteOperation, None]):
23
+ async def process_batch(self, *, batch: list[WriteOperation]) -> list[None | Exception]:
24
+ results = []
25
+ with BatchQuery() as b:
26
+ for op in batch:
27
+ if op.operation == "INSERT":
28
+ if op.data is None:
29
+ results.append(ValueError("data must be provided for INSERT operations"))
30
+ else:
31
+ op.model.batch(b).create(**op.data)
32
+ results.append(None)
33
+ elif op.operation == "UPDATE":
34
+ if op.key is None or op.data is None:
35
+ results.append(ValueError("key and data must be provided for UPDATE operations"))
36
+ else:
37
+ op.model.objects(**op.key).batch(b).update(**op.data)
38
+ results.append(None)
39
+ elif op.operation == "DELETE":
40
+ if op.key is None:
41
+ results.append(ValueError("key must be provided for DELETE operations"))
42
+ else:
43
+ op.model.objects(**op.key).batch(b).delete()
44
+ results.append(None)
45
+ return results
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.1
2
+ Name: async-batcher
3
+ Version: 0.1.0
4
+ Summary: A service to batch your http requests.
5
+ Home-page: https://github.com/hussein-awala/async-batcher
6
+ License: Apache-2.0
7
+ Keywords: python,asyncio,rest,grpc,fastapi
8
+ Author: Hussein Awala
9
+ Author-email: hussein@awala.fr
10
+ Requires-Python: >=3.8,<4.0
11
+ Classifier: License :: OSI Approved :: Apache Software License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Provides-Extra: aws
19
+ Provides-Extra: keras
20
+ Provides-Extra: scylla
21
+ Provides-Extra: sklearn
22
+ Requires-Dist: aioboto3 (>=12.3,<13.0) ; extra == "aws"
23
+ Requires-Dist: keras (>=2.12,<3.0) ; extra == "keras"
24
+ Requires-Dist: scikit-learn (>=1.2,<2.0) ; extra == "sklearn"
25
+ Requires-Dist: scylla-driver (>=3,<4) ; extra == "scylla"
26
+ Project-URL: Bug Tracker, https://github.com/hussein-awala/async-batcher/issues
27
+ Project-URL: Documentation, https://github.com/hussein-awala/async-batcher/blob/main/README.md
28
+ Project-URL: Repository, https://github.com/hussein-awala/async-batcher
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Async Batcher
32
+
33
+ This project provides a Python library to batch the asynchronous requests and handle them in batches.
34
+
35
+ ## How to use
36
+
37
+ To use the library, you need to install the package in your environment. You can install the package using pip:
38
+
39
+ ```bash
40
+ pip install async-batcher
41
+ ```
42
+
43
+ Then, you can create a new `AsyncBatcher` class by implementing the `process_batch` method:
44
+
45
+ ```python
46
+ from async_batcher.batcher import AsyncBatcher
47
+
48
+ class MyAsyncBatcher(AsyncBatcher):
49
+ async def process_batch(self, batch):
50
+ # Process the batch
51
+ print(batch)
52
+
53
+ # Create a new instance of the `MyAsyncBatcher` class
54
+ async_batcher = MyAsyncBatcher(batch_size=20)
55
+ async_batcher.start()
56
+ ```
57
+
58
+ ## Benchmark
59
+
60
+ To evaluate the performance of the `AsyncBatcher` library, we used the [Keras example](examples/keras)
61
+ with [locust](https://locust.io/) to simulate multiple users making requests to the server.
62
+
63
+ In this example, we have a tensorflow model that takes ~11ms to make a single prediction and ~13ms to process
64
+ a batch of 200 predictions.
65
+
66
+ For this benchmark, we ran the FastAPI server with Uvicorn with 12 workers,
67
+ on a MacBook Pro with Apple M2 Max chip (12 cores).
68
+
69
+ Predict endpoint:
70
+
71
+ ![predict__total_requests_per_second.png](assets%2Fpredict__total_requests_per_second.png)
72
+
73
+ Optimized Predict endpoint:
74
+
75
+ ![optimized_predict__total_requests_per_second.png](assets%2Foptimized_predict__total_requests_per_second.png)
76
+
77
+ As we can see from the graphs, for the `/predict` endpoint, the failure rate started to increase linearly at the RPS
78
+ of ~284 requests/second, and the 95th percentile response time was ~1100ms at this point. Then with the increase of
79
+ the RPS, the number of successful requests was ~130 requests/second in average, with a high failure rate
80
+ (~300 from ~400 requests/second).
81
+
82
+ For the `/optimized_predict` endpoint, the failure was smaller than 3 requests/second (0.00625%) during the whole test,
83
+ the average response time was increasing slightly with the increase of the RPS, and it reached ~120ms in average at
84
+ the end of the test (> 480 requests/second), with a 95th percentile response time almost stable and smaller than 500ms.
85
+
86
+ You can find check the reports for more details:
87
+ - [predict__report.html](assets/predict__report.html)
88
+ - [optimized_predict__report.html](assets/optimized_predict__report.html)
89
+ ## Use cases
90
+
91
+ The `AsyncBatcher` library can be used in any application that needs to handle asynchronous requests in batches,
92
+ such as:
93
+ - Serving machine learning models that optimize the batch processing (e.g. TensorFlow, PyTorch, Scikit-learn, etc.)
94
+ - Storing multiple records in a database in a single query to optimize the I/O operations (or to reduce the cost of the
95
+ database operations, e.g. AWS DynamoDB)
96
+ - Sending multiple messages in a single request to optimize the network operations (or to reduce the cost of the network
97
+ operations, e.g. Kafka, RabbitMQ, AWS SQS, AWS SNS, etc.)
98
+
99
+
@@ -0,0 +1,16 @@
1
+ async_batcher/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
+ async_batcher/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ async_batcher/aws/dynamodb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ async_batcher/aws/dynamodb/get.py,sha256=fkYjx4RhhiQ0uXF6Me5nTGZ9MSNYmaAG8rZ9_B681mI,4063
5
+ async_batcher/aws/dynamodb/write.py,sha256=bWuSaF435nEhix9TeaQFNSBwqAhtY-8yE1lZYDtRXa0,3679
6
+ async_batcher/batcher.py,sha256=M1CxKnoRW-0zadBFaVdclRTup9jD3Jw26O033cuUz_o,5683
7
+ async_batcher/ml/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ async_batcher/ml/keras/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ async_batcher/ml/keras/model.py,sha256=x9t9YFcbR6BM7SjQyv1mh5Nikg9SigWF9wkdODLbNlU,1657
10
+ async_batcher/ml/sklearn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ async_batcher/ml/sklearn/model.py,sha256=hh_xtR0o24V5Yk8OH7tQONR0C-FD4OAv8tPRKT6rctg,1765
12
+ async_batcher/scylladb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ async_batcher/scylladb/update.py,sha256=a3X8Bea8n_bwJYw-SrZ-dMgSy2noQnBMNkXXM3AOs7Q,1745
14
+ async_batcher-0.1.0.dist-info/METADATA,sha256=TA1GlST7JFBf_62ih3FURPXp7tJiEvPEzaRDYamFDMs,4205
15
+ async_batcher-0.1.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
16
+ async_batcher-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.8.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any