diff --git a/CHANGELOG.md b/CHANGELOG.md index 16a2c7a..2bdc089 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ All notable changes are recorded here. Versions follow The 0.1.0 entry describes what the library is rather than what changed, since there was nothing to have changed from. Everything above it is ordinary. +## 0.22.3 — 2026-10-20 + +### Fixed + +- **A reader that passes no `s3_options` opens a published table again.** + 0.22.1's manifest read handed pyiceberg no properties when a reader passed + no options, rather than resolving them from the environment as litelink + does, so a non-AWS endpoint (`AWS_ENDPOINT_URL`) never reached it and the + open asked AWS itself (`ACCESS_DENIED`). Readers passing explicit options + were unaffected. + ## 0.22.2 — 2026-10-20 ### Fixed diff --git a/pyproject.toml b/pyproject.toml index 2ab5246..8b77e47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "streamcast" -version = "0.22.2" +version = "0.22.3" description = "A JSON WebSocket pubsub framework for structured data. Every message lands in an Iceberg table: no ETL, no second copy." readme = "README.md" authors = [ diff --git a/src/streamcast/_published.py b/src/streamcast/_published.py index 9021da8..e8e5073 100644 --- a/src/streamcast/_published.py +++ b/src/streamcast/_published.py @@ -166,7 +166,11 @@ def _counted_from_manifests( column, or a delete file whose rows a count would include — and the caller asks DuckDB as before. """ - properties = {} if s3_options is None else s3_options.catalog_properties() + # Resolved, as litelink resolves its own: what the caller did not set + # comes from the environment, the endpoint included. Unresolved, a reader + # that passes no options gave pyiceberg none, and a non-AWS endpoint in + # the environment never reached it (0.22.1). + properties = (s3_options or S3Options()).resolved().catalog_properties() table = StaticTable.from_metadata(metadata, properties=properties) field = table.schema().find_field(COLUMN) low: int | None = None diff --git a/tests/test_published.py b/tests/test_published.py index ed8574e..825c932 100644 --- a/tests/test_published.py +++ b/tests/test_published.py @@ -211,6 +211,33 @@ def test_an_s3_table_reads_with_the_readers_own_credentials(tmp_path, s3, bucket table.close() +@pytest.mark.replication +def test_an_s3_table_opened_without_options_reads_with_the_environments( + tmp_path, s3, bucket +): + """A reader that passes no `s3_options` reads with what the environment + resolves (the `s3` fixture puts its endpoint and key there), the extent + read from the manifests included: 0.22.1 handed pyiceberg no properties, + so a non-AWS endpoint in the environment never reached it and the open + asked AWS itself (ACCESS_DENIED).""" + log = litelink.new( + tmp_path, "trades", schema=SCHEMA, published=bucket, s3_options=s3 + ) + with log: + log.extend([{"i": i} for i in range(4)]) + while log.seal(flush=True) is not None: + pass + + log.publish(flush=True) + + table = _published.Table.open(bucket, "trades", None) + try: + assert table.extent == (1, 5) + assert table.record_count == 4 + finally: + table.close() + + def publish(log: litelink.WriteHandle, start: int, count: int) -> None: log.extend([{"i": i} for i in range(start, start + count)]) while log.seal(flush=True) is not None: diff --git a/uv.lock b/uv.lock index 70e6113..f5bc1c8 100644 --- a/uv.lock +++ b/uv.lock @@ -1970,7 +1970,7 @@ wheels = [ [[package]] name = "streamcast" -version = "0.22.2" +version = "0.22.3" source = { editable = "." } dependencies = [ { name = "litelink" },