Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
29 changes: 19 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,26 @@ $(STUBS_DIR): $(COMPUTE_WIT)
uv run --extra dev componentize-py -d wit --world-module wit_world -w $(TARGET_WORLD) bindings $(STUBS_DIR)

# Build our composed wasm using fastly-compute-py build
$(BUILD_DIR)/%.composed.wasm: wit/viceroy.wit wit/deps/fastly/compute.wit fastly_compute/wsgi.py fastly_compute/runtime_patching/patches.py | $(BUILD_DIR) $(STUBS_DIR)
$(BUILD_DIR)/%.composed.wasm: wit/viceroy.wit wit/deps/fastly/compute.wit fastly_compute/wsgi.py fastly_compute/_bindings/__init__.py | $(BUILD_DIR) $(STUBS_DIR)
@echo "Building $* example with fastly-compute-py..."
@test -d $(EXAMPLES_DIR)/$* || (echo "Error: Example directory $(EXAMPLES_DIR)/$* not found" && exit 1)
cd $(EXAMPLES_DIR)/$* && $(FASTLY_COMPUTE_PY) build --output ../../$@

# The script that writes the exceptions and the patches always rewrites
# everything, so we can depend on the mod date of only 1 file. We choose
# patches.py, because its name doesn't depend on the WIT contents.
fastly_compute/runtime_patching/patches.py: scripts/generate_patches/*.py $(shell find scripts/generate_patches/templates -name "*.jinja") $(COMPUTE_WIT)
uv run python -m scripts.generate_patches
# The generate_bindings script regenerates all _bindings/ modules and
# _error_mapping.py together. We depend on _bindings/__init__.py as the
# sentinel since the script always rewrites every file.
fastly_compute/_bindings/__init__.py: scripts/generate_bindings/*.py \
scripts/wit/*.py \
$(shell find scripts/generate_bindings/templates -name "*.jinja") \
$(COMPUTE_WIT)
uv run python -m scripts.generate_bindings

# The generate_exceptions script generates the exception hierarchy.
fastly_compute/exceptions/types/error.py: scripts/generate_exceptions/*.py \
scripts/wit/*.py \
$(shell find scripts/generate_exceptions/templates -name "*.jinja") \
$(COMPUTE_WIT)
uv run python -m scripts.generate_exceptions

# Create build directory
$(BUILD_DIR):
Expand All @@ -86,13 +96,12 @@ list-examples:

# Clean build artifacts
clean:
rm -rf $(BUILD_DIR) $(STUBS_DIR)
rm -f fastly_compute/runtime_patching/patches.py
rm -rf $(BUILD_DIR) $(STUBS_DIR) fastly_compute/_bindings
cd fastly_compute/exceptions && rm -rf acl http_body http_req kv_store types
cd crates/fastly-compute-py && cargo clean

# Development tools
lint: fastly_compute/runtime_patching/patches.py | $(STUBS_DIR)
lint: fastly_compute/_bindings/__init__.py fastly_compute/exceptions/types/error.py | $(STUBS_DIR)
@echo "Checking version synchronization..."
uv run python scripts/check_version_sync.py
@echo "Linting Python code..."
Expand All @@ -101,7 +110,7 @@ lint: fastly_compute/runtime_patching/patches.py | $(STUBS_DIR)
@echo "Linting Rust code..."
cd crates/fastly-compute-py && cargo clippy -- -D warnings

lint-fix: fastly_compute/runtime_patching/patches.py
lint-fix: fastly_compute/_bindings/__init__.py fastly_compute/exceptions/types/error.py
@echo "Fixing Python code..."
uv run --extra dev ruff check --fix .
@echo "Fixing Rust code..."
Expand Down
6 changes: 0 additions & 6 deletions fastly_compute/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,3 @@

# Testing utilities are available but not imported by default
# Users can import them explicitly: from fastly_compute.testing import ViceroyTestBase

from fastly_compute.runtime_patching.patches import patch

# Before anything from the fastly_compute package is used, do our monkeypatching
# to make the WIT-generated code act more Pythonically:
patch()
2 changes: 2 additions & 0 deletions fastly_compute/_bindings/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# This package is automatically generated by scripts/generate_bindings.
# Do not edit directly.
48 changes: 48 additions & 0 deletions fastly_compute/_bindings/acl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# This file is automatically generated by scripts/generate_bindings.
# Do not edit directly.
"""Blocklists using [Access Control Lists] (ACLs)

[Access Control Lists]: https://www.fastly.com/documentation/reference/api/acls/
"""

from __future__ import annotations

from typing import Self

from wit_world.imports import acl as _wit
from wit_world.imports.types import IpAddress

from fastly_compute._bindings.async_io import Pollable
from fastly_compute._error_mapping import MAPPINGS, remap_wit_errors
from fastly_compute._resource import FastlyResource

__all__ = [
"Acl",
"IpAddress",
]


class Acl(FastlyResource[_wit.Acl]):
"""An ACL."""

@classmethod
@remap_wit_errors(MAPPINGS)
def open(cls, name: str) -> Self:
"""Opens an ACL linked to the current service with the given link name.

:raises ~fastly_compute.exceptions.types.open_error.OpenError:
"""
return cls(_wit.Acl.open(name))

@remap_wit_errors(MAPPINGS)
def lookup(self, ip_addr: IpAddress) -> Pollable | None:
"""Performs a lookup of the given IP address in the ACL.

If any matches are found, the result is a JSON-encoded HTTP body.

If no matches are found, then `None` is returned. This corresponds
to an HTTP error code of 204, “No Content”.

:raises ~fastly_compute.exceptions.acl.acl_error.AclError:
"""
return Pollable(self._wit_resource.lookup(ip_addr))
97 changes: 97 additions & 0 deletions fastly_compute/_bindings/async_io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# This file is automatically generated by scripts/generate_bindings.
# Do not edit directly.
"""Async IO support.

This module provides several utilities for performing I/O asynchronously.
See the documentation for `async-io.pollable` for a description of the kinds
of events it supports.

In the future, this interface is expected to be replaced by
[integrated async features].

[integrated async features]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md#-async-explainer
"""

from __future__ import annotations

from typing import Self

from wit_world.imports import async_io as _wit

from fastly_compute._resource import FastlyResource

__all__ = [
"Pollable",
"select",
"select_with_timeout",
]


class Pollable(FastlyResource[_wit.Pollable]):
"""An object supporting generic async operations.

Can be a `http-body.body`, `http-req.pending-response`, `http-req.pending-request`,
`cache.pending-entry`. `kv-store.pending-lookup`, `kv-store.pending-insert`,
`kv-store.pending-delete`, or `kv-store.pending-list`.

Each async item has an associated I/O action:

* Pending requests: awaiting the response headers / `response` object
* Normal bodies: reading bytes from the body
* Streaming bodies: writing bytes to the body

For writing bytes, there is a large buffer associated with the handle that bytes
can eagerly be written into, even before the origin itself consumes that data.
"""

def is_ready(self) -> bool:
"""Make a nonblocking attempt to complete the I/O operation.

Returns `True` if the given async item is “ready” for its associated I/O action, `False`
otherwise.

If an object is ready, the I/O action is guaranteed to complete without blocking.

Valid object handles includes bodies and pending requests. See the `async-io.pollable`
definition for more details, including what I/O actions are associated with each handle
type.
"""
return self._wit_resource.is_ready()

@classmethod
def new_ready(cls) -> Self:
"""Create a new trivial `pollable` which reports being immediately ready."""
return cls(_wit.Pollable.new_ready())


def select(handles: list[Pollable]) -> int:
"""Blocks until one of the given objects is ready for I/O.

If an object is ready, the I/O action is guaranteed to complete without blocking.

Valid object handles includes bodies and pending requests. See the `async-io.pollable`
definition for more details, including what I/O actions are associated with each handle
type.

Returns the *index* (not handle!) of the first object that is ready.

Traps if the list is empty.
"""
return _wit.select(handles)


def select_with_timeout(handles: list[Pollable], timeout_ms: int) -> int | None:
"""Blocks until one of the given objects is ready for I/O, or the timeout expires.

If an object is ready, the I/O action is guaranteed to complete without blocking.

Valid object handles includes bodies and pending requests. See the `async-io.pollable`
definition for more details, including what I/O actions are associated with each handle
type.

The timeout is specified in milliseconds.

Returns the *index* (not handle!) of the first object that is ready, or `None` if the
timeout expires before any objects are ready for I/O.
"""
return _wit.select_with_timeout(handles, timeout_ms)
Loading
Loading