fin-infra 0.1.62__tar.gz → 0.1.64__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.
- {fin_infra-0.1.62 → fin_infra-0.1.64}/PKG-INFO +1 -1
- {fin_infra-0.1.62 → fin_infra-0.1.64}/pyproject.toml +1 -1
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/analytics/spending.py +8 -6
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/banking/__init__.py +7 -4
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/brokerage/__init__.py +4 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/categorization/__init__.py +1 -1
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/categorization/add.py +2 -1
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/categorization/ease.py +1 -1
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/categorization/engine.py +1 -1
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/categorization/llm_layer.py +2 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/compliance/__init__.py +4 -3
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/credit/add.py +3 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/credit/experian/auth.py +3 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/credit/experian/client.py +2 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/credit/experian/provider.py +2 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/goals/add.py +2 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/goals/management.py +3 -3
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/goals/milestones.py +2 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/investments/__init__.py +11 -4
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/investments/ease.py +11 -7
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/investments/models.py +1 -1
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/investments/providers/plaid.py +3 -3
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/investments/providers/snaptrade.py +2 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/markets/__init__.py +8 -3
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/normalization/providers/exchangerate.py +3 -3
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/banking/plaid_client.py +4 -3
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/banking/teller_client.py +13 -7
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/brokerage/alpaca.py +5 -5
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/market/ccxt_crypto.py +5 -3
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/tax/mock.py +3 -3
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/recurring/detectors_llm.py +2 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/recurring/insights.py +2 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/recurring/normalizer.py +2 -1
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/recurring/normalizers.py +2 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/security/encryption.py +2 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/security/pii_patterns.py +1 -1
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/security/token_store.py +3 -1
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/utils/http.py +3 -2
- {fin_infra-0.1.62 → fin_infra-0.1.64}/LICENSE +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/README.md +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/__main__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/analytics/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/analytics/add.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/analytics/cash_flow.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/analytics/ease.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/analytics/models.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/analytics/portfolio.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/analytics/projections.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/analytics/rebalancing.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/analytics/savings.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/analytics/scenarios.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/banking/history.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/banking/utils.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/budgets/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/budgets/add.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/budgets/alerts.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/budgets/ease.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/budgets/models.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/budgets/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/budgets/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/budgets/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/budgets/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/budgets/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/budgets/templates.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/budgets/tracker.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/cashflows/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/cashflows/core.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/categorization/models.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/categorization/rules.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/categorization/taxonomy.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/chat/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/chat/ease.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/chat/planning.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/cli/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/cli/cmds/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/cli/cmds/scaffold_cmds.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/clients/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/clients/base.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/clients/plaid.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/credit/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/credit/experian/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/credit/experian/parser.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/credit/mock.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/crypto/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/crypto/insights.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/documents/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/documents/add.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/documents/analysis.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/documents/ease.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/documents/models.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/documents/ocr.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/documents/storage.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/exceptions.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/goals/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/goals/funding.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/goals/models.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/goals/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/goals/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/goals/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/goals/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/goals/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/insights/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/insights/aggregator.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/insights/models.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/investments/add.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/investments/providers/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/investments/providers/base.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/investments/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/investments/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/investments/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/investments/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/investments/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/models/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/models/accounts.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/models/brokerage.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/models/candle.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/models/credit.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/models/money.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/models/quotes.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/models/tax.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/models/transactions.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/net_worth/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/net_worth/add.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/net_worth/aggregator.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/net_worth/calculator.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/net_worth/ease.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/net_worth/goals.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/net_worth/insights.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/net_worth/models.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/net_worth/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/net_worth/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/net_worth/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/net_worth/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/net_worth/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/normalization/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/normalization/currency_converter.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/normalization/models.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/normalization/providers/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/normalization/providers/static_mappings.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/normalization/symbol_resolver.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/obs/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/obs/classifier.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/banking/base.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/base.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/brokerage/base.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/credit/experian.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/identity/stripe_identity.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/market/alphavantage.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/market/base.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/market/coingecko.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/market/yahoo.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/registry.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/tax/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/tax/irs.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/providers/tax/taxbit.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/py.typed +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/recurring/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/recurring/add.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/recurring/detector.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/recurring/ease.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/recurring/models.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/recurring/summary.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/scaffold/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/scaffold/budgets.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/scaffold/goals.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/security/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/security/add.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/security/audit.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/security/models.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/security/pii_filter.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/settings.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/tax/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/tax/add.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/tax/tlh.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/utils/__init__.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/utils/retry.py +0 -0
- {fin_infra-0.1.62 → fin_infra-0.1.64}/src/fin_infra/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: fin-infra
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.64
|
|
4
4
|
Summary: Financial infrastructure toolkit: banking connections, market data, credit, cashflows, and brokerage integrations
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: finance,banking,plaid,brokerage,markets,credit,tax,cashflow,fintech,infra
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "fin-infra"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.64"
|
|
4
4
|
description = "Financial infrastructure toolkit: banking connections, market data, credit, cashflows, and brokerage integrations"
|
|
5
5
|
authors = ["Ali Khatami <aliikhatami94@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -162,12 +162,13 @@ async def analyze_spending(
|
|
|
162
162
|
)
|
|
163
163
|
|
|
164
164
|
return SpendingInsight(
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
# Convert Decimal to float for model compatibility (intentional for Pydantic field types)
|
|
166
|
+
top_merchants=[(m, float(v)) for m, v in top_merchants],
|
|
167
|
+
category_breakdown={k: float(v) for k, v in category_totals.items()},
|
|
167
168
|
spending_trends=spending_trends,
|
|
168
169
|
anomalies=anomalies,
|
|
169
170
|
period_days=days,
|
|
170
|
-
total_spending=total_spending,
|
|
171
|
+
total_spending=float(total_spending) if total_spending else 0.0,
|
|
171
172
|
)
|
|
172
173
|
|
|
173
174
|
|
|
@@ -359,9 +360,10 @@ async def _detect_spending_anomalies(
|
|
|
359
360
|
anomalies.append(
|
|
360
361
|
SpendingAnomaly(
|
|
361
362
|
category=category,
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
363
|
+
# Convert Decimal to float for model compatibility (intentional for Pydantic field types)
|
|
364
|
+
current_amount=float(current_amount),
|
|
365
|
+
average_amount=float(average_amount),
|
|
366
|
+
deviation_percent=float(deviation_percent),
|
|
365
367
|
severity=severity,
|
|
366
368
|
)
|
|
367
369
|
)
|
|
@@ -45,7 +45,7 @@ from __future__ import annotations
|
|
|
45
45
|
|
|
46
46
|
import os
|
|
47
47
|
from datetime import date
|
|
48
|
-
from typing import TYPE_CHECKING, Optional
|
|
48
|
+
from typing import TYPE_CHECKING, Optional, cast
|
|
49
49
|
|
|
50
50
|
from pydantic import BaseModel, Field
|
|
51
51
|
|
|
@@ -199,7 +199,7 @@ def easy_banking(provider: str = "teller", **config) -> BankingProvider:
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
# Use provider registry to dynamically load and configure provider
|
|
202
|
-
return resolve("banking", provider, **config)
|
|
202
|
+
return cast(BankingProvider, resolve("banking", provider, **config))
|
|
203
203
|
|
|
204
204
|
|
|
205
205
|
def add_banking(
|
|
@@ -397,10 +397,13 @@ def add_banking(
|
|
|
397
397
|
}
|
|
398
398
|
"""
|
|
399
399
|
# Get all transactions from provider
|
|
400
|
+
# Convert date to ISO string format as expected by BankingProvider.transactions()
|
|
401
|
+
start_date_str: str | None = start_date.isoformat() if start_date else None
|
|
402
|
+
end_date_str: str | None = end_date.isoformat() if end_date else None
|
|
400
403
|
transactions = banking.transactions(
|
|
401
404
|
access_token=access_token,
|
|
402
|
-
start_date=
|
|
403
|
-
end_date=
|
|
405
|
+
start_date=start_date_str,
|
|
406
|
+
end_date=end_date_str,
|
|
404
407
|
)
|
|
405
408
|
|
|
406
409
|
# Apply filters
|
|
@@ -212,7 +212,9 @@ def add_brokerage(
|
|
|
212
212
|
|
|
213
213
|
# Initialize provider if string or None
|
|
214
214
|
if isinstance(provider, str):
|
|
215
|
-
|
|
215
|
+
# Cast provider string to Literal type for type checker
|
|
216
|
+
provider_literal: Literal["alpaca"] | None = provider if provider == "alpaca" else None # type: ignore[assignment]
|
|
217
|
+
brokerage_provider = easy_brokerage(provider=provider_literal, mode=mode, **config)
|
|
216
218
|
elif provider is None:
|
|
217
219
|
brokerage_provider = easy_brokerage(mode=mode, **config)
|
|
218
220
|
else:
|
|
@@ -241,7 +243,7 @@ def add_brokerage(
|
|
|
241
243
|
Returns list of positions with symbol, quantity, P/L, etc.
|
|
242
244
|
"""
|
|
243
245
|
try:
|
|
244
|
-
positions = brokerage_provider.positions()
|
|
246
|
+
positions = list(brokerage_provider.positions()) # Convert Iterable to list for len()
|
|
245
247
|
return {"positions": positions, "count": len(positions)}
|
|
246
248
|
except Exception as e:
|
|
247
249
|
raise HTTPException(status_code=500, detail=f"Error fetching positions: {str(e)}")
|
|
@@ -44,7 +44,7 @@ from .taxonomy import Category, CategoryGroup, get_all_categories, get_category_
|
|
|
44
44
|
try:
|
|
45
45
|
from .llm_layer import LLMCategorizer
|
|
46
46
|
except ImportError:
|
|
47
|
-
LLMCategorizer = None
|
|
47
|
+
LLMCategorizer = None # type: ignore[assignment,misc]
|
|
48
48
|
|
|
49
49
|
__all__ = [
|
|
50
50
|
# Easy setup
|
|
@@ -96,7 +96,8 @@ def add_categorization(
|
|
|
96
96
|
start_time = time.perf_counter()
|
|
97
97
|
|
|
98
98
|
try:
|
|
99
|
-
|
|
99
|
+
# Await the async categorize method
|
|
100
|
+
prediction = await engine.categorize(
|
|
100
101
|
merchant_name=request.merchant_name,
|
|
101
102
|
user_id=request.user_id,
|
|
102
103
|
include_alternatives=request.include_alternatives,
|
|
@@ -15,7 +15,7 @@ Expected performance:
|
|
|
15
15
|
|
|
16
16
|
import hashlib
|
|
17
17
|
import logging
|
|
18
|
-
from typing import Optional, List, Tuple
|
|
18
|
+
from typing import Optional, List, Tuple, cast
|
|
19
19
|
from pydantic import BaseModel, Field
|
|
20
20
|
|
|
21
21
|
# ai-infra imports
|
|
@@ -245,7 +245,7 @@ class LLMCategorizer:
|
|
|
245
245
|
f"Must be one of {len(valid_categories)} valid categories."
|
|
246
246
|
)
|
|
247
247
|
|
|
248
|
-
return response
|
|
248
|
+
return cast(CategoryPrediction, response)
|
|
249
249
|
|
|
250
250
|
def _build_system_prompt(self) -> str:
|
|
251
251
|
"""Build system prompt with few-shot examples (reused across all requests)."""
|
|
@@ -21,7 +21,7 @@ from __future__ import annotations
|
|
|
21
21
|
|
|
22
22
|
import logging
|
|
23
23
|
from datetime import datetime
|
|
24
|
-
from typing import Any, Callable, TYPE_CHECKING
|
|
24
|
+
from typing import Any, Callable, TYPE_CHECKING, cast
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
27
|
from fastapi import FastAPI, Request, Response
|
|
@@ -118,7 +118,8 @@ def add_compliance_tracking(
|
|
|
118
118
|
|
|
119
119
|
# Track only GET requests (data access)
|
|
120
120
|
if method != "GET":
|
|
121
|
-
|
|
121
|
+
from starlette.responses import Response as StarletteResponse
|
|
122
|
+
return cast("Response", await call_next(request))
|
|
122
123
|
|
|
123
124
|
# Determine if path is a compliance-tracked endpoint
|
|
124
125
|
event = None
|
|
@@ -148,7 +149,7 @@ def add_compliance_tracking(
|
|
|
148
149
|
if on_event:
|
|
149
150
|
on_event(event, context)
|
|
150
151
|
|
|
151
|
-
return response
|
|
152
|
+
return cast("Response", response)
|
|
152
153
|
|
|
153
154
|
logger.info(
|
|
154
155
|
"Compliance tracking enabled",
|
|
@@ -23,6 +23,7 @@ Example:
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
import logging
|
|
26
|
+
from typing import cast
|
|
26
27
|
|
|
27
28
|
from fastapi import FastAPI, Depends, HTTPException, status
|
|
28
29
|
|
|
@@ -175,7 +176,7 @@ def add_credit(
|
|
|
175
176
|
# Don't fail request if webhook publishing fails
|
|
176
177
|
logger.warning(f"Failed to publish credit.score_changed webhook: {e}")
|
|
177
178
|
|
|
178
|
-
return score
|
|
179
|
+
return cast(CreditScore, score)
|
|
179
180
|
|
|
180
181
|
@router.post("/report", response_model=CreditReport)
|
|
181
182
|
@credit_resource.cache_read(ttl=cache_ttl, suffix="report")
|
|
@@ -219,7 +220,7 @@ def add_credit(
|
|
|
219
220
|
detail="Credit bureau service unavailable",
|
|
220
221
|
)
|
|
221
222
|
|
|
222
|
-
return report
|
|
223
|
+
return cast(CreditReport, report)
|
|
223
224
|
|
|
224
225
|
# Mount router with dual routes (with/without trailing slash)
|
|
225
226
|
app.include_router(router, include_in_schema=True)
|
|
@@ -24,6 +24,7 @@ Example:
|
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
import base64
|
|
27
|
+
from typing import cast
|
|
27
28
|
|
|
28
29
|
import httpx
|
|
29
30
|
from svc_infra.cache import cache_read
|
|
@@ -85,7 +86,7 @@ class ExperianAuthManager:
|
|
|
85
86
|
>>> headers = {"Authorization": f"Bearer {token}"}
|
|
86
87
|
"""
|
|
87
88
|
# Call the cached implementation with client_id for cache key
|
|
88
|
-
return await self._get_token_cached(client_id=self.client_id)
|
|
89
|
+
return cast(str, await self._get_token_cached(client_id=self.client_id))
|
|
89
90
|
|
|
90
91
|
@cache_read(
|
|
91
92
|
key="oauth_token:experian:{client_id}", # Use client_id for uniqueness
|
|
@@ -140,7 +141,7 @@ class ExperianAuthManager:
|
|
|
140
141
|
|
|
141
142
|
# Parse and return token
|
|
142
143
|
data = response.json()
|
|
143
|
-
return data["access_token"]
|
|
144
|
+
return cast(str, data["access_token"])
|
|
144
145
|
|
|
145
146
|
async def invalidate(self) -> None:
|
|
146
147
|
"""Invalidate cached token for THIS client (force refresh on next get_token call).
|
|
@@ -14,7 +14,7 @@ Example:
|
|
|
14
14
|
>>> data = await client.get_credit_score("user123")
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
from typing import Any
|
|
17
|
+
from typing import Any, cast
|
|
18
18
|
|
|
19
19
|
import httpx
|
|
20
20
|
from tenacity import (
|
|
@@ -155,7 +155,7 @@ class ExperianClient:
|
|
|
155
155
|
**kwargs,
|
|
156
156
|
)
|
|
157
157
|
response.raise_for_status()
|
|
158
|
-
return response.json()
|
|
158
|
+
return cast(dict[str, Any], response.json())
|
|
159
159
|
|
|
160
160
|
except httpx.HTTPStatusError as e:
|
|
161
161
|
# Parse error response
|
|
@@ -31,7 +31,7 @@ Example:
|
|
|
31
31
|
|
|
32
32
|
import logging
|
|
33
33
|
from datetime import datetime, timezone
|
|
34
|
-
from typing import Literal
|
|
34
|
+
from typing import Literal, cast
|
|
35
35
|
|
|
36
36
|
from fin_infra.credit.experian.auth import ExperianAuthManager
|
|
37
37
|
from fin_infra.credit.experian.client import ExperianClient
|
|
@@ -360,4 +360,4 @@ class ExperianProvider(CreditProvider):
|
|
|
360
360
|
signature_key=signature_key,
|
|
361
361
|
)
|
|
362
362
|
|
|
363
|
-
return data.get("subscriptionId", "unknown")
|
|
363
|
+
return cast(str, data.get("subscriptionId", "unknown"))
|
|
@@ -29,7 +29,7 @@ add_goals(app)
|
|
|
29
29
|
|
|
30
30
|
import logging
|
|
31
31
|
from datetime import datetime
|
|
32
|
-
from typing import List, Optional
|
|
32
|
+
from typing import Any, List, Optional, cast
|
|
33
33
|
|
|
34
34
|
from fastapi import FastAPI, HTTPException, status, Query, Body
|
|
35
35
|
from pydantic import BaseModel, Field
|
|
@@ -469,7 +469,7 @@ def add_goals(
|
|
|
469
469
|
# Get all milestones from the goal (check_milestones only returns newly reached ones)
|
|
470
470
|
goal = get_goal(goal_id)
|
|
471
471
|
milestones = goal.get("milestones", [])
|
|
472
|
-
return milestones
|
|
472
|
+
return cast(list[dict[Any, Any]], milestones)
|
|
473
473
|
except KeyError:
|
|
474
474
|
raise HTTPException(
|
|
475
475
|
status_code=status.HTTP_404_NOT_FOUND, detail=f"Goal {goal_id} not found"
|
|
@@ -41,7 +41,7 @@ Example:
|
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
43
|
from datetime import datetime
|
|
44
|
-
from typing import Any
|
|
44
|
+
from typing import Any, cast
|
|
45
45
|
|
|
46
46
|
from pydantic import BaseModel, Field
|
|
47
47
|
|
|
@@ -839,7 +839,7 @@ def get_goal(goal_id: str) -> dict[str, Any]:
|
|
|
839
839
|
if goal_id not in _GOALS_STORE:
|
|
840
840
|
raise KeyError(f"Goal not found: {goal_id}")
|
|
841
841
|
|
|
842
|
-
return _GOALS_STORE[goal_id]
|
|
842
|
+
return cast(dict[str, Any], _GOALS_STORE[goal_id])
|
|
843
843
|
|
|
844
844
|
|
|
845
845
|
def update_goal(
|
|
@@ -885,7 +885,7 @@ def update_goal(
|
|
|
885
885
|
|
|
886
886
|
Goal(**goal) # Will raise ValidationError if invalid
|
|
887
887
|
|
|
888
|
-
return goal
|
|
888
|
+
return cast(dict[str, Any], goal)
|
|
889
889
|
|
|
890
890
|
|
|
891
891
|
def delete_goal(goal_id: str) -> None:
|
|
@@ -26,7 +26,7 @@ Example:
|
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
28
|
from datetime import datetime
|
|
29
|
-
from typing import Any
|
|
29
|
+
from typing import Any, cast
|
|
30
30
|
|
|
31
31
|
from fin_infra.goals.management import get_goal, update_goal
|
|
32
32
|
from fin_infra.goals.models import Milestone
|
|
@@ -229,7 +229,7 @@ def get_next_milestone(goal_id: str) -> dict[str, Any] | None:
|
|
|
229
229
|
# Find first unreached milestone (sorted by amount)
|
|
230
230
|
for milestone in milestones:
|
|
231
231
|
if not milestone.get("reached", False):
|
|
232
|
-
return milestone
|
|
232
|
+
return cast(dict[str, Any], milestone)
|
|
233
233
|
|
|
234
234
|
return None
|
|
235
235
|
|
|
@@ -51,7 +51,8 @@ from typing import TYPE_CHECKING, Literal
|
|
|
51
51
|
if TYPE_CHECKING:
|
|
52
52
|
from fastapi import FastAPI
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
# Use the local InvestmentProvider base class (same as providers use)
|
|
55
|
+
from .providers.base import InvestmentProvider
|
|
55
56
|
|
|
56
57
|
# Lazy imports to avoid loading provider SDKs unless needed
|
|
57
58
|
_provider_cache: dict[str, InvestmentProvider] = {}
|
|
@@ -114,6 +115,7 @@ def easy_investments(
|
|
|
114
115
|
return _provider_cache[cache_key]
|
|
115
116
|
|
|
116
117
|
# Lazy import and initialize provider
|
|
118
|
+
instance: InvestmentProvider
|
|
117
119
|
if provider == "plaid":
|
|
118
120
|
from .providers.plaid import PlaidInvestmentProvider
|
|
119
121
|
|
|
@@ -172,14 +174,19 @@ def add_investments(
|
|
|
172
174
|
>>> # GET /investments/transactions
|
|
173
175
|
>>> # etc.
|
|
174
176
|
"""
|
|
175
|
-
from .add import add_investments_impl
|
|
177
|
+
from .add import add_investments as add_investments_impl
|
|
178
|
+
from .providers.base import InvestmentProvider as InvestmentProviderBase
|
|
179
|
+
|
|
180
|
+
# Resolve provider from string Literal to actual InvestmentProvider instance
|
|
181
|
+
resolved_provider: InvestmentProviderBase | None = None
|
|
182
|
+
if provider is not None:
|
|
183
|
+
resolved_provider = easy_investments(provider=provider, **provider_config) # type: ignore[assignment]
|
|
176
184
|
|
|
177
185
|
return add_investments_impl(
|
|
178
186
|
app,
|
|
179
|
-
provider=
|
|
187
|
+
provider=resolved_provider,
|
|
180
188
|
prefix=prefix,
|
|
181
189
|
tags=tags or ["Investments"],
|
|
182
|
-
**provider_config,
|
|
183
190
|
)
|
|
184
191
|
|
|
185
192
|
|
|
@@ -112,19 +112,20 @@ def easy_investments(
|
|
|
112
112
|
- Most other SnapTrade brokerages support trading operations
|
|
113
113
|
"""
|
|
114
114
|
# Auto-detect provider from environment if not specified
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
detected_provider: str | None = provider
|
|
116
|
+
if detected_provider is None:
|
|
117
|
+
detected_provider = _detect_provider()
|
|
117
118
|
|
|
118
119
|
# Validate provider
|
|
119
|
-
if
|
|
120
|
+
if detected_provider not in ("plaid", "snaptrade"):
|
|
120
121
|
raise ValueError(
|
|
121
|
-
f"Invalid provider: {
|
|
122
|
+
f"Invalid provider: {detected_provider}. Must be 'plaid' or 'snaptrade'."
|
|
122
123
|
)
|
|
123
124
|
|
|
124
125
|
# Instantiate provider
|
|
125
|
-
if
|
|
126
|
+
if detected_provider == "plaid":
|
|
126
127
|
return _create_plaid_provider(**config)
|
|
127
|
-
elif
|
|
128
|
+
elif detected_provider == "snaptrade":
|
|
128
129
|
return _create_snaptrade_provider(**config)
|
|
129
130
|
|
|
130
131
|
# Should never reach here
|
|
@@ -233,10 +234,13 @@ def _create_snaptrade_provider(**config: Any) -> InvestmentProvider:
|
|
|
233
234
|
"Example: easy_investments(provider='snaptrade', client_id='...', consumer_key='...')"
|
|
234
235
|
)
|
|
235
236
|
|
|
237
|
+
# Ensure base_url is a string (default is set in SnapTradeInvestmentProvider)
|
|
238
|
+
resolved_base_url: str = base_url if isinstance(base_url, str) else "https://api.snaptrade.com/api/v1"
|
|
239
|
+
|
|
236
240
|
return SnapTradeInvestmentProvider(
|
|
237
241
|
client_id=client_id,
|
|
238
242
|
consumer_key=consumer_key,
|
|
239
|
-
base_url=
|
|
243
|
+
base_url=resolved_base_url,
|
|
240
244
|
)
|
|
241
245
|
|
|
242
246
|
|
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
from datetime import date
|
|
12
12
|
from decimal import Decimal
|
|
13
|
-
from typing import Any, Dict, List, Optional
|
|
13
|
+
from typing import Any, Dict, List, Optional, cast
|
|
14
14
|
|
|
15
15
|
from plaid.api import plaid_api
|
|
16
16
|
from plaid.model.investments_holdings_get_request import InvestmentsHoldingsGetRequest
|
|
@@ -103,7 +103,7 @@ class PlaidInvestmentProvider(InvestmentProvider):
|
|
|
103
103
|
"development": plaid.Environment.Sandbox, # Map development to sandbox
|
|
104
104
|
"production": plaid.Environment.Production,
|
|
105
105
|
}
|
|
106
|
-
return hosts.get(environment.lower(), plaid.Environment.Sandbox)
|
|
106
|
+
return cast(str, hosts.get(environment.lower(), plaid.Environment.Sandbox))
|
|
107
107
|
|
|
108
108
|
async def get_holdings(
|
|
109
109
|
self, access_token: str, account_ids: Optional[List[str]] = None
|
|
@@ -370,7 +370,7 @@ class PlaidInvestmentProvider(InvestmentProvider):
|
|
|
370
370
|
isin=plaid_security.get("isin"),
|
|
371
371
|
sedol=plaid_security.get("sedol"),
|
|
372
372
|
ticker_symbol=plaid_security.get("ticker_symbol"),
|
|
373
|
-
name=plaid_security.get("name"),
|
|
373
|
+
name=plaid_security.get("name") or "Unknown Security",
|
|
374
374
|
type=self._normalize_security_type(plaid_security.get("type", "other")),
|
|
375
375
|
sector=plaid_security.get("sector"),
|
|
376
376
|
close_price=close_price,
|
|
@@ -11,7 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
from datetime import date
|
|
13
13
|
from decimal import Decimal
|
|
14
|
-
from typing import Any, Dict, List, Optional
|
|
14
|
+
from typing import Any, Dict, List, Optional, cast
|
|
15
15
|
|
|
16
16
|
import httpx
|
|
17
17
|
|
|
@@ -393,7 +393,7 @@ class SnapTradeInvestmentProvider(InvestmentProvider):
|
|
|
393
393
|
url = f"{self.base_url}/connections"
|
|
394
394
|
response = await self.client.get(url, headers=auth_headers)
|
|
395
395
|
response.raise_for_status()
|
|
396
|
-
return await response.json()
|
|
396
|
+
return cast(list[dict[str, Any]], await response.json())
|
|
397
397
|
|
|
398
398
|
except httpx.HTTPStatusError as e:
|
|
399
399
|
raise self._transform_error(e)
|
|
@@ -193,7 +193,11 @@ def add_market_data(
|
|
|
193
193
|
if isinstance(provider, MarketDataProvider):
|
|
194
194
|
market = provider
|
|
195
195
|
else:
|
|
196
|
-
|
|
196
|
+
# Cast provider to Literal type for type checker
|
|
197
|
+
provider_literal: Literal["alphavantage", "yahoo"] | None = (
|
|
198
|
+
provider if provider in ("alphavantage", "yahoo", None) else None # type: ignore[assignment]
|
|
199
|
+
)
|
|
200
|
+
market = easy_market(provider=provider_literal, **config)
|
|
197
201
|
|
|
198
202
|
# Create router (public - no auth required)
|
|
199
203
|
router = public_router(prefix=prefix, tags=["Market Data"])
|
|
@@ -223,14 +227,15 @@ def add_market_data(
|
|
|
223
227
|
try:
|
|
224
228
|
candles = market.history(symbol, period=period, interval=interval)
|
|
225
229
|
# Convert to dicts if they're Pydantic models
|
|
226
|
-
candles_list = []
|
|
230
|
+
candles_list: list[dict] = []
|
|
227
231
|
for candle in candles:
|
|
228
232
|
if hasattr(candle, "model_dump"):
|
|
229
233
|
candles_list.append(candle.model_dump())
|
|
230
234
|
elif hasattr(candle, "dict"):
|
|
231
235
|
candles_list.append(candle.dict())
|
|
232
236
|
else:
|
|
233
|
-
|
|
237
|
+
# Cast to dict for type compatibility
|
|
238
|
+
candles_list.append(dict(candle) if hasattr(candle, "__iter__") else {"data": candle})
|
|
234
239
|
return {"candles": candles_list}
|
|
235
240
|
except Exception as e:
|
|
236
241
|
raise HTTPException(status_code=400, detail=str(e))
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
from datetime import date as DateType
|
|
5
|
-
from typing import Optional
|
|
5
|
+
from typing import Optional, cast
|
|
6
6
|
|
|
7
7
|
import httpx
|
|
8
8
|
|
|
@@ -66,10 +66,10 @@ class ExchangeRateClient:
|
|
|
66
66
|
raise ExchangeRateAPIError(
|
|
67
67
|
f"API returned error: {data.get('error-type', 'unknown')}"
|
|
68
68
|
)
|
|
69
|
-
return data["conversion_rates"]
|
|
69
|
+
return cast(dict[str, float], data["conversion_rates"])
|
|
70
70
|
else:
|
|
71
71
|
# Free tier response format
|
|
72
|
-
return data["rates"]
|
|
72
|
+
return cast(dict[str, float], data["rates"])
|
|
73
73
|
|
|
74
74
|
except httpx.HTTPError as e:
|
|
75
75
|
raise ExchangeRateAPIError(f"HTTP error fetching rates: {e}")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from datetime import date, datetime, timedelta
|
|
4
|
+
from typing import Any, cast
|
|
4
5
|
|
|
5
6
|
# Plaid SDK v25+ uses new API structure
|
|
6
7
|
try:
|
|
@@ -96,7 +97,7 @@ class PlaidClient(BankingProvider):
|
|
|
96
97
|
language="en",
|
|
97
98
|
)
|
|
98
99
|
response = self.client.link_token_create(request)
|
|
99
|
-
return response["link_token"]
|
|
100
|
+
return cast(str, response["link_token"])
|
|
100
101
|
|
|
101
102
|
def exchange_public_token(self, public_token: str) -> dict:
|
|
102
103
|
request = ItemPublicTokenExchangeRequest(public_token=public_token)
|
|
@@ -146,8 +147,8 @@ class PlaidClient(BankingProvider):
|
|
|
146
147
|
# Return all balances
|
|
147
148
|
return {"balances": [acc.get("balances", {}) for acc in accounts]}
|
|
148
149
|
|
|
149
|
-
def identity(self, access_token: str) -> dict:
|
|
150
|
+
def identity(self, access_token: str) -> dict[Any, Any]:
|
|
150
151
|
"""Fetch identity/account holder information."""
|
|
151
152
|
request = IdentityGetRequest(access_token=access_token)
|
|
152
153
|
response = self.client.identity_get(request)
|
|
153
|
-
return response.to_dict()
|
|
154
|
+
return cast(dict[Any, Any], response.to_dict())
|
|
@@ -24,7 +24,7 @@ from __future__ import annotations
|
|
|
24
24
|
|
|
25
25
|
import ssl
|
|
26
26
|
import httpx
|
|
27
|
-
from typing import Any
|
|
27
|
+
from typing import Any, cast
|
|
28
28
|
|
|
29
29
|
from ..base import BankingProvider
|
|
30
30
|
|
|
@@ -93,7 +93,13 @@ class TellerClient(BankingProvider):
|
|
|
93
93
|
ssl_context.load_cert_chain(certfile=cert_path, keyfile=key_path)
|
|
94
94
|
client_kwargs["verify"] = ssl_context
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
# Create client with explicit parameters to satisfy type checker
|
|
97
|
+
self.client = httpx.Client(
|
|
98
|
+
base_url=str(client_kwargs["base_url"]),
|
|
99
|
+
timeout=float(client_kwargs["timeout"]), # type: ignore[arg-type]
|
|
100
|
+
headers=client_kwargs["headers"], # type: ignore[arg-type]
|
|
101
|
+
verify=client_kwargs.get("verify", True), # type: ignore[arg-type]
|
|
102
|
+
)
|
|
97
103
|
|
|
98
104
|
def _request(self, method: str, path: str, **kwargs: Any) -> Any:
|
|
99
105
|
"""Make HTTP request to Teller API with error handling.
|
|
@@ -139,7 +145,7 @@ class TellerClient(BankingProvider):
|
|
|
139
145
|
"products": ["accounts", "transactions", "balances", "identity"],
|
|
140
146
|
},
|
|
141
147
|
)
|
|
142
|
-
return response.get("enrollment_id", "")
|
|
148
|
+
return cast(str, response.get("enrollment_id", ""))
|
|
143
149
|
|
|
144
150
|
def exchange_public_token(self, public_token: str) -> dict:
|
|
145
151
|
"""Exchange public token for access token.
|
|
@@ -186,7 +192,7 @@ class TellerClient(BankingProvider):
|
|
|
186
192
|
auth=(access_token, ""),
|
|
187
193
|
)
|
|
188
194
|
response.raise_for_status()
|
|
189
|
-
return response.json()
|
|
195
|
+
return cast(list[dict[Any, Any]], response.json())
|
|
190
196
|
|
|
191
197
|
def transactions(
|
|
192
198
|
self,
|
|
@@ -229,7 +235,7 @@ class TellerClient(BankingProvider):
|
|
|
229
235
|
params=params,
|
|
230
236
|
)
|
|
231
237
|
response.raise_for_status()
|
|
232
|
-
return response.json()
|
|
238
|
+
return cast(list[dict[Any, Any]], response.json())
|
|
233
239
|
|
|
234
240
|
def balances(self, access_token: str, account_id: str | None = None) -> dict:
|
|
235
241
|
"""Fetch current balances.
|
|
@@ -261,7 +267,7 @@ class TellerClient(BankingProvider):
|
|
|
261
267
|
)
|
|
262
268
|
|
|
263
269
|
response.raise_for_status()
|
|
264
|
-
return response.json()
|
|
270
|
+
return cast(dict[Any, Any], response.json())
|
|
265
271
|
|
|
266
272
|
def identity(self, access_token: str) -> dict:
|
|
267
273
|
"""Fetch identity/account holder information.
|
|
@@ -285,7 +291,7 @@ class TellerClient(BankingProvider):
|
|
|
285
291
|
auth=(access_token, ""),
|
|
286
292
|
)
|
|
287
293
|
response.raise_for_status()
|
|
288
|
-
return response.json()
|
|
294
|
+
return cast(dict[Any, Any], response.json())
|
|
289
295
|
|
|
290
296
|
def __del__(self) -> None:
|
|
291
297
|
"""Close HTTP client on cleanup."""
|