prefect-fxmacrodata 0.1.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.
- prefect_fxmacrodata/__init__.py +19 -0
- prefect_fxmacrodata/credentials.py +30 -0
- prefect_fxmacrodata/flows.py +42 -0
- prefect_fxmacrodata/models.py +51 -0
- prefect_fxmacrodata/py.typed +0 -0
- prefect_fxmacrodata/tasks.py +197 -0
- prefect_fxmacrodata/tasks.pyi +93 -0
- prefect_fxmacrodata-0.1.0.dist-info/METADATA +169 -0
- prefect_fxmacrodata-0.1.0.dist-info/RECORD +13 -0
- prefect_fxmacrodata-0.1.0.dist-info/WHEEL +5 -0
- prefect_fxmacrodata-0.1.0.dist-info/entry_points.txt +2 -0
- prefect_fxmacrodata-0.1.0.dist-info/licenses/LICENSE +21 -0
- prefect_fxmacrodata-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""FXMacroData credentials, tasks and research flows for Prefect."""
|
|
2
|
+
|
|
3
|
+
from .credentials import FXMacroDataCredentials
|
|
4
|
+
from .flows import macro_snapshot
|
|
5
|
+
from .models import FXMacroDataResult, MacroSnapshot, OperationSchema
|
|
6
|
+
from .tasks import OPERATION_TASKS, complete_history, discover_operations, operation_schemas
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
__all__ = [
|
|
10
|
+
"FXMacroDataCredentials",
|
|
11
|
+
"FXMacroDataResult",
|
|
12
|
+
"MacroSnapshot",
|
|
13
|
+
"OperationSchema",
|
|
14
|
+
"OPERATION_TASKS",
|
|
15
|
+
"complete_history",
|
|
16
|
+
"discover_operations",
|
|
17
|
+
"macro_snapshot",
|
|
18
|
+
"operation_schemas",
|
|
19
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Reusable FXMacroData connection configuration for Prefect."""
|
|
2
|
+
|
|
3
|
+
from fxmacrodata_public import FXMacroDataClient
|
|
4
|
+
from prefect.blocks.core import Block
|
|
5
|
+
from pydantic import Field, SecretStr
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FXMacroDataCredentials(Block):
|
|
9
|
+
"""Store a subscriber's API key using Prefect's encrypted secret field.
|
|
10
|
+
|
|
11
|
+
Tasks take the saved Block name and load this configuration inside the run.
|
|
12
|
+
An empty key explicitly selects public evaluation access.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
_block_type_name = "FXMacroData Credentials"
|
|
16
|
+
_documentation_url = (
|
|
17
|
+
"https://fxmacrodata.com/documentation/reference?utm_source=github"
|
|
18
|
+
"&utm_medium=referral&utm_campaign=prefect&utm_content=docs"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
api_key: SecretStr = Field(
|
|
22
|
+
default_factory=lambda: SecretStr(""),
|
|
23
|
+
description="Your FXMacroData subscription API key; leave empty for USD evaluation.",
|
|
24
|
+
repr=False,
|
|
25
|
+
)
|
|
26
|
+
timeout: float = Field(default=30, ge=1, le=120, allow_inf_nan=False)
|
|
27
|
+
|
|
28
|
+
def get_client(self) -> FXMacroDataClient:
|
|
29
|
+
"""Create a bounded client; use it as a context manager."""
|
|
30
|
+
return FXMacroDataClient(api_key=self.api_key.get_secret_value(), timeout=self.timeout)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Research workflows composed from ordinary Prefect tasks."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from prefect import flow
|
|
6
|
+
|
|
7
|
+
from .models import MacroSnapshot
|
|
8
|
+
from .tasks import OPERATION_TASKS, complete_history
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@flow(name="fxmacrodata-macro-snapshot", persist_result=False, log_prints=False)
|
|
12
|
+
async def macro_snapshot(
|
|
13
|
+
currency: str,
|
|
14
|
+
indicator: str,
|
|
15
|
+
credentials_block: str | None = None,
|
|
16
|
+
start_date: str | None = None,
|
|
17
|
+
end_date: str | None = None,
|
|
18
|
+
max_pages: int = 10000,
|
|
19
|
+
) -> MacroSnapshot:
|
|
20
|
+
"""Fetch complete available indicator history and the release calendar.
|
|
21
|
+
|
|
22
|
+
The caller chooses the currency and history range; calendar dates use the
|
|
23
|
+
API's current release-calendar window independently of the history range.
|
|
24
|
+
Original source fields,
|
|
25
|
+
values, publication timestamps and unavailable states are retained.
|
|
26
|
+
"""
|
|
27
|
+
arguments = {"currency": currency, "indicator": indicator}
|
|
28
|
+
if start_date is not None:
|
|
29
|
+
arguments["start_date"] = start_date
|
|
30
|
+
if end_date is not None:
|
|
31
|
+
arguments["end_date"] = end_date
|
|
32
|
+
history, calendar = await asyncio.gather(
|
|
33
|
+
complete_history("indicator_history", arguments, credentials_block, max_pages),
|
|
34
|
+
OPERATION_TASKS["release_calendar"]({"currency": currency}, credentials_block),
|
|
35
|
+
)
|
|
36
|
+
return MacroSnapshot(
|
|
37
|
+
currency=currency,
|
|
38
|
+
indicator=indicator,
|
|
39
|
+
history=history,
|
|
40
|
+
calendar=calendar,
|
|
41
|
+
source_urls=list(dict.fromkeys([history.source_url, calendar.source_url])),
|
|
42
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Typed envelopes that retain FXMacroData's original responses."""
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from fxmacrodata_public import Result
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FXMacroDataResult(BaseModel):
|
|
11
|
+
"""Original payload and provenance plus an additive row-oriented view."""
|
|
12
|
+
|
|
13
|
+
model_config = ConfigDict(frozen=True)
|
|
14
|
+
|
|
15
|
+
operation: str
|
|
16
|
+
payload: Any = Field(repr=False)
|
|
17
|
+
source_url: str
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def from_result(cls, result: Result) -> "FXMacroDataResult":
|
|
21
|
+
"""Copy a client result without changing values or metadata."""
|
|
22
|
+
return cls(
|
|
23
|
+
operation=result.operation,
|
|
24
|
+
payload=deepcopy(result.payload),
|
|
25
|
+
source_url=result.source_url,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def records(self) -> list[dict[str, Any]]:
|
|
29
|
+
"""Return the public client's additive record view."""
|
|
30
|
+
return Result(self.operation, self.payload, self.source_url).records()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class OperationSchema(BaseModel):
|
|
34
|
+
"""Discover a native task and its unchanged API argument schema."""
|
|
35
|
+
|
|
36
|
+
name: str
|
|
37
|
+
description: str
|
|
38
|
+
method: str
|
|
39
|
+
path: str
|
|
40
|
+
input_schema: dict[str, Any]
|
|
41
|
+
supports_complete_history: bool
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MacroSnapshot(BaseModel):
|
|
45
|
+
"""History and scheduled releases for a research flow's next task."""
|
|
46
|
+
|
|
47
|
+
currency: str
|
|
48
|
+
indicator: str
|
|
49
|
+
history: FXMacroDataResult
|
|
50
|
+
calendar: FXMacroDataResult
|
|
51
|
+
source_urls: list[str] = Field(default_factory=list)
|
|
File without changes
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Named Prefect tasks for every published FXMacroData client operation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from threading import Event
|
|
8
|
+
from typing import Any, Callable
|
|
9
|
+
|
|
10
|
+
from fxmacrodata_public import FXMacroDataError, Result, list_operations
|
|
11
|
+
from fxmacrodata_public.pagination import IncompleteHistoryError, collect_history
|
|
12
|
+
from prefect import task
|
|
13
|
+
from prefect.cache_policies import NO_CACHE
|
|
14
|
+
|
|
15
|
+
from .credentials import FXMacroDataCredentials
|
|
16
|
+
from .models import FXMacroDataResult, OperationSchema
|
|
17
|
+
|
|
18
|
+
_OPERATIONS = {operation.name: operation for operation in list_operations()}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FXMacroDataTaskError(RuntimeError):
|
|
22
|
+
"""A task failed without retaining the underlying request or credentials."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def operation_schemas() -> tuple[OperationSchema, ...]:
|
|
26
|
+
"""Return the exact schemas used by the installed public client."""
|
|
27
|
+
return tuple(
|
|
28
|
+
OperationSchema(
|
|
29
|
+
name=op.name,
|
|
30
|
+
description=op.description,
|
|
31
|
+
method=op.method,
|
|
32
|
+
path=op.path,
|
|
33
|
+
input_schema=deepcopy(op.input_schema),
|
|
34
|
+
supports_complete_history=(
|
|
35
|
+
op.method == "GET" and any(p["name"] == "offset" for p in op.parameters)
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
for op in _OPERATIONS.values()
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def _credentials(block_name: str | None) -> FXMacroDataCredentials:
|
|
43
|
+
if block_name is None:
|
|
44
|
+
return FXMacroDataCredentials()
|
|
45
|
+
if not isinstance(block_name, str) or not block_name:
|
|
46
|
+
raise FXMacroDataTaskError("Supply a saved FXMacroData Credentials Block name.")
|
|
47
|
+
try:
|
|
48
|
+
return await FXMacroDataCredentials.aload(block_name)
|
|
49
|
+
except Exception:
|
|
50
|
+
raise FXMacroDataTaskError("Unable to load the FXMacroData Credentials Block.") from None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def _run_blocking(work: Callable[[Event], FXMacroDataResult]) -> FXMacroDataResult:
|
|
54
|
+
"""Drain one bounded request on cancellation and stop before the next page."""
|
|
55
|
+
cancelled = Event()
|
|
56
|
+
future = asyncio.create_task(asyncio.to_thread(work, cancelled))
|
|
57
|
+
try:
|
|
58
|
+
return await asyncio.shield(future)
|
|
59
|
+
except asyncio.CancelledError:
|
|
60
|
+
cancelled.set()
|
|
61
|
+
try:
|
|
62
|
+
await asyncio.shield(future)
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
raise
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _check_cancelled(cancelled: Event) -> None:
|
|
69
|
+
if cancelled.is_set():
|
|
70
|
+
raise FXMacroDataTaskError("FXMacroData request cancelled.")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def _execute(
|
|
74
|
+
operation: str,
|
|
75
|
+
arguments: dict[str, Any] | None,
|
|
76
|
+
credentials_block: str | None,
|
|
77
|
+
*,
|
|
78
|
+
complete: bool = False,
|
|
79
|
+
max_pages: int = 10000,
|
|
80
|
+
) -> FXMacroDataResult:
|
|
81
|
+
connection = await _credentials(credentials_block)
|
|
82
|
+
if operation not in _OPERATIONS:
|
|
83
|
+
raise FXMacroDataTaskError("Unknown operation. Use operation_schemas for discovery.")
|
|
84
|
+
op = _OPERATIONS[operation]
|
|
85
|
+
if complete and not (op.method == "GET" and any(p["name"] == "offset" for p in op.parameters)):
|
|
86
|
+
raise FXMacroDataTaskError("Complete history requires an offset-paginated REST operation.")
|
|
87
|
+
if type(max_pages) is not int or not 1 <= max_pages <= 10000:
|
|
88
|
+
raise FXMacroDataTaskError("max_pages must be an integer between 1 and 10000.")
|
|
89
|
+
|
|
90
|
+
def work(cancelled: Event) -> FXMacroDataResult:
|
|
91
|
+
try:
|
|
92
|
+
with connection.get_client() as client:
|
|
93
|
+
_check_cancelled(cancelled)
|
|
94
|
+
if complete:
|
|
95
|
+
source_url = "https://fxmacrodata.com"
|
|
96
|
+
|
|
97
|
+
def fetch(page: dict[str, Any]) -> Any:
|
|
98
|
+
nonlocal source_url
|
|
99
|
+
_check_cancelled(cancelled)
|
|
100
|
+
response = client.execute(operation, page)
|
|
101
|
+
source_url = response.source_url
|
|
102
|
+
return response.payload
|
|
103
|
+
|
|
104
|
+
payload = collect_history(
|
|
105
|
+
fetch,
|
|
106
|
+
arguments,
|
|
107
|
+
max_pages=max_pages,
|
|
108
|
+
request_id=operation,
|
|
109
|
+
require_pagination=True,
|
|
110
|
+
)
|
|
111
|
+
result = Result(operation, payload, source_url)
|
|
112
|
+
else:
|
|
113
|
+
result = client.execute(operation, arguments)
|
|
114
|
+
return FXMacroDataResult.from_result(result)
|
|
115
|
+
except IncompleteHistoryError:
|
|
116
|
+
raise FXMacroDataTaskError(
|
|
117
|
+
"History is incomplete or changed during download; restart with a larger "
|
|
118
|
+
"page budget if needed. No partial history was returned."
|
|
119
|
+
) from None
|
|
120
|
+
except FXMacroDataError:
|
|
121
|
+
raise FXMacroDataTaskError(
|
|
122
|
+
"FXMacroData could not complete this operation. Check its input schema, "
|
|
123
|
+
"your subscription access and service availability."
|
|
124
|
+
) from None
|
|
125
|
+
except FXMacroDataTaskError:
|
|
126
|
+
raise
|
|
127
|
+
except Exception:
|
|
128
|
+
raise FXMacroDataTaskError(
|
|
129
|
+
"FXMacroData request failed; retry after checking access."
|
|
130
|
+
) from None
|
|
131
|
+
|
|
132
|
+
return await _run_blocking(work)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _operation_task(name: str):
|
|
136
|
+
async def run(
|
|
137
|
+
arguments: dict[str, Any] | None = None,
|
|
138
|
+
credentials_block: str | None = None,
|
|
139
|
+
) -> FXMacroDataResult:
|
|
140
|
+
return await _execute(name, arguments, credentials_block)
|
|
141
|
+
|
|
142
|
+
run.__name__ = name
|
|
143
|
+
run.__qualname__ = name
|
|
144
|
+
run.__doc__ = _OPERATIONS[name].description
|
|
145
|
+
return task(
|
|
146
|
+
name=f"fxmacrodata-{name.replace('_', '-')}",
|
|
147
|
+
tags={"fxmacrodata"},
|
|
148
|
+
cache_policy=NO_CACHE,
|
|
149
|
+
persist_result=False,
|
|
150
|
+
log_prints=False,
|
|
151
|
+
retries=0,
|
|
152
|
+
)(run)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
OPERATION_TASKS = {name: _operation_task(name) for name in _OPERATIONS}
|
|
156
|
+
globals().update(OPERATION_TASKS)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@task(
|
|
160
|
+
name="fxmacrodata-complete-history",
|
|
161
|
+
cache_policy=NO_CACHE,
|
|
162
|
+
persist_result=False,
|
|
163
|
+
log_prints=False,
|
|
164
|
+
retries=0,
|
|
165
|
+
tags={"fxmacrodata"},
|
|
166
|
+
)
|
|
167
|
+
async def complete_history(
|
|
168
|
+
operation: str,
|
|
169
|
+
arguments: dict[str, Any] | None = None,
|
|
170
|
+
credentials_block: str | None = None,
|
|
171
|
+
max_pages: int = 10000,
|
|
172
|
+
) -> FXMacroDataResult:
|
|
173
|
+
"""Collect and verify every history page, retaining per-page metadata.
|
|
174
|
+
|
|
175
|
+
This downloads the available history for the supplied filters, starting at
|
|
176
|
+
offset zero. It fails instead of returning a partial dataset. Single-page
|
|
177
|
+
operation tasks remain available for applications managing their own pages.
|
|
178
|
+
"""
|
|
179
|
+
if arguments and arguments.get("offset", 0) != 0:
|
|
180
|
+
raise FXMacroDataTaskError("Complete history starts at offset zero.")
|
|
181
|
+
return await _execute(
|
|
182
|
+
operation, arguments, credentials_block, complete=True, max_pages=max_pages
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@task(name="fxmacrodata-discover-operations", cache_policy=NO_CACHE, persist_result=False)
|
|
187
|
+
async def discover_operations() -> tuple[OperationSchema, ...]:
|
|
188
|
+
"""List every installed operation and its unchanged argument schema."""
|
|
189
|
+
return operation_schemas()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
__all__ = [
|
|
193
|
+
"OPERATION_TASKS",
|
|
194
|
+
"complete_history",
|
|
195
|
+
"discover_operations",
|
|
196
|
+
"operation_schemas",
|
|
197
|
+
] + list(OPERATION_TASKS)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from typing import Any, Coroutine
|
|
2
|
+
|
|
3
|
+
from prefect import Task
|
|
4
|
+
|
|
5
|
+
from .models import FXMacroDataResult, OperationSchema
|
|
6
|
+
|
|
7
|
+
class FXMacroDataTaskError(RuntimeError): ...
|
|
8
|
+
|
|
9
|
+
def operation_schemas() -> tuple[OperationSchema, ...]: ...
|
|
10
|
+
|
|
11
|
+
complete_history: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
12
|
+
discover_operations: Task[..., Coroutine[Any, Any, tuple[OperationSchema, ...]]]
|
|
13
|
+
OPERATION_TASKS: dict[str, Task[..., Coroutine[Any, Any, FXMacroDataResult]]]
|
|
14
|
+
|
|
15
|
+
health: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
16
|
+
ping: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
17
|
+
forex: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
18
|
+
intraday_reference_rates: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
19
|
+
fx_sources: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
20
|
+
fx_source_universe: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
21
|
+
data_catalogue: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
22
|
+
release_calendar: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
23
|
+
market_sessions: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
24
|
+
rate_differentials: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
25
|
+
curves: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
26
|
+
financial_prices: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
27
|
+
press_releases: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
28
|
+
risk_sentiment: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
29
|
+
factors: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
30
|
+
event_predictions: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
31
|
+
latest_announcements: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
32
|
+
indicator_history: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
33
|
+
cot: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
34
|
+
latest_commodities: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
35
|
+
commodities: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
36
|
+
announcement_changes: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
37
|
+
stream_events: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
38
|
+
reference_rates: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
39
|
+
session_rates: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
40
|
+
prediction_coverage: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
41
|
+
currency_prediction_coverage: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
42
|
+
currency_predictions: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
43
|
+
research_panel: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
44
|
+
mcp_ping: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
45
|
+
mcp_mcp_capabilities: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
46
|
+
mcp_mcp_auth_guide: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
47
|
+
mcp_subscribe_for_mcp_access: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
48
|
+
mcp_data_catalogue: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
49
|
+
mcp_risk_sentiment: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
50
|
+
mcp_macro_news: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
51
|
+
mcp_release_calendar: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
52
|
+
mcp_release_calendar_visual_artifact: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
53
|
+
mcp_event_predictions: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
54
|
+
mcp_latest_announcements: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
55
|
+
mcp_announcement_changes: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
56
|
+
mcp_press_releases: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
57
|
+
mcp_macro_factor: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
58
|
+
mcp_fx_reference_sources: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
59
|
+
mcp_fx_reference_universe: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
60
|
+
mcp_fx_intraday_reference_rates: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
61
|
+
mcp_rate_curve: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
62
|
+
mcp_rate_differentials: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
63
|
+
mcp_latest_commodities: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
64
|
+
mcp_forex: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
65
|
+
mcp_seasonality: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
66
|
+
mcp_indicator_query: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
67
|
+
mcp_plot_visual_artifact: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
68
|
+
mcp_indicator_visual_artifact: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
69
|
+
mcp_forex_visual_artifact: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
70
|
+
mcp_commodities_visual_artifact: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
71
|
+
mcp_cot_visual_artifact: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
72
|
+
mcp_policy_rate_differential_visual_artifact: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
73
|
+
mcp_macro_briefing_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
74
|
+
mcp_indicator_intel_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
75
|
+
mcp_pair_intel_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
76
|
+
mcp_macro_heatmap_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
77
|
+
mcp_policy_scenario_modeler_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
78
|
+
mcp_macro_war_room_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
79
|
+
mcp_event_impact_replay_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
80
|
+
mcp_quant_scenario_lab_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
81
|
+
mcp_known_at_time_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
82
|
+
mcp_macro_regime_classifier_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
83
|
+
mcp_release_risk_score_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
84
|
+
mcp_portfolio_risk_engine_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
85
|
+
mcp_fx_trade_setup_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
86
|
+
mcp_fx_backtest_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
87
|
+
mcp_macro_research_pack_task: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
88
|
+
mcp_prediction_coverage: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
89
|
+
mcp_market_sessions: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
90
|
+
mcp_cot_data: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
91
|
+
mcp_commodities: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
92
|
+
mcp_financial_prices: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
93
|
+
mcp_official_dataset_family: Task[..., Coroutine[Any, Any, FXMacroDataResult]]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prefect-fxmacrodata
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FXMacroData subscription data in Prefect tasks and research flows
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://fxmacrodata.com/?utm_source=github&utm_medium=referral&utm_campaign=prefect&utm_content=readme
|
|
7
|
+
Project-URL: Subscribe, https://fxmacrodata.com/subscribe?utm_source=github&utm_medium=referral&utm_campaign=prefect&utm_content=subscribe
|
|
8
|
+
Project-URL: Documentation, https://fxmacrodata.com/documentation/reference?utm_source=github&utm_medium=referral&utm_campaign=prefect&utm_content=docs
|
|
9
|
+
Requires-Python: <3.15,>=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: prefect<4,>=3.9.1
|
|
13
|
+
Requires-Dist: fxmacrodata-public-client==0.1.1
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# FXMacroData for Prefect
|
|
17
|
+
|
|
18
|
+
Bring your FXMacroData subscription into scheduled Prefect research workflows:
|
|
19
|
+
cross-currency indicators, complete available histories, FX reference rates,
|
|
20
|
+
commodities, positioning and release analysis. Store your API key in a Prefect
|
|
21
|
+
Credentials Block and pass its name to native tasks.
|
|
22
|
+
|
|
23
|
+
**[Subscribe to FXMacroData](https://fxmacrodata.com/subscribe?utm_source=github&utm_medium=referral&utm_campaign=prefect&utm_content=subscribe)**
|
|
24
|
+
for subscriber coverage and deeper available history. Public USD catalogue,
|
|
25
|
+
recent indicator history and calendar access let you evaluate the workflow
|
|
26
|
+
before subscribing without an API key. Availability varies by dataset.
|
|
27
|
+
|
|
28
|
+
## Install and connect
|
|
29
|
+
|
|
30
|
+
Use Python 3.10–3.14 and Prefect 3.9.1 or later within version 3. From the release artifacts directory,
|
|
31
|
+
install the adapter wheel into the environment that runs your flows. Its pinned
|
|
32
|
+
FXMacroData public client is available on PyPI and resolves automatically:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python -m pip install ./prefect_fxmacrodata-0.1.0-py3-none-any.whl
|
|
36
|
+
prefect block register -m prefect_fxmacrodata
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The adapter uses the supplied release artifact; its public-client dependency
|
|
40
|
+
comes from PyPI. Configure your Prefect server or Cloud workspace through Prefect's
|
|
41
|
+
normal setup. In **Blocks**, create an **FXMacroData Credentials** block, enter
|
|
42
|
+
your subscription API key and save it as `fxmacrodata-research`. The key uses
|
|
43
|
+
Prefect's secret-field storage. The timeout controls each HTTP request, in seconds.
|
|
44
|
+
|
|
45
|
+
To create the same block interactively without putting a key in source code:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from getpass import getpass
|
|
49
|
+
from pydantic import SecretStr
|
|
50
|
+
from prefect_fxmacrodata import FXMacroDataCredentials
|
|
51
|
+
|
|
52
|
+
FXMacroDataCredentials(
|
|
53
|
+
api_key=SecretStr(getpass("FXMacroData API key: ")),
|
|
54
|
+
).save("fxmacrodata-research")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Create the block during setup, outside a flow. Flows and tasks take only its name;
|
|
58
|
+
do not put keys in flow parameters, operation arguments or logs. Omitting
|
|
59
|
+
`credentials_block` explicitly uses public evaluation access, even if an API-key
|
|
60
|
+
environment variable is set.
|
|
61
|
+
|
|
62
|
+
## Build a research flow
|
|
63
|
+
|
|
64
|
+
`macro_snapshot` is a Prefect subflow that collects every available history page
|
|
65
|
+
for the selected series and retrieves its release calendar. Pass the typed
|
|
66
|
+
snapshot to your own downstream task:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import asyncio
|
|
70
|
+
from prefect import flow, task
|
|
71
|
+
from prefect_fxmacrodata import MacroSnapshot, macro_snapshot
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@task(persist_result=False)
|
|
75
|
+
def count_observations(snapshot: MacroSnapshot) -> int:
|
|
76
|
+
return len(snapshot.history.records())
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@flow(persist_result=False)
|
|
80
|
+
async def research_run():
|
|
81
|
+
snapshot = await macro_snapshot(
|
|
82
|
+
"EUR",
|
|
83
|
+
"policy_rate",
|
|
84
|
+
credentials_block="fxmacrodata-research",
|
|
85
|
+
start_date="2020-01-01",
|
|
86
|
+
end_date="2026-01-01",
|
|
87
|
+
)
|
|
88
|
+
return count_observations(snapshot)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
asyncio.run(research_run())
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
To evaluate with USD, omit the credentials and historical date range:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
snapshot = await macro_snapshot("USD", "policy_rate")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Subscriber history can extend beyond the public evaluation window where the
|
|
102
|
+
series supports it.
|
|
103
|
+
Deploy or schedule this flow with your normal Prefect work pool and deployment process.
|
|
104
|
+
The date filters select indicator history. The calendar uses the API's current
|
|
105
|
+
release-calendar window for the currency; it is not filtered to the historical
|
|
106
|
+
range or one indicator. Use the `release_calendar` task directly for different
|
|
107
|
+
calendar filters.
|
|
108
|
+
|
|
109
|
+
## Use individual operations
|
|
110
|
+
|
|
111
|
+
Every packaged operation is a named Prefect task, with normal task states,
|
|
112
|
+
dependencies, `with_options`, retries and scheduling through its enclosing flow.
|
|
113
|
+
Both REST and hosted MCP operations are available:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from prefect_fxmacrodata import operation_schemas
|
|
117
|
+
from prefect_fxmacrodata.tasks import indicator_history, mcp_macro_briefing_task
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
async def inspect_series():
|
|
121
|
+
page = await indicator_history(
|
|
122
|
+
{"currency": "GBP", "indicator": "inflation", "limit": 100},
|
|
123
|
+
credentials_block="fxmacrodata-research",
|
|
124
|
+
)
|
|
125
|
+
briefing = await mcp_macro_briefing_task(
|
|
126
|
+
{"currency": "GBP"},
|
|
127
|
+
credentials_block="fxmacrodata-research",
|
|
128
|
+
)
|
|
129
|
+
return page, briefing
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
schemas = {operation.name: operation.input_schema for operation in operation_schemas()}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
See [CAPABILITIES.md](CAPABILITIES.md) for the exact operation inventory. The
|
|
136
|
+
original input schemas are available through `operation_schemas()` or the native
|
|
137
|
+
`discover_operations` task. `OPERATION_TASKS[name]` supports configuration-driven
|
|
138
|
+
flows without losing operation-specific task names.
|
|
139
|
+
|
|
140
|
+
Results are `FXMacroDataResult` models. `payload` retains the entire service
|
|
141
|
+
response, including original units, provenance, unavailable values and MCP
|
|
142
|
+
content; `source_url` records the source endpoint. `records()` adds a convenient
|
|
143
|
+
row view without replacing the payload. No publication timestamps or missing
|
|
144
|
+
observations are inferred.
|
|
145
|
+
|
|
146
|
+
## History and failure behavior
|
|
147
|
+
|
|
148
|
+
Individual operation tasks return a single response using the API's pagination
|
|
149
|
+
rules. Use `complete_history("indicator_history", arguments, credentials_block)`
|
|
150
|
+
for a complete available dataset within the requested filters. It starts at
|
|
151
|
+
offset zero, verifies contiguous pages and dataset versions when supplied, and
|
|
152
|
+
fails if completion cannot be established. Its payload retains every page's
|
|
153
|
+
metadata and returns rows in chronological order. `max_pages` is an explicit
|
|
154
|
+
safety budget; an error never passes an incomplete history off as complete.
|
|
155
|
+
Applications that need resumable storage can persist individual page responses
|
|
156
|
+
and control `offset` and `dataset_version` through the named operation tasks.
|
|
157
|
+
|
|
158
|
+
Event streaming is a bounded capture using `max_events` and `max_seconds`.
|
|
159
|
+
The response retains the original completion flag and reason. Cancellation
|
|
160
|
+
finishes the current bounded HTTP request, closes the client, and stops before
|
|
161
|
+
the next history page. MCP sessions are closed after each task.
|
|
162
|
+
|
|
163
|
+
Tasks disable result persistence and caching by default. Opt in only when the
|
|
164
|
+
destination and access controls suit your data licence. Task errors omit raw
|
|
165
|
+
responses, authenticated URLs and credentials. Transient retries are opt-in with
|
|
166
|
+
Prefect's `with_options(retries=..., retry_delay_seconds=...)`.
|
|
167
|
+
|
|
168
|
+
[FXMacroData API reference](https://fxmacrodata.com/documentation/reference?utm_source=github&utm_medium=referral&utm_campaign=prefect&utm_content=docs)
|
|
169
|
+
describes access requirements and supported arguments for each dataset.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
prefect_fxmacrodata/__init__.py,sha256=LLUeTNhnUbaqBbtcEtGWWzN0kaRCujYUrnQJ_17AOYk,591
|
|
2
|
+
prefect_fxmacrodata/credentials.py,sha256=-GXhRmlaCAWRMGTb9SXiSsmj1NRkjZNUBoqfebHUss4,1201
|
|
3
|
+
prefect_fxmacrodata/flows.py,sha256=YoBoUCaJRUqefKACZYO7P85FXVGBrrxRRNw8CjJ5bGw,1543
|
|
4
|
+
prefect_fxmacrodata/models.py,sha256=qR_yHfRrquwhu3a31xVpDSuY_T1Mhqp0q0E3rsGiwaQ,1507
|
|
5
|
+
prefect_fxmacrodata/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
prefect_fxmacrodata/tasks.py,sha256=YiGQfzl_XeXTttDTMAq4Rx9cXOJA6H2Q_JTXhJRAEMk,7234
|
|
7
|
+
prefect_fxmacrodata/tasks.pyi,sha256=uTk-2XMNN9TlubBPAuKh7Oa7D3vGgm7ATP0ilPfuZtw,6205
|
|
8
|
+
prefect_fxmacrodata-0.1.0.dist-info/licenses/LICENSE,sha256=rklK7Fl8aL1eWGubsrozZ7rCNTsN-MATw4M6LQIMv10,1092
|
|
9
|
+
prefect_fxmacrodata-0.1.0.dist-info/METADATA,sha256=aILHtAzR5xMQhI_-Lw4mARuP1K9oMCu_JKmRLMcJBig,7448
|
|
10
|
+
prefect_fxmacrodata-0.1.0.dist-info/WHEEL,sha256=16fc_9Rp854SLcdhaDEjr0u65p0e3FhZxihNgcnCE98,91
|
|
11
|
+
prefect_fxmacrodata-0.1.0.dist-info/entry_points.txt,sha256=eC7MV_AR-BG1wfdU3NTJKb0Fha9W70pjVqPwKT1Y2Ro,64
|
|
12
|
+
prefect_fxmacrodata-0.1.0.dist-info/top_level.txt,sha256=nHUn4WUIwHIoRhR8fwgU4wWSCyKbWQeKvFVHJoTSYaY,20
|
|
13
|
+
prefect_fxmacrodata-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robert Tidball
|
|
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 @@
|
|
|
1
|
+
prefect_fxmacrodata
|