tesorotools-python 0.0.38__tar.gz → 0.0.40__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.
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/PKG-INFO +1 -1
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/pyproject.toml +1 -1
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/__init__.py +10 -2
- tesorotools_python-0.0.40/src/tesorotools/_build_context.py +59 -0
- tesorotools_python-0.0.40/src/tesorotools/orchestration.py +100 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/providers/__init__.py +7 -2
- tesorotools_python-0.0.40/src/tesorotools/providers/base.py +213 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/providers/bde.py +3 -1
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/providers/ecb.py +3 -1
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/table.py +35 -52
- tesorotools_python-0.0.38/src/tesorotools/_build_context.py +0 -49
- tesorotools_python-0.0.38/src/tesorotools/providers/base.py +0 -114
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/.gitignore +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/_registry.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/artists/__init__.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/artists/_common.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/artists/barh_plot.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/artists/line_plot.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/artists/stacked.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/artists/type_curve.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/README.md +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/fonts/README.md +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/plots.yaml +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/tesoro.mplstyle +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/data_sources/__init__.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/data_sources/debug.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/database/__init__.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/database/local.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/database/push.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/database/shared.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/dependencies/__init__.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/dependencies/node.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/dependencies/resolution.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/driver.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/manifest.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/offsets/__init__.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/offsets/offsets.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/offsets/outliers.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/pipeline/__init__.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/pipeline/diagnose.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/pipeline/engine.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/pipeline/rules.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/py.typed +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/__init__.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/__init__.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/content.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/images.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/section.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/subtitle.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/text.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/title.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/report.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/testing/__init__.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/testing/compare.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/utils/__init__.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/utils/config.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/utils/format.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/utils/globals.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/utils/matplotlib.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/utils/series.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/utils/shortcuts.py +0 -0
- {tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/utils/template.py +0 -0
|
@@ -51,7 +51,12 @@ from tesorotools.artists import (
|
|
|
51
51
|
StackedBarPlot,
|
|
52
52
|
TypeCurve,
|
|
53
53
|
)
|
|
54
|
-
from tesorotools.
|
|
54
|
+
from tesorotools.orchestration import CompositeRegistry, iter_contexts
|
|
55
|
+
from tesorotools.providers.base import (
|
|
56
|
+
DataProvider,
|
|
57
|
+
RegistryProtocol,
|
|
58
|
+
bootstrap_providers,
|
|
59
|
+
)
|
|
55
60
|
from tesorotools.render import (
|
|
56
61
|
Content,
|
|
57
62
|
Image,
|
|
@@ -91,9 +96,9 @@ __all__ = [
|
|
|
91
96
|
"Artist",
|
|
92
97
|
"BdeProvider",
|
|
93
98
|
"BuildContext",
|
|
99
|
+
"CompositeRegistry",
|
|
94
100
|
"Content",
|
|
95
101
|
"DataProvider",
|
|
96
|
-
"DataProviderProtocol",
|
|
97
102
|
"EcbProvider",
|
|
98
103
|
"Format",
|
|
99
104
|
"HorizontalBarChart",
|
|
@@ -101,6 +106,7 @@ __all__ = [
|
|
|
101
106
|
"Images",
|
|
102
107
|
"Legend",
|
|
103
108
|
"LinePlot",
|
|
109
|
+
"RegistryProtocol",
|
|
104
110
|
"Report",
|
|
105
111
|
"Section",
|
|
106
112
|
"StackedAreaPlot",
|
|
@@ -114,9 +120,11 @@ __all__ = [
|
|
|
114
120
|
"all_artists",
|
|
115
121
|
"all_providers",
|
|
116
122
|
"all_tags",
|
|
123
|
+
"bootstrap_providers",
|
|
117
124
|
"get_artist",
|
|
118
125
|
"get_provider",
|
|
119
126
|
"iter_artists",
|
|
127
|
+
"iter_contexts",
|
|
120
128
|
"iter_providers",
|
|
121
129
|
"iter_tags",
|
|
122
130
|
"register_artist",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Shared context object for ``build_for`` provider factories.
|
|
2
|
+
|
|
3
|
+
Each provider class has a :meth:`tesorotools.DataProvider.build_for`
|
|
4
|
+
classmethod that decides whether to instantiate itself based
|
|
5
|
+
on the registry and runtime context. ``BuildContext`` is the
|
|
6
|
+
shared input to that method.
|
|
7
|
+
|
|
8
|
+
The base fields cover the cross-project minimum:
|
|
9
|
+
|
|
10
|
+
* ``registry`` -- the catalog the orchestrator uses to decide
|
|
11
|
+
which series each provider must serve. Typed as
|
|
12
|
+
:class:`tesorotools.providers.base.RegistryProtocol` so
|
|
13
|
+
that ``build_for`` bodies are statically checked.
|
|
14
|
+
* ``consumer`` -- a free-form string identifying the calling
|
|
15
|
+
workflow (e.g. ``"diary"``, ``"weekly"``). ``build_for``
|
|
16
|
+
implementations dispatch on it via
|
|
17
|
+
``registry.all_cids_for_provider(ctx.consumer, ...)``.
|
|
18
|
+
* ``mock`` / ``mock_seed`` -- toggle for deterministic
|
|
19
|
+
fixtures during tests and demos.
|
|
20
|
+
* ``provider_config`` -- per-provider knobs keyed by
|
|
21
|
+
:attr:`tesorotools.DataProvider.PROVIDER_NAME`. Lets each
|
|
22
|
+
provider read its own sub-config (API keys, fallback
|
|
23
|
+
policy, historical file paths, ...) without polluting the
|
|
24
|
+
shared context surface.
|
|
25
|
+
|
|
26
|
+
Cross-provider settings that genuinely belong to the context
|
|
27
|
+
(a project-wide cache directory, say) can still go on a
|
|
28
|
+
``BuildContext`` subclass:
|
|
29
|
+
|
|
30
|
+
.. code-block:: python
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class DiaryBuildContext(BuildContext):
|
|
34
|
+
cache_dir: Path = Path(".cache")
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
from collections.abc import Mapping
|
|
40
|
+
from dataclasses import dataclass, field
|
|
41
|
+
from typing import TYPE_CHECKING, Any
|
|
42
|
+
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
from tesorotools.providers.base import RegistryProtocol
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _empty_provider_config() -> dict[str, Mapping[str, Any]]:
|
|
48
|
+
return {}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class BuildContext:
|
|
53
|
+
registry: "RegistryProtocol"
|
|
54
|
+
consumer: str
|
|
55
|
+
mock: bool = False
|
|
56
|
+
mock_seed: int | None = None
|
|
57
|
+
provider_config: Mapping[str, Mapping[str, Any]] = field(
|
|
58
|
+
default_factory=_empty_provider_config
|
|
59
|
+
)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Optional orchestration helpers for multi-consumer setups.
|
|
2
|
+
|
|
3
|
+
The single-consumer flow is already short with the core
|
|
4
|
+
contract (one ``BuildContext``, one
|
|
5
|
+
:func:`tesorotools.providers.base.bootstrap_providers` call).
|
|
6
|
+
Projects that fan out across several consumers and several
|
|
7
|
+
registries end up rewriting the same wiring: pick a registry
|
|
8
|
+
list, wrap it in a composite when there is more than one,
|
|
9
|
+
build the context, populate ``provider_config``, call
|
|
10
|
+
``bootstrap_providers``. The two helpers below collapse that
|
|
11
|
+
to a one-liner per consumer.
|
|
12
|
+
|
|
13
|
+
These helpers are deliberately optional. A project that
|
|
14
|
+
ignores them is unaffected; a project building its second or
|
|
15
|
+
third consumer can pull them in without rewriting any
|
|
16
|
+
provider.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from collections.abc import Callable, Iterator, Mapping
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from tesorotools._build_context import BuildContext
|
|
25
|
+
from tesorotools.providers.base import RegistryProtocol
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CompositeRegistry:
|
|
29
|
+
"""Concatenate several :class:`RegistryProtocol` instances
|
|
30
|
+
as if they were one.
|
|
31
|
+
|
|
32
|
+
Iteration order over *parts* is the lookup order for
|
|
33
|
+
:meth:`get_provider_meta` (first match wins). For
|
|
34
|
+
:meth:`all_cids_for_provider` the result is the
|
|
35
|
+
concatenation; if the same canonical ID appears in two
|
|
36
|
+
parts, both are returned -- duplicates should be resolved
|
|
37
|
+
upstream by the consumer's registry layout, not here.
|
|
38
|
+
|
|
39
|
+
The class itself satisfies :class:`RegistryProtocol`, so
|
|
40
|
+
callers cannot tell whether they are talking to one
|
|
41
|
+
registry or several.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, parts: list[RegistryProtocol]) -> None:
|
|
45
|
+
self._parts = parts
|
|
46
|
+
|
|
47
|
+
def all_cids_for_provider(
|
|
48
|
+
self, consumer: str, provider_name: str
|
|
49
|
+
) -> list[str]:
|
|
50
|
+
return [
|
|
51
|
+
cid
|
|
52
|
+
for r in self._parts
|
|
53
|
+
for cid in r.all_cids_for_provider(consumer, provider_name)
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
def get_provider_meta(
|
|
57
|
+
self, canonical_id: str, provider_name: str
|
|
58
|
+
) -> dict[str, Any]:
|
|
59
|
+
for r in self._parts:
|
|
60
|
+
try:
|
|
61
|
+
return r.get_provider_meta(canonical_id, provider_name)
|
|
62
|
+
except KeyError:
|
|
63
|
+
continue
|
|
64
|
+
raise KeyError(f"unknown cid {canonical_id!r}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def iter_contexts(
|
|
68
|
+
consumer_registries: Mapping[str, list[RegistryProtocol]],
|
|
69
|
+
provider_config_for: Callable[
|
|
70
|
+
[str], Mapping[str, Mapping[str, Any]]
|
|
71
|
+
] = lambda _: {},
|
|
72
|
+
*,
|
|
73
|
+
mock: bool = False,
|
|
74
|
+
mock_seed: int | None = None,
|
|
75
|
+
) -> Iterator[BuildContext]:
|
|
76
|
+
"""Yield one :class:`BuildContext` per consumer.
|
|
77
|
+
|
|
78
|
+
Multi-registry consumers are wrapped in
|
|
79
|
+
:class:`CompositeRegistry` transparently; single-registry
|
|
80
|
+
consumers receive their registry unchanged so that
|
|
81
|
+
``ctx.registry is registries[0]`` holds when the consumer
|
|
82
|
+
only has one.
|
|
83
|
+
|
|
84
|
+
*provider_config_for* is called once per consumer and its
|
|
85
|
+
result is attached as :attr:`BuildContext.provider_config`.
|
|
86
|
+
The default returns an empty mapping for every consumer.
|
|
87
|
+
"""
|
|
88
|
+
for consumer, registries in consumer_registries.items():
|
|
89
|
+
registry: RegistryProtocol = (
|
|
90
|
+
registries[0]
|
|
91
|
+
if len(registries) == 1
|
|
92
|
+
else CompositeRegistry(registries)
|
|
93
|
+
)
|
|
94
|
+
yield BuildContext(
|
|
95
|
+
registry=registry,
|
|
96
|
+
consumer=consumer,
|
|
97
|
+
mock=mock,
|
|
98
|
+
mock_seed=mock_seed,
|
|
99
|
+
provider_config=provider_config_for(consumer),
|
|
100
|
+
)
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/providers/__init__.py
RENAMED
|
@@ -9,7 +9,11 @@ extras.
|
|
|
9
9
|
|
|
10
10
|
from typing import TYPE_CHECKING, Any
|
|
11
11
|
|
|
12
|
-
from tesorotools.providers.base import
|
|
12
|
+
from tesorotools.providers.base import (
|
|
13
|
+
DataProvider,
|
|
14
|
+
RegistryProtocol,
|
|
15
|
+
bootstrap_providers,
|
|
16
|
+
)
|
|
13
17
|
|
|
14
18
|
if TYPE_CHECKING:
|
|
15
19
|
from tesorotools.providers.bde import BdeProvider
|
|
@@ -18,8 +22,9 @@ if TYPE_CHECKING:
|
|
|
18
22
|
__all__ = [
|
|
19
23
|
"BdeProvider",
|
|
20
24
|
"DataProvider",
|
|
21
|
-
"DataProviderProtocol",
|
|
22
25
|
"EcbProvider",
|
|
26
|
+
"RegistryProtocol",
|
|
27
|
+
"bootstrap_providers",
|
|
23
28
|
]
|
|
24
29
|
|
|
25
30
|
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Abstract base class for data providers and the contract
|
|
2
|
+
glue around it.
|
|
3
|
+
|
|
4
|
+
A provider knows how to download time series from a specific
|
|
5
|
+
external source (Bank of Spain, Eikon, ECB, etc.). It does
|
|
6
|
+
not know anything about local storage, incremental updates,
|
|
7
|
+
or presentation -- those concerns belong elsewhere.
|
|
8
|
+
|
|
9
|
+
Every provider returns data in the same shape: a DataFrame
|
|
10
|
+
with a DatetimeIndex and one column per series code.
|
|
11
|
+
|
|
12
|
+
In addition to ``fetch`` and ``is_available``, this module
|
|
13
|
+
formalises the construction contract that consumer projects
|
|
14
|
+
were already converging on informally:
|
|
15
|
+
|
|
16
|
+
* :attr:`DataProvider.PROVIDER_NAME` -- the stable identifier
|
|
17
|
+
every consumer ends up adding to its provider classes.
|
|
18
|
+
Required at definition time; ``__init_subclass__`` raises
|
|
19
|
+
``TypeError`` if a concrete subclass forgets it.
|
|
20
|
+
* :class:`RegistryProtocol` -- the two methods consumers
|
|
21
|
+
uniformly call on whatever catalog they pass through
|
|
22
|
+
``BuildContext.registry``. Formalising them lets
|
|
23
|
+
``build_for`` be statically typed.
|
|
24
|
+
* :meth:`DataProvider.build_for` -- a default factory that
|
|
25
|
+
covers the simple "no constructor args, single instance"
|
|
26
|
+
case and skips construction when the registry has no
|
|
27
|
+
canonical IDs for this provider in the current consumer.
|
|
28
|
+
Providers that need configuration, multiple instances, or
|
|
29
|
+
fallback logic override it.
|
|
30
|
+
* :func:`bootstrap_providers` -- the trivial loop every
|
|
31
|
+
consumer ends up writing on top of ``build_for``.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
from abc import ABC, abstractmethod
|
|
37
|
+
from collections.abc import Iterable
|
|
38
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Protocol
|
|
39
|
+
|
|
40
|
+
import pandas as pd
|
|
41
|
+
|
|
42
|
+
if TYPE_CHECKING:
|
|
43
|
+
from tesorotools._build_context import BuildContext
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RegistryProtocol(Protocol):
|
|
47
|
+
"""Structural type for whatever catalog a project passes
|
|
48
|
+
through :attr:`BuildContext.registry`.
|
|
49
|
+
|
|
50
|
+
Only the two methods :meth:`DataProvider.build_for` reads
|
|
51
|
+
are formalised. Projects with idiosyncratic registries
|
|
52
|
+
(composite layouts, different storage backends) keep
|
|
53
|
+
working as long as they expose these two methods.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def all_cids_for_provider(
|
|
57
|
+
self, consumer: str, provider_name: str
|
|
58
|
+
) -> list[str]:
|
|
59
|
+
"""Return every canonical ID this provider must serve
|
|
60
|
+
for *consumer*. Empty list means "this provider is
|
|
61
|
+
not needed for this consumer"."""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
def get_provider_meta(
|
|
65
|
+
self, canonical_id: str, provider_name: str
|
|
66
|
+
) -> dict[str, Any]:
|
|
67
|
+
"""Return the per-instrument metadata block this
|
|
68
|
+
provider expects for *canonical_id*."""
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class DataProvider(ABC):
|
|
73
|
+
"""Base class for all data providers.
|
|
74
|
+
|
|
75
|
+
Subclasses must:
|
|
76
|
+
|
|
77
|
+
* set :attr:`PROVIDER_NAME` (enforced at class-definition
|
|
78
|
+
time by :meth:`__init_subclass__`);
|
|
79
|
+
* implement :meth:`fetch` and :meth:`is_available`
|
|
80
|
+
(enforced at instantiation time by ``ABC``).
|
|
81
|
+
|
|
82
|
+
They MAY override :meth:`build_for` to customise
|
|
83
|
+
construction (multiple instances, constructor arguments,
|
|
84
|
+
fallback logic).
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
#: Stable, lowercase identifier under which this provider
|
|
88
|
+
#: registers in the global ``register_provider`` index.
|
|
89
|
+
#: Concrete subclasses MUST set it; abstract intermediates
|
|
90
|
+
#: may leave it unset.
|
|
91
|
+
PROVIDER_NAME: ClassVar[str]
|
|
92
|
+
|
|
93
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
94
|
+
super().__init_subclass__(**kwargs)
|
|
95
|
+
# ``ABCMeta.__new__`` populates ``__abstractmethods__``
|
|
96
|
+
# *after* ``__init_subclass__`` runs, so we compute
|
|
97
|
+
# the set ourselves to detect intermediate ABCs that
|
|
98
|
+
# still have abstract methods. Those will never be
|
|
99
|
+
# instantiated; forcing them to declare
|
|
100
|
+
# ``PROVIDER_NAME`` would be noise.
|
|
101
|
+
if _has_abstract_methods(cls):
|
|
102
|
+
return
|
|
103
|
+
if "PROVIDER_NAME" not in cls.__dict__ and not any(
|
|
104
|
+
"PROVIDER_NAME" in base.__dict__ for base in cls.__mro__[1:-1]
|
|
105
|
+
):
|
|
106
|
+
raise TypeError(
|
|
107
|
+
f"{cls.__name__} must define a class-level "
|
|
108
|
+
"``PROVIDER_NAME: ClassVar[str]`` "
|
|
109
|
+
"(required by tesorotools.DataProvider)."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def fetch(
|
|
114
|
+
self,
|
|
115
|
+
codes: list[str],
|
|
116
|
+
start: str | None = None,
|
|
117
|
+
end: str | None = None,
|
|
118
|
+
) -> pd.DataFrame:
|
|
119
|
+
"""Download series data for a date range.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
codes
|
|
124
|
+
Provider-specific series codes to download.
|
|
125
|
+
start
|
|
126
|
+
Start date as ISO string (e.g. ``"2025-01-01"``).
|
|
127
|
+
If ``None``, the provider decides the earliest
|
|
128
|
+
date (typically the full available history).
|
|
129
|
+
end
|
|
130
|
+
End date as ISO string. If ``None``, the
|
|
131
|
+
provider fetches up to the latest available data.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
pd.DataFrame
|
|
136
|
+
- Index: ``DatetimeIndex`` named ``"date"``,
|
|
137
|
+
tz-naive, normalized to midnight.
|
|
138
|
+
- Columns: one per code in ``codes``.
|
|
139
|
+
- Values: ``float64``, with ``NaN`` for missing
|
|
140
|
+
observations.
|
|
141
|
+
"""
|
|
142
|
+
...
|
|
143
|
+
|
|
144
|
+
@abstractmethod
|
|
145
|
+
def is_available(self) -> bool:
|
|
146
|
+
"""Check whether this provider can serve data.
|
|
147
|
+
|
|
148
|
+
Returns ``True`` if the external service is reachable
|
|
149
|
+
and ready. Useful for fail-fast checks before
|
|
150
|
+
starting a long download, or for choosing between
|
|
151
|
+
a primary provider and a fallback.
|
|
152
|
+
"""
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def build_for(cls, ctx: "BuildContext") -> dict[str, "DataProvider"]:
|
|
157
|
+
"""Default factory used by :func:`bootstrap_providers`.
|
|
158
|
+
|
|
159
|
+
Skips construction entirely if no canonical ID asks
|
|
160
|
+
for this provider in *ctx.consumer*; otherwise
|
|
161
|
+
instantiates with no arguments and registers under
|
|
162
|
+
:attr:`PROVIDER_NAME`.
|
|
163
|
+
|
|
164
|
+
Override when the provider needs constructor
|
|
165
|
+
arguments, returns multiple instances, or has
|
|
166
|
+
fallback logic. Calling ``cls()`` on a class with
|
|
167
|
+
required ``__init__`` arguments raises ``TypeError``;
|
|
168
|
+
that is intentional -- a provider that needs
|
|
169
|
+
configuration is forced to override this method.
|
|
170
|
+
"""
|
|
171
|
+
if not ctx.registry.all_cids_for_provider(
|
|
172
|
+
ctx.consumer, cls.PROVIDER_NAME
|
|
173
|
+
):
|
|
174
|
+
return {}
|
|
175
|
+
return {cls.PROVIDER_NAME: cls()}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _has_abstract_methods(cls: type) -> bool:
|
|
179
|
+
"""Return True if *cls* still has abstract methods.
|
|
180
|
+
|
|
181
|
+
``ABCMeta`` populates ``__abstractmethods__`` after
|
|
182
|
+
``__init_subclass__`` runs, so we compute the abstract
|
|
183
|
+
set manually by walking the MRO bottom-up. An abstract
|
|
184
|
+
method introduced higher in the MRO is "discharged" when
|
|
185
|
+
a lower class overrides it with a concrete implementation.
|
|
186
|
+
"""
|
|
187
|
+
abstracts: set[str] = set()
|
|
188
|
+
for base in reversed(cls.__mro__):
|
|
189
|
+
for name, value in vars(base).items():
|
|
190
|
+
if getattr(value, "__isabstractmethod__", False):
|
|
191
|
+
abstracts.add(name)
|
|
192
|
+
elif name in abstracts:
|
|
193
|
+
abstracts.discard(name)
|
|
194
|
+
return bool(abstracts)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def bootstrap_providers(
|
|
198
|
+
ctx: "BuildContext",
|
|
199
|
+
classes: Iterable[type[DataProvider]],
|
|
200
|
+
) -> dict[str, DataProvider]:
|
|
201
|
+
"""Run :meth:`DataProvider.build_for` over *classes* and
|
|
202
|
+
merge the results.
|
|
203
|
+
|
|
204
|
+
Iteration order over *classes* is preserved for log
|
|
205
|
+
readability; behaviour does not depend on it. When two
|
|
206
|
+
classes return the same key, the later one wins, matching
|
|
207
|
+
plain ``dict.update`` semantics; that is the consumer's
|
|
208
|
+
problem to avoid.
|
|
209
|
+
"""
|
|
210
|
+
out: dict[str, DataProvider] = {}
|
|
211
|
+
for cls in classes:
|
|
212
|
+
out.update(cls.build_for(ctx))
|
|
213
|
+
return out
|
|
@@ -30,7 +30,7 @@ Notes
|
|
|
30
30
|
from __future__ import annotations
|
|
31
31
|
|
|
32
32
|
import logging
|
|
33
|
-
from typing import TypedDict, cast
|
|
33
|
+
from typing import ClassVar, TypedDict, cast
|
|
34
34
|
|
|
35
35
|
import pandas as pd
|
|
36
36
|
import requests
|
|
@@ -72,6 +72,8 @@ class BdeProvider(DataProvider):
|
|
|
72
72
|
Maximum seconds to wait per HTTP request.
|
|
73
73
|
"""
|
|
74
74
|
|
|
75
|
+
PROVIDER_NAME: ClassVar[str] = "bde"
|
|
76
|
+
|
|
75
77
|
def __init__(
|
|
76
78
|
self,
|
|
77
79
|
*,
|
|
@@ -36,7 +36,7 @@ from __future__ import annotations
|
|
|
36
36
|
import csv
|
|
37
37
|
import io
|
|
38
38
|
import logging
|
|
39
|
-
from typing import cast
|
|
39
|
+
from typing import ClassVar, cast
|
|
40
40
|
|
|
41
41
|
import pandas as pd
|
|
42
42
|
import requests
|
|
@@ -106,6 +106,8 @@ class EcbProvider(DataProvider):
|
|
|
106
106
|
tests or for custom retry policies.
|
|
107
107
|
"""
|
|
108
108
|
|
|
109
|
+
PROVIDER_NAME: ClassVar[str] = "ecb"
|
|
110
|
+
|
|
109
111
|
def __init__(
|
|
110
112
|
self,
|
|
111
113
|
*,
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/table.py
RENAMED
|
@@ -15,6 +15,7 @@ from docx.oxml.ns import nsdecls, qn
|
|
|
15
15
|
from docx.shared import Inches, Pt, RGBColor
|
|
16
16
|
from docx.table import Table as TableDocx
|
|
17
17
|
from docx.table import _Cell as TableCell
|
|
18
|
+
from docx.table import _Row as TableRow
|
|
18
19
|
from yaml import MappingNode
|
|
19
20
|
|
|
20
21
|
from tesorotools.utils.config import read_config
|
|
@@ -49,46 +50,6 @@ TEXTO_TABLAS: int = 9
|
|
|
49
50
|
CENTER = WD_ALIGN_PARAGRAPH.CENTER
|
|
50
51
|
|
|
51
52
|
|
|
52
|
-
def _set_cell_border(cell: TableCell, **kwargs: Any) -> None:
|
|
53
|
-
"""
|
|
54
|
-
Set cell`s border
|
|
55
|
-
Usage:
|
|
56
|
-
|
|
57
|
-
set_cell_border(
|
|
58
|
-
cell,
|
|
59
|
-
top={"sz": 12, "val": "single", "color": "#FF0000", "space": "0"},
|
|
60
|
-
bottom={"sz": 12, "color": "#00FF00", "val": "single"},
|
|
61
|
-
start={"sz": 24, "val": "dashed", "shadow": "true"},
|
|
62
|
-
end={"sz": 12, "val": "dashed"},
|
|
63
|
-
)
|
|
64
|
-
"""
|
|
65
|
-
tc = cell._tc
|
|
66
|
-
tcPr = tc.get_or_add_tcPr()
|
|
67
|
-
|
|
68
|
-
# check for tag existence, if none found, create one
|
|
69
|
-
tcBorders: Any = tcPr.first_child_found_in("w:tcBorders") # type: ignore[reportUnknownMemberType]
|
|
70
|
-
if tcBorders is None:
|
|
71
|
-
tcBorders = OxmlElement("w:tcBorders") # type: ignore[reportUnknownVariableType]
|
|
72
|
-
tcPr.append(tcBorders) # type: ignore[reportUnknownMemberType]
|
|
73
|
-
|
|
74
|
-
# list over all available tags
|
|
75
|
-
for edge in ("start", "top", "end", "bottom", "insideH", "insideV"):
|
|
76
|
-
edge_data: Any = kwargs.get(edge)
|
|
77
|
-
if edge_data:
|
|
78
|
-
tag = "w:{}".format(edge)
|
|
79
|
-
|
|
80
|
-
# check for tag existence, if none found, then create one
|
|
81
|
-
element: Any = tcBorders.find(qn(tag)) # type: ignore[reportUnknownMemberType]
|
|
82
|
-
if element is None:
|
|
83
|
-
element = OxmlElement(tag) # type: ignore[reportUnknownVariableType]
|
|
84
|
-
tcBorders.append(element) # type: ignore[reportUnknownMemberType]
|
|
85
|
-
|
|
86
|
-
# looks like order of attributes is important
|
|
87
|
-
for key in ["sz", "val", "color", "space", "shadow"]:
|
|
88
|
-
if key in edge_data:
|
|
89
|
-
element.set(qn("w:{}".format(key)), str(edge_data[key])) # type: ignore[reportUnknownMemberType]
|
|
90
|
-
|
|
91
|
-
|
|
92
53
|
def _style_horizontal_blocks_header(cell: TableCell) -> None:
|
|
93
54
|
cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
94
55
|
cell.paragraphs[0].runs[0].font.size = Pt(12)
|
|
@@ -192,25 +153,47 @@ def _separate_blocks(
|
|
|
192
153
|
"got a flat Index. Set block_sep=False or wrap rows in a "
|
|
193
154
|
"MultiIndex with the block name as level 0."
|
|
194
155
|
)
|
|
156
|
+
_disable_implicit_last_row(table_docx)
|
|
195
157
|
blocks: list[str] = list(index.get_level_values(level=0).unique())
|
|
196
158
|
previous_rows: int = 0
|
|
197
159
|
for block in blocks[:-1]:
|
|
198
160
|
block_size: int = len(index[index.get_level_values(level=0) == block])
|
|
199
|
-
|
|
200
|
-
_separate_cell(cell)
|
|
161
|
+
_separate_row(table_docx.rows[block_size + previous_rows])
|
|
201
162
|
previous_rows += block_size
|
|
202
163
|
|
|
203
164
|
|
|
204
|
-
def
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
165
|
+
def _separate_row(row: TableRow) -> None:
|
|
166
|
+
"""Mark *row* as ``lastRow`` via conditional-formatting reference.
|
|
167
|
+
|
|
168
|
+
The visible separator border belongs to the active table style
|
|
169
|
+
(``<w:tblStylePr w:type="lastRow">`` in ``styles.xml``). Emitting
|
|
170
|
+
only ``<w:cnfStyle>`` on ``<w:trPr>`` keeps direct formatting out of
|
|
171
|
+
``<w:tcPr>``, which is the construct Word's co-authoring merge
|
|
172
|
+
engine corrupts in shared OneDrive/SharePoint documents. The
|
|
173
|
+
consumer's ``template.docx`` must define the ``lastRow`` conditional
|
|
174
|
+
formatting on the table style referenced by ``plots.yaml``.
|
|
175
|
+
"""
|
|
176
|
+
trPr: Any = row._tr.get_or_add_trPr() # type: ignore[reportUnknownMemberType]
|
|
177
|
+
cnfStyle: Any = OxmlElement("w:cnfStyle") # type: ignore[reportUnknownVariableType]
|
|
178
|
+
# 12-bit mask, ECMA-376 §17.4.7. Bit 1 (zero-indexed) = lastRow.
|
|
179
|
+
cnfStyle.set(qn("w:val"), "010000000000") # type: ignore[reportUnknownMemberType]
|
|
180
|
+
trPr.append(cnfStyle) # type: ignore[reportUnknownMemberType]
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _disable_implicit_last_row(table_docx: TableDocx) -> None:
|
|
184
|
+
"""Force ``<w:tblLook w:lastRow="0"/>`` so the real last row stays plain.
|
|
185
|
+
|
|
186
|
+
With block_sep we mark *interior* rows as ``lastRow`` via
|
|
187
|
+
``cnfStyle``. The table's own last row must not pick up the same
|
|
188
|
+
conditional formatting automatically, so we disable the implicit
|
|
189
|
+
``lastRow`` flag at the ``<w:tblLook>`` level.
|
|
190
|
+
"""
|
|
191
|
+
tblPr: Any = table_docx._element.tblPr # type: ignore[reportUnknownMemberType]
|
|
192
|
+
tblLook: Any = tblPr.find(qn("w:tblLook")) # type: ignore[reportUnknownMemberType]
|
|
193
|
+
if tblLook is None:
|
|
194
|
+
tblLook = OxmlElement("w:tblLook") # type: ignore[reportUnknownVariableType]
|
|
195
|
+
tblPr.append(tblLook) # type: ignore[reportUnknownMemberType]
|
|
196
|
+
tblLook.set(qn("w:lastRow"), "0") # type: ignore[reportUnknownMemberType]
|
|
214
197
|
|
|
215
198
|
|
|
216
199
|
def _is_bright(hex_color: str) -> bool:
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"""Shared context object for ``build_for`` provider/artist factories.
|
|
2
|
-
|
|
3
|
-
Consumer projects increasingly converge on a pattern where
|
|
4
|
-
each provider/artist class has a ``build_for(cls, ctx) ->
|
|
5
|
-
dict[str, ...]`` classmethod that decides whether to
|
|
6
|
-
instantiate itself based on the catalog and runtime context.
|
|
7
|
-
|
|
8
|
-
``BuildContext`` is the minimal shape we expect callers to
|
|
9
|
-
share across that pattern. It deliberately stays small so
|
|
10
|
-
projects can subclass or compose freely:
|
|
11
|
-
|
|
12
|
-
.. code-block:: python
|
|
13
|
-
|
|
14
|
-
@dataclass(frozen=True)
|
|
15
|
-
class DiaryBuildContext(BuildContext):
|
|
16
|
-
lseg_fallback: Literal["mock", "raise"] = "mock"
|
|
17
|
-
|
|
18
|
-
The base fields capture the cross-project minimum:
|
|
19
|
-
|
|
20
|
-
* ``registry`` -- whatever catalog the orchestrator uses to
|
|
21
|
-
decide which series each provider must serve. Typed as
|
|
22
|
-
``Any`` because each project's catalog has a different
|
|
23
|
-
shape.
|
|
24
|
-
* ``consumer`` -- a free-form string identifying the calling
|
|
25
|
-
workflow (e.g. ``"diary"``, ``"weekly"``). ``build_for``
|
|
26
|
-
implementations dispatch on this.
|
|
27
|
-
* ``mock`` / ``mock_seed`` -- toggle for deterministic
|
|
28
|
-
fixtures during tests and demos.
|
|
29
|
-
* ``historic_file`` -- optional path to a historical dump
|
|
30
|
-
used by some providers as a fallback.
|
|
31
|
-
|
|
32
|
-
See ``docs/extending.md`` for the recommended ``build_for``
|
|
33
|
-
classmethod shape.
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
from __future__ import annotations
|
|
37
|
-
|
|
38
|
-
from dataclasses import dataclass
|
|
39
|
-
from pathlib import Path
|
|
40
|
-
from typing import Any
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@dataclass(frozen=True)
|
|
44
|
-
class BuildContext:
|
|
45
|
-
registry: Any
|
|
46
|
-
consumer: str
|
|
47
|
-
mock: bool = False
|
|
48
|
-
mock_seed: int | None = None
|
|
49
|
-
historic_file: Path | None = None
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
"""Abstract base class for data providers.
|
|
2
|
-
|
|
3
|
-
A provider knows how to download time series from a specific
|
|
4
|
-
external source (Bank of Spain, Eikon, ECB, etc.). It does
|
|
5
|
-
not know anything about local storage, incremental updates,
|
|
6
|
-
or presentation -- those concerns belong elsewhere.
|
|
7
|
-
|
|
8
|
-
The interface is intentionally minimal: ``fetch`` to download
|
|
9
|
-
data and ``is_available`` to check connectivity. Any
|
|
10
|
-
provider-specific configuration (API keys, rate limits, etc.)
|
|
11
|
-
belongs in the concrete class constructor, not in the
|
|
12
|
-
abstract interface.
|
|
13
|
-
|
|
14
|
-
Every provider returns data in the same shape: a DataFrame
|
|
15
|
-
with a DatetimeIndex and one column per series code.
|
|
16
|
-
|
|
17
|
-
This module exposes both ``DataProvider`` (an ``ABC`` --
|
|
18
|
-
recommended base class because it enforces ``fetch`` and
|
|
19
|
-
``is_available`` at instantiation time) and
|
|
20
|
-
``DataProviderProtocol`` (a ``runtime_checkable`` Protocol
|
|
21
|
-
mirroring the same surface). The Protocol exists for
|
|
22
|
-
callers that need to compose Protocols, e.g.
|
|
23
|
-
|
|
24
|
-
.. code-block:: python
|
|
25
|
-
|
|
26
|
-
class DiaryProvider(DataProviderProtocol, Protocol):
|
|
27
|
-
PROVIDER_NAME: ClassVar[str]
|
|
28
|
-
@classmethod
|
|
29
|
-
def build_for(cls, ctx: BuildContext) -> dict[str, ...]: ...
|
|
30
|
-
|
|
31
|
-
without dragging in the ABC's runtime enforcement.
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
from __future__ import annotations
|
|
35
|
-
|
|
36
|
-
from abc import ABC, abstractmethod
|
|
37
|
-
from typing import Protocol, runtime_checkable
|
|
38
|
-
|
|
39
|
-
import pandas as pd
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class DataProvider(ABC):
|
|
43
|
-
"""Base class for all data providers.
|
|
44
|
-
|
|
45
|
-
Subclasses must implement ``fetch`` and ``is_available``.
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
@abstractmethod
|
|
49
|
-
def fetch(
|
|
50
|
-
self,
|
|
51
|
-
codes: list[str],
|
|
52
|
-
start: str | None = None,
|
|
53
|
-
end: str | None = None,
|
|
54
|
-
) -> pd.DataFrame:
|
|
55
|
-
"""Download series data for a date range.
|
|
56
|
-
|
|
57
|
-
Parameters
|
|
58
|
-
----------
|
|
59
|
-
codes
|
|
60
|
-
Provider-specific series codes to download.
|
|
61
|
-
start
|
|
62
|
-
Start date as ISO string (e.g. ``"2025-01-01"``).
|
|
63
|
-
If ``None``, the provider decides the earliest
|
|
64
|
-
date (typically the full available history).
|
|
65
|
-
end
|
|
66
|
-
End date as ISO string. If ``None``, the
|
|
67
|
-
provider fetches up to the latest available data.
|
|
68
|
-
|
|
69
|
-
Returns
|
|
70
|
-
-------
|
|
71
|
-
pd.DataFrame
|
|
72
|
-
- Index: ``DatetimeIndex`` named ``"date"``,
|
|
73
|
-
tz-naive, normalized to midnight.
|
|
74
|
-
- Columns: one per code in ``codes``.
|
|
75
|
-
- Values: ``float64``, with ``NaN`` for missing
|
|
76
|
-
observations.
|
|
77
|
-
"""
|
|
78
|
-
...
|
|
79
|
-
|
|
80
|
-
@abstractmethod
|
|
81
|
-
def is_available(self) -> bool:
|
|
82
|
-
"""Check whether this provider can serve data.
|
|
83
|
-
|
|
84
|
-
Returns ``True`` if the external service is reachable
|
|
85
|
-
and ready. Useful for fail-fast checks before
|
|
86
|
-
starting a long download, or for choosing between
|
|
87
|
-
a primary provider and a fallback.
|
|
88
|
-
"""
|
|
89
|
-
...
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@runtime_checkable
|
|
93
|
-
class DataProviderProtocol(Protocol):
|
|
94
|
-
"""Structural counterpart of :class:`DataProvider`.
|
|
95
|
-
|
|
96
|
-
Anything with ``fetch(codes, start=None, end=None)`` and
|
|
97
|
-
``is_available()`` satisfies this Protocol. Subclassing
|
|
98
|
-
is unnecessary; useful when callers need to compose
|
|
99
|
-
Protocols (``class DiaryProvider(DataProviderProtocol,
|
|
100
|
-
Protocol): ...``) without inheriting an ABC.
|
|
101
|
-
|
|
102
|
-
Prefer subclassing :class:`DataProvider` for new concrete
|
|
103
|
-
providers: the ABC enforces the contract at
|
|
104
|
-
instantiation time, the Protocol does not.
|
|
105
|
-
"""
|
|
106
|
-
|
|
107
|
-
def fetch(
|
|
108
|
-
self,
|
|
109
|
-
codes: list[str],
|
|
110
|
-
start: str | None = None,
|
|
111
|
-
end: str | None = None,
|
|
112
|
-
) -> pd.DataFrame: ...
|
|
113
|
-
|
|
114
|
-
def is_available(self) -> bool: ...
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/artists/barh_plot.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/artists/line_plot.py
RENAMED
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/artists/type_curve.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/fonts/README.md
RENAMED
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/assets/tesoro.mplstyle
RENAMED
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/data_sources/__init__.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/data_sources/debug.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/database/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/dependencies/__init__.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/dependencies/node.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/dependencies/resolution.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/pipeline/__init__.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/pipeline/diagnose.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/__init__.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/content.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/images.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/section.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/subtitle.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/text.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.38 → tesorotools_python-0.0.40}/src/tesorotools/render/content/title.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|