Skip to content

Commit

Permalink
SFA: Load single-file applications from remote resources
Browse files Browse the repository at this point in the history
  • Loading branch information
amotl committed Nov 9, 2024
1 parent ef7c920 commit 214dbd4
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 11 deletions.
2 changes: 2 additions & 0 deletions pueblo/sfa/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ not be used together.
# Invoke Python entrypoint with given specification.
PYTHONPATH=$(pwd) sfa run tests.testdata.entrypoint:main
sfa run tests/testdata/entrypoint.py:main
sfa run https://github.com/pyveci/pueblo/raw/refs/heads/sfa/tests/testdata/entrypoint.py#main
sfa run github://pyveci:pueblo@/tests/testdata/entrypoint.py#main
```


Expand Down
35 changes: 28 additions & 7 deletions pueblo/sfa/core.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import importlib.util
import logging
import sys
import typing as t
from pathlib import Path
from tempfile import NamedTemporaryFile
from types import ModuleType
from urllib.parse import urlparse

from attrs import define

logger = logging.getLogger(__name__)


class InvalidTarget(Exception):
pass
Expand All @@ -29,6 +33,7 @@ def from_spec(cls, spec: str, default_property=None):
:param default_property: Name of the property to load if not specified in target (default: "api")
:return:
"""
is_url = False
if cls.is_valid_url(spec):
# Decode launch target location address from URL.
# URL: https://example.org/acme/app.py#foo
Expand All @@ -51,7 +56,6 @@ def from_spec(cls, spec: str, default_property=None):
if default_property is None:
raise ValueError("Property can not be discovered, and no default property was supplied")
prop = default_property

Check warning on line 58 in pueblo/sfa/core.py

View check run for this annotation

Codecov / codecov/patch

pueblo/sfa/core.py#L55-L58

Added lines #L55 - L58 were not covered by tests
is_url = False

return cls(target=target, property=prop, is_url=is_url)

Expand Down Expand Up @@ -102,9 +106,10 @@ def run(self, *args, **kwargs):
def load(self):
target = self.address.target
prop = self.address.property
is_url = self.address.is_url

# Sanity checks, as suggested by @coderabbitai. Thanks.
if not target or (":" in target and len(target.split(":")) != 2):
if not is_url and (not target or (":" in target and len(target.split(":")) != 2)):
raise InvalidTarget(

Check warning on line 113 in pueblo/sfa/core.py

View check run for this annotation

Codecov / codecov/patch

pueblo/sfa/core.py#L113

Added line #L113 was not covered by tests
f"Invalid target format: {target}. "
"Use either a Python module entrypoint specification, "
Expand Down Expand Up @@ -139,13 +144,29 @@ def load(self):

def load_any(self):
if self.address.is_url:
mod = None
import fsspec

url = urlparse(self.address.target)
url_path = Path(url.path)
name = "_".join([url_path.parent.stem, url_path.stem])
suffix = url_path.suffix
app_file = NamedTemporaryFile(prefix=f"{name}_", suffix=suffix, delete=False)
target = app_file.name
logger.info(f"Loading remote single-file application, source: {url}")
logger.info(f"Writing remote single-file application, target: {target}")
fs = fsspec.open(f"simplecache::{self.address.target}")
with fs as f:
app_file.write(f.read())
app_file.flush()
path = Path(app_file.name)
else:
path = Path(self.address.target)
if path.is_file():
mod = self.load_file(path)
else:
mod = importlib.import_module(self.address.target)

if path.is_file():
mod = self.load_file(path)
else:
mod = importlib.import_module(self.address.target)

self._module = mod

@staticmethod
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ optional-dependencies.release = [
]
optional-dependencies.sfa = [
"attrs",
"fsspec[github,http,libarchive,s3]<2024.11",
"tomli<3",
]
optional-dependencies.test = [
"pueblo[testing]",
Expand Down
Empty file added tests/sfa/__init__.py
Empty file.
File renamed without changes.
11 changes: 7 additions & 4 deletions tests/test_sfa.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ def test_address_url():
@pytest.mark.parametrize(
"spec",
[
"tests.testdata.entrypoint:main",
"tests/testdata/entrypoint.py:main",
"tests.sfa.basic:main",
"tests/sfa/basic.py:main",
"https://github.com/pyveci/pueblo/raw/refs/heads/sfa/tests/sfa/basic.py#main",
],
)
def test_application_api_success(capsys, spec):
Expand All @@ -53,6 +54,7 @@ def test_application_api_success(capsys, spec):
[
"pueblo.context:pueblo_cache_path",
"pueblo/context.py:pueblo_cache_path",
"https://github.com/pyveci/pueblo/raw/refs/heads/main/pueblo/context.py#pueblo_cache_path",
],
)
def test_application_api_not_callable(capsys, spec):
Expand All @@ -65,8 +67,9 @@ def test_application_api_not_callable(capsys, spec):
@pytest.mark.parametrize(
"spec",
[
"tests.testdata.entrypoint:main",
"tests/testdata/entrypoint.py:main",
"tests.sfa.basic:main",
"tests/sfa/basic.py:main",
"https://github.com/pyveci/pueblo/raw/refs/heads/sfa/tests/sfa/basic.py#main",
],
)
def test_application_cli(mocker, capfd, spec):
Expand Down

0 comments on commit 214dbd4

Please sign in to comment.