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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [prefect.collections]
2
+ prefect_fxmacrodata = prefect_fxmacrodata
@@ -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