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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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