aggregate_api 1.0.0__py3-none-any.whl
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.
- aggregate_api/__init__.py +41 -0
- aggregate_api/__main__.py +154 -0
- aggregate_api/app.py +206 -0
- aggregate_api/audit.py +395 -0
- aggregate_api/bounds.py +331 -0
- aggregate_api/cache.py +319 -0
- aggregate_api/capability.py +823 -0
- aggregate_api/completion.py +219 -0
- aggregate_api/config.py +363 -0
- aggregate_api/cors.py +61 -0
- aggregate_api/examples.py +620 -0
- aggregate_api/layer_pricing.py +840 -0
- aggregate_api/library.py +94 -0
- aggregate_api/library_notes.py +96 -0
- aggregate_api/models.py +1407 -0
- aggregate_api/net.py +281 -0
- aggregate_api/pnl.py +101 -0
- aggregate_api/pricing.py +778 -0
- aggregate_api/resources.py +257 -0
- aggregate_api/routes/__init__.py +8 -0
- aggregate_api/routes/decl.py +327 -0
- aggregate_api/routes/examples.py +82 -0
- aggregate_api/routes/meta.py +282 -0
- aggregate_api/routes/objects.py +4119 -0
- aggregate_api/routes/status.py +466 -0
- aggregate_api/serializers.py +565 -0
- aggregate_api/sessions.py +353 -0
- aggregate_api/static/aggregate-api-logo-512.png +0 -0
- aggregate_api/static/aggregate-api-logo.png +0 -0
- aggregate_api/static/aggregate-api-trim.png +0 -0
- aggregate_api/static/android-chrome-192x192.png +0 -0
- aggregate_api/static/android-chrome-512x512.png +0 -0
- aggregate_api/static/apple-touch-icon.png +0 -0
- aggregate_api/static/assets/bootstrap-icons-BeopsB42.woff +0 -0
- aggregate_api/static/assets/bootstrap-icons-mSm7cUeB.woff2 +0 -0
- aggregate_api/static/assets/bootstrap-ohb1VZ53.js +5 -0
- aggregate_api/static/assets/codemirror-h62DHGGa.js +14 -0
- aggregate_api/static/assets/csv-grid.worker-DKzHGXac.js +4 -0
- aggregate_api/static/assets/echarts-B7o9sc00.js +40 -0
- aggregate_api/static/assets/echarts-gl-DG1Uf6wE.js +4282 -0
- aggregate_api/static/assets/lite-CUlcD8p4.css +1 -0
- aggregate_api/static/assets/lite-Dd2TnT4M.js +1 -0
- aggregate_api/static/assets/main-Bxhxa55v.css +9 -0
- aggregate_api/static/assets/main-CmoEiPit.js +9 -0
- aggregate_api/static/assets/tables-BHCF7qIF.js +8 -0
- aggregate_api/static/assets/tables-CxvajLr7.css +1 -0
- aggregate_api/static/favicon-16x16.png +0 -0
- aggregate_api/static/favicon-32x32.png +0 -0
- aggregate_api/static/favicon.ico +0 -0
- aggregate_api/static/index.html +912 -0
- aggregate_api/static/lite.html +83 -0
- aggregate_api/static/logo.png +0 -0
- aggregate_api/static/site.webmanifest +14 -0
- aggregate_api/static/sw.js +78 -0
- aggregate_api/status.py +536 -0
- aggregate_api/status_page.html +546 -0
- aggregate_api/tables.py +316 -0
- aggregate_api-1.0.0.dist-info/METADATA +187 -0
- aggregate_api-1.0.0.dist-info/RECORD +63 -0
- aggregate_api-1.0.0.dist-info/WHEEL +5 -0
- aggregate_api-1.0.0.dist-info/entry_points.txt +2 -0
- aggregate_api-1.0.0.dist-info/licenses/LICENSE +28 -0
- aggregate_api-1.0.0.dist-info/top_level.txt +1 -0
aggregate_api/library.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""The recipe base every build in this process resolves against.
|
|
2
|
+
|
|
3
|
+
One underwriter, reached through :func:`get_underwriter`, and every path that
|
|
4
|
+
parses a program or reads a recipe goes through it. Before this module the api
|
|
5
|
+
had two: ``examples.py`` built a private :class:`Underwriter` for the menu when
|
|
6
|
+
``--library`` was set, while all three build paths used ``aggregate.build``, the
|
|
7
|
+
shipped singleton. That split is invisible under the default library, because
|
|
8
|
+
both are then the same object, and is wrong under a custom one:
|
|
9
|
+
|
|
10
|
+
* an entry is browsable in the Examples menu but any entry referencing a sibling
|
|
11
|
+
by name fails to build, because the base the parser resolves against never
|
|
12
|
+
read the file;
|
|
13
|
+
* the ``.agg`` download lists the session rows of a base the menu did not come
|
|
14
|
+
from.
|
|
15
|
+
|
|
16
|
+
Why a function and not a module-level object: the library is chosen by a setting
|
|
17
|
+
read at process start, and tests flip that setting per case. ``lru_cache`` gives
|
|
18
|
+
the singleton without the import-time evaluation a module-level object would
|
|
19
|
+
force, matching :func:`aggregate_api.config.get_settings`, and
|
|
20
|
+
``create_app`` clears it for the same reason it clears that one.
|
|
21
|
+
|
|
22
|
+
The base is process-global and mutable: ``build(program)`` registers what it
|
|
23
|
+
parses. Whether that base is shared or per session is a question for the session
|
|
24
|
+
work; this module answers only "which library", and answers it once.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import time
|
|
30
|
+
import warnings
|
|
31
|
+
from functools import lru_cache
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
from . import status
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@lru_cache(maxsize=1)
|
|
38
|
+
def get_underwriter():
|
|
39
|
+
"""Return the ``Underwriter`` whose recipe base backs this process.
|
|
40
|
+
|
|
41
|
+
Defaults to ``aggregate.build``, the shipped singleton loaded from
|
|
42
|
+
``library.agg``. ``AGGAPI_LIBRARY`` overrides it with a custom ``.agg``: a
|
|
43
|
+
private :class:`Underwriter` is pointed at that file via ``databases=`` and
|
|
44
|
+
loaded, so a curated library goes through exactly the same recipe machinery
|
|
45
|
+
as the shipped one.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
aggregate.underwriter.Underwriter
|
|
50
|
+
|
|
51
|
+
Notes
|
|
52
|
+
-----
|
|
53
|
+
A configured but missing path warns and falls back rather than failing every
|
|
54
|
+
route, which is right for a stale setting on a server. The ``--library``
|
|
55
|
+
flag is deliberately stricter and exits instead: see
|
|
56
|
+
:func:`aggregate_api.__main__.resolve_library` for why the asymmetry is
|
|
57
|
+
intended rather than an oversight.
|
|
58
|
+
|
|
59
|
+
**The load is explicit and timed**, for ``GET /v1/status``. ``load()`` with
|
|
60
|
+
no argument is idempotent (it returns an empty list once the configured
|
|
61
|
+
request has been read), so calling it here costs nothing when the base is
|
|
62
|
+
already loaded and moves nothing when it is not: every caller reaches this
|
|
63
|
+
base through ``fork()``, which loads the parent itself. What it buys is a
|
|
64
|
+
number. The recorded figure is what *this* call paid, so a process reading
|
|
65
|
+
the shipped library reports a near-zero time, correctly: the read happened
|
|
66
|
+
when ``aggregate`` was imported, not here. A custom ``.agg`` reports the
|
|
67
|
+
real cost of reading it, which is the case where the number is worth
|
|
68
|
+
knowing.
|
|
69
|
+
"""
|
|
70
|
+
from .config import get_settings
|
|
71
|
+
|
|
72
|
+
started = time.monotonic()
|
|
73
|
+
custom = get_settings().library
|
|
74
|
+
if not custom:
|
|
75
|
+
from aggregate import build
|
|
76
|
+
|
|
77
|
+
uw = build
|
|
78
|
+
else:
|
|
79
|
+
path = Path(custom)
|
|
80
|
+
if not path.is_file():
|
|
81
|
+
warnings.warn(
|
|
82
|
+
f"AGGAPI_LIBRARY={custom!r} not found; using the shipped library",
|
|
83
|
+
stacklevel=2,
|
|
84
|
+
)
|
|
85
|
+
from aggregate import build
|
|
86
|
+
|
|
87
|
+
uw = build
|
|
88
|
+
else:
|
|
89
|
+
from aggregate import Underwriter
|
|
90
|
+
|
|
91
|
+
uw = Underwriter(databases=(str(path),))
|
|
92
|
+
uw.load()
|
|
93
|
+
status.record_library_load((time.monotonic() - started) * 1000)
|
|
94
|
+
return uw
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Telling the library's warnings from everyone else's.
|
|
2
|
+
|
|
3
|
+
``warnings.catch_warnings(record=True)`` plus ``simplefilter('always')`` is the
|
|
4
|
+
only way to capture what ``aggregate`` says on its way to an answer, and it is a
|
|
5
|
+
blunt instrument: the filters are process global and ``'always'`` lifts the
|
|
6
|
+
default suppressions, so the block catches every warning raised anywhere in the
|
|
7
|
+
interpreter for as long as it is open, including ones this service provokes
|
|
8
|
+
about itself.
|
|
9
|
+
|
|
10
|
+
That is not hypothetical. The api's audit log leaked sqlite connections until
|
|
11
|
+
a70, and a garbage collection landing inside a capture block reported
|
|
12
|
+
``unclosed database in <sqlite3.Connection ...>`` as something the reader's
|
|
13
|
+
program had done. The leak is fixed; this module is the other half, and it is
|
|
14
|
+
the half that keeps the *next* stray warning, from any library in the
|
|
15
|
+
environment, out of a reader's face.
|
|
16
|
+
|
|
17
|
+
Every capture site in this package goes through :func:`library_warnings`, and
|
|
18
|
+
the one site that also needs the logging channel (the build route) uses
|
|
19
|
+
:func:`from_library` directly inside its own richer context manager.
|
|
20
|
+
|
|
21
|
+
The test is **path containment, not the category and not the name.** A library
|
|
22
|
+
``UserWarning`` is indistinguishable from anyone else's by type, and
|
|
23
|
+
``aggregate_api`` contains the string ``aggregate``, so a substring test would
|
|
24
|
+
claim this service's own warnings as the library's.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import warnings
|
|
30
|
+
from contextlib import contextmanager
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
import aggregate as _aggregate_pkg
|
|
34
|
+
|
|
35
|
+
#: Where the ``aggregate`` package lives on disk. Resolved once at import: the
|
|
36
|
+
#: path cannot move while the process runs, and ``Path.resolve`` on every
|
|
37
|
+
#: warning of every build would be work for an answer that never changes.
|
|
38
|
+
_LIBRARY_ROOT = str(Path(_aggregate_pkg.__file__).resolve().parent)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def from_library(filename: str) -> bool:
|
|
42
|
+
"""Was this warning raised from inside ``aggregate``?
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
filename : str
|
|
47
|
+
``warnings.WarningMessage.filename``, the source file of the
|
|
48
|
+
``warn()`` call.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
bool
|
|
53
|
+
True when the file sits under the installed ``aggregate`` package.
|
|
54
|
+
|
|
55
|
+
Notes
|
|
56
|
+
-----
|
|
57
|
+
Resolved on both sides so a junctioned or symlinked checkout compares
|
|
58
|
+
equal, which this repo's ``.venv`` arrangement makes a live concern rather
|
|
59
|
+
than a theoretical one.
|
|
60
|
+
"""
|
|
61
|
+
if not filename:
|
|
62
|
+
return False
|
|
63
|
+
try:
|
|
64
|
+
return Path(filename).resolve().is_relative_to(_LIBRARY_ROOT)
|
|
65
|
+
except (OSError, ValueError): # pragma: no cover -- unresolvable path
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@contextmanager
|
|
70
|
+
def library_warnings():
|
|
71
|
+
"""Capture ``warnings.warn`` from ``aggregate``, and only from it.
|
|
72
|
+
|
|
73
|
+
Yields
|
|
74
|
+
------
|
|
75
|
+
list of str
|
|
76
|
+
Empty while the block runs and filled on exit, so callers extend their
|
|
77
|
+
own list *after* the ``with``, not inside it.
|
|
78
|
+
|
|
79
|
+
Examples
|
|
80
|
+
--------
|
|
81
|
+
>>> with library_warnings() as caught: # doctest: +SKIP
|
|
82
|
+
... panel = obj.evaluate(premium)
|
|
83
|
+
>>> warns.extend(caught) # doctest: +SKIP
|
|
84
|
+
|
|
85
|
+
Notes
|
|
86
|
+
-----
|
|
87
|
+
Filled on exit rather than as each warning arrives because
|
|
88
|
+
``catch_warnings(record=True)`` hands back a list it appends to itself, and
|
|
89
|
+
the filtering has to happen once that list is complete.
|
|
90
|
+
"""
|
|
91
|
+
kept: list[str] = []
|
|
92
|
+
with warnings.catch_warnings(record=True) as caught:
|
|
93
|
+
warnings.simplefilter("always")
|
|
94
|
+
yield kept
|
|
95
|
+
kept.extend(str(w.message) for w in caught
|
|
96
|
+
if from_library(getattr(w, "filename", "")))
|