garvisometerui 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+ The format follows Keep a Changelog; this project uses Semantic Versioning.
5
+
6
+ ## 0.1.0
7
+
8
+ First public release.
9
+
10
+ - Standard-library-only loader that downloads a *profile* (a plain Python
11
+ program you host) and calls its `run(**kwargs)` entry point.
12
+ - Bounded retries with linear backoff for transient CDN/proxy errors.
13
+ - Downloaded source cached under the system temp directory (override with
14
+ `GARVISOMETERUI_CACHE`) so the executed program can be inspected after a run.
15
+ - No third-party dependencies, no build step, no platform-specific code.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Garvisometerui project contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include CHANGELOG.md
3
+ include LICENSE
4
+ recursive-include tests *.py
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: garvisometerui
3
+ Version: 0.1.0
4
+ Summary: Tiny standard-library loader and runner for remote Python profiles
5
+ License-Expression: MIT
6
+ Keywords: runner,profile,remote,notebook,cpu
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Provides-Extra: test
16
+ Requires-Dist: pytest; extra == "test"
17
+ Dynamic: license-file
18
+
19
+ # garvisometerui
20
+
21
+ A tiny, **standard-library-only** loader for *remote profiles*.
22
+
23
+ A *profile* is just an ordinary Python program you host yourself. The first
24
+ time it is needed, `garvisometerui` downloads it and calls its `run()` entry
25
+ point with the keyword arguments you pass along. The package has no
26
+ dependencies, no build step and no platform-specific code, so it installs and
27
+ imports anywhere.
28
+
29
+ ```python
30
+ from garvisometerui import run
31
+
32
+ run(profile="cpu-eval",
33
+ url="https://example/serve/eval-profile.py",
34
+ budget=43200)
35
+ ```
36
+
37
+ ## Why it exists
38
+
39
+ Long-running CPU jobs that start from a notebook usually need a pile of
40
+ one-off scripts that differ per deployment but are far too small to justify a
41
+ separate package each time. `garvisometerui` keeps the installable part
42
+ trivial and lets the deployment-specific program live on your own server: you
43
+ iterate the program without re-releasing anything, and the published library
44
+ stays honest about how little it does.
45
+
46
+ ## API
47
+
48
+ | function | description |
49
+ |---|---|
50
+ | `run(profile, url, **kwargs)` | download the profile, then call its `run(**kwargs)`; `profile` is forwarded so one program can serve several profiles |
51
+ | `load(profile, url)` | same download step, returns the executed module without calling it |
52
+
53
+ `url` can also be supplied through the `GARVISOMETERUI_PROFILE` environment
54
+ variable. Downloads retry transient CDN/proxy errors and cache the executed
55
+ source (under `GARVISOMETERUI_CACHE`) so it can be inspected afterwards.
56
+
57
+ ## Profile contract
58
+
59
+ A profile is any Python source exposing a `run(**kwargs)` callable. It decides
60
+ what it does with the arguments - this package only fetches and executes it.
61
+
62
+ ## License
63
+
64
+ MIT
@@ -0,0 +1,46 @@
1
+ # garvisometerui
2
+
3
+ A tiny, **standard-library-only** loader for *remote profiles*.
4
+
5
+ A *profile* is just an ordinary Python program you host yourself. The first
6
+ time it is needed, `garvisometerui` downloads it and calls its `run()` entry
7
+ point with the keyword arguments you pass along. The package has no
8
+ dependencies, no build step and no platform-specific code, so it installs and
9
+ imports anywhere.
10
+
11
+ ```python
12
+ from garvisometerui import run
13
+
14
+ run(profile="cpu-eval",
15
+ url="https://example/serve/eval-profile.py",
16
+ budget=43200)
17
+ ```
18
+
19
+ ## Why it exists
20
+
21
+ Long-running CPU jobs that start from a notebook usually need a pile of
22
+ one-off scripts that differ per deployment but are far too small to justify a
23
+ separate package each time. `garvisometerui` keeps the installable part
24
+ trivial and lets the deployment-specific program live on your own server: you
25
+ iterate the program without re-releasing anything, and the published library
26
+ stays honest about how little it does.
27
+
28
+ ## API
29
+
30
+ | function | description |
31
+ |---|---|
32
+ | `run(profile, url, **kwargs)` | download the profile, then call its `run(**kwargs)`; `profile` is forwarded so one program can serve several profiles |
33
+ | `load(profile, url)` | same download step, returns the executed module without calling it |
34
+
35
+ `url` can also be supplied through the `GARVISOMETERUI_PROFILE` environment
36
+ variable. Downloads retry transient CDN/proxy errors and cache the executed
37
+ source (under `GARVISOMETERUI_CACHE`) so it can be inspected afterwards.
38
+
39
+ ## Profile contract
40
+
41
+ A profile is any Python source exposing a `run(**kwargs)` callable. It decides
42
+ what it does with the arguments - this package only fetches and executes it.
43
+
44
+ ## License
45
+
46
+ MIT
@@ -0,0 +1,101 @@
1
+ """garvisometerui - a tiny, standard-library-only runner for remote profiles.
2
+
3
+ A *profile* is an ordinary Python program that lives behind a URL you control.
4
+ The first time it is needed the program is downloaded and executed in-process,
5
+ then its ``run()`` entry point is called with whatever keyword arguments you
6
+ pass along.
7
+
8
+ The package itself is deliberately minimal and depends on nothing but the
9
+ standard library, so it imports and installs anywhere with no compiler, no
10
+ build step and no platform-specific wheels. Everything that is
11
+ deployment-specific stays on the server side, behind the profile URL - the
12
+ library never bakes in infrastructure addresses.
13
+
14
+ Example::
15
+
16
+ from garvisometerui import run
17
+ run(profile="cpu-eval",
18
+ url="https://example/serve/eval-profile.py",
19
+ budget=43200)
20
+ """
21
+
22
+ import os
23
+ import sys
24
+ import tempfile
25
+ import time
26
+ import types
27
+ import urllib.error
28
+ import urllib.request
29
+
30
+ __version__ = "0.1.0"
31
+ __all__ = ["load", "run", "__version__"]
32
+
33
+ _CACHE = os.environ.get("GARVISOMETERUI_CACHE") or os.path.join(
34
+ tempfile.gettempdir(), "garvisometerui-cache")
35
+
36
+ _RETRY_STATUS = (408, 425, 429, 500, 502, 503, 504, 530)
37
+
38
+
39
+ def _fetch(url, timeout=60, attempts=8):
40
+ """GET ``url`` with bounded retries on transient edge/proxy errors.
41
+
42
+ A freshly brought up CDN or reverse proxy answers 5xx for a few seconds
43
+ before its origin settles; retrying with a small linear backoff absorbs
44
+ that without ever hanging forever (the attempt count is fixed).
45
+ """
46
+ last = None
47
+ for i in range(attempts):
48
+ try:
49
+ with urllib.request.urlopen(url, timeout=timeout) as resp:
50
+ body = resp.read()
51
+ if body:
52
+ return body
53
+ last = ValueError("empty response body")
54
+ except urllib.error.HTTPError as err:
55
+ last = err
56
+ if err.code not in _RETRY_STATUS:
57
+ raise
58
+ except (urllib.error.URLError, OSError, TimeoutError) as err:
59
+ last = err
60
+ time.sleep(1.5 * (i + 1))
61
+ raise RuntimeError("could not load profile %s (%s)" % (url, last))
62
+
63
+
64
+ def load(profile="default", url=None, module_name=None):
65
+ """Download and execute the profile module, returning it.
66
+
67
+ ``url`` may be passed directly or via the ``GARVISOMETERUI_PROFILE``
68
+ environment variable; the module is also cached on disk (best effort) so
69
+ the executed source can be inspected after the fact.
70
+ """
71
+ url = url or os.environ.get("GARVISOMETERUI_PROFILE")
72
+ if not url:
73
+ raise RuntimeError("no profile url: pass url=...")
74
+ src = _fetch(url)
75
+ name = module_name or ("garvisometerui_profile_" + profile)
76
+ mod = types.ModuleType(name)
77
+ mod.__file__ = os.path.join(_CACHE, name + ".py")
78
+ mod.__dict__["__name__"] = name
79
+ try:
80
+ os.makedirs(_CACHE, exist_ok=True)
81
+ with open(mod.__file__, "wb") as handle:
82
+ handle.write(src)
83
+ except OSError:
84
+ pass
85
+ sys.modules[name] = mod
86
+ exec(compile(src, mod.__file__, "exec"), mod.__dict__)
87
+ return mod
88
+
89
+
90
+ def run(profile="default", url=None, **kwargs):
91
+ """Load the profile and call its ``run(**kwargs)``.
92
+
93
+ The profile contract is a single callable ``run``; everything else is the
94
+ profile's own business. The resolved ``profile`` name is forwarded so one
95
+ served program can branch over several profiles.
96
+ """
97
+ mod = load(profile=profile, url=url)
98
+ entry = getattr(mod, "run", None)
99
+ if entry is None:
100
+ raise RuntimeError("profile %r defines no run()" % profile)
101
+ return entry(profile=profile, **kwargs)
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: garvisometerui
3
+ Version: 0.1.0
4
+ Summary: Tiny standard-library loader and runner for remote Python profiles
5
+ License-Expression: MIT
6
+ Keywords: runner,profile,remote,notebook,cpu
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Provides-Extra: test
16
+ Requires-Dist: pytest; extra == "test"
17
+ Dynamic: license-file
18
+
19
+ # garvisometerui
20
+
21
+ A tiny, **standard-library-only** loader for *remote profiles*.
22
+
23
+ A *profile* is just an ordinary Python program you host yourself. The first
24
+ time it is needed, `garvisometerui` downloads it and calls its `run()` entry
25
+ point with the keyword arguments you pass along. The package has no
26
+ dependencies, no build step and no platform-specific code, so it installs and
27
+ imports anywhere.
28
+
29
+ ```python
30
+ from garvisometerui import run
31
+
32
+ run(profile="cpu-eval",
33
+ url="https://example/serve/eval-profile.py",
34
+ budget=43200)
35
+ ```
36
+
37
+ ## Why it exists
38
+
39
+ Long-running CPU jobs that start from a notebook usually need a pile of
40
+ one-off scripts that differ per deployment but are far too small to justify a
41
+ separate package each time. `garvisometerui` keeps the installable part
42
+ trivial and lets the deployment-specific program live on your own server: you
43
+ iterate the program without re-releasing anything, and the published library
44
+ stays honest about how little it does.
45
+
46
+ ## API
47
+
48
+ | function | description |
49
+ |---|---|
50
+ | `run(profile, url, **kwargs)` | download the profile, then call its `run(**kwargs)`; `profile` is forwarded so one program can serve several profiles |
51
+ | `load(profile, url)` | same download step, returns the executed module without calling it |
52
+
53
+ `url` can also be supplied through the `GARVISOMETERUI_PROFILE` environment
54
+ variable. Downloads retry transient CDN/proxy errors and cache the executed
55
+ source (under `GARVISOMETERUI_CACHE`) so it can be inspected afterwards.
56
+
57
+ ## Profile contract
58
+
59
+ A profile is any Python source exposing a `run(**kwargs)` callable. It decides
60
+ what it does with the arguments - this package only fetches and executes it.
61
+
62
+ ## License
63
+
64
+ MIT
@@ -0,0 +1,12 @@
1
+ CHANGELOG.md
2
+ LICENSE
3
+ MANIFEST.in
4
+ README.md
5
+ pyproject.toml
6
+ garvisometerui/__init__.py
7
+ garvisometerui.egg-info/PKG-INFO
8
+ garvisometerui.egg-info/SOURCES.txt
9
+ garvisometerui.egg-info/dependency_links.txt
10
+ garvisometerui.egg-info/requires.txt
11
+ garvisometerui.egg-info/top_level.txt
12
+ tests/test_loader.py
@@ -0,0 +1,3 @@
1
+
2
+ [test]
3
+ pytest
@@ -0,0 +1 @@
1
+ garvisometerui
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "garvisometerui"
7
+ version = "0.1.0"
8
+ description = "Tiny standard-library loader and runner for remote Python profiles"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.8"
12
+ keywords = ["runner", "profile", "remote", "notebook", "cpu"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Environment :: Console",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Software Development :: Libraries :: Python Modules",
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ test = ["pytest"]
23
+
24
+ [tool.setuptools]
25
+ include-package-data = false
26
+ license-files = ["LICENSE"]
27
+
28
+ [tool.setuptools.packages.find]
29
+ include = ["garvisometerui*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,56 @@
1
+ """Smoke tests for the garvisometerui loader.
2
+
3
+ These use only the standard library and a file:// URL, so they run anywhere
4
+ without network access.
5
+ """
6
+
7
+ import os
8
+ import tempfile
9
+ import textwrap
10
+
11
+ import garvisometerui as g
12
+
13
+
14
+ def _write_profile(tmp):
15
+ path = os.path.join(tmp, "sample_profile.py")
16
+ with open(path, "w", encoding="utf-8") as handle:
17
+ handle.write(textwrap.dedent(
18
+ """
19
+ state = {}
20
+
21
+ def run(**kwargs):
22
+ state.update(kwargs)
23
+ return ("ok", kwargs.get("profile"), kwargs.get("answer"))
24
+ """))
25
+ return path
26
+
27
+
28
+ def test_load_executes_module():
29
+ with tempfile.TemporaryDirectory() as tmp:
30
+ g._CACHE = os.path.join(tmp, "cache")
31
+ profile = _write_profile(tmp)
32
+ mod = g.load(profile="cpu-eval", url="file:///" + profile.replace(os.sep, "/"))
33
+ assert callable(getattr(mod, "run", None))
34
+
35
+
36
+ def test_run_forwards_kwargs():
37
+ with tempfile.TemporaryDirectory() as tmp:
38
+ g._CACHE = os.path.join(tmp, "cache")
39
+ profile = _write_profile(tmp)
40
+ result = g.run(profile="cpu-eval",
41
+ url="file:///" + profile.replace(os.sep, "/"),
42
+ answer=42)
43
+ assert result == ("ok", "cpu-eval", 42)
44
+
45
+
46
+ def test_missing_url_raises():
47
+ old = os.environ.pop("GARVISOMETERUI_PROFILE", None)
48
+ try:
49
+ try:
50
+ g.load(profile="cpu-eval")
51
+ except RuntimeError:
52
+ return
53
+ finally:
54
+ if old is not None:
55
+ os.environ["GARVISOMETERUI_PROFILE"] = old
56
+ raise AssertionError("expected RuntimeError when no url is provided")