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
10 changes: 7 additions & 3 deletions posit-bakery/posit_bakery/config/dependencies/positron.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import abc
from functools import cache
from typing import Literal, ClassVar

from pydantic import ConfigDict
Expand Down Expand Up @@ -31,18 +32,21 @@ def releases_url(target_arch: str = _DEFAULT_ARCH) -> str:
arch = _ARCH_MAP[target_arch]
return POSITRON_RELEASES_URL_TEMPLATE.format(arch=arch)

def _fetch_versions(self) -> list[DependencyVersion]:
@staticmethod
@cache
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.

roborev noted that the cached result can be mutated. We may want to note that or find a way to always return a copy.

def _fetch_versions() -> list[DependencyVersion]:
"""Fetch available Positron versions from Posit CDN.

Uses the default architecture for version discovery since the version
list is identical across architectures.

This method uses caching to avoid repeated network requests.
Memoized so the fetch+parse runs once per bakery invocation regardless
of how many constraint instances ask for it.

:return: A sorted list of available Positron versions.
"""
session = cached_session()
response = session.get(self.releases_url())
response = session.get(PositronDependency.releases_url())
response.raise_for_status()

releases = response.json().get("releases", [])
Expand Down
8 changes: 6 additions & 2 deletions posit-bakery/posit_bakery/config/dependencies/python.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import abc
from functools import cache
from typing import Literal, ClassVar

from pydantic import ConfigDict
Expand All @@ -17,10 +18,13 @@ class PythonDependency(BakeryYAMLModel, abc.ABC):

dependency: Literal[SupportedDependencies.PYTHON] = SupportedDependencies.PYTHON

def _fetch_versions(self) -> list[DependencyVersion]:
@staticmethod
@cache
def _fetch_versions() -> list[DependencyVersion]:
"""Fetch available Python versions from astral-sh/python-build-standalone.

This method uses caching to avoid repeated network requests.
Memoized so the fetch+parse runs once per bakery invocation regardless
of how many constraint instances ask for it.

The results only include cpython builds for linux.
Prerelease versions are excluded.
Expand Down
12 changes: 8 additions & 4 deletions posit-bakery/posit_bakery/config/dependencies/quarto.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import abc
from functools import cache
from typing import Annotated, Literal, ClassVar

from pydantic import ConfigDict, Field, field_validator
Expand Down Expand Up @@ -28,11 +29,14 @@ class QuartoDependency(BakeryYAMLModel, abc.ABC):
),
]

def _fetch_versions(self) -> list[DependencyVersion]:
@staticmethod
@cache
def _fetch_versions(prerelease: bool = False) -> list[DependencyVersion]:
"""Fetch available Quarto versions.
Only the latest patch version for each minor version is included.

This method uses caching to avoid repeated network requests.
Memoized so the fetch+parse runs once per bakery invocation per
``prerelease`` value (at most two entries).

:return: A sorted list of available Quarto versions.
"""
Expand All @@ -44,7 +48,7 @@ def _fetch_versions(self) -> list[DependencyVersion]:
response.raise_for_status()
versions.append(DependencyVersion(response.json().get("version")))

if self.prerelease:
if prerelease:
# Fetch prerelease version
response = session.get(QUARTO_PRERELEASE_URL)
response.raise_for_status()
Expand All @@ -64,7 +68,7 @@ def available_versions(self) -> list[DependencyVersion]:

:return: A sorted list of available Quarto versions.
"""
return self._fetch_versions()
return self._fetch_versions(self.prerelease)


class QuartoDependencyVersions(DependencyVersions, QuartoDependency):
Expand Down
8 changes: 6 additions & 2 deletions posit-bakery/posit_bakery/config/dependencies/r.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import abc
from functools import cache
from typing import Literal, ClassVar

from pydantic import ConfigDict
Expand All @@ -17,10 +18,13 @@ class RDependency(BakeryYAMLModel, abc.ABC):

dependency: Literal[SupportedDependencies.R] = SupportedDependencies.R

def _fetch_versions(self) -> list[DependencyVersion]:
@staticmethod
@cache
def _fetch_versions() -> list[DependencyVersion]:
"""Fetch available R versions from Posit.

This method uses caching to avoid repeated network requests.
Memoized so the fetch+parse runs once per bakery invocation regardless
of how many constraint instances ask for it.

The results exclude "devel" and "next" versions.

Expand Down
29 changes: 16 additions & 13 deletions posit-bakery/posit_bakery/util.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import os
from functools import cache
from pathlib import Path
from shutil import which
from typing import Union
Expand Down Expand Up @@ -63,17 +64,19 @@ def auto_path() -> Path:
return context


def cached_session(**kwargs) -> CachedSession:
"""Create a cached requests session with default settings."""
session_kwargs = {
"cache_name": "bakery_cache",
"expire_after": 3600,
"backend": "filesystem",
"use_temp": True,
"allowable_methods": ["GET"],
"allowable_codes": [200],
"stale_if_error": True,
}
session_kwargs.update(kwargs)
@cache
def cached_session() -> CachedSession:
Comment on lines +67 to +68
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.

We shouldn't need to test core language functionality IMO. This isn't a complicated setup.

"""Return a process-wide cached requests session.

return CachedSession(**session_kwargs)
Memoized so backend initialization and the accompanying log chatter only
happen once per bakery invocation.
"""
return CachedSession(
cache_name="bakery_cache",
expire_after=3600,
backend="filesystem",
use_temp=True,
allowable_methods=["GET"],
allowable_codes=[200],
stale_if_error=True,
)
Comment on lines +74 to +82
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.

I have a crazy idea: what if we cached this somehow as part of setup-bakery? Give it some sort of reasonable TTL and then recall the artifact it creates across all jobs in a workflow. That way we can resolve #304 and edge cases where new versions can crop up between build time and merge time.

15 changes: 15 additions & 0 deletions posit-bakery/test/config/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,21 @@ def patch_testdata_response(url: str):

@pytest.fixture(scope="function")
def disable_requests_caching(mocker):
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.

Should this be autouse?

# The session factory and fetch helpers are decorated with @functools.cache,
# so values returned by an earlier test would otherwise survive into the
# next one. Clear the per-process caches so each test's patches take effect.
from posit_bakery.config.dependencies.positron import PositronDependency
from posit_bakery.config.dependencies.python import PythonDependency
from posit_bakery.config.dependencies.quarto import QuartoDependency
from posit_bakery.config.dependencies.r import RDependency
from posit_bakery.util import cached_session

cached_session.cache_clear()
PythonDependency._fetch_versions.cache_clear()
RDependency._fetch_versions.cache_clear()
QuartoDependency._fetch_versions.cache_clear()
PositronDependency._fetch_versions.cache_clear()

return mocker.patch("posit_bakery.util.CachedSession", spec=requests.Session)


Expand Down
Loading