Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions fastly_compute/_resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Internal base class for Fastly resource wrappers
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we call this _resource.py, then we don't have to scream at the read so much about it being internal. :-)


This module provides an internal generic base class for wrapping WIT binding
resources with consistent lifecycle management and context manager protocol.

**Note**: This module is for internal SDK use only. End users should not
need to import or use these classes directly. Instead, use the public resource
classes like ConfigStore, RateCounter, PenaltyBox, and LogEndpoint.
"""

from __future__ import annotations

from types import TracebackType
from typing import Protocol, Self


class WitResource(Protocol):
"""Internal protocol for WIT-generated resource types.

This protocol defines the context manager interface that all WIT resources
must implement for resource lifecycle management.
"""

def __enter__(self) -> Self:
"""Enter the context manager."""
...

def __exit__(
self,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
traceback: TracebackType | None,
) -> bool | None:
"""Exit the context manager and release resources."""
...


class FastlyResource[T: WitResource]:
"""Internal base class for Fastly resource wrappers.

This generic base class provides consistent context manager protocol and
resource lifecycle management for all Fastly resource types that wrap
WIT bindings (e.g., ConfigStore, RateCounter, PenaltyBox, LogEndpoint).

The type parameter T represents the underlying WIT binding resource type
and must satisfy the WitResource protocol (context manager support).
"""

def __init__(self, wit_resource: T):
"""Initialize the resource wrapper with an inner WIT binding.

:param wit_resource: The underlying WIT binding resource to wrap
"""
self._wit_resource = wit_resource

def close(self) -> None:
"""Explicitly close the resource, releasing its resources.

This is called automatically when using the resource as a context
manager. If not called explicitly, resources will eventually be freed
by the garbage collector.

Note: Attempting to use the resource after it is closed will result
in a trap.
"""
self._wit_resource.__exit__(None, None, None)

def __enter__(self) -> Self:
"""Context manager entry.

Allows use of the resource in a 'with' statement.

Example::

with Resource.open("foo") as foo:
value = foo.bar("baz")
"""
return self

def __exit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
"""Context manager exit.

Use of the context manager will free up the underlying host resource on
exit. Referencing the resource after context manager exit will result in
a trap.

Exception information from the context is passed through to the inner
resource's __exit__ method for proper cleanup.

:param exc_type: Exception type if an exception occurred, None otherwise
:param exc_val: Exception value if an exception occurred, None otherwise
:param exc_tb: Exception traceback if an exception occurred, None otherwise
"""
self._wit_resource.__exit__(exc_type, exc_val, exc_tb)
38 changes: 4 additions & 34 deletions fastly_compute/config_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@

from wit_world.imports import config_store as wit_config_store

from ._resource import FastlyResource

# The maximum value for a u32, used to signal that we don't want to cap
# the length of values returned by the host. In practice, this limit
# is at 8KB, though that could change.
_MAX_U32 = 0xFFFFFFFF


class ConfigStore:
class ConfigStore(FastlyResource[wit_config_store.Store]):
"""Interface to Fastly Config Store.

Config Stores provide read-only access to configuration data that can be
Expand All @@ -33,10 +35,6 @@ class ConfigStore:
api_url = config.get("api_url", "https://api.example.com")
"""

def __init__(self, store: wit_config_store.Store):
"""Private constructor. Use ConfigStore.open() instead."""
self._store = store

@classmethod
def open(cls, name: str) -> Self:
"""Open a config store by name.
Expand Down Expand Up @@ -67,7 +65,7 @@ def get(self, key: str, default: str | None = None) -> str | None:
config = ConfigStore.open("app-config")
api_url = config.get("api_url", "https://api.example.com")
"""
result = self._store.get(key, _MAX_U32)
result = self._wit_resource.get(key, _MAX_U32)
if result is None:
result = default

Expand All @@ -90,31 +88,3 @@ def __contains__(self, key: str) -> bool:
if not isinstance(key, str):
raise KeyError("Key must be a str")
return self.get(key) is not None

def close(self) -> None:
"""Explicitly close the config store, releasing its resources.

This is called automatically when using the config store as a context
manager. If not called explicitly, resources will eventually be freed
by the garbage collector.

.. note:: Attempting to use the config store after it is closed will result
in a trap.
"""
self._store.__exit__(None, None, None)

def __enter__(self) -> Self:
"""Context manager entry.

Allows use of ConfigStore in a 'with' statement.
"""
return self

def __exit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit.

Use of the context manager will free up the underlying host resource on
exit. Referencing the resource after context manager exit will result in
a trap.
"""
self.close()
38 changes: 4 additions & 34 deletions fastly_compute/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@

from wit_world.imports import log as wit_log

from ._resource import FastlyResource

class LogEndpoint:

class LogEndpoint(FastlyResource[wit_log.Endpoint]):
"""Interface to a Fastly logging endpoint.

Logging endpoints send log data to configured Real-Time Log Streaming
Expand All @@ -42,10 +44,6 @@ class LogEndpoint:
endpoint.write(b"Binary log data")
"""

def __init__(self, endpoint: wit_log.Endpoint):
"""Private constructor. Use LogEndpoint.open() instead."""
self._endpoint = endpoint

@classmethod
def open(cls, name: str) -> Self:
r"""Open a logging endpoint by name.
Expand Down Expand Up @@ -88,35 +86,7 @@ def write(self, msg: bytes | str) -> None:
"""
if isinstance(msg, str):
msg = msg.encode("utf-8")
self._endpoint.write(msg)

def close(self) -> None:
"""Explicitly close the logging endpoint, releasing its resources.

This is called automatically when using the endpoint as a context
manager. If not called explicitly, resources will eventually be freed
by the garbage collector.

Note: Attempting to use the endpoint after it is closed will result
in a trap.
"""
self._endpoint.__exit__(None, None, None)

def __enter__(self) -> Self:
"""Context manager entry.

Allows use of resource in a 'with' statement.
"""
return self

def __exit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit.

Use of the context manager will free up the underlying host resource on
exit. Referencing the resource after context manager exit will result in
a trap.
"""
self._endpoint.__exit__(exc_type, exc_val, exc_tb)
self._wit_resource.write(msg)


class FastlyLogHandler(logging.Handler):
Expand Down
Loading