Skip to content

Commit

Permalink
Initial shim support.
Browse files Browse the repository at this point in the history
This is pretty complete support with decent tests. The
`insta=science-util` command family is still un-done.
  • Loading branch information
jsirois committed Dec 23, 2024
1 parent b116e38 commit eae9450
Show file tree
Hide file tree
Showing 19 changed files with 1,057 additions and 33 deletions.
5 changes: 2 additions & 3 deletions python/.gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Sdist build artifacts dirs.
# Build artifacts dirs.
/*.egg-info/
/build/

# Python bytecode.
__pycache__/

# UV outputs artifacts here.
/dist/
/dist/
17 changes: 10 additions & 7 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
# inst-science
# insta-science

The `inst-science` Python project distribution provides two convenience console scripts to make
The `insta-science` Python project distribution provides two convenience console scripts to make
bootstrapping `science` for use in Python project easier:
+ `inst-science`: This is a shim script that ensures `science` is installed and forwards all
supplied arguments to it. Instead of `science`, just use `inst-science`. You can configure the
+ `insta-science`: This is a shim script that ensures `science` is installed and forwards all
supplied arguments to it. Instead of `science`, just use `insta-science`. You can configure the
`science` version to use, where to find `science` binaries and where to install them via the
`[tool.inst-science]` table in your `pyproject.toml` file.
+ `inst-science-util`: This script provides utilities for managing `science` binaries. In
`[tool.insta-science]` table in your `pyproject.toml` file.
+ `insta-science-util`: This script provides utilities for managing `science` binaries. In
particular, it supports downloading families of `science` binaries for various platforms for
use in internal serving systems for offline or isolated installation.

This project is under active early development and APIs and configuration are likely to change
rapidly in breaking ways until the 1.0 release.

## Development

Development uses [`uv`](https://docs.astral.sh/uv/getting-started/installation/). Install as you
best see fit.

With `uv` installed, running `uv run dev-cmd` is enough to get the tools inst-science uses installed
With `uv` installed, running `uv run dev-cmd` is enough to get the tools insta-science uses installed
and run against the codebase. This includes formatting code, linting code, performing type checks
and then running tests.
8 changes: 0 additions & 8 deletions python/inst_science/shim.py

This file was deleted.

File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright 2024 Science project contributors.
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from inst_science import shim
from insta_science import shim

if __name__ == "__main__":
shim.science()
59 changes: 59 additions & 0 deletions python/insta_science/a_scie.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copyright 2024 Science project contributors.
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

from dataclasses import dataclass
from datetime import timedelta
from pathlib import PurePath

from packaging.version import Version

from insta_science.fetcher import fetch_and_verify
from insta_science.hashing import Digest, Fingerprint
from insta_science.model import Science, Url
from insta_science.platform import Platform


@dataclass(frozen=True)
class _LoadResult:
path: PurePath
binary_name: str


def _load_project_release(
project_name: str,
binary_name: str,
version: Version | None = None,
fingerprint: Digest | Fingerprint | None = None,
platform: Platform = Platform.current(),
) -> _LoadResult:
qualified_binary_name = platform.qualified_binary_name(binary_name)
base_url = f"https://github.com/a-scie/{project_name}/releases"
if version:
version_path = f"download/v{version}"
ttl = None
else:
version_path = "latest/download"
ttl = timedelta(days=5)
path = fetch_and_verify(
url=Url(f"{base_url}/{version_path}/{qualified_binary_name}"),
fingerprint=fingerprint,
executable=True,
ttl=ttl,
)
return _LoadResult(path=path, binary_name=qualified_binary_name)


def science(
specification: Science | None = None, platform: Platform = Platform.current()
) -> PurePath:
version = specification.version if specification else None
fingerprint = specification.digest if specification and specification.digest else None
return _load_project_release(
project_name="lift",
binary_name="science-fat",
version=version,
fingerprint=fingerprint,
platform=platform,
).path
92 changes: 92 additions & 0 deletions python/insta_science/cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright 2024 Science project contributors.
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

import atexit
import hashlib
import os
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime, timedelta
from functools import cache
from pathlib import Path
from typing import Iterator, Union

import appdirs
from filelock import FileLock
from typing_extensions import TypeAlias


@dataclass(frozen=True)
class Complete:
path: Path


@dataclass(frozen=True)
class Missing:
path: Path
work: Path


CacheResult: TypeAlias = Union[Complete, Missing]


_TTL_EXPIRY_FORMAT = "%m/%d/%y %H:%M:%S"


@dataclass(frozen=True)
class DownloadCache:
base_dir: Path

@contextmanager
def get_or_create(self, url: str, ttl: timedelta | None = None) -> Iterator[CacheResult]:
"""A context manager that yields a `cache result.
If the cache result is `Missing`, the block yielded to should materialize the given url
to the `Missing.work` path. Upon successful exit from this context manager, the given url's
content will exist at the cache result path.
"""
cached_file = self.base_dir / hashlib.sha256(url.encode()).hexdigest()

ttl_file = cached_file.with_suffix(".ttl") if ttl else None
if ttl_file and not ttl_file.exists():
cached_file.unlink(missing_ok=True)
elif ttl_file:
try:
datetime_object = datetime.strptime(
ttl_file.read_text().strip(), _TTL_EXPIRY_FORMAT
)
if datetime.now() > datetime_object:
cached_file.unlink(missing_ok=True)
except ValueError:
cached_file.unlink(missing_ok=True)

if cached_file.exists():
yield Complete(path=cached_file)
return

cached_file.parent.mkdir(parents=True, exist_ok=True)
with FileLock(str(cached_file.with_name(f"{cached_file.name}.lck"))):
if cached_file.exists():
yield Complete(path=cached_file)
return

work = cached_file.with_name(f"{cached_file.name}.work")
work.unlink(missing_ok=True)
atexit.register(work.unlink, missing_ok=True)
yield Missing(path=cached_file, work=work)
if not work.exists():
return
work.rename(cached_file)
if ttl_file and ttl:
ttl_file.write_text((datetime.now() + ttl).strftime(_TTL_EXPIRY_FORMAT))


@cache
def download_cache() -> DownloadCache:
return DownloadCache(
base_dir=Path(
os.environ.get("INSTA_SCIENCE_CACHE", appdirs.user_cache_dir(appname="insta-science"))
)
)
18 changes: 18 additions & 0 deletions python/insta_science/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2024 Science project contributors.
# Licensed under the Apache License, Version 2.0 (see LICENSE).


class InputError(ValueError):
"""An error caused by bad input.
These errors are discriminated by the main application as containing error information bound
for a user who may have supplied bad input that they can correct or may be running the
application in a bad environmental setup that they can correct.
By default, backtraces will not be displayed for these exceptions since they are not
exceptional cases; they're anticipated errors.
"""


class InvalidProjectError(InputError):
"""Indicates bad pyproject.toml configuration for `[tool.insta-science]`."""
Loading

0 comments on commit eae9450

Please sign in to comment.