Skip to content

Add FXMacroData tool to the Atomic Forge - #297

Open
roberttidball wants to merge 3 commits into
Eigenwise:mainfrom
fxmacrodata:fxmacrodata-tool
Open

roberttidball wants to merge 3 commits into
Eigenwise:mainfrom
fxmacrodata:fxmacrodata-tool

Conversation

@roberttidball

Copy link
Copy Markdown

I run FXMacroData, the macro data API this tool calls.

Summary

Adds atomic-forge/tools/fxmacrodata, a Forge tool for official-source macroeconomic data: release history for an indicator with the official announcement timestamp of each print (CPI, GDP, unemployment, payrolls, policy rates, yields), the latest value of every indicator for a currency, upcoming release dates, the indicator catalogue, FX spot history, CFTC COT positioning, and commodity prices.

One tool covers these through an endpoint literal in the input schema, the same way fia_signals selects its sub-tool, with currency, indicator, quote, start_date, end_date, limit and offset alongside it. The output keeps the API's own row fields in data, puts paging info in pagination, and keeps the rest of the top-level response (source, units, provenance) in metadata, so nothing is dropped or renamed.

USD data, the USD release calendar and the catalogue work without a key, so the tool runs out of the box. Other currencies, FX, COT and commodities need a key, read from FXMacroDataToolConfig.api_key or the FXMACRODATA_API_KEY environment variable and sent as X-API-Key. Like weather, the tool reports problems in an error field instead of raising, because the common failure (asking for EUR data without a key) comes back with a message the agent can act on.

Changes

  • atomic-forge/tools/fxmacrodata/tool/fxmacrodata.py: the tool (requests, sync run plus run_async via asyncio.to_thread)
  • atomic-forge/tools/fxmacrodata/tests/test_fxmacrodata.py: 23 unit tests (request path and query for every endpoint, input validation before any request is made, row/pagination/metadata split, catalogue mapping, key from config and from env, key never in the returned URL, HTTP errors with and without a JSON body, network errors, async)
  • README.md, pyproject.toml, requirements.txt, .coveragerc: same shape as the other tools
  • atomic-forge/README.md, AGENTS.md, docs/guides/tools.md, README.md: one listing line each, after Fía Signals
  • uv.lock: the new workspace member only (additive)

Verification

  • flake8 (repo .flake8) clean on the new directory; black --check with the repo's line length passes apart from the blank line after imports, which follows the existing tools
  • pytest atomic-forge/tools/fxmacrodata/tests: 23 passed
  • uv lock --check passes
  • Live keyless run: USD CPI history, the USD CPI release dates, the catalogue and the latest USD values all returned data; EUR policy rate and EUR/USD returned the API's 401 message in error

@Eigenwise Eigenwise left a comment •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Historical review of 79f02c32.

The macro history/calendar tool has useful distinct coverage, and your affiliation and free/paid split are disclosed. I reproduced four issues offline at 79f02c32 using actual Requests/Pydantic and dummy keys:

  • Credential forwarding, tool/fxmacrodata.py:174: a 302 from the HTTPS API to an unrelated HTTP host carries X-API-Key to that host. Keep authenticated requests on the intended HTTPS origin.
  • Secret in returned errors, lines106/173/190: a key with leading whitespace triggers InvalidHeader, and stringifying that exception returns the complete key. Error messages must exclude credential values.
  • Malformed responses, lines192-200: HTTP200 with {detail: 'upstream failure'} or invalid data values becomes error=None, data=[]. Other malformed bodies raise output ValidationError outside the advertised error-return contract. Validate the response shape and return a useful error.
  • Date inputs, lines142-148: impossible dates and reversed ranges reach the request; an empty start date silently disappears. Use real date validation and range checks.

The author suite passes: 23 tests. The independent failure probes still reproduce these cases. Canonical Black127 and repository Flake8 also pass, so import spacing isn't a blocker.

On this historical head, native production complexity was CC14 for build_request and CC8 for run. The earlier score-only refactor request is withdrawn.

Please remove the UTM referral parameters from the README links, including the subscription link, and use neutral service URLs. No live API calls, real credentials or purchases were used in this review.

The later offline review of 184709f3 confirms redirect/header/error/date repairs and neutral links. At 0ed527b4, 63 author tests and 132 independent observations also confirm pagination, config masking and ordinary nested-response redaction repairs. The findings above describe the historical head; the current review instead identifies redaction collisions/validation bypass and malformed-port error handling.

@roberttidball

Copy link
Copy Markdown
Author

Thanks for the careful review and the reproductions. All four are fixed in 184709f:

  • Credential forwarding: requests go out with allow_redirects=False and a 3xx comes back as an error, and a non-HTTPS base_url is refused before any request. Tests: test_redirect_to_other_http_host_does_not_forward_api_key (real Requests, 302 to an HTTP host; only the HTTPS URL is ever sent) and test_non_https_base_url_is_refused.
  • Secret in errors: the key is stripped and checked for header-safe characters up front, request exceptions are reported by type name only, and every error passes through a redaction step. Tests: test_key_with_leading_whitespace_is_stripped_and_never_echoed, test_key_that_cannot_be_a_header_is_reported_without_its_value, test_request_exception_text_never_reaches_the_output, test_upstream_detail_echoing_the_key_is_redacted.
  • Malformed responses: row endpoints require an object whose data is a list of objects and whose pagination is an object; the catalogue requires a non-empty mapping of objects. Anything else, including 200 with {detail: ...} or a non-JSON body, returns error (with the detail when present) instead of data=[] or a ValidationError. Tests: test_malformed_200_responses_return_an_error (9 cases), test_non_json_200_response_returns_an_error.
  • Dates: dates are parsed with date.fromisoformat behind a strict YYYY-MM-DD pattern, an empty string is an error rather than dropped, and start_date after end_date is rejected. Test: test_impossible_empty_and_reversed_dates_are_rejected.

Also from your notes: request building and response parsing are split into small functions (radon max CC is now 5, build_request and run are 1 and 2), the new Any annotations are replaced with Pydantic's JsonValue, abbreviated names are spelled out, and the README links no longer carry referral parameters. 44 tests pass with 100% line coverage, and Flake8 and Black (line length 127) are clean.

@Eigenwise Eigenwise left a comment •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Historical review of 184709f3.

Rechecked 184709f3. The redirect, unsafe-header, returned-error and date repairs pass the offline probes. Twenty redirect variants send only the intended HTTPS request, invalid dates send zero requests, and the referral parameters are gone. All 24 production functions now have genuine measured CRAP scores below 6.

Two remaining blockers in tool/fxmacrodata.py:

  • Malformed pagination, lines209-212: HTTP200 {data: [], pagination: []} still returns error=None, pagination=None. false, 0 and an empty string also pass. Validate a present pagination value before or {} normalizes it.
  • Credential exposure, lines114-117 and278-282: the complete dummy key appears in the actual config repr/str. A successful response with {data: [], detail: 'echo ' + key} also copies it into metadata and output repr. Hide credentials in config representations and protect successful output paths as well as error strings.

A lower-priority configuration concern: malformed HTTPS base URLs such as https:// raise InvalidURL/ValueError outside the advertised error-return path, before any request. Validate config early or make that exception contract explicit.

The author suite passes all 44 tests. Seventy-seven independent offline Requests/Pydantic observations verify the repairs and reproduce the remaining cases. Black127 and canonical Flake8 pass. Keep the useful assertions. No live API, real credentials, vendor installation or purchases were used.

The later offline review of 0ed527b4 confirms pagination validation, config masking and ordinary nested-response redaction repairs. It still reproduces redaction collisions/validation bypass and the malformed-port error path. The 44-test result and 77 observations above belong to 184709f3.

@roberttidball

Copy link
Copy Markdown
Author

Thanks for rechecking. All three are addressed in 0ed527b:

  • Malformed pagination: a present pagination must be an object whose documented fields have their documented types (counts are int and not bool, next_offset int or null, flags bool); only absent or null counts as no pagination, and nothing is normalised before the check. Tests: test_present_malformed_pagination_is_an_error ([], false, 0, "" and five wrong-type objects) and test_null_pagination_is_treated_as_absent.
  • Credential exposure: api_key is now a SecretStr, so the config repr, str and JSON dump show it masked, and every successful response is redacted recursively (values and keys) before rows, metadata or pagination are built, as well as the error strings. Tests: test_config_repr_and_str_hide_the_key, test_key_echoed_in_a_successful_response_is_redacted (your {data: [], detail: 'echo ' + key} case plus a row and a field name), test_key_echoed_in_a_catalogue_response_is_redacted.
  • Base URL: base_url is validated when the config is built (https scheme and a non-empty host, trailing slash stripped), so https:// and similar values raise a ValidationError up front and can no longer reach a request. The earlier runtime HTTPS check moved there, so test_non_https_base_url_is_refused became test_invalid_base_url_is_rejected_by_config (six cases) with test_base_url_is_normalised_by_config.

63 tests pass with 100% line coverage, every function is at or below complexity 5, and Black (127) and Flake8 are clean.

@Eigenwise Eigenwise left a comment •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rechecked 0ed527b4 offline: all 63 author tests pass, and 132 independent Requests/Pydantic observations confirm the pagination, config masking and ordinary nested-response redaction repairs. All 27 production functions have genuine measured native CRAP scores below 6.

Two remaining findings in tool/fxmacrodata.py:

  • Redaction loses data and bypasses validation, lines278-279 and313: a successful row with two keys that sanitize to [redacted] silently loses one value. Catalogue, metadata and pagination collisions reproduce too. With the dummy key total_count, pagination.total_count=True is renamed before validation and returns error=None. Validate the original structure first and preserve colliding values when sanitizing output.
  • Malformed port escapes the error-output path, lines140-143 and312: https://api.fxmacrodata.com:bad/v1 passes config validation, then run raises Requests.InvalidURL before any send. Validate the port at config creation or handle that failure consistently.

Production coverage is 167/167 statements and 32/32 measured branches, with 13 unchanged main-example exclusions. Current-head style was not verified; keep the useful assertions. No live API, real credentials, vendor installation or source changes.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants