Files
openfe/openfecli/fetching.py
Alyssa Travitz 2311a2f2d9 ruff formatting part 2 - everything but the tests (#1610)
* add more checks

* make precommit manual

* apply formatting to pyproject.toml

* add TODO

* remove unneeded, add a few more

* add ruff, but turn everything off

* add openfe known first party

* format highest-level files

* first half of openfe protocols

* second half of openfe protocols

* openfe protocols formatting, with alyssa's fmt skips

* add ruff formatter to precommit

* fmt: off all vendored _rfe_utils code

* addressing review comments

* format openfe/utils

* format openfe/setup

* first batch of cli formatting

* second batch of cli formatting

* formatting the rest of openfecli commands

* format openfecli/parameters

* format openfe/storage

* run precommit

* Update openfecli/commands/gather.py

Co-authored-by: Irfan Alibay <IAlibay@users.noreply.github.com>

* update example notebooks branch for v1.7.0 docs changes (#1615)

* bump example notebooks branch

* add ipykernel to env

* roll back to fixing septop branch

* i dont think we want ipykernel

* bump to tmp_fix_docs branch

* point to branch revert-237-v1.7_cookbooks

* point to latest example notebooks release

* remove colab button, point to updated example notebooks, reorg landing page (#1618)

* remove colab button from example notebooks in docs

* point to example notebooks 2025.10.2

* replace 'try' with CLI

---------

Co-authored-by: Irfan Alibay <IAlibay@users.noreply.github.com>
2025-10-24 14:09:45 -07:00

172 lines
4.8 KiB
Python

import click
from plugcli.plugin_management import CommandPlugin
import urllib.request
import importlib.resources
import shutil
from .utils import write
import pathlib
class _Fetcher:
"""Base class for fetchers. Defines the API and plugin creation.
Parameters
----------
resources: Iterable[Tuple[str, str]]
resources to be downloaded, as (source, filename)
short_name: str
name of the command used after openfe fetch
short_help: str
short help shown in openfe fetch --help
long_help: str
help shown in openfe fetch short_name --help
requires_ofe: Tuple
minimum version of OpenFE required
"""
REQUIRES_INTERNET = None
def __init__(
self,
resources,
short_name,
short_help,
requires_ofe,
section=None,
long_help=None,
):
self._resources = resources
self.short_name = short_name
self.short_help = short_help
self.requires_ofe = requires_ofe
self.section = section
self.long_help = long_help
@property
def resources(self):
yield from self._resources
def __call__(self, directory: pathlib.Path):
raise NotImplementedError()
@property
def plugin(self):
"""Plugin used by this fetcher"""
docs = self.long_help or ""
docs += "\n\nThis will fetch the following files:\n\n"
# if you're getting a problem with unpacking here, you probably
# forgot to make resources a list of tuple of (base, filename)
for _, filename in self.resources:
docs += f"* {filename}\n"
if self.REQUIRES_INTERNET is True:
short_help = self.short_help + " [requires internet]"
section = "Requires Internet"
elif self.REQUIRES_INTERNET is False:
short_help = self.short_help
section = "Built-in"
else: # -no-cov-
raise RuntimeError("Class must set boolean REQUIRES_INTERNET")
@click.command(
self.short_name,
short_help=short_help,
help=docs,
)
@click.option(
'-d', '--directory', default='.',
help="output directory, defaults to current directory",
type=click.Path(file_okay=False, dir_okay=True, writable=True),
) # fmt: skip
def command(directory):
directory = pathlib.Path(directory)
directory.mkdir(parents=True, exist_ok=True)
self(directory)
return FetchablePlugin(
command,
section=self.section,
requires_ofe=self.requires_ofe,
fetcher=self,
)
class URLFetcher(_Fetcher):
"""Fetcher for URLs.
Resources should be (base, filename), e.g., ("https://google.com/",
"index.html).
"""
REQUIRES_INTERNET = True
def __call__(self, dest_dir):
for base, filename in self.resources:
# let's just prevent one footgun here
if not base.endswith("/"):
base += "/"
write(f"Fetching {base}{filename}")
with urllib.request.urlopen(base + filename) as resp:
contents = resp.read()
with open(dest_dir / filename, mode="wb") as f:
f.write(contents)
class PkgResourceFetcher(_Fetcher):
"""Fetcher for data included with the package
Resources should be (package, filename), e.g., ("openfecli",
"__init__.py").
"""
REQUIRES_INTERNET = False
def __call__(self, dest_dir):
for package, filename in self.resources:
ref = importlib.resources.files(package) / filename
write(f"Fetching {str(ref)}")
with importlib.resources.as_file(ref) as f:
shutil.copyfile(ref, dest_dir / filename)
# should work, but don't want to write tests yet or deal with typing
# class MixedResourcesFetcher(_Fetcher):
# @property
# def REQUIRES_INTERNET(self):
# return any([fetcher.REQUIRES_INTERNET
# for fetcher in self._resources])
# @property
# def resources(self):
# for resource in self._resources:
# yield from resource.resources
# def __call__(self):
# for fetcher in self._resources:
# fetcher()
class FetchablePlugin(CommandPlugin):
"""Plugin class for Fetchables.
This includes the fetcher to simplify testing and introspection.
"""
def __init__(self, command, section, requires_ofe, fetcher):
super().__init__(
command=command,
section=section,
requires_lib=requires_ofe,
requires_cli=requires_ofe,
)
self.fetcher = fetcher
@property
def filenames(self):
return [res[1] for res in self.fetcher.resources]