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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
11 changes: 11 additions & 0 deletions .github/workflows/preview-deployments.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
macos:
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
Expand Down Expand Up @@ -48,11 +49,20 @@ jobs:
windows:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
target: [x64, x86]
steps:
- uses: actions/checkout@v4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/preview-deployments.yml

Repository: sparckles/Robyn

Length of output: 6341


🏁 Script executed:

find .github/workflows -type f -name "*.yml" -o -name "*.yaml" | head -20

Repository: sparckles/Robyn

Length of output: 257


🏁 Script executed:

rg "actions/checkout" .github/workflows/ -A 3

Repository: sparckles/Robyn

Length of output: 4313


🌐 Web query:

GitHub Actions security best practices 2024 2025 actions/checkout pinning

💡 Result:

Securing GitHub Actions workflows, especially when using actions/checkout and other third-party dependencies, relies on several established best practices as of 2026 [1][2][3]. The most critical security measure is ensuring that all actions are referenced by their full-length commit SHA [1][2][4]. Action Pinning Using mutable version tags (e.g., @v4) is insecure because a compromised upstream repository can update the tag to point to a malicious commit [1][5][2]. You must pin actions to a full 40-character commit SHA [2][6][4]. To maintain readability and updateability, it is standard practice to include the version number in a trailing comment [2][6]: - uses: actions/checkout@11bd719 # v4.2.2 Automate this process using tools like Dependabot or Renovate, which can automatically open pull requests to update these SHAs [5][2][6]. Core Security Best Practices 1. Least Privilege Permissions: Never rely on default permissions [2]. Use the permissions key at the workflow or job level to explicitly define only the scopes required [2]. Start with permissions: {} and add necessary scopes incrementally [2]. 2. OIDC for Cloud Authentication: Avoid long-lived secrets (e.g., AWS_ACCESS_KEY_ID) [2]. Use OpenID Connect (OIDC) to federate identity with your cloud provider, allowing the runner to request short-lived, job-specific access tokens [2][7][3]. 3. Protect Secrets: Ensure secrets are scoped to specific environments and use environment protection rules to require manual approval for high-risk deployments [2][3]. 4. Audit Third-Party Actions: Vet third-party actions before inclusion, favoring those from verified creators or those with significant community maintenance [8][9]. If an action is simple, consider implementing the required logic directly in your workflow using native shell commands [3]. 5. Governance Policies: Use repository or organization-level policies to restrict which actions can be used [10][4][3]. This allows you to block all third-party actions by default, requiring explicit approval for those you trust [8][3]. 6. Avoid Risky Triggers: Be cautious with the pull_request_target trigger, especially when combined with actions that check out untrusted code from forks [1][2]. For such workflows, ensure the checkout step runs in a separate, isolated job that does not have access to sensitive secrets [1][3]. For ongoing monitoring, use static analysis tools to scan workflow files for misconfigurations, such as unpinned actions or overly broad permissions [1][2][3].

Citations:


🌐 Web query:

actions/checkout persist-credentials false security hardening

💡 Result:

Setting persist-credentials: false on actions/checkout is a recommended GitHub Actions security hardening step, because it prevents Git credentials (from the checkout step) from being persisted in the workflow’s runner git configuration, reducing exposure if the workflow (or later steps/actions) is compromised [1]. What persist-credentials changes - By default, actions/checkout can persist an auth token/credentials in the local git configuration so later steps can run authenticated git commands; persist-credentials: false opts out of that persistence [2]. How to apply it (example) - Use it on checkout steps that are only reading source code (most CI jobs). For example: - uses: actions/checkout@ with: persist-credentials: false Additional hardening considerations (high impact) 1) Least-privilege for GITHUB_TOKEN - Even with persist-credentials: false, hardening should include restricting the workflow/job permissions for GITHUB_TOKEN (e.g., contents: read for read-only jobs) to minimize blast radius [2][3]. 2) Avoid risky triggers/checkout patterns - Ensure you don’t check out and execute untrusted fork code in privileged contexts (e.g., pull_request_target), which is a common high-risk scenario unrelated to persist-credentials but often co-occurs in insecure workflows [1][4]. Net effect - persist-credentials: false reduces the chance that later workflow steps can reuse stored git credentials left behind by the checkout action, so it’s a sensible default for read-only workflows [1][2].

Citations:


Harden all actions/checkout invocations by pinning to commit SHA and disabling credential persistence.

All four checkout steps in this workflow (lines 25, 57, 92, 125) are using mutable version tags (@v4), which is less secure than pinning to a full commit SHA. Additionally, credential persistence is enabled by default. For CI supply-chain hardening, pin each to a commit SHA (with a version comment for readability) and set persist-credentials: false to prevent leakage of Git credentials if the workflow is compromised.

Example patch
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@<full_commit_sha_for_v4> # v4.2.2
+        with:
+          persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 57-60: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 57-57: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/preview-deployments.yml at line 57, All four invocations
of actions/checkout in this workflow are using mutable version tags (`@v4`)
instead of pinned commit SHAs, which is less secure for supply-chain hardening.
Additionally, credential persistence is enabled by default. For each of the four
actions/checkout steps (lines 25, 57, 92, and 125), replace the `@v4` tag with a
pinned full commit SHA (keeping a version comment for readability) and add
persist-credentials: false configuration option to prevent Git credentials from
being leaked if the workflow is compromised.

Source: Linters/SAST tools

# The Microsoft Store python alias stubs intermittently raise EACCES when a
# Node-based action probes PATH for python3, failing the whole job. Remove
# them; setup-python below installs the real interpreter on PATH.
- name: Remove Windows Store python aliases (avoids flaky EACCES)
shell: pwsh
run: |
Remove-Item "$env:LOCALAPPDATA\Microsoft\WindowsApps\python.exe" -Force -ErrorAction SilentlyContinue
Remove-Item "$env:LOCALAPPDATA\Microsoft\WindowsApps\python3.exe" -Force -ErrorAction SilentlyContinue
- name: Install uv
uses: astral-sh/setup-uv@v3
- uses: actions/setup-python@v6
Expand All @@ -74,6 +84,7 @@ jobs:
linux:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
target: [x86_64, i686]
Expand Down
21 changes: 20 additions & 1 deletion .github/workflows/python-CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
name: ${{ matrix.os }} tests with python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
Expand All @@ -53,4 +53,23 @@ jobs:
with:
python-versions: ${{ matrix.python-version }}
- name: Test with Nox
if: runner.os != 'Windows'
run: nox --non-interactive --error-on-missing-interpreter -p ${{ matrix.python-version }}
# Windows extension-module builds intermittently hit a transient linker
# failure (LNK1181: cannot open input file 'pythonXY.lib') from the hosted
# toolchain. Retry so a one-off linker hiccup doesn't fail the whole run.
- name: Test with Nox (Windows, retry transient linker failures)
if: runner.os == 'Windows'
shell: bash
run: |
for attempt in 1 2 3; do
echo "::group::nox attempt $attempt"
if nox --non-interactive --error-on-missing-interpreter -p ${{ matrix.python-version }}; then
echo "::endgroup::"
exit 0
fi
echo "::endgroup::"
echo "nox attempt $attempt failed; retrying..."
done
echo "nox failed after 3 attempts"
exit 1
Comment on lines +65 to +75

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Retry loop should only retry the known transient linker failure.

The current Windows loop retries every nox failure. That can hide unrelated intermittent regressions (tests/lint/build) when a second run passes. Scope retries to the specific LNK1181 ... python*.lib signature and fail immediately for all other failures.

Suggested patch
       - name: Test with Nox (Windows, retry transient linker failures)
         if: runner.os == 'Windows'
         shell: bash
         run: |
           for attempt in 1 2 3; do
             echo "::group::nox attempt $attempt"
-            if nox --non-interactive --error-on-missing-interpreter -p ${{ matrix.python-version }}; then
+            nox --non-interactive --error-on-missing-interpreter -p ${{ matrix.python-version }} 2>&1 | tee nox.log
+            status=${PIPESTATUS[0]}
+            if [ "$status" -eq 0 ]; then
               echo "::endgroup::"
               exit 0
             fi
+            if ! grep -Eq "LNK1181: cannot open input file 'python[0-9]+\.lib'" nox.log; then
+              echo "::endgroup::"
+              echo "nox failed for a non-transient reason; not retrying."
+              exit "$status"
+            fi
             echo "::endgroup::"
             echo "nox attempt $attempt failed; retrying..."
           done
           echo "nox failed after 3 attempts"
           exit 1
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for attempt in 1 2 3; do
echo "::group::nox attempt $attempt"
if nox --non-interactive --error-on-missing-interpreter -p ${{ matrix.python-version }}; then
echo "::endgroup::"
exit 0
fi
echo "::endgroup::"
echo "nox attempt $attempt failed; retrying..."
done
echo "nox failed after 3 attempts"
exit 1
for attempt in 1 2 3; do
echo "::group::nox attempt $attempt"
nox --non-interactive --error-on-missing-interpreter -p ${{ matrix.python-version }} 2>&1 | tee nox.log
status=${PIPESTATUS[0]}
if [ "$status" -eq 0 ]; then
echo "::endgroup::"
exit 0
fi
if ! grep -Eq "LNK1181: cannot open input file 'python[0-9]+\.lib'" nox.log; then
echo "::endgroup::"
echo "nox failed for a non-transient reason; not retrying."
exit "$status"
fi
echo "::endgroup::"
echo "nox attempt $attempt failed; retrying..."
done
echo "nox failed after 3 attempts"
exit 1
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 67-67: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/python-CI.yml around lines 65 - 75, The retry loop in the
nox command block retries every failure indiscriminately, which can mask real
issues when they pass on subsequent runs. Modify the retry logic to capture the
nox command output and only retry when the specific LNK1181 linker error
(related to python*.lib) is detected in the output. For all other failure types,
the loop should exit immediately with a failure status rather than attempting
retries. This ensures that only the known transient linker issue gets retried
while actual test, lint, or build failures fail fast.

14 changes: 13 additions & 1 deletion .github/workflows/release-CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
macos:
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
Expand Down Expand Up @@ -52,11 +53,20 @@ jobs:
windows:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
target: [x64, x86]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the release-CI.yml file
find . -name "release-CI.yml" -o -name "release-ci.yml" | head -20

Repository: sparckles/Robyn

Length of output: 94


🏁 Script executed:

# List all workflow files to understand the structure
find .github/workflows -type f -name "*.yml" -o -name "*.yaml" 2>/dev/null | head -20

Repository: sparckles/Robyn

Length of output: 257


🏁 Script executed:

# Examine the release-CI.yml file around line 61
if [ -f ".github/workflows/release-CI.yml" ]; then
  cat -n .github/workflows/release-CI.yml | sed -n '50,70p'
elif [ -f ".github/workflows/release-ci.yml" ]; then
  cat -n .github/workflows/release-ci.yml | sed -n '50,70p'
else
  echo "File not found, searching..."
  find .github -name "*release*" -name "*.yml"
fi

Repository: sparckles/Robyn

Length of output: 1140


🏁 Script executed:

# Search for all checkout actions in workflow files to see if hardening exists elsewhere
rg "actions/checkout" .github/workflows --no-heading -B2 -A2

Repository: sparckles/Robyn

Length of output: 5022


🏁 Script executed:

# Check the full release-CI.yml to see all checkout actions and any hardening applied
cat -n .github/workflows/release-CI.yml | grep -A5 -B5 "actions/checkout"

Repository: sparckles/Robyn

Length of output: 1899


🏁 Script executed:

# Search for any persist-credentials settings across all workflows
rg "persist-credentials" .github/workflows/

Repository: sparckles/Robyn

Length of output: 41


🏁 Script executed:

# Check if any checkout actions are pinned to commit SHAs
rg "actions/checkout@[a-f0-9]" .github/workflows/

Repository: sparckles/Robyn

Length of output: 41


🏁 Script executed:

# Get the exact lines for all checkout usages in release-CI.yml
cat .github/workflows/release-CI.yml | grep -n "actions/checkout"

Repository: sparckles/Robyn

Length of output: 209


🏁 Script executed:

# Check if any checkout action in any workflow has persist-credentials or is pinned
for file in .github/workflows/*.yml; do
  echo "=== $file ==="
  cat "$file" | grep -A10 "actions/checkout"
done

Repository: sparckles/Robyn

Length of output: 5688


🏁 Script executed:

# Check the entire release-CI.yml to see if any checkout has special configuration
cat .github/workflows/release-CI.yml

Repository: sparckles/Robyn

Length of output: 7618


🌐 Web query:

GitHub Actions checkout security best practices commit SHA pinning persist-credentials

💡 Result:

To secure your GitHub Actions workflows using actions/checkout, follow these industry-standard best practices: 1. Use persist-credentials: false: By default, actions/checkout persists the GITHUB_TOKEN or other credentials in the local git configuration [1][2]. This makes the token available to any subsequent step or third-party action in your job, increasing the risk of accidental exposure or misuse [3][2]. You should explicitly set this to false unless your workflow specifically requires authenticated git operations (e.g., pushing commits back to the repository) [4][3]. - uses: actions/checkout@v4 with: persist-credentials: false 2. Pin Actions to a Full Commit SHA: Using mutable tags like @v4 or @v4.1.1 is insecure because an attacker who compromises the action repository could move the tag to a malicious commit [5][6]. Always pin third-party actions, including actions/checkout, to their full 40-character commit SHA [5][6]. You can retain readability by adding the version tag as a comment [5]. - uses: actions/checkout@b4ffde6 # v4.1.1 3. Additional Security Considerations: - Principle of Least Privilege: Always define the permissions block at the job or workflow level, granting only the minimum required access (e.g., permissions: contents: read) rather than relying on default permissions [5]. - Avoid Risky Triggers: Be cautious with events like pull_request_target, which can execute fork code in a trusted context [4][5]. Always review the security implications before using them [1]. - Automation: Consider using automated security tools (e.g., Dependabot, Zizmor) to enforce these practices, such as automatically updating SHA pins and identifying insecure configurations [4][5].

Citations:


Pin checkout action to commit SHA and disable credential persistence.

The actions/checkout@v4 step should be commit-SHA pinned and configured with persist-credentials: false to reduce token exposure and prevent tag-retargeting attacks.

Suggested patch
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+        with:
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@v4
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 61-64: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 61-61: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-CI.yml at line 61, The actions/checkout@v4 step
needs to be hardened for security by pinning it to a specific commit SHA instead
of using the version tag, and by adding the persist-credentials configuration
set to false. Locate the uses statement with actions/checkout@v4, replace the
version tag with a specific commit SHA (e.g., `@abc123def`...), and add a with
section that includes persist-credentials: false to prevent token exposure and
mitigate tag-retargeting attacks.

Source: Linters/SAST tools

# The Microsoft Store python alias stubs intermittently raise EACCES when a
# Node-based action probes PATH for python3, failing the whole job. Remove
# them; setup-python below installs the real interpreter on PATH.
- name: Remove Windows Store python aliases (avoids flaky EACCES)
shell: pwsh
run: |
Remove-Item "$env:LOCALAPPDATA\Microsoft\WindowsApps\python.exe" -Force -ErrorAction SilentlyContinue
Remove-Item "$env:LOCALAPPDATA\Microsoft\WindowsApps\python3.exe" -Force -ErrorAction SilentlyContinue
- name: Install uv
uses: astral-sh/setup-uv@v3
- uses: actions/setup-python@v6
Expand All @@ -82,6 +92,7 @@ jobs:
linux:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
target: [x86_64, i686]
Expand Down Expand Up @@ -113,6 +124,7 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.enable_linux_cross)
strategy:
fail-fast: false
matrix:
python:
[
Expand Down
4 changes: 4 additions & 0 deletions src/asyncio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
// Licensed under the BSD 3-Clause License
// See: https://github.com/emmett-framework/granian/blob/master/LICENSE

// Adapted runtime scaffolding: some helpers aren't wired up on every code path
// yet, so allow dead code at the module level rather than scattering attributes.
#![allow(dead_code)]

use pyo3::{prelude::*, sync::PyOnceLock};
use std::convert::Into;
use std::ffi::CString;
Expand Down
4 changes: 4 additions & 0 deletions src/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
// Licensed under the BSD 3-Clause License
// See: https://github.com/emmett-framework/granian/blob/master/LICENSE

// Adapted runtime scaffolding: some helpers aren't wired up on every code path
// yet, so allow dead code at the module level rather than scattering attributes.
#![allow(dead_code)]

use pyo3::{exceptions::PyStopIteration, prelude::*, IntoPyObjectExt};
use std::sync::{atomic, Arc, OnceLock, RwLock};
use tokio::sync::Notify;
Expand Down
3 changes: 3 additions & 0 deletions src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// Licensed under the BSD 3-Clause License
// See: https://github.com/emmett-framework/granian/blob/master/LICENSE

// Adapted scaffolding: not every conversion variant is constructed yet.
#![allow(dead_code)]

use pyo3::prelude::*;
pub(crate) enum FutureResultToPy {
None,
Expand Down
38 changes: 19 additions & 19 deletions src/executors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ fn extract_response_type_fast(output: &Bound<'_, PyAny>) -> PyResult<ResponseTyp
let py = output.py();

// 1. PyResponse pyclass downcast — zero getattr calls.
// `downcast_exact` (not `downcast`) so that user `Response` subclasses
// `cast_exact` (not `downcast`) so that user `Response` subclasses
// with overridden properties fall through to the getattr-based slow
// path below, preserving their custom read semantics.
if let Ok(py_resp) = output.downcast_exact::<PyResponse>() {
if let Ok(py_resp) = output.cast_exact::<PyResponse>() {
let borrowed = py_resp.borrow();
let description =
crate::types::get_description_from_pyobject(borrowed.description.bind(py))?;
Expand All @@ -110,10 +110,10 @@ fn extract_response_type_fast(output: &Bound<'_, PyAny>) -> PyResult<ResponseTyp
// Uses subclass-aware `downcast` to match the Python router's
// `isinstance(res, (dict, list, ...))` check; orjson serializes
// dict/list subclasses as their base type by default.
if output.downcast::<PyDict>().is_ok() || output.downcast::<PyList>().is_ok() {
if output.cast::<PyDict>().is_ok() || output.cast::<PyList>().is_ok() {
let dumps = orjson_dumps(py)?;
let encoded = dumps.call1((output,))?;
let bytes = encoded.downcast::<PyBytes>()?.as_bytes().to_vec();
let bytes = encoded.cast::<PyBytes>()?.as_bytes().to_vec();
return Ok(ResponseType::Standard(response_from_bytes(
bytes,
json_headers(),
Expand All @@ -122,14 +122,14 @@ fn extract_response_type_fast(output: &Bound<'_, PyAny>) -> PyResult<ResponseTyp

// 3. Bare str/bytes — `downcast` (subclass-aware) to match the Python
// router's `isinstance` gate.
if let Ok(s) = output.downcast::<PyString>() {
if let Ok(s) = output.cast::<PyString>() {
let bytes = s.to_string().into_bytes();
return Ok(ResponseType::Standard(response_from_bytes(
bytes,
text_plain_headers(),
)));
}
if let Ok(b) = output.downcast::<PyBytes>() {
if let Ok(b) = output.cast::<PyBytes>() {
let bytes = b.as_bytes().to_vec();
return Ok(ResponseType::Standard(response_from_bytes(
bytes,
Expand Down Expand Up @@ -162,7 +162,7 @@ where
T: Clone + for<'py> IntoPyObject<'py>,
for<'py> <T as IntoPyObject<'py>>::Error: std::fmt::Debug,
{
let handler = function.handler.bind(py).downcast()?;
let handler = function.handler.bind(py).cast()?;

// 0-param handlers: skip Request→Python conversion entirely
if function.number_of_params == 0 {
Expand Down Expand Up @@ -279,7 +279,7 @@ where
for<'py> <T as IntoPyObject<'py>>::Error: std::fmt::Debug,
{
if function.is_async {
let output: Py<PyAny> = Python::with_gil(|py| -> PyResult<_> {
let output: Py<PyAny> = Python::attach(|py| -> PyResult<_> {
let coroutine = get_function_output(function, py, input)?;
let awaitable = match context {
Some(ctx) => wrap_coro_in_context(py, ctx.bind(py), coroutine)?,
Expand All @@ -289,7 +289,7 @@ where
})?
.await?;

Python::with_gil(|py| -> Result<MiddlewareReturn> {
Python::attach(|py| -> Result<MiddlewareReturn> {
// Try response extraction first, then request
match output.extract::<Response>(py) {
Ok(response) => Ok(MiddlewareReturn::Response(response)),
Expand All @@ -300,7 +300,7 @@ where
}
})
} else {
Python::with_gil(|py| -> Result<MiddlewareReturn> {
Python::attach(|py| -> Result<MiddlewareReturn> {
let output = match context {
Some(ctx) => get_function_output_in_context(function, py, ctx.bind(py), input)?,
None => get_function_output(function, py, input)?,
Expand Down Expand Up @@ -332,7 +332,7 @@ where
for<'py> <T as IntoPyObject<'py>>::Error: std::fmt::Debug,
for<'py> <U as IntoPyObject<'py>>::Error: std::fmt::Debug,
{
let handler = function.handler.bind(py).downcast()?;
let handler = function.handler.bind(py).cast()?;

if function.number_of_params == 0 {
return handler.call0();
Expand Down Expand Up @@ -471,7 +471,7 @@ pub async fn execute_after_middleware_function(
context: Option<&Py<PyAny>>,
) -> Result<MiddlewareReturn> {
if function.is_async {
let output: Py<PyAny> = Python::with_gil(|py| -> PyResult<_> {
let output: Py<PyAny> = Python::attach(|py| -> PyResult<_> {
let coroutine = get_function_output_with_two_args(function, py, request, response)?;
let awaitable = match context {
Some(ctx) => wrap_coro_in_context(py, ctx.bind(py), coroutine)?,
Expand All @@ -481,7 +481,7 @@ pub async fn execute_after_middleware_function(
})?
.await?;

Python::with_gil(|py| -> Result<MiddlewareReturn> {
Python::attach(|py| -> Result<MiddlewareReturn> {
// Try response extraction first, then request
match output.extract::<Response>(py) {
Ok(response) => Ok(MiddlewareReturn::Response(response)),
Expand All @@ -492,7 +492,7 @@ pub async fn execute_after_middleware_function(
}
})
} else {
Python::with_gil(|py| -> Result<MiddlewareReturn> {
Python::attach(|py| -> Result<MiddlewareReturn> {
let output = match context {
Some(ctx) => get_function_output_with_two_args_in_context(
function,
Expand Down Expand Up @@ -522,7 +522,7 @@ pub async fn execute_http_function(
context: Option<&Py<PyAny>>,
) -> PyResult<ResponseType> {
if function.is_async {
let output = Python::with_gil(|py| -> PyResult<_> {
let output = Python::attach(|py| -> PyResult<_> {
let coroutine = get_function_output(function, py, request)?;
let awaitable = match context {
Some(ctx) => wrap_coro_in_context(py, ctx.bind(py), coroutine)?,
Expand All @@ -532,9 +532,9 @@ pub async fn execute_http_function(
})?
.await?;

Python::with_gil(|py| extract_response_type(output, py))
Python::attach(|py| extract_response_type(output, py))
} else {
Python::with_gil(|py| {
Python::attach(|py| {
let output = match context {
Some(ctx) => get_function_output_in_context(function, py, ctx.bind(py), request)?,
None => get_function_output(function, py, request)?,
Expand All @@ -560,15 +560,15 @@ pub async fn execute_startup_handler(
) -> Result<()> {
if let Some(function) = event_handler {
if function.is_async {
Python::with_gil(|py| {
Python::attach(|py| {
pyo3_async_runtimes::into_future_with_locals(
task_locals,
function.handler.bind(py).call0()?,
)
})?
.await?;
} else {
Python::with_gil(|py| function.handler.call0(py))?;
Python::attach(|py| function.handler.call0(py))?;
}
}
Ok(())
Expand Down
10 changes: 5 additions & 5 deletions src/executors/web_socket_executors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ pub fn execute_ws_function(
ws: &WebSocketConnector,
) {
if function.is_async {
let fut = match Python::with_gil(|py| {
let handler = function.handler.bind(py).downcast()?;
let fut = match Python::attach(|py| {
let handler = function.handler.bind(py).cast()?;
let result = handler.call1((ws.clone(),))?;
pyo3_async_runtimes::into_future_with_locals(task_locals, result)
}) {
Expand All @@ -26,7 +26,7 @@ pub fn execute_ws_function(
};
let f = async move {
match fut.await {
Ok(output) => Python::with_gil(|py| match output.extract::<Option<String>>(py) {
Ok(output) => Python::attach(|py| match output.extract::<Option<String>>(py) {
Ok(msg) => msg,
Err(e) => {
log::error!("Failed to extract WebSocket handler result: {}", e);
Expand All @@ -47,8 +47,8 @@ pub fn execute_ws_function(
});
ctx.spawn(f);
} else {
Python::with_gil(|py| {
let handler = match function.handler.bind(py).downcast() {
Python::attach(|py| {
let handler = match function.handler.bind(py).cast() {
Ok(h) => h,
Err(e) => {
log::error!("Failed to get sync WebSocket handler: {}", e);
Expand Down
2 changes: 1 addition & 1 deletion src/routers/http_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl Router<(FunctionInfo, HashMap<String, String>), HttpMethod> for HttpRouter
route_params.insert(key.to_string(), value.to_string());
}

let function_info = Python::with_gil(|_| res.value.to_owned());
let function_info = Python::attach(|_| res.value.to_owned());
return Some((function_info, route_params));
}

Expand Down
2 changes: 1 addition & 1 deletion src/routers/middleware_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl Router<(Vec<FunctionInfo>, HashMap<String, String>), MiddlewareType> for Mi
route_params.insert(key.to_string(), value.to_string());
}

let functions = Python::with_gil(|_| res.value.to_owned());
let functions = Python::attach(|_| res.value.to_owned());

Some((functions, route_params))
}
Expand Down
4 changes: 4 additions & 0 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
// Licensed under the BSD 3-Clause License
// See: https://github.com/emmett-framework/granian/blob/master/LICENSE

// Adapted runtime scaffolding: some helpers aren't wired up on every code path
// yet, so allow dead code at the module level rather than scattering attributes.
#![allow(dead_code)]

use futures::FutureExt;
use pyo3::{prelude::*, IntoPyObjectExt};
use std::{future::Future, sync::Arc, sync::OnceLock};
Expand Down
Loading
Loading