avrokit 0.0.7__tar.gz → 0.1.2__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.
- {avrokit-0.0.7 → avrokit-0.1.2}/Makefile +5 -4
- {avrokit-0.0.7 → avrokit-0.1.2}/PKG-INFO +7 -8
- {avrokit-0.0.7 → avrokit-0.1.2}/README.md +2 -6
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/io/reader.py +2 -1
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/io/writer.py +2 -7
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/httpserver.py +4 -1
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/tojson.py +14 -1
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/url/base.py +34 -2
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/url/file.py +13 -15
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/url/google.py +3 -2
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/url/http.py +8 -4
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/url/s3.py +6 -3
- {avrokit-0.0.7 → avrokit-0.1.2}/pyproject.toml +6 -2
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_io_schema.py +223 -20
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_tools_httpserver.py +78 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_tools_integration.py +70 -0
- avrokit-0.1.2/tests/test_url_contract.py +151 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_url_file.py +13 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_url_google.py +19 -0
- avrokit-0.1.2/tests/test_url_http.py +681 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_url_s3.py +19 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/uv.lock +259 -3
- avrokit-0.0.7/tests/test_url_http.py +0 -91
- {avrokit-0.0.7 → avrokit-0.1.2}/.github/workflows/check.yml +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/.github/workflows/release.yml +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/.gitignore +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/LICENSE +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/LICENSES/Apache-2.0.txt +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/__init__.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/asyncio/__init__.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/asyncio/reader.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/asyncio/writer.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/io/__init__.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/io/compact.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/io/schema.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/py.typed +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/__init__.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/__main__.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/base.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/cat.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/concat.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/count.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/filesort.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/fromparquet.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/getmeta.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/getschema.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/partition.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/repair.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/stats.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/tools/toparquet.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/url/__init__.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/url/factory.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/avrokit/url/utils.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/__init__.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_additional_coverage.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_asyncio.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_exports.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_io.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_plan_coverage.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_property_based.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_schema_advanced.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_tools_repair.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/tests/test_url.py +0 -0
- {avrokit-0.0.7 → avrokit-0.1.2}/uv.lock.license +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
-
.PHONY: install test test-coverage lint
|
|
5
|
+
.PHONY: install test test-coverage lint fix typecheck format build publish lock check license-check bump help
|
|
6
6
|
|
|
7
7
|
help: ## Display this help
|
|
8
8
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
|
@@ -25,9 +25,6 @@ test-coverage: ## Run tests with coverage report
|
|
|
25
25
|
lint: ## Run ruff linter
|
|
26
26
|
uv run ruff check .
|
|
27
27
|
|
|
28
|
-
lint-fix: ## Run ruff linter and apply fixes
|
|
29
|
-
uv run ruff check --fix .
|
|
30
|
-
|
|
31
28
|
typecheck: ## Run type checker
|
|
32
29
|
uv run ty check
|
|
33
30
|
|
|
@@ -40,6 +37,10 @@ bump: ## Bump version: make bump part=patch|minor|major
|
|
|
40
37
|
lock: ## Update the lockfile
|
|
41
38
|
uv lock
|
|
42
39
|
|
|
40
|
+
fix: ## Run ruff linter and apply fixes
|
|
41
|
+
uv run ruff format .
|
|
42
|
+
uv run ruff check --fix .
|
|
43
|
+
|
|
43
44
|
check: lint typecheck format test license-check ## Run all checks (lint, typecheck, format, test)
|
|
44
45
|
|
|
45
46
|
license-check: ## Check that all files have REUSE license headers
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: avrokit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Python utilities for working with Avro data files
|
|
5
5
|
Project-URL: Homepage, https://github.com/brandtg/avrokit
|
|
6
6
|
Project-URL: Repository, https://github.com/brandtg/avrokit
|
|
@@ -12,10 +12,12 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
19
|
Classifier: Typing :: Typed
|
|
18
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.10
|
|
19
21
|
Requires-Dist: avro>=1.12.1
|
|
20
22
|
Requires-Dist: boto3-stubs[s3]>=1.38.8
|
|
21
23
|
Requires-Dist: datetime-truncate>=1.1.1
|
|
@@ -23,6 +25,7 @@ Requires-Dist: filelock>=3.18.0
|
|
|
23
25
|
Requires-Dist: pyarrow>=22.0.0
|
|
24
26
|
Requires-Dist: requests>=2.32.3
|
|
25
27
|
Requires-Dist: types-requests>=2.32.0.20250328
|
|
28
|
+
Requires-Dist: typing-extensions>=4.12.0
|
|
26
29
|
Provides-Extra: all
|
|
27
30
|
Requires-Dist: boto3>=1.38.7; extra == 'all'
|
|
28
31
|
Requires-Dist: google-cloud-storage>=3.6.0; extra == 'all'
|
|
@@ -80,7 +83,6 @@ pip install avrokit[all]
|
|
|
80
83
|
```bash
|
|
81
84
|
git clone https://github.com/brandtg/avrokit.git
|
|
82
85
|
cd avrokit
|
|
83
|
-
poetry env use python3.12
|
|
84
86
|
make install
|
|
85
87
|
```
|
|
86
88
|
|
|
@@ -321,15 +323,12 @@ avrokit toparquet INPUT.avro OUTPUT.parquet
|
|
|
321
323
|
### Prerequisites
|
|
322
324
|
|
|
323
325
|
- Python 3.12+
|
|
324
|
-
-
|
|
326
|
+
- uv
|
|
325
327
|
- Docker (for GCS/S3 integration tests)
|
|
326
328
|
|
|
327
329
|
### Setup
|
|
328
330
|
|
|
329
331
|
```bash
|
|
330
|
-
# Set up Python environment
|
|
331
|
-
poetry env use python3.12
|
|
332
|
-
|
|
333
332
|
# Install dependencies
|
|
334
333
|
make install
|
|
335
334
|
|
|
@@ -347,7 +346,7 @@ make test
|
|
|
347
346
|
make test-coverage
|
|
348
347
|
|
|
349
348
|
# Run tests in parallel (default)
|
|
350
|
-
|
|
349
|
+
uv run pytest -n auto
|
|
351
350
|
```
|
|
352
351
|
|
|
353
352
|
### Code Quality
|
|
@@ -44,7 +44,6 @@ pip install avrokit[all]
|
|
|
44
44
|
```bash
|
|
45
45
|
git clone https://github.com/brandtg/avrokit.git
|
|
46
46
|
cd avrokit
|
|
47
|
-
poetry env use python3.12
|
|
48
47
|
make install
|
|
49
48
|
```
|
|
50
49
|
|
|
@@ -285,15 +284,12 @@ avrokit toparquet INPUT.avro OUTPUT.parquet
|
|
|
285
284
|
### Prerequisites
|
|
286
285
|
|
|
287
286
|
- Python 3.12+
|
|
288
|
-
-
|
|
287
|
+
- uv
|
|
289
288
|
- Docker (for GCS/S3 integration tests)
|
|
290
289
|
|
|
291
290
|
### Setup
|
|
292
291
|
|
|
293
292
|
```bash
|
|
294
|
-
# Set up Python environment
|
|
295
|
-
poetry env use python3.12
|
|
296
|
-
|
|
297
293
|
# Install dependencies
|
|
298
294
|
make install
|
|
299
295
|
|
|
@@ -311,7 +307,7 @@ make test
|
|
|
311
307
|
make test-coverage
|
|
312
308
|
|
|
313
309
|
# Run tests in parallel (default)
|
|
314
|
-
|
|
310
|
+
uv run pytest -n auto
|
|
315
311
|
```
|
|
316
312
|
|
|
317
313
|
### Code Quality
|
|
@@ -6,10 +6,11 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
from collections.abc import Generator, Iterator, Sequence
|
|
8
8
|
from contextlib import contextmanager
|
|
9
|
-
from typing import IO, Any,
|
|
9
|
+
from typing import IO, Any, cast
|
|
10
10
|
|
|
11
11
|
from avro.datafile import DataFileReader
|
|
12
12
|
from avro.io import DatumReader
|
|
13
|
+
from typing_extensions import Self
|
|
13
14
|
|
|
14
15
|
from ..url import URL
|
|
15
16
|
|
|
@@ -10,18 +10,13 @@ from collections.abc import Generator, Mapping, Sequence
|
|
|
10
10
|
from contextlib import contextmanager
|
|
11
11
|
from datetime import datetime
|
|
12
12
|
from itertools import groupby
|
|
13
|
-
from typing import
|
|
14
|
-
IO,
|
|
15
|
-
Any,
|
|
16
|
-
Literal,
|
|
17
|
-
Protocol,
|
|
18
|
-
Self,
|
|
19
|
-
)
|
|
13
|
+
from typing import IO, Any, Literal, Protocol
|
|
20
14
|
|
|
21
15
|
from avro.datafile import NULL_CODEC, DataFileWriter
|
|
22
16
|
from avro.io import DatumWriter
|
|
23
17
|
from avro.schema import Schema
|
|
24
18
|
from datetime_truncate import truncate as truncate_datetime
|
|
19
|
+
from typing_extensions import Self
|
|
25
20
|
|
|
26
21
|
from avrokit.url.utils import flatten_urls
|
|
27
22
|
|
|
@@ -17,6 +17,7 @@ from avro.io import DatumReader
|
|
|
17
17
|
|
|
18
18
|
from ..io import avro_reader, avro_schema, avro_writer, read_avro_schema
|
|
19
19
|
from ..url import URL, parse_url
|
|
20
|
+
from .tojson import AvroJsonEncoder
|
|
20
21
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
22
23
|
|
|
@@ -195,7 +196,9 @@ class AvroHTTPRequestHandler(BaseHTTPRequestHandler):
|
|
|
195
196
|
self.send_header("Content-type", "application/jsonl")
|
|
196
197
|
self.end_headers()
|
|
197
198
|
for record in reader:
|
|
198
|
-
self.wfile.write(
|
|
199
|
+
self.wfile.write(
|
|
200
|
+
json.dumps(record, cls=AvroJsonEncoder).encode("utf-8")
|
|
201
|
+
)
|
|
199
202
|
self.wfile.write(b"\n")
|
|
200
203
|
elif self._is_avro_binary(accept):
|
|
201
204
|
with data_url as f:
|
|
@@ -5,11 +5,24 @@
|
|
|
5
5
|
import argparse
|
|
6
6
|
import json
|
|
7
7
|
import sys
|
|
8
|
+
from datetime import date, datetime, time
|
|
9
|
+
from decimal import Decimal
|
|
8
10
|
|
|
9
11
|
from ..io import avro_reader
|
|
10
12
|
from ..url import flatten_urls, parse_url
|
|
11
13
|
|
|
12
14
|
|
|
15
|
+
class AvroJsonEncoder(json.JSONEncoder):
|
|
16
|
+
"""Encodes Avro logical types (datetime, date, time, decimal) as JSON."""
|
|
17
|
+
|
|
18
|
+
def default(self, o: object) -> object:
|
|
19
|
+
if isinstance(o, datetime | date | time):
|
|
20
|
+
return o.isoformat()
|
|
21
|
+
if isinstance(o, Decimal):
|
|
22
|
+
return str(o)
|
|
23
|
+
return super().default(o)
|
|
24
|
+
|
|
25
|
+
|
|
13
26
|
class ToJsonTool:
|
|
14
27
|
"""
|
|
15
28
|
Dumps Avro data file(s) as JSON, record per line.
|
|
@@ -30,5 +43,5 @@ class ToJsonTool:
|
|
|
30
43
|
for url in urls:
|
|
31
44
|
with avro_reader(url) as reader:
|
|
32
45
|
for record in reader:
|
|
33
|
-
json.dump(record, sys.stdout)
|
|
46
|
+
json.dump(record, sys.stdout, cls=AvroJsonEncoder)
|
|
34
47
|
sys.stdout.write("\n")
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import fnmatch
|
|
7
8
|
from abc import ABC, abstractmethod
|
|
8
9
|
from collections.abc import Sequence
|
|
9
10
|
from typing import IO, Any
|
|
10
|
-
from urllib.parse import urlparse
|
|
11
|
+
from urllib.parse import urlparse, urlunparse
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class URL(ABC):
|
|
@@ -22,13 +23,44 @@ class URL(ABC):
|
|
|
22
23
|
def __str__(self) -> str:
|
|
23
24
|
return self.url
|
|
24
25
|
|
|
25
|
-
@abstractmethod
|
|
26
26
|
def expand(self) -> Sequence[URL]:
|
|
27
27
|
"""
|
|
28
28
|
Expands the URL into all the URLs that represent concrete resources.
|
|
29
|
+
|
|
30
|
+
If the final path element contains glob wildcards, the parent path is
|
|
31
|
+
expanded and the results are filtered client-side with fnmatch.
|
|
32
|
+
"""
|
|
33
|
+
if self._has_glob():
|
|
34
|
+
return self._expand_glob()
|
|
35
|
+
return self._expand()
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def _expand(self) -> Sequence[URL]:
|
|
39
|
+
"""
|
|
40
|
+
Expands the URL into all the URLs that represent concrete resources.
|
|
29
41
|
"""
|
|
30
42
|
...
|
|
31
43
|
|
|
44
|
+
def _has_glob(self) -> bool:
|
|
45
|
+
return any(c in self.parsed_url.path for c in "*?[")
|
|
46
|
+
|
|
47
|
+
def _expand_glob(self) -> Sequence[URL]:
|
|
48
|
+
path = self.parsed_url.path
|
|
49
|
+
wildcard_idx = min(idx for idx in (path.find(c) for c in "*?[") if idx != -1)
|
|
50
|
+
prefix = path[:wildcard_idx]
|
|
51
|
+
parent = self._with_path(prefix)
|
|
52
|
+
return [
|
|
53
|
+
url
|
|
54
|
+
for url in parent.expand()
|
|
55
|
+
if url.url != parent.url and fnmatch.fnmatch(url.parsed_url.path, path)
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
def _with_path(self, path: str) -> URL:
|
|
59
|
+
new_url = urlunparse(
|
|
60
|
+
(self.parsed_url.scheme, self.parsed_url.netloc, path, "", "", "")
|
|
61
|
+
)
|
|
62
|
+
return type(self)(new_url, mode=self.mode)
|
|
63
|
+
|
|
32
64
|
@abstractmethod
|
|
33
65
|
def delete(self) -> None:
|
|
34
66
|
"""
|
|
@@ -6,7 +6,9 @@ import glob
|
|
|
6
6
|
import os
|
|
7
7
|
import shutil
|
|
8
8
|
from collections.abc import Sequence
|
|
9
|
-
from typing import IO, Any
|
|
9
|
+
from typing import IO, Any
|
|
10
|
+
|
|
11
|
+
from typing_extensions import override
|
|
10
12
|
|
|
11
13
|
from .base import URL
|
|
12
14
|
|
|
@@ -19,20 +21,7 @@ class FileURL(URL):
|
|
|
19
21
|
self.fh: IO[Any] | None = None
|
|
20
22
|
|
|
21
23
|
@override
|
|
22
|
-
def
|
|
23
|
-
# Check if the path contains wildcard characters
|
|
24
|
-
if "*" in self.path or "?" in self.path or "[" in self.path:
|
|
25
|
-
# Use glob to expand the pattern
|
|
26
|
-
matches = glob.glob(self.path)
|
|
27
|
-
if not matches:
|
|
28
|
-
# No matches found, return empty list
|
|
29
|
-
return []
|
|
30
|
-
# Return sorted list of FileURL objects for all matches
|
|
31
|
-
return [
|
|
32
|
-
FileURL(self.prefix + match, mode=self.mode)
|
|
33
|
-
for match in sorted(matches)
|
|
34
|
-
]
|
|
35
|
-
|
|
24
|
+
def _expand(self) -> Sequence[URL]:
|
|
36
25
|
# If it's a regular file or doesn't exist (no expansion needed)
|
|
37
26
|
if os.path.isfile(self.path) or not os.path.exists(self.path):
|
|
38
27
|
return [self]
|
|
@@ -46,6 +35,15 @@ class FileURL(URL):
|
|
|
46
35
|
)
|
|
47
36
|
return acc
|
|
48
37
|
|
|
38
|
+
@override
|
|
39
|
+
def _expand_glob(self) -> Sequence[URL]:
|
|
40
|
+
matches = glob.glob(self.path)
|
|
41
|
+
if not matches:
|
|
42
|
+
return []
|
|
43
|
+
return [
|
|
44
|
+
FileURL(self.prefix + match, mode=self.mode) for match in sorted(matches)
|
|
45
|
+
]
|
|
46
|
+
|
|
49
47
|
@override
|
|
50
48
|
def delete(self) -> None:
|
|
51
49
|
# If it's a wildcard pattern, expand and delete all matching files
|
|
@@ -7,11 +7,12 @@ import os
|
|
|
7
7
|
import tempfile
|
|
8
8
|
from collections.abc import Generator, Sequence
|
|
9
9
|
from contextlib import contextmanager
|
|
10
|
-
from typing import IO, Any, cast
|
|
10
|
+
from typing import IO, Any, cast
|
|
11
11
|
|
|
12
12
|
from google.api_core.exceptions import NotFound
|
|
13
13
|
from google.auth.credentials import AnonymousCredentials
|
|
14
14
|
from google.cloud import storage
|
|
15
|
+
from typing_extensions import override
|
|
15
16
|
|
|
16
17
|
from .base import URL
|
|
17
18
|
|
|
@@ -58,7 +59,7 @@ class GoogleCloudStorageURL(URL):
|
|
|
58
59
|
return self.url == value.url
|
|
59
60
|
|
|
60
61
|
@override
|
|
61
|
-
def
|
|
62
|
+
def _expand(self) -> Sequence[URL]:
|
|
62
63
|
with google_cloud_storage_client() as client:
|
|
63
64
|
try:
|
|
64
65
|
path = self.path
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
import io
|
|
6
6
|
import tempfile
|
|
7
7
|
from collections.abc import Sequence
|
|
8
|
-
from typing import IO, Any
|
|
8
|
+
from typing import IO, Any
|
|
9
9
|
|
|
10
10
|
import requests
|
|
11
|
+
from typing_extensions import override
|
|
11
12
|
|
|
12
13
|
from .base import URL
|
|
13
14
|
|
|
@@ -35,7 +36,7 @@ class HttpURL(URL):
|
|
|
35
36
|
self._current_request_buffer: IO[Any] | None = None
|
|
36
37
|
|
|
37
38
|
@override
|
|
38
|
-
def
|
|
39
|
+
def _expand(self) -> Sequence[URL]:
|
|
39
40
|
# N.b. no way to "expand" an HTTP URL, i.e. discover sub resources
|
|
40
41
|
return [self]
|
|
41
42
|
|
|
@@ -57,9 +58,12 @@ class HttpURL(URL):
|
|
|
57
58
|
try:
|
|
58
59
|
response = requests.head(self.url, timeout=5)
|
|
59
60
|
if response.ok:
|
|
60
|
-
|
|
61
|
+
content_length = response.headers.get("Content-Length")
|
|
62
|
+
if content_length is not None:
|
|
63
|
+
return int(content_length)
|
|
64
|
+
return 0
|
|
61
65
|
return 0
|
|
62
|
-
except requests.RequestException:
|
|
66
|
+
except (requests.RequestException, ValueError):
|
|
63
67
|
return 0
|
|
64
68
|
|
|
65
69
|
@override
|
|
@@ -6,10 +6,11 @@ import io
|
|
|
6
6
|
import tempfile
|
|
7
7
|
from collections.abc import Sequence
|
|
8
8
|
from contextlib import contextmanager
|
|
9
|
-
from typing import IO, Any, cast
|
|
9
|
+
from typing import IO, Any, cast
|
|
10
10
|
|
|
11
11
|
import boto3
|
|
12
12
|
from mypy_boto3_s3 import S3Client
|
|
13
|
+
from typing_extensions import override
|
|
13
14
|
|
|
14
15
|
from .base import URL
|
|
15
16
|
|
|
@@ -33,7 +34,7 @@ class S3URL(URL):
|
|
|
33
34
|
self._current_local_stream: IO[Any] | None = None
|
|
34
35
|
|
|
35
36
|
@override
|
|
36
|
-
def
|
|
37
|
+
def _expand(self) -> Sequence[URL]:
|
|
37
38
|
with s3_client() as client:
|
|
38
39
|
path = self.path
|
|
39
40
|
if not path.endswith("/"):
|
|
@@ -48,7 +49,9 @@ class S3URL(URL):
|
|
|
48
49
|
for obj in page["Contents"]:
|
|
49
50
|
key = obj.get("Key")
|
|
50
51
|
if key:
|
|
51
|
-
urls.append(
|
|
52
|
+
urls.append(
|
|
53
|
+
S3URL(f"s3://{self.bucket}/{key}", mode=self.mode)
|
|
54
|
+
)
|
|
52
55
|
if not urls:
|
|
53
56
|
# N.b. we also include URLs that don't exist yet here because there is nothing to expand
|
|
54
57
|
return [self]
|
|
@@ -4,18 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "avrokit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Python utilities for working with Avro data files"
|
|
9
9
|
authors = [{ name = "Greg Brandt", email = "brandt.greg@gmail.com" }]
|
|
10
10
|
license = "Apache-2.0"
|
|
11
11
|
readme = "README.md"
|
|
12
|
-
requires-python = ">=3.
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
13
|
keywords = ["avro", "data", "serialization", "gcs", "s3", "parquet"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 3 - Alpha",
|
|
16
16
|
"Intended Audience :: Developers",
|
|
17
17
|
"License :: OSI Approved :: Apache Software License",
|
|
18
18
|
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
19
21
|
"Programming Language :: Python :: 3.12",
|
|
20
22
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
21
23
|
"Typing :: Typed",
|
|
@@ -28,6 +30,7 @@ dependencies = [
|
|
|
28
30
|
"boto3-stubs[s3]>=1.38.8",
|
|
29
31
|
"types-requests>=2.32.0.20250328",
|
|
30
32
|
"pyarrow>=22.0.0",
|
|
33
|
+
"typing-extensions>=4.12.0",
|
|
31
34
|
]
|
|
32
35
|
|
|
33
36
|
[project.optional-dependencies]
|
|
@@ -66,6 +69,7 @@ filterwarnings = ["ignore::DeprecationWarning:botocore.*"]
|
|
|
66
69
|
|
|
67
70
|
[tool.ruff]
|
|
68
71
|
exclude = []
|
|
72
|
+
target-version = "py310"
|
|
69
73
|
|
|
70
74
|
[tool.ruff.lint]
|
|
71
75
|
select = ["E", "F", "I", "UP"]
|