datacurrent 1.0.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.
- datacurrent/__init__.py +157 -0
- datacurrent/_version.py +6 -0
- datacurrent/buffering/__init__.py +8 -0
- datacurrent/buffering/backpressure.py +47 -0
- datacurrent/buffering/queue.py +304 -0
- datacurrent/concurrency/__init__.py +8 -0
- datacurrent/concurrency/limits.py +106 -0
- datacurrent/concurrency/tasks.py +86 -0
- datacurrent/config.py +86 -0
- datacurrent/core/__init__.py +17 -0
- datacurrent/core/_util.py +42 -0
- datacurrent/core/lifecycle.py +81 -0
- datacurrent/core/operators.py +844 -0
- datacurrent/core/pipeline.py +64 -0
- datacurrent/core/sink.py +70 -0
- datacurrent/core/stream.py +566 -0
- datacurrent/events/__init__.py +9 -0
- datacurrent/events/bus.py +380 -0
- datacurrent/events/routing.py +59 -0
- datacurrent/events/subscriptions.py +75 -0
- datacurrent/exceptions.py +191 -0
- datacurrent/integrations/__init__.py +18 -0
- datacurrent/integrations/fastapi.py +76 -0
- datacurrent/integrations/starlette.py +75 -0
- datacurrent/observability/__init__.py +17 -0
- datacurrent/observability/logging.py +128 -0
- datacurrent/observability/metrics.py +127 -0
- datacurrent/py.typed +1 -0
- datacurrent/reliability/__init__.py +14 -0
- datacurrent/reliability/retry.py +134 -0
- datacurrent/reliability/timeout.py +39 -0
- datacurrent/transports/__init__.py +31 -0
- datacurrent/transports/_deps.py +23 -0
- datacurrent/transports/base.py +80 -0
- datacurrent/transports/http.py +211 -0
- datacurrent/transports/memory.py +171 -0
- datacurrent/transports/sse.py +374 -0
- datacurrent/transports/websocket.py +277 -0
- datacurrent-1.0.0.dist-info/METADATA +250 -0
- datacurrent-1.0.0.dist-info/RECORD +43 -0
- datacurrent-1.0.0.dist-info/WHEEL +5 -0
- datacurrent-1.0.0.dist-info/licenses/LICENSE +20 -0
- datacurrent-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datacurrent
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python streaming, made simple: async streams, operators, backpressure and real-time transports.
|
|
5
|
+
Author: DataCurrent contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: streaming,async,asyncio,real-time,data pipeline,stream processing,backpressure,reactive,event bus,pubsub,websocket,server-sent events,http streaming,concurrency,observability,http,https
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Framework :: AsyncIO
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Topic :: Internet
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: httpx>=0.27
|
|
27
|
+
Requires-Dist: websockets>=12
|
|
28
|
+
Provides-Extra: starlette
|
|
29
|
+
Requires-Dist: starlette>=0.36; extra == "starlette"
|
|
30
|
+
Provides-Extra: fastapi
|
|
31
|
+
Requires-Dist: fastapi>=0.110; extra == "fastapi"
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Requires-Dist: starlette>=0.36; extra == "all"
|
|
34
|
+
Requires-Dist: fastapi>=0.110; extra == "all"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-cov>=5; extra == "dev"
|
|
39
|
+
Requires-Dist: hypothesis>=6; extra == "dev"
|
|
40
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
41
|
+
Requires-Dist: twine>=5; extra == "dev"
|
|
42
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
43
|
+
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
44
|
+
Requires-Dist: fastapi>=0.110; extra == "dev"
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
|
|
47
|
+
# DataCurrent
|
|
48
|
+
|
|
49
|
+
### Python streaming, made simple.
|
|
50
|
+
|
|
51
|
+
DataCurrent is a production-ready asynchronous streaming library. It gives you
|
|
52
|
+
one consistent API for lists, async generators, queues, WebSockets, Server-Sent
|
|
53
|
+
Events and streaming HTTP responses.
|
|
54
|
+
|
|
55
|
+
**Version 1.0.0 · Stable / Production**
|
|
56
|
+
|
|
57
|
+
- 279 tests passing, with 100% line and branch coverage.
|
|
58
|
+
- Required networking dependencies install automatically.
|
|
59
|
+
- No broker, database, API key or hosted service.
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install datacurrent
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Python 3.11 or newer is required. `httpx` and `websockets` are installed with the
|
|
68
|
+
package, so HTTP streaming, SSE and WebSocket clients work immediately.
|
|
69
|
+
|
|
70
|
+
Framework helpers are optional:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install "datacurrent[fastapi]"
|
|
74
|
+
pip install "datacurrent[starlette]"
|
|
75
|
+
pip install "datacurrent[all]"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Quick start
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import asyncio
|
|
82
|
+
from datacurrent import Stream
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def main() -> None:
|
|
86
|
+
result = await (
|
|
87
|
+
Stream.from_iterable(range(10))
|
|
88
|
+
.filter(lambda number: number % 2 == 0)
|
|
89
|
+
.map(lambda number: number * number)
|
|
90
|
+
.take(4)
|
|
91
|
+
.collect()
|
|
92
|
+
)
|
|
93
|
+
print(result) # [0, 4, 16, 36]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
asyncio.run(main())
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Streams are lazy and single-consumer:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
stream = Stream.from_iterable([1, 2, 3])
|
|
103
|
+
|
|
104
|
+
async for item in stream:
|
|
105
|
+
print(item)
|
|
106
|
+
|
|
107
|
+
# A second iteration raises StreamConsumedError.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Common terminals:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
await Stream.from_iterable([1, 2, 3]).collect() # [1, 2, 3]
|
|
114
|
+
await Stream.from_iterable([1, 2, 3]).count() # 3
|
|
115
|
+
await Stream.from_iterable([3, 1, 2]).reduce(max) # 3
|
|
116
|
+
await Stream.empty().first("fallback") # "fallback"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Async callbacks work everywhere a callback is accepted:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
users = await Stream.from_iterable(user_ids).map(fetch_user, concurrency=8).collect()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Operators
|
|
126
|
+
|
|
127
|
+
`map`, `filter`, `flat_map`, `take`, `skip`, `batch`, `buffer`, `distinct`,
|
|
128
|
+
`enumerate`, `window`, `debounce`, `throttle`, `timeout`, `retry`, `recover`,
|
|
129
|
+
`tap`, `zip`, `merge` and `concat`.
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
batches = Stream.from_iterable(range(1_000)).filter(lambda number: number % 3 == 0).batch(100)
|
|
133
|
+
|
|
134
|
+
async for batch in batches:
|
|
135
|
+
print(len(batch))
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Backpressure
|
|
139
|
+
|
|
140
|
+
Buffers are bounded and block the producer by default. Data is not silently
|
|
141
|
+
dropped.
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
stream = Stream.from_iterable(range(100_000)).buffer(size=64)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Lossy behaviour must be explicit and is counted:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
stream = Stream.from_iterable(range(10_000)).buffer(
|
|
151
|
+
size=16,
|
|
152
|
+
overflow="drop_oldest",
|
|
153
|
+
)
|
|
154
|
+
items = await stream.collect()
|
|
155
|
+
print(len(items), stream.stats["dropped"])
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Policies: `block`, `drop_newest`, `drop_oldest`, `error`.
|
|
159
|
+
|
|
160
|
+
## Real-time data
|
|
161
|
+
|
|
162
|
+
### WebSocket
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from datacurrent import WebSocketStream
|
|
166
|
+
|
|
167
|
+
async with WebSocketStream.connect("wss://example.com/events") as stream:
|
|
168
|
+
await stream.send_json({"subscribe": "trades"})
|
|
169
|
+
async for message in stream:
|
|
170
|
+
print(message)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Server-Sent Events
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from datacurrent import SSEStream
|
|
177
|
+
|
|
178
|
+
async with SSEStream.connect("https://example.com/events") as stream:
|
|
179
|
+
async for event in stream:
|
|
180
|
+
print(event.event, event.json())
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Streaming HTTP
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from datacurrent import HTTPStream
|
|
187
|
+
|
|
188
|
+
async with HTTPStream("https://example.com/feed.ndjson") as response:
|
|
189
|
+
async for line in response.iter_lines():
|
|
190
|
+
handle(line)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Fan-out and events
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from datacurrent import Channel, EventBus
|
|
197
|
+
|
|
198
|
+
channel = Channel(buffer_size=64)
|
|
199
|
+
first = channel.subscribe()
|
|
200
|
+
second = channel.subscribe()
|
|
201
|
+
await channel.publish("deploy finished")
|
|
202
|
+
|
|
203
|
+
bus = EventBus()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@bus.subscribe("user.*")
|
|
207
|
+
async def on_user(event):
|
|
208
|
+
print(event.topic, event.data)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
await bus.publish("user.created", {"user_id": 123})
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Retries
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from datacurrent import RetryPolicy
|
|
218
|
+
|
|
219
|
+
stream = Stream.from_factory(connect_and_read).retry(
|
|
220
|
+
RetryPolicy(max_attempts=3, base_delay=0.2, jitter=0.1)
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Retries require a replayable source, such as a factory or a list. A consumed
|
|
225
|
+
generator raises `NonReplayableStreamError` instead of silently restarting.
|
|
226
|
+
|
|
227
|
+
## Why DataCurrent
|
|
228
|
+
|
|
229
|
+
- One model for iterables, queues, HTTP, SSE and WebSockets.
|
|
230
|
+
- Backpressure by default, with explicit and counted drop policies.
|
|
231
|
+
- Bounded concurrency and cancellation-safe cleanup.
|
|
232
|
+
- No infrastructure: no broker, database or external account.
|
|
233
|
+
- Explicit delivery semantics; no false durability claims.
|
|
234
|
+
- Typed API and sync or async callbacks throughout.
|
|
235
|
+
|
|
236
|
+
HTTP clients such as `httpx` and `aiohttp` handle requests; DataCurrent adds the
|
|
237
|
+
stream engine around them. Reactive libraries provide operator algebra, while
|
|
238
|
+
broker frameworks require Kafka, Redis or RabbitMQ. DataCurrent is the
|
|
239
|
+
in-process streaming layer that works without any of them.
|
|
240
|
+
|
|
241
|
+
## Compatibility and limits
|
|
242
|
+
|
|
243
|
+
- Python 3.11+ on asyncio; no Trio or AnyIO backend.
|
|
244
|
+
- In-memory `Channel` and `EventBus` delivery is at most once.
|
|
245
|
+
- No durable broker adapters or OpenTelemetry exporter in 1.0.0.
|
|
246
|
+
- Not published to PyPI; the project name is provisional.
|
|
247
|
+
|
|
248
|
+
## License
|
|
249
|
+
|
|
250
|
+
MIT.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
datacurrent/__init__.py,sha256=-LVJm3Qd2yjbLOYt2NO9mdViiwPnWuzLoEtValxiDKg,3387
|
|
2
|
+
datacurrent/_version.py,sha256=FZ-fDfPIlmCOb96GbhTHdze7YhOilXVqbGvUOdNv1SM,179
|
|
3
|
+
datacurrent/config.py,sha256=AQvYeNdQvkePSY-EbE0Q0JhEswVKExXzLnJ9Lg_1ESM,2963
|
|
4
|
+
datacurrent/exceptions.py,sha256=Nra8RGSm-bwx7iKt6CK_VeRhvINwjVHgEXdRToHZ5jI,6124
|
|
5
|
+
datacurrent/py.typed,sha256=MEVxnsuDFCH3jsQWkwO_BItWvy5RNDh3BRxEDX8yV4M,70
|
|
6
|
+
datacurrent/buffering/__init__.py,sha256=RUIxcxAHF8LumK1ijgvnbWuW6mDE3GJVXRYcJwavfN4,249
|
|
7
|
+
datacurrent/buffering/backpressure.py,sha256=DwxQg7CVQ8tZyYcyYrgeEW9e5EDokeLRHXV0Ew5wti8,1732
|
|
8
|
+
datacurrent/buffering/queue.py,sha256=YSv_wA-kvMEllSTgmCnZqWQiqXGoDfDqYf1DRPYk470,9786
|
|
9
|
+
datacurrent/concurrency/__init__.py,sha256=9b91jurrbN9ImZpzJ9TEBXmOXYFGISdGKid7JzF4m30,257
|
|
10
|
+
datacurrent/concurrency/limits.py,sha256=FNCbe6tKbL2IbfhoZ8-l0ycH9sYXk_d2_jxLGXFBdAk,2933
|
|
11
|
+
datacurrent/concurrency/tasks.py,sha256=8ptFva17eu42rfKeiCLmbjlNY-yjae1OrG1tPZOzIMw,2743
|
|
12
|
+
datacurrent/core/__init__.py,sha256=eHvFAkOuodDQav1C2BcLIXQeXxP7ek_rtNIREg-Ieek,394
|
|
13
|
+
datacurrent/core/_util.py,sha256=kXtOe--SgQ5tigGBeG0_TiakmM5dSYywD3s5TdpD43E,1272
|
|
14
|
+
datacurrent/core/lifecycle.py,sha256=TgYtrMbvByR3401jSigEItLXi_M_x841JCW0mf-tTkI,2432
|
|
15
|
+
datacurrent/core/operators.py,sha256=jDqYoWOAMUNGiaBrY4fgisehPuL4K8j7Ag8EoH62xDs,25430
|
|
16
|
+
datacurrent/core/pipeline.py,sha256=CIbcP3_l3t7SKajgplg9zycoyIpv9nb24ssvfKkZcWQ,1848
|
|
17
|
+
datacurrent/core/sink.py,sha256=NOy6fpc4y_vWDEU3m9AXyCj_2EDcUTxxjAqsgif_nYk,2078
|
|
18
|
+
datacurrent/core/stream.py,sha256=vN3pjR39RHMI2wVMmDpZQydcboTI3mCJZO-k7qOUBXM,20270
|
|
19
|
+
datacurrent/events/__init__.py,sha256=2QGcVd5f7XX7m1tuVokL4ioICgs_IsFu_w1rS_ypqNA,279
|
|
20
|
+
datacurrent/events/bus.py,sha256=1c_-q4SU4dGl9THdTGJ7umoTc2iNyVBudukgcio3pRw,13722
|
|
21
|
+
datacurrent/events/routing.py,sha256=am9L59dcnmeBVEmuuyfT6H0amiGuAWoSA8W42OxlSH0,1890
|
|
22
|
+
datacurrent/events/subscriptions.py,sha256=Ye4Y0lNkZuL8xbrRrvUQuLdblKoolb7VZ0DiMSo5b8w,2271
|
|
23
|
+
datacurrent/integrations/__init__.py,sha256=E44jhwY2DQDO3yYjw5IF61IyO4l5E6z-sIun7Cr_m_s,479
|
|
24
|
+
datacurrent/integrations/fastapi.py,sha256=236CnlNOdXUFA-OZnSGxfZaUhc2NHw7y2hSHXKAgyQo,2419
|
|
25
|
+
datacurrent/integrations/starlette.py,sha256=VMMOWt8WKmoRWulY_q622Jfib16C2o6Co9oVs3SJiuQ,2156
|
|
26
|
+
datacurrent/observability/__init__.py,sha256=98V3UcsXK9v1CM81kTiT6JbSr2Kc0v43ypuHSeYs9so,403
|
|
27
|
+
datacurrent/observability/logging.py,sha256=yGsZfrTK0qBArsbdVc-ouZVor49XucIDGCbITCXmqv0,3464
|
|
28
|
+
datacurrent/observability/metrics.py,sha256=A5YWDo8lkSfCIygkzJwtceFJXLsteEFb6FDaTpayOgo,3646
|
|
29
|
+
datacurrent/reliability/__init__.py,sha256=jyfZU6cT_i-Fhn4O6y3RKZfn4D4EcJXiOoLPLM24oak,314
|
|
30
|
+
datacurrent/reliability/retry.py,sha256=VVlnvKdZ9RQJo4Ptb98V-644A9_sNqXbORhHirhuOFg,4965
|
|
31
|
+
datacurrent/reliability/timeout.py,sha256=XTYHTlen4IO72452M8KvjJT3w1QJdrhIkQRM9a_kb1w,1135
|
|
32
|
+
datacurrent/transports/__init__.py,sha256=mU6RnvlCtYHyfJ_vCF7hZCFAj9bG306tr2MVZYs4yBM,610
|
|
33
|
+
datacurrent/transports/_deps.py,sha256=vbUjFWdFLG3SxGMyGF8Ncu7uMcRloERHOZlA87ueRbw,623
|
|
34
|
+
datacurrent/transports/base.py,sha256=GQx-eiAkP9sSUaMsY-oG_3qOS1EGHS6SpZ-1_PMAr5s,2397
|
|
35
|
+
datacurrent/transports/http.py,sha256=HXz1H4D-6JhejcQsZp9H0tcDqu2dxd-abxWOcuD-mL4,7497
|
|
36
|
+
datacurrent/transports/memory.py,sha256=BGAZyUbu_-7l8KDFATxBctHb8rQz9_Dkyn-c0ChbZn4,5495
|
|
37
|
+
datacurrent/transports/sse.py,sha256=bUeH2N7DA0aJGlDN7irJ_yVo5eLtS104Ek9o6mBV4iY,13147
|
|
38
|
+
datacurrent/transports/websocket.py,sha256=RNBqzQv6rXZarXJywgfoIO0VBN6c1rX94Y5Zzpr9dGg,10589
|
|
39
|
+
datacurrent-1.0.0.dist-info/licenses/LICENSE,sha256=8uPxhvCmrJv91ttoxbEdtoh7xoifHgKLacwpMmjLYKU,1073
|
|
40
|
+
datacurrent-1.0.0.dist-info/METADATA,sha256=-G30SZO8MCyA67E6-OD5wx1rymB6cny20vXc9aoWtLc,7186
|
|
41
|
+
datacurrent-1.0.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
42
|
+
datacurrent-1.0.0.dist-info/top_level.txt,sha256=CUBOKcU2k3gV2eRDU5JYl49MClij1kYAjK8TN19Ws9s,12
|
|
43
|
+
datacurrent-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CoderProandTester
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
datacurrent
|