tracktolib 0.51.1__tar.gz → 0.53.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.
- {tracktolib-0.51.1 → tracktolib-0.53.1}/PKG-INFO +4 -3
- {tracktolib-0.51.1 → tracktolib-0.53.1}/pyproject.toml +6 -4
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/logs.py +2 -2
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/s3/s3.py +42 -9
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/tests.py +1 -1
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/utils.py +37 -8
- {tracktolib-0.51.1 → tracktolib-0.53.1}/LICENSE +0 -0
- {tracktolib-0.51.1 → tracktolib-0.53.1}/README.md +0 -0
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/__init__.py +0 -0
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/api.py +0 -0
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/http_utils.py +0 -0
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/pg/__init__.py +0 -0
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/pg/query.py +0 -0
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/pg/utils.py +0 -0
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/pg_sync.py +0 -0
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/pg_utils.py +0 -0
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/s3/__init__.py +0 -0
- {tracktolib-0.51.1 → tracktolib-0.53.1}/tracktolib/s3/minio.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tracktolib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.53.1
|
|
4
4
|
Summary: Utility library for python
|
|
5
5
|
Home-page: https://github.com/tracktor/tracktolib
|
|
6
6
|
License: MIT
|
|
@@ -12,6 +12,7 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
16
|
Provides-Extra: api
|
|
16
17
|
Provides-Extra: http
|
|
17
18
|
Provides-Extra: logs
|
|
@@ -22,14 +23,14 @@ Provides-Extra: s3-minio
|
|
|
22
23
|
Provides-Extra: tests
|
|
23
24
|
Requires-Dist: aiobotocore (>=2.9.0) ; extra == "s3"
|
|
24
25
|
Requires-Dist: asyncpg (>=0.27.0) ; extra == "pg"
|
|
25
|
-
Requires-Dist: deepdiff (>=
|
|
26
|
+
Requires-Dist: deepdiff (>=8.1.0) ; extra == "tests"
|
|
26
27
|
Requires-Dist: fastapi (>=0.103.2) ; extra == "api"
|
|
27
28
|
Requires-Dist: httpx (>=0.25.0) ; extra == "http"
|
|
28
29
|
Requires-Dist: minio (>=7.2.0) ; extra == "s3-minio"
|
|
29
30
|
Requires-Dist: psycopg (>=3.1.12) ; extra == "pg-sync"
|
|
30
31
|
Requires-Dist: pycryptodome (>=3.20.0) ; extra == "s3-minio"
|
|
31
32
|
Requires-Dist: pydantic (>=2) ; extra == "api"
|
|
32
|
-
Requires-Dist: python-json-logger (>=2.
|
|
33
|
+
Requires-Dist: python-json-logger (>=3.2.1) ; extra == "logs"
|
|
33
34
|
Requires-Dist: rich (>=13.6.0) ; extra == "pg"
|
|
34
35
|
Project-URL: Repository, https://github.com/tracktor/tracktolib
|
|
35
36
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "tracktolib"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.53.1"
|
|
4
4
|
description = "Utility library for python"
|
|
5
5
|
authors = ["Julien Brayere <julien.brayere@tracktor.fr>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -18,13 +18,13 @@ include = [
|
|
|
18
18
|
[tool.poetry.dependencies]
|
|
19
19
|
python = ">=3.12,<4.0"
|
|
20
20
|
|
|
21
|
-
python-json-logger = { version = ">=2.
|
|
21
|
+
python-json-logger = { version = ">=3.2.1", optional = true }
|
|
22
22
|
psycopg = { version = ">=3.1.12", optional = true }
|
|
23
23
|
minio = { version = ">=7.2.0", optional = true }
|
|
24
24
|
# Needed for minio, otherwise it raises ModuleNotFoundError: No module named 'Crypto.Cipher'
|
|
25
25
|
pycryptodome = { version = ">=3.20.0", optional = true }
|
|
26
26
|
aiobotocore = { version = ">=2.9.0", optional = true }
|
|
27
|
-
deepdiff = { version = ">=
|
|
27
|
+
deepdiff = { version = ">=8.1.0", optional = true }
|
|
28
28
|
httpx = { version = ">=0.25.0", optional = true }
|
|
29
29
|
fastapi = { version = ">=0.103.2", optional = true }
|
|
30
30
|
pydantic = { version = ">=2", optional = true }
|
|
@@ -56,6 +56,8 @@ psycopg = "^3.2.1"
|
|
|
56
56
|
minio = "^7.2.7"
|
|
57
57
|
pycryptodome = "^3.20.0"
|
|
58
58
|
aiobotocore = "^2.13.1"
|
|
59
|
+
python-json-logger = "^3.2.0"
|
|
60
|
+
|
|
59
61
|
|
|
60
62
|
[build-system]
|
|
61
63
|
requires = ["poetry-core"]
|
|
@@ -81,7 +83,7 @@ pythonPlatform = "Linux"
|
|
|
81
83
|
|
|
82
84
|
[tool.commitizen]
|
|
83
85
|
name = "cz_conventional_commits"
|
|
84
|
-
version = "0.
|
|
86
|
+
version = "0.53.1"
|
|
85
87
|
tag_format = "$version"
|
|
86
88
|
version_files = [
|
|
87
89
|
"pyproject.toml:version"
|
|
@@ -3,7 +3,7 @@ from typing import Literal, overload, Any, TypeGuard
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
|
|
5
5
|
try:
|
|
6
|
-
from pythonjsonlogger import
|
|
6
|
+
from pythonjsonlogger.json import JsonFormatter
|
|
7
7
|
except ImportError:
|
|
8
8
|
raise ImportError('Please install pythonjsonlogger or tracktolib with "log" to use this module')
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ LogFormat = Literal["json", "console"]
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@dataclass
|
|
14
|
-
class CustomJsonFormatter(
|
|
14
|
+
class CustomJsonFormatter(JsonFormatter):
|
|
15
15
|
version: str
|
|
16
16
|
|
|
17
17
|
def __init__(self, version: str, *args, **kwargs):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import datetime as dt
|
|
1
2
|
from io import BytesIO
|
|
2
3
|
from pathlib import Path
|
|
3
|
-
import
|
|
4
|
-
from typing import TypedDict, Literal
|
|
4
|
+
from typing import TypedDict, Literal, Callable
|
|
5
5
|
|
|
6
6
|
try:
|
|
7
7
|
from aiobotocore.client import AioBaseClient
|
|
@@ -36,18 +36,48 @@ async def upload_file(
|
|
|
36
36
|
return resp
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
type ContentLength = int
|
|
40
|
+
type ChunkSize = int
|
|
41
|
+
type OnUpdateDownload = Callable[[ChunkSize], None]
|
|
42
|
+
type OnStartDownload = Callable[[ContentLength], None]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def download_file(
|
|
46
|
+
client: AioBaseClient,
|
|
47
|
+
bucket: str,
|
|
48
|
+
path: str,
|
|
49
|
+
*,
|
|
50
|
+
chunk_size: int = -1,
|
|
51
|
+
on_start: OnStartDownload | None = None,
|
|
52
|
+
on_update: OnUpdateDownload | None = None,
|
|
53
|
+
) -> BytesIO | None:
|
|
40
54
|
"""
|
|
41
|
-
Loads a file from a s3 bucket
|
|
55
|
+
Loads a file from a s3 bucket.
|
|
56
|
+
If chunk_size is -1, the file will be loaded in one go
|
|
57
|
+
otherwise, the file will be loaded in chunks of size `chunk_size`.
|
|
58
|
+
When downloading in chunked, you can specify an `on_start` and `on_update`
|
|
59
|
+
callback to get the total size of the file and the size of each chunk downloaded respectively.
|
|
42
60
|
"""
|
|
43
61
|
try:
|
|
44
62
|
resp = await client.get_object(Bucket=bucket, Key=path) # type: ignore
|
|
45
63
|
except client.exceptions.NoSuchKey:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
if on_start is not None:
|
|
67
|
+
on_start(resp["ContentLength"])
|
|
68
|
+
|
|
69
|
+
async with resp["Body"] as stream:
|
|
70
|
+
if chunk_size == -1:
|
|
49
71
|
_data = await stream.read()
|
|
50
72
|
_file = BytesIO(_data)
|
|
73
|
+
else:
|
|
74
|
+
chunks = []
|
|
75
|
+
while chunk := await stream.content.read(chunk_size):
|
|
76
|
+
chunks.append(chunk)
|
|
77
|
+
if on_update is not None:
|
|
78
|
+
on_update(len(chunk))
|
|
79
|
+
_file = BytesIO(b"".join(chunks)) if chunks else None
|
|
80
|
+
|
|
51
81
|
return _file
|
|
52
82
|
|
|
53
83
|
|
|
@@ -120,6 +150,7 @@ async def list_files(
|
|
|
120
150
|
search_query: str | None = None,
|
|
121
151
|
max_items: int | None = None,
|
|
122
152
|
page_size: int | None = None,
|
|
153
|
+
starting_token: str | None = None,
|
|
123
154
|
) -> list[S3Item]:
|
|
124
155
|
"""
|
|
125
156
|
See https://jmespath.org/ for the search query syntax.
|
|
@@ -128,9 +159,11 @@ async def list_files(
|
|
|
128
159
|
paginator = client.get_paginator("list_objects")
|
|
129
160
|
config = {}
|
|
130
161
|
if max_items is not None:
|
|
131
|
-
config["
|
|
162
|
+
config["MaxItems"] = max_items
|
|
132
163
|
if page_size is not None:
|
|
133
|
-
config["
|
|
164
|
+
config["PageSize"] = page_size
|
|
165
|
+
if starting_token is not None:
|
|
166
|
+
config["StartingToken"] = starting_token
|
|
134
167
|
|
|
135
168
|
page_iterator = paginator.paginate(Bucket=bucket, Prefix=path, PaginationConfig=config if config else {})
|
|
136
169
|
filtered_iterator = page_iterator.search(search_query) if search_query else page_iterator
|
|
@@ -10,20 +10,49 @@ import subprocess
|
|
|
10
10
|
from decimal import Decimal
|
|
11
11
|
from ipaddress import IPv4Address, IPv6Address
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from typing import Iterable, TypeVar, Iterator, Literal, overload, Any
|
|
13
|
+
from typing import Iterable, TypeVar, Iterator, Literal, overload, Any, Callable
|
|
14
|
+
|
|
14
15
|
|
|
15
16
|
T = TypeVar("T")
|
|
16
17
|
|
|
18
|
+
type OnCmdUpdate = Callable[[str], None]
|
|
19
|
+
type OnCmdDone = Callable[[str, str, int], None]
|
|
20
|
+
|
|
17
21
|
|
|
18
|
-
def exec_cmd(
|
|
22
|
+
def exec_cmd(
|
|
23
|
+
cmd: str | list[str],
|
|
24
|
+
*,
|
|
25
|
+
env: dict | None = None,
|
|
26
|
+
on_update: OnCmdUpdate | None = None,
|
|
27
|
+
on_done: OnCmdDone | None = None,
|
|
28
|
+
**kwargs,
|
|
29
|
+
) -> str:
|
|
19
30
|
default_shell = os.getenv("SHELL", "/bin/bash")
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
cmd,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
process = subprocess.Popen(
|
|
33
|
+
cmd,
|
|
34
|
+
shell=True,
|
|
35
|
+
stdout=subprocess.PIPE,
|
|
36
|
+
stderr=subprocess.PIPE,
|
|
37
|
+
executable=default_shell,
|
|
38
|
+
env=env,
|
|
39
|
+
text=True,
|
|
40
|
+
**kwargs,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if on_update is not None:
|
|
44
|
+
for line in process.stderr or []:
|
|
45
|
+
on_update(line)
|
|
46
|
+
|
|
47
|
+
stdout, stderr = process.communicate()
|
|
48
|
+
exit_code = process.wait()
|
|
49
|
+
|
|
50
|
+
if on_done is not None:
|
|
51
|
+
on_done(stdout, stderr, exit_code)
|
|
52
|
+
|
|
53
|
+
if exit_code != 0:
|
|
54
|
+
raise Exception(stderr)
|
|
55
|
+
return stdout
|
|
27
56
|
|
|
28
57
|
|
|
29
58
|
async def aexec_cmd(cmd: str | list[str], *, encoding: str = "utf-8", env: dict | None = None) -> str:
|
|
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
|