diff --git a/docs/source/api-reference.md b/docs/source/api-reference.md index d45607d..ee41889 100644 --- a/docs/source/api-reference.md +++ b/docs/source/api-reference.md @@ -97,8 +97,6 @@ .. autoenum:: Encryption -.. autoenum:: StorageClass - .. autoenum:: TimestampingMode .. autoclass:: Timestamping diff --git a/s2-specs b/s2-specs index 973e0d9..0bd8bba 160000 --- a/s2-specs +++ b/s2-specs @@ -1 +1 @@ -Subproject commit 973e0d92166ee1386b71d4e7ade8bd0fc2aaf4b8 +Subproject commit 0bd8bba1fc113587790b4d62826d36107ac2aee4 diff --git a/src/s2_sdk/__init__.py b/src/s2_sdk/__init__.py index fde2544..deaee8c 100644 --- a/src/s2_sdk/__init__.py +++ b/src/s2_sdk/__init__.py @@ -52,7 +52,6 @@ Scalar, SeqNum, SequencedRecord, - StorageClass, StreamConfig, StreamInfo, StreamMetricSet, @@ -106,7 +105,6 @@ "LocationInfo", "CommandRecord", "metered_bytes", - "StorageClass", "TimestampingMode", "Timestamping", "StreamConfig", diff --git a/src/s2_sdk/_mappers.py b/src/s2_sdk/_mappers.py index c9f6ed3..f979e09 100644 --- a/src/s2_sdk/_mappers.py +++ b/src/s2_sdk/_mappers.py @@ -31,7 +31,6 @@ Scalar, SeqNum, SequencedRecord, - StorageClass, StreamConfig, StreamInfo, StreamPosition, @@ -88,6 +87,8 @@ def location_info_from_json(data: dict[str, Any]) -> LocationInfo: return LocationInfo( name=data["name"], is_private=data["is_private"], + storage_classes=data.get("storage_classes"), + default_storage_class=data.get("default_storage_class"), ) @@ -96,7 +97,7 @@ def stream_config_to_json(config: StreamConfig | None) -> dict[str, Any] | None: return None result: dict[str, Any] = {} if config.storage_class is not None: - result["storage_class"] = config.storage_class.value + result["storage_class"] = config.storage_class if config.retention_policy is not None: result["retention_policy"] = _retention_policy_to_json(config.retention_policy) if config.timestamping is not None: @@ -137,9 +138,8 @@ def stream_config_from_json(data: dict[str, Any]) -> StreamConfig: doe = data.get("delete_on_empty") delete_on_empty_min_age = doe.get("min_age_secs") if doe else None - sc = data.get("storage_class") return StreamConfig( - storage_class=StorageClass(sc) if sc else None, + storage_class=data.get("storage_class"), retention_policy=retention_policy, timestamping=timestamping, delete_on_empty_min_age=delete_on_empty_min_age, diff --git a/src/s2_sdk/_types.py b/src/s2_sdk/_types.py index b81de14..1d1fdc7 100644 --- a/src/s2_sdk/_types.py +++ b/src/s2_sdk/_types.py @@ -376,12 +376,11 @@ class LocationInfo: is_private: bool """Whether this location is an account-private placement.""" + storage_classes: list[str] | None = None + """Storage classes available to the account in this location.""" -class StorageClass(_DocEnum): - """Storage class for recent appends.""" - - STANDARD = "standard", "Offers end-to-end latencies under 500 ms." - EXPRESS = "express", "Offers end-to-end latencies under 50 ms." + default_storage_class: str | None = None + """Default storage class for this location.""" class TimestampingMode(_DocEnum): @@ -492,8 +491,8 @@ class Timestamping: class StreamConfig: """Stream configuration.""" - storage_class: StorageClass | None = None - """Storage class for recent appends. Defaults to ``EXPRESS``.""" + storage_class: str | None = None + """Storage class for recent appends. See :meth:`S2.list_locations`.""" retention_policy: int | Literal["infinite"] | None = None """Retention duration in seconds, or ``"infinite"``. Default is 7 days.""" diff --git a/tests/test_account_ops.py b/tests/test_account_ops.py index 111d1c1..1574092 100644 --- a/tests/test_account_ops.py +++ b/tests/test_account_ops.py @@ -14,7 +14,6 @@ PrefixMatch, S2Basin, S2ServerError, - StorageClass, StreamConfig, Timestamping, TimestampingMode, @@ -36,7 +35,7 @@ async def test_create_basin(self, s2: S2, basin_name: str): async def test_create_basin_with_config(self, s2: S2, basin_name: str): config = BasinConfig( default_stream_config=StreamConfig( - storage_class=StorageClass.STANDARD, + storage_class="standard", retention_policy=86400 * 7, timestamping=Timestamping( mode=TimestampingMode.CLIENT_REQUIRE, @@ -54,10 +53,7 @@ async def test_create_basin_with_config(self, s2: S2, basin_name: str): retrieved_config = await s2.get_basin_config(basin_name) assert retrieved_config.default_stream_config is not None - assert ( - retrieved_config.default_stream_config.storage_class - == StorageClass.STANDARD - ) + assert retrieved_config.default_stream_config.storage_class == "standard" assert retrieved_config.default_stream_config.retention_policy == 86400 * 7 assert ( retrieved_config.default_stream_config.timestamping.mode @@ -129,7 +125,7 @@ async def test_locations(self, s2: S2): async def test_reconfigure_basin(self, s2: S2, basin: S2Basin): config = BasinConfig( default_stream_config=StreamConfig( - storage_class=StorageClass.STANDARD, + storage_class="standard", retention_policy=3600, ), create_stream_on_append=True, @@ -138,9 +134,7 @@ async def test_reconfigure_basin(self, s2: S2, basin: S2Basin): updated_config = await s2.reconfigure_basin(basin.name, config=config) assert updated_config.default_stream_config is not None - assert ( - updated_config.default_stream_config.storage_class == StorageClass.STANDARD - ) + assert updated_config.default_stream_config.storage_class == "standard" assert updated_config.default_stream_config.retention_policy == 3600 assert updated_config.create_stream_on_append is True diff --git a/tests/test_basin_ops.py b/tests/test_basin_ops.py index 748f628..91ed962 100644 --- a/tests/test_basin_ops.py +++ b/tests/test_basin_ops.py @@ -3,11 +3,11 @@ import pytest from s2_sdk import ( + S2, EnsureStatus, S2Basin, S2ServerError, S2Stream, - StorageClass, StreamConfig, Timestamping, TimestampingMode, @@ -34,7 +34,7 @@ async def test_create_stream_with_config( basin = shared_basin config = StreamConfig( - storage_class=StorageClass.STANDARD, + storage_class="standard", retention_policy=86400 * 3, timestamping=Timestamping( mode=TimestampingMode.ARRIVAL, @@ -48,7 +48,7 @@ async def test_create_stream_with_config( assert stream_info.name == stream_name retrieved_config = await basin.get_stream_config(stream_name) - assert retrieved_config.storage_class == StorageClass.STANDARD + assert retrieved_config.storage_class == "standard" assert retrieved_config.retention_policy == 86400 * 3 assert retrieved_config.timestamping is not None assert retrieved_config.timestamping.mode == TimestampingMode.ARRIVAL @@ -58,17 +58,20 @@ async def test_create_stream_with_config( finally: await basin.delete_stream(stream_name) - async def test_default_stream_config(self, shared_basin: S2Basin, stream: S2Stream): - basin = shared_basin - - config = await basin.get_stream_config(stream.name) - assert config.storage_class == StorageClass.EXPRESS + async def test_default_stream_config( + self, s2: S2, shared_basin: S2Basin, stream: S2Stream + ): + config = await shared_basin.get_stream_config(stream.name) + basin_config = await s2.get_basin_config(shared_basin.name) + defaults = basin_config.default_stream_config + if defaults is not None and defaults.storage_class is not None: + assert config.storage_class == defaults.storage_class assert config.retention_policy == 86400 * 7 async def test_reconfigure_stream(self, shared_basin: S2Basin, stream: S2Stream): basin = shared_basin config = StreamConfig( - storage_class=StorageClass.STANDARD, + storage_class="standard", retention_policy="infinite", timestamping=Timestamping( mode=TimestampingMode.CLIENT_REQUIRE, uncapped=True @@ -77,7 +80,7 @@ async def test_reconfigure_stream(self, shared_basin: S2Basin, stream: S2Stream) ) updated_config = await basin.reconfigure_stream(stream.name, config=config) - assert updated_config.storage_class == StorageClass.STANDARD + assert updated_config.storage_class == "standard" assert updated_config.retention_policy == "infinite" assert updated_config.timestamping is not None assert updated_config.timestamping.mode == TimestampingMode.CLIENT_REQUIRE @@ -85,7 +88,7 @@ async def test_reconfigure_stream(self, shared_basin: S2Basin, stream: S2Stream) assert updated_config.delete_on_empty_min_age == 1800 config = StreamConfig( - storage_class=StorageClass.EXPRESS, + storage_class="express", retention_policy=86400 * 90, timestamping=Timestamping( mode=TimestampingMode.CLIENT_PREFER, uncapped=False @@ -93,7 +96,7 @@ async def test_reconfigure_stream(self, shared_basin: S2Basin, stream: S2Stream) delete_on_empty_min_age=3600, ) updated_config = await basin.reconfigure_stream(stream.name, config=config) - assert updated_config.storage_class == StorageClass.EXPRESS + assert updated_config.storage_class == "express" assert updated_config.retention_policy == 86400 * 90 assert updated_config.timestamping.mode == TimestampingMode.CLIENT_PREFER assert updated_config.timestamping.uncapped is False @@ -225,12 +228,12 @@ async def test_list_streams_with_prefix( async def test_create_stream_storage_class_express( self, shared_basin: S2Basin, stream_name: str ): - config = StreamConfig(storage_class=StorageClass.EXPRESS) + config = StreamConfig(storage_class="express") info = await shared_basin.create_stream(name=stream_name, config=config) try: assert info.name == stream_name retrieved = await shared_basin.get_stream_config(stream_name) - assert retrieved.storage_class == StorageClass.EXPRESS + assert retrieved.storage_class == "express" finally: await shared_basin.delete_stream(stream_name) @@ -290,9 +293,9 @@ async def test_reconfigure_stream_storage_class( self, shared_basin: S2Basin, stream: S2Stream ): updated = await shared_basin.reconfigure_stream( - stream.name, config=StreamConfig(storage_class=StorageClass.STANDARD) + stream.name, config=StreamConfig(storage_class="standard") ) - assert updated.storage_class == StorageClass.STANDARD + assert updated.storage_class == "standard" async def test_reconfigure_stream_retention( self, shared_basin: S2Basin, stream: S2Stream @@ -500,9 +503,9 @@ async def test_reconfigure_stream_storage_class_express( await shared_basin.create_stream(name=stream_name) try: updated = await shared_basin.reconfigure_stream( - stream_name, config=StreamConfig(storage_class=StorageClass.EXPRESS) + stream_name, config=StreamConfig(storage_class="express") ) - assert updated.storage_class == StorageClass.EXPRESS + assert updated.storage_class == "express" except S2ServerError: pass # Free tier may not support Express finally: @@ -571,7 +574,7 @@ async def test_reconfigure_stream_nonexistent_errors(self, shared_basin: S2Basin with pytest.raises(S2ServerError): await shared_basin.reconfigure_stream( "nonexistent-stream-xyz", - config=StreamConfig(storage_class=StorageClass.STANDARD), + config=StreamConfig(storage_class="standard"), ) async def test_delete_stream_already_deleting_is_idempotent( diff --git a/tests/test_stream_ops.py b/tests/test_stream_ops.py index f4ff9a0..126c886 100644 --- a/tests/test_stream_ops.py +++ b/tests/test_stream_ops.py @@ -26,7 +26,6 @@ S2Stream, SeqNum, SeqNumMismatchError, - StorageClass, StreamConfig, TailOffset, Timestamp, @@ -861,7 +860,7 @@ async def test_auto_create_config_overrides_basin_defaults_and_inherits_unset_fi create_stream_on_append=True, create_stream_on_read=True, default_stream_config=StreamConfig( - storage_class=StorageClass.STANDARD, + storage_class="standard", retention_policy=7200, ), ), @@ -880,7 +879,7 @@ async def test_auto_create_config_overrides_basin_defaults_and_inherits_unset_fi ) actual = await basin.get_stream_config(stream_name) - assert actual.storage_class == StorageClass.STANDARD + assert actual.storage_class == "standard" assert actual.retention_policy == 3600 assert actual.timestamping is not None assert actual.timestamping.mode == TimestampingMode.CLIENT_PREFER @@ -899,7 +898,7 @@ async def test_auto_create_config_is_ignored_for_existing_stream( await basin.create_stream( stream_name, config=StreamConfig( - storage_class=StorageClass.EXPRESS, + storage_class="express", retention_policy=7200, ), ) @@ -907,13 +906,13 @@ async def test_auto_create_config_is_ignored_for_existing_stream( basin.stream(stream_name), operation, StreamConfig( - storage_class=StorageClass.STANDARD, + storage_class="standard", retention_policy=3600, ), ) actual = await basin.get_stream_config(stream_name) - assert actual.storage_class == StorageClass.EXPRESS + assert actual.storage_class == "express" assert actual.retention_policy == 7200