esuls 0.1.1__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.
- esuls-0.1.1/LICENSE +21 -0
- esuls-0.1.1/PKG-INFO +290 -0
- esuls-0.1.1/README.md +269 -0
- esuls-0.1.1/pyproject.toml +32 -0
- esuls-0.1.1/setup.cfg +4 -0
- esuls-0.1.1/src/esuls/__init__.py +21 -0
- esuls-0.1.1/src/esuls/db_cli.py +439 -0
- esuls-0.1.1/src/esuls/request_cli.py +384 -0
- esuls-0.1.1/src/esuls/utils.py +29 -0
- esuls-0.1.1/src/esuls.egg-info/PKG-INFO +290 -0
- esuls-0.1.1/src/esuls.egg-info/SOURCES.txt +12 -0
- esuls-0.1.1/src/esuls.egg-info/dependency_links.txt +1 -0
- esuls-0.1.1/src/esuls.egg-info/requires.txt +5 -0
- esuls-0.1.1/src/esuls.egg-info/top_level.txt +1 -0
esuls-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 IperGiove
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
esuls-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: esuls
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Utility library for async database operations, HTTP requests, and parallel execution
|
|
5
|
+
Author-email: IperGiove <ipergiove@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ipergiove/esuls
|
|
8
|
+
Project-URL: Repository, https://github.com/ipergiove/esuls
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.14
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: aiosqlite>=0.21.0
|
|
16
|
+
Requires-Dist: curl-cffi>=0.13.0
|
|
17
|
+
Requires-Dist: fake-useragent>=2.2.0
|
|
18
|
+
Requires-Dist: httpx>=0.28.1
|
|
19
|
+
Requires-Dist: loguru>=0.7.3
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# esuls
|
|
23
|
+
|
|
24
|
+
A Python utility library for async database operations, HTTP requests, and parallel execution utilities.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **AsyncDB** - Type-safe async SQLite with dataclass schemas
|
|
29
|
+
- **Async HTTP client** - High-performance HTTP client with retry logic and connection pooling
|
|
30
|
+
- **Parallel utilities** - Async parallel execution with concurrency control
|
|
31
|
+
- **CloudFlare bypass** - curl-cffi integration for bypassing protections
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# With pip
|
|
37
|
+
pip install esuls
|
|
38
|
+
|
|
39
|
+
# With uv
|
|
40
|
+
uv pip install esuls
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### Parallel Execution
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import asyncio
|
|
49
|
+
from esuls import run_parallel
|
|
50
|
+
|
|
51
|
+
async def fetch_data(id):
|
|
52
|
+
await asyncio.sleep(1)
|
|
53
|
+
return f"Data {id}"
|
|
54
|
+
|
|
55
|
+
async def main():
|
|
56
|
+
# Run multiple async functions in parallel with concurrency limit
|
|
57
|
+
results = await run_parallel(
|
|
58
|
+
lambda: fetch_data(1),
|
|
59
|
+
lambda: fetch_data(2),
|
|
60
|
+
lambda: fetch_data(3),
|
|
61
|
+
limit=20 # Max concurrent tasks
|
|
62
|
+
)
|
|
63
|
+
print(results)
|
|
64
|
+
|
|
65
|
+
asyncio.run(main())
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Database Client (AsyncDB)
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import asyncio
|
|
72
|
+
from dataclasses import dataclass, field
|
|
73
|
+
from esuls import AsyncDB, BaseModel
|
|
74
|
+
|
|
75
|
+
# Define your schema
|
|
76
|
+
@dataclass
|
|
77
|
+
class User(BaseModel):
|
|
78
|
+
name: str = field(metadata={"index": True})
|
|
79
|
+
email: str = field(metadata={"unique": True})
|
|
80
|
+
age: int = 0
|
|
81
|
+
|
|
82
|
+
async def main():
|
|
83
|
+
# Initialize database
|
|
84
|
+
db = AsyncDB(db_path="users.db", table_name="users", schema_class=User)
|
|
85
|
+
|
|
86
|
+
# Save data
|
|
87
|
+
user = User(name="Alice", email="alice@example.com", age=30)
|
|
88
|
+
await db.save(user)
|
|
89
|
+
|
|
90
|
+
# Save multiple items
|
|
91
|
+
users = [
|
|
92
|
+
User(name="Bob", email="bob@example.com", age=25),
|
|
93
|
+
User(name="Charlie", email="charlie@example.com", age=35)
|
|
94
|
+
]
|
|
95
|
+
await db.save_batch(users)
|
|
96
|
+
|
|
97
|
+
# Query data
|
|
98
|
+
results = await db.find(name="Alice")
|
|
99
|
+
print(results)
|
|
100
|
+
|
|
101
|
+
# Query with filters
|
|
102
|
+
adults = await db.find(age__gte=18, order_by="-age")
|
|
103
|
+
|
|
104
|
+
# Count
|
|
105
|
+
count = await db.count(age__gte=18)
|
|
106
|
+
|
|
107
|
+
# Get by ID
|
|
108
|
+
user = await db.get_by_id(user_id)
|
|
109
|
+
|
|
110
|
+
# Delete
|
|
111
|
+
await db.delete(user_id)
|
|
112
|
+
|
|
113
|
+
asyncio.run(main())
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Query Operators:**
|
|
117
|
+
- `field__eq` - Equal (default)
|
|
118
|
+
- `field__gt` - Greater than
|
|
119
|
+
- `field__gte` - Greater than or equal
|
|
120
|
+
- `field__lt` - Less than
|
|
121
|
+
- `field__lte` - Less than or equal
|
|
122
|
+
- `field__neq` - Not equal
|
|
123
|
+
- `field__like` - SQL LIKE
|
|
124
|
+
- `field__in` - IN operator (pass a list)
|
|
125
|
+
|
|
126
|
+
### HTTP Request Client
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
import asyncio
|
|
130
|
+
from esuls import AsyncRequest, make_request
|
|
131
|
+
|
|
132
|
+
# Using context manager (recommended for multiple requests)
|
|
133
|
+
async def example1():
|
|
134
|
+
async with AsyncRequest() as client:
|
|
135
|
+
response = await client.request(
|
|
136
|
+
url="https://api.example.com/data",
|
|
137
|
+
method="GET",
|
|
138
|
+
add_user_agent=True,
|
|
139
|
+
max_attempt=3,
|
|
140
|
+
timeout_request=30
|
|
141
|
+
)
|
|
142
|
+
if response:
|
|
143
|
+
data = response.json()
|
|
144
|
+
print(data)
|
|
145
|
+
|
|
146
|
+
# Using standalone function (uses shared connection pool)
|
|
147
|
+
async def example2():
|
|
148
|
+
response = await make_request(
|
|
149
|
+
url="https://api.example.com/users",
|
|
150
|
+
method="POST",
|
|
151
|
+
json_data={"name": "Alice", "email": "alice@example.com"},
|
|
152
|
+
headers={"Authorization": "Bearer token"},
|
|
153
|
+
max_attempt=5,
|
|
154
|
+
force_response=True # Return response even on error
|
|
155
|
+
)
|
|
156
|
+
if response:
|
|
157
|
+
print(response.status_code)
|
|
158
|
+
print(response.text)
|
|
159
|
+
|
|
160
|
+
asyncio.run(example1())
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Request Parameters:**
|
|
164
|
+
- `url` - Request URL
|
|
165
|
+
- `method` - HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
166
|
+
- `headers` - Request headers
|
|
167
|
+
- `cookies` - Cookies dict
|
|
168
|
+
- `params` - URL parameters
|
|
169
|
+
- `json_data` - JSON body
|
|
170
|
+
- `files` - Multipart file upload
|
|
171
|
+
- `proxy` - Proxy URL
|
|
172
|
+
- `timeout_request` - Timeout in seconds (default: 60)
|
|
173
|
+
- `max_attempt` - Max retry attempts (default: 10)
|
|
174
|
+
- `force_response` - Return response even on error (default: False)
|
|
175
|
+
- `json_response` - Validate JSON response (default: False)
|
|
176
|
+
- `json_response_check` - Check for key in JSON response
|
|
177
|
+
- `skip_response` - Skip if text contains pattern(s)
|
|
178
|
+
- `exception_sleep` - Delay between retries in seconds (default: 10)
|
|
179
|
+
- `add_user_agent` - Add random User-Agent header (default: False)
|
|
180
|
+
|
|
181
|
+
### CloudFlare Bypass
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
import asyncio
|
|
185
|
+
from esuls import make_request_cffi
|
|
186
|
+
|
|
187
|
+
async def fetch_protected_page():
|
|
188
|
+
html = await make_request_cffi("https://protected-site.com")
|
|
189
|
+
if html:
|
|
190
|
+
print(html)
|
|
191
|
+
|
|
192
|
+
asyncio.run(fetch_protected_page())
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Development
|
|
196
|
+
|
|
197
|
+
### Project Structure
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
utils/
|
|
201
|
+
├── pyproject.toml
|
|
202
|
+
├── README.md
|
|
203
|
+
├── LICENSE
|
|
204
|
+
└── src/
|
|
205
|
+
└── esuls/
|
|
206
|
+
├── __init__.py
|
|
207
|
+
├── utils.py # Parallel execution utilities
|
|
208
|
+
├── db_cli.py # AsyncDB with dataclass schemas
|
|
209
|
+
└── request_cli.py # Async HTTP client
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Local Development Installation
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# Navigate to the project
|
|
216
|
+
cd utils
|
|
217
|
+
|
|
218
|
+
# Install in editable mode with uv
|
|
219
|
+
uv pip install -e .
|
|
220
|
+
|
|
221
|
+
# Or with pip
|
|
222
|
+
pip install -e .
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Building and Publishing
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# With uv
|
|
229
|
+
uv build
|
|
230
|
+
twine upload dist/*
|
|
231
|
+
|
|
232
|
+
# Or with traditional tools
|
|
233
|
+
pip install build twine
|
|
234
|
+
python -m build
|
|
235
|
+
twine upload dist/*
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Advanced Features
|
|
239
|
+
|
|
240
|
+
### AsyncDB Schema Definition
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from dataclasses import dataclass, field
|
|
244
|
+
from esuls import BaseModel
|
|
245
|
+
from datetime import datetime
|
|
246
|
+
from typing import Optional, List
|
|
247
|
+
import enum
|
|
248
|
+
|
|
249
|
+
class Status(enum.Enum):
|
|
250
|
+
ACTIVE = "active"
|
|
251
|
+
INACTIVE = "inactive"
|
|
252
|
+
|
|
253
|
+
@dataclass
|
|
254
|
+
class User(BaseModel):
|
|
255
|
+
# BaseModel provides: id, created_at, updated_at
|
|
256
|
+
|
|
257
|
+
# Indexed field
|
|
258
|
+
email: str = field(metadata={"index": True, "unique": True})
|
|
259
|
+
|
|
260
|
+
# Simple fields
|
|
261
|
+
name: str = ""
|
|
262
|
+
age: int = 0
|
|
263
|
+
|
|
264
|
+
# Enum support
|
|
265
|
+
status: Status = Status.ACTIVE
|
|
266
|
+
|
|
267
|
+
# JSON-serialized complex types
|
|
268
|
+
tags: List[str] = field(default_factory=list)
|
|
269
|
+
|
|
270
|
+
# Optional fields
|
|
271
|
+
phone: Optional[str] = None
|
|
272
|
+
|
|
273
|
+
# Table constraints (optional)
|
|
274
|
+
__table_constraints__ = [
|
|
275
|
+
"CHECK (age >= 0)"
|
|
276
|
+
]
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Connection Pooling & Performance
|
|
280
|
+
|
|
281
|
+
The HTTP client uses:
|
|
282
|
+
- Shared connection pool (prevents "too many open files" errors)
|
|
283
|
+
- Automatic retry with exponential backoff
|
|
284
|
+
- SSL optimization
|
|
285
|
+
- Random User-Agent rotation
|
|
286
|
+
- Cookie and header persistence
|
|
287
|
+
|
|
288
|
+
## License
|
|
289
|
+
|
|
290
|
+
MIT License
|
esuls-0.1.1/README.md
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# esuls
|
|
2
|
+
|
|
3
|
+
A Python utility library for async database operations, HTTP requests, and parallel execution utilities.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **AsyncDB** - Type-safe async SQLite with dataclass schemas
|
|
8
|
+
- **Async HTTP client** - High-performance HTTP client with retry logic and connection pooling
|
|
9
|
+
- **Parallel utilities** - Async parallel execution with concurrency control
|
|
10
|
+
- **CloudFlare bypass** - curl-cffi integration for bypassing protections
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# With pip
|
|
16
|
+
pip install esuls
|
|
17
|
+
|
|
18
|
+
# With uv
|
|
19
|
+
uv pip install esuls
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Parallel Execution
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import asyncio
|
|
28
|
+
from esuls import run_parallel
|
|
29
|
+
|
|
30
|
+
async def fetch_data(id):
|
|
31
|
+
await asyncio.sleep(1)
|
|
32
|
+
return f"Data {id}"
|
|
33
|
+
|
|
34
|
+
async def main():
|
|
35
|
+
# Run multiple async functions in parallel with concurrency limit
|
|
36
|
+
results = await run_parallel(
|
|
37
|
+
lambda: fetch_data(1),
|
|
38
|
+
lambda: fetch_data(2),
|
|
39
|
+
lambda: fetch_data(3),
|
|
40
|
+
limit=20 # Max concurrent tasks
|
|
41
|
+
)
|
|
42
|
+
print(results)
|
|
43
|
+
|
|
44
|
+
asyncio.run(main())
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Database Client (AsyncDB)
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import asyncio
|
|
51
|
+
from dataclasses import dataclass, field
|
|
52
|
+
from esuls import AsyncDB, BaseModel
|
|
53
|
+
|
|
54
|
+
# Define your schema
|
|
55
|
+
@dataclass
|
|
56
|
+
class User(BaseModel):
|
|
57
|
+
name: str = field(metadata={"index": True})
|
|
58
|
+
email: str = field(metadata={"unique": True})
|
|
59
|
+
age: int = 0
|
|
60
|
+
|
|
61
|
+
async def main():
|
|
62
|
+
# Initialize database
|
|
63
|
+
db = AsyncDB(db_path="users.db", table_name="users", schema_class=User)
|
|
64
|
+
|
|
65
|
+
# Save data
|
|
66
|
+
user = User(name="Alice", email="alice@example.com", age=30)
|
|
67
|
+
await db.save(user)
|
|
68
|
+
|
|
69
|
+
# Save multiple items
|
|
70
|
+
users = [
|
|
71
|
+
User(name="Bob", email="bob@example.com", age=25),
|
|
72
|
+
User(name="Charlie", email="charlie@example.com", age=35)
|
|
73
|
+
]
|
|
74
|
+
await db.save_batch(users)
|
|
75
|
+
|
|
76
|
+
# Query data
|
|
77
|
+
results = await db.find(name="Alice")
|
|
78
|
+
print(results)
|
|
79
|
+
|
|
80
|
+
# Query with filters
|
|
81
|
+
adults = await db.find(age__gte=18, order_by="-age")
|
|
82
|
+
|
|
83
|
+
# Count
|
|
84
|
+
count = await db.count(age__gte=18)
|
|
85
|
+
|
|
86
|
+
# Get by ID
|
|
87
|
+
user = await db.get_by_id(user_id)
|
|
88
|
+
|
|
89
|
+
# Delete
|
|
90
|
+
await db.delete(user_id)
|
|
91
|
+
|
|
92
|
+
asyncio.run(main())
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Query Operators:**
|
|
96
|
+
- `field__eq` - Equal (default)
|
|
97
|
+
- `field__gt` - Greater than
|
|
98
|
+
- `field__gte` - Greater than or equal
|
|
99
|
+
- `field__lt` - Less than
|
|
100
|
+
- `field__lte` - Less than or equal
|
|
101
|
+
- `field__neq` - Not equal
|
|
102
|
+
- `field__like` - SQL LIKE
|
|
103
|
+
- `field__in` - IN operator (pass a list)
|
|
104
|
+
|
|
105
|
+
### HTTP Request Client
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import asyncio
|
|
109
|
+
from esuls import AsyncRequest, make_request
|
|
110
|
+
|
|
111
|
+
# Using context manager (recommended for multiple requests)
|
|
112
|
+
async def example1():
|
|
113
|
+
async with AsyncRequest() as client:
|
|
114
|
+
response = await client.request(
|
|
115
|
+
url="https://api.example.com/data",
|
|
116
|
+
method="GET",
|
|
117
|
+
add_user_agent=True,
|
|
118
|
+
max_attempt=3,
|
|
119
|
+
timeout_request=30
|
|
120
|
+
)
|
|
121
|
+
if response:
|
|
122
|
+
data = response.json()
|
|
123
|
+
print(data)
|
|
124
|
+
|
|
125
|
+
# Using standalone function (uses shared connection pool)
|
|
126
|
+
async def example2():
|
|
127
|
+
response = await make_request(
|
|
128
|
+
url="https://api.example.com/users",
|
|
129
|
+
method="POST",
|
|
130
|
+
json_data={"name": "Alice", "email": "alice@example.com"},
|
|
131
|
+
headers={"Authorization": "Bearer token"},
|
|
132
|
+
max_attempt=5,
|
|
133
|
+
force_response=True # Return response even on error
|
|
134
|
+
)
|
|
135
|
+
if response:
|
|
136
|
+
print(response.status_code)
|
|
137
|
+
print(response.text)
|
|
138
|
+
|
|
139
|
+
asyncio.run(example1())
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Request Parameters:**
|
|
143
|
+
- `url` - Request URL
|
|
144
|
+
- `method` - HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
145
|
+
- `headers` - Request headers
|
|
146
|
+
- `cookies` - Cookies dict
|
|
147
|
+
- `params` - URL parameters
|
|
148
|
+
- `json_data` - JSON body
|
|
149
|
+
- `files` - Multipart file upload
|
|
150
|
+
- `proxy` - Proxy URL
|
|
151
|
+
- `timeout_request` - Timeout in seconds (default: 60)
|
|
152
|
+
- `max_attempt` - Max retry attempts (default: 10)
|
|
153
|
+
- `force_response` - Return response even on error (default: False)
|
|
154
|
+
- `json_response` - Validate JSON response (default: False)
|
|
155
|
+
- `json_response_check` - Check for key in JSON response
|
|
156
|
+
- `skip_response` - Skip if text contains pattern(s)
|
|
157
|
+
- `exception_sleep` - Delay between retries in seconds (default: 10)
|
|
158
|
+
- `add_user_agent` - Add random User-Agent header (default: False)
|
|
159
|
+
|
|
160
|
+
### CloudFlare Bypass
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
import asyncio
|
|
164
|
+
from esuls import make_request_cffi
|
|
165
|
+
|
|
166
|
+
async def fetch_protected_page():
|
|
167
|
+
html = await make_request_cffi("https://protected-site.com")
|
|
168
|
+
if html:
|
|
169
|
+
print(html)
|
|
170
|
+
|
|
171
|
+
asyncio.run(fetch_protected_page())
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Development
|
|
175
|
+
|
|
176
|
+
### Project Structure
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
utils/
|
|
180
|
+
├── pyproject.toml
|
|
181
|
+
├── README.md
|
|
182
|
+
├── LICENSE
|
|
183
|
+
└── src/
|
|
184
|
+
└── esuls/
|
|
185
|
+
├── __init__.py
|
|
186
|
+
├── utils.py # Parallel execution utilities
|
|
187
|
+
├── db_cli.py # AsyncDB with dataclass schemas
|
|
188
|
+
└── request_cli.py # Async HTTP client
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Local Development Installation
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# Navigate to the project
|
|
195
|
+
cd utils
|
|
196
|
+
|
|
197
|
+
# Install in editable mode with uv
|
|
198
|
+
uv pip install -e .
|
|
199
|
+
|
|
200
|
+
# Or with pip
|
|
201
|
+
pip install -e .
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Building and Publishing
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# With uv
|
|
208
|
+
uv build
|
|
209
|
+
twine upload dist/*
|
|
210
|
+
|
|
211
|
+
# Or with traditional tools
|
|
212
|
+
pip install build twine
|
|
213
|
+
python -m build
|
|
214
|
+
twine upload dist/*
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Advanced Features
|
|
218
|
+
|
|
219
|
+
### AsyncDB Schema Definition
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from dataclasses import dataclass, field
|
|
223
|
+
from esuls import BaseModel
|
|
224
|
+
from datetime import datetime
|
|
225
|
+
from typing import Optional, List
|
|
226
|
+
import enum
|
|
227
|
+
|
|
228
|
+
class Status(enum.Enum):
|
|
229
|
+
ACTIVE = "active"
|
|
230
|
+
INACTIVE = "inactive"
|
|
231
|
+
|
|
232
|
+
@dataclass
|
|
233
|
+
class User(BaseModel):
|
|
234
|
+
# BaseModel provides: id, created_at, updated_at
|
|
235
|
+
|
|
236
|
+
# Indexed field
|
|
237
|
+
email: str = field(metadata={"index": True, "unique": True})
|
|
238
|
+
|
|
239
|
+
# Simple fields
|
|
240
|
+
name: str = ""
|
|
241
|
+
age: int = 0
|
|
242
|
+
|
|
243
|
+
# Enum support
|
|
244
|
+
status: Status = Status.ACTIVE
|
|
245
|
+
|
|
246
|
+
# JSON-serialized complex types
|
|
247
|
+
tags: List[str] = field(default_factory=list)
|
|
248
|
+
|
|
249
|
+
# Optional fields
|
|
250
|
+
phone: Optional[str] = None
|
|
251
|
+
|
|
252
|
+
# Table constraints (optional)
|
|
253
|
+
__table_constraints__ = [
|
|
254
|
+
"CHECK (age >= 0)"
|
|
255
|
+
]
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Connection Pooling & Performance
|
|
259
|
+
|
|
260
|
+
The HTTP client uses:
|
|
261
|
+
- Shared connection pool (prevents "too many open files" errors)
|
|
262
|
+
- Automatic retry with exponential backoff
|
|
263
|
+
- SSL optimization
|
|
264
|
+
- Random User-Agent rotation
|
|
265
|
+
- Cookie and header persistence
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
MIT License
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "esuls"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Utility library for async database operations, HTTP requests, and parallel execution"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.14"
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "IperGiove", email = "ipergiove@gmail.com"}
|
|
13
|
+
]
|
|
14
|
+
license = {text = "MIT"}
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
# All dependencies (always installed)
|
|
22
|
+
dependencies = [
|
|
23
|
+
"aiosqlite>=0.21.0",
|
|
24
|
+
"curl-cffi>=0.13.0",
|
|
25
|
+
"fake-useragent>=2.2.0",
|
|
26
|
+
"httpx>=0.28.1",
|
|
27
|
+
"loguru>=0.7.3",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/ipergiove/esuls"
|
|
32
|
+
Repository = "https://github.com/ipergiove/esuls"
|
esuls-0.1.1/setup.cfg
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
esuls - Utility library for async database operations, HTTP requests, and parallel execution
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.0"
|
|
6
|
+
|
|
7
|
+
# Import all utilities
|
|
8
|
+
from .utils import run_parallel
|
|
9
|
+
from .db_cli import AsyncDB, BaseModel
|
|
10
|
+
from .request_cli import AsyncRequest, make_request, make_request_cffi, Response
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'__version__',
|
|
14
|
+
'run_parallel',
|
|
15
|
+
'AsyncDB',
|
|
16
|
+
'BaseModel',
|
|
17
|
+
'AsyncRequest',
|
|
18
|
+
'make_request',
|
|
19
|
+
'make_request_cffi',
|
|
20
|
+
'Response',
|
|
21
|
+
]
|