fin-infra 0.1.69__py3-none-any.whl → 0.4.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.
Files changed (131) hide show
  1. fin_infra/__init__.py +53 -3
  2. fin_infra/analytics/__init__.py +13 -2
  3. fin_infra/analytics/add.py +24 -24
  4. fin_infra/analytics/cash_flow.py +3 -3
  5. fin_infra/analytics/ease.py +19 -20
  6. fin_infra/analytics/models.py +5 -5
  7. fin_infra/analytics/portfolio.py +18 -18
  8. fin_infra/analytics/projections.py +1 -3
  9. fin_infra/analytics/spending.py +4 -5
  10. fin_infra/banking/__init__.py +27 -28
  11. fin_infra/banking/history.py +12 -13
  12. fin_infra/banking/utils.py +27 -26
  13. fin_infra/brokerage/__init__.py +29 -31
  14. fin_infra/budgets/__init__.py +3 -3
  15. fin_infra/budgets/add.py +16 -17
  16. fin_infra/budgets/alerts.py +4 -4
  17. fin_infra/budgets/ease.py +1 -2
  18. fin_infra/budgets/models.py +1 -2
  19. fin_infra/budgets/templates.py +4 -4
  20. fin_infra/budgets/tracker.py +4 -4
  21. fin_infra/cashflows/__init__.py +3 -3
  22. fin_infra/cashflows/core.py +1 -1
  23. fin_infra/categorization/__init__.py +1 -1
  24. fin_infra/categorization/add.py +2 -3
  25. fin_infra/categorization/ease.py +3 -3
  26. fin_infra/categorization/engine.py +18 -15
  27. fin_infra/categorization/llm_layer.py +13 -10
  28. fin_infra/categorization/models.py +3 -4
  29. fin_infra/categorization/rules.py +2 -4
  30. fin_infra/categorization/taxonomy.py +2 -2
  31. fin_infra/chat/__init__.py +6 -6
  32. fin_infra/chat/planning.py +1 -2
  33. fin_infra/cli/cmds/scaffold_cmds.py +16 -17
  34. fin_infra/clients/__init__.py +23 -1
  35. fin_infra/clients/base.py +1 -1
  36. fin_infra/clients/plaid.py +2 -2
  37. fin_infra/compliance/__init__.py +5 -4
  38. fin_infra/credit/add.py +6 -7
  39. fin_infra/credit/experian/auth.py +2 -2
  40. fin_infra/credit/experian/client.py +1 -1
  41. fin_infra/credit/experian/parser.py +5 -5
  42. fin_infra/credit/experian/provider.py +4 -4
  43. fin_infra/crypto/__init__.py +9 -11
  44. fin_infra/crypto/insights.py +4 -3
  45. fin_infra/documents/add.py +6 -8
  46. fin_infra/documents/analysis.py +9 -9
  47. fin_infra/documents/ease.py +14 -14
  48. fin_infra/documents/models.py +5 -6
  49. fin_infra/documents/ocr.py +7 -7
  50. fin_infra/documents/storage.py +21 -13
  51. fin_infra/exceptions.py +0 -1
  52. fin_infra/goals/__init__.py +8 -8
  53. fin_infra/goals/add.py +36 -36
  54. fin_infra/goals/funding.py +4 -6
  55. fin_infra/goals/management.py +5 -6
  56. fin_infra/goals/milestones.py +7 -8
  57. fin_infra/goals/models.py +9 -13
  58. fin_infra/insights/__init__.py +6 -3
  59. fin_infra/insights/aggregator.py +1 -1
  60. fin_infra/investments/__init__.py +3 -3
  61. fin_infra/investments/add.py +23 -23
  62. fin_infra/investments/ease.py +2 -2
  63. fin_infra/investments/models.py +27 -29
  64. fin_infra/investments/providers/base.py +12 -13
  65. fin_infra/investments/providers/plaid.py +52 -26
  66. fin_infra/investments/providers/snaptrade.py +19 -19
  67. fin_infra/investments/scaffold_templates/README.md +17 -17
  68. fin_infra/markets/__init__.py +7 -5
  69. fin_infra/models/__init__.py +10 -10
  70. fin_infra/models/accounts.py +4 -5
  71. fin_infra/models/brokerage.py +2 -1
  72. fin_infra/models/candle.py +1 -0
  73. fin_infra/models/money.py +1 -0
  74. fin_infra/models/quotes.py +4 -3
  75. fin_infra/models/tax.py +2 -1
  76. fin_infra/models/transactions.py +4 -5
  77. fin_infra/net_worth/__init__.py +8 -1
  78. fin_infra/net_worth/aggregator.py +5 -3
  79. fin_infra/net_worth/calculator.py +1 -1
  80. fin_infra/net_worth/insights.py +7 -8
  81. fin_infra/normalization/__init__.py +4 -4
  82. fin_infra/normalization/currency_converter.py +7 -8
  83. fin_infra/normalization/models.py +9 -10
  84. fin_infra/normalization/providers/exchangerate.py +5 -5
  85. fin_infra/normalization/providers/static_mappings.py +1 -1
  86. fin_infra/normalization/symbol_resolver.py +3 -4
  87. fin_infra/obs/classifier.py +3 -3
  88. fin_infra/providers/banking/plaid_client.py +5 -5
  89. fin_infra/providers/banking/teller_client.py +7 -6
  90. fin_infra/providers/base.py +27 -2
  91. fin_infra/providers/brokerage/alpaca.py +4 -4
  92. fin_infra/providers/market/alphavantage.py +6 -11
  93. fin_infra/providers/market/ccxt_crypto.py +19 -3
  94. fin_infra/providers/market/coingecko.py +5 -6
  95. fin_infra/providers/market/yahoo.py +23 -8
  96. fin_infra/providers/tax/__init__.py +1 -1
  97. fin_infra/providers/tax/irs.py +1 -1
  98. fin_infra/providers/tax/mock.py +5 -5
  99. fin_infra/providers/tax/taxbit.py +1 -1
  100. fin_infra/recurring/__init__.py +6 -6
  101. fin_infra/recurring/add.py +6 -5
  102. fin_infra/recurring/detector.py +7 -7
  103. fin_infra/recurring/detectors_llm.py +10 -10
  104. fin_infra/recurring/ease.py +6 -8
  105. fin_infra/recurring/insights.py +25 -24
  106. fin_infra/recurring/normalizer.py +7 -7
  107. fin_infra/recurring/normalizers.py +31 -30
  108. fin_infra/recurring/summary.py +13 -15
  109. fin_infra/scaffold/budgets.py +9 -9
  110. fin_infra/scaffold/goals.py +9 -9
  111. fin_infra/security/__init__.py +8 -8
  112. fin_infra/security/add.py +1 -2
  113. fin_infra/security/audit.py +6 -7
  114. fin_infra/security/encryption.py +6 -6
  115. fin_infra/security/models.py +7 -7
  116. fin_infra/security/pii_filter.py +16 -16
  117. fin_infra/security/token_store.py +2 -3
  118. fin_infra/settings.py +2 -1
  119. fin_infra/tax/__init__.py +1 -1
  120. fin_infra/tax/add.py +5 -4
  121. fin_infra/tax/tlh.py +10 -10
  122. fin_infra/utils/__init__.py +15 -1
  123. fin_infra/utils/deprecation.py +161 -0
  124. fin_infra/utils/http.py +4 -3
  125. fin_infra/utils/retry.py +2 -1
  126. {fin_infra-0.1.69.dist-info → fin_infra-0.4.0.dist-info}/METADATA +30 -16
  127. fin_infra-0.4.0.dist-info/RECORD +181 -0
  128. fin_infra-0.1.69.dist-info/RECORD +0 -180
  129. {fin_infra-0.1.69.dist-info → fin_infra-0.4.0.dist-info}/LICENSE +0 -0
  130. {fin_infra-0.1.69.dist-info → fin_infra-0.4.0.dist-info}/WHEEL +0 -0
  131. {fin_infra-0.1.69.dist-info → fin_infra-0.4.0.dist-info}/entry_points.txt +0 -0
@@ -9,21 +9,21 @@ import re
9
9
  from typing import Any
10
10
 
11
11
  from .pii_patterns import (
12
- ACCOUNT_PATTERN,
13
12
  ACCOUNT_CONTEXT,
13
+ ACCOUNT_PATTERN,
14
14
  CARD_PATTERN,
15
- CVV_PATTERN,
16
15
  CVV_CONTEXT,
16
+ CVV_PATTERN,
17
17
  EIN_PATTERN,
18
18
  EMAIL_PATTERN,
19
19
  PHONE_PATTERN,
20
- ROUTING_PATTERN,
21
20
  ROUTING_CONTEXT,
22
- SSN_PATTERN,
23
- SSN_NO_DASH,
21
+ ROUTING_PATTERN,
24
22
  SSN_CONTEXT,
25
- luhn_checksum,
23
+ SSN_NO_DASH,
24
+ SSN_PATTERN,
26
25
  is_valid_routing_number,
26
+ luhn_checksum,
27
27
  )
28
28
 
29
29
 
@@ -116,8 +116,8 @@ class FinancialPIIFilter(logging.Filter):
116
116
  Mask Social Security Numbers.
117
117
 
118
118
  Examples:
119
- 123-45-6789 ***-**-6789
120
- 123456789 *****6789 (with context)
119
+ 123-45-6789 -> ***-**-6789
120
+ 123456789 -> *****6789 (with context)
121
121
  """
122
122
  # With dashes (high confidence)
123
123
  text = SSN_PATTERN.sub(lambda m: f"***-**-{m.group()[-4:]}", text)
@@ -145,7 +145,7 @@ class FinancialPIIFilter(logging.Filter):
145
145
  Mask Employer Identification Numbers.
146
146
 
147
147
  Example:
148
- 12-3456789 **-****789
148
+ 12-3456789 -> **-****789
149
149
  """
150
150
  return EIN_PATTERN.sub(lambda m: f"**-****{m.group()[-3:]}", text)
151
151
 
@@ -154,8 +154,8 @@ class FinancialPIIFilter(logging.Filter):
154
154
  Mask credit card numbers using Luhn validation.
155
155
 
156
156
  Examples:
157
- 4111 1111 1111 1111 **** **** **** 1111
158
- 4111111111111111 ************1111
157
+ 4111 1111 1111 1111 -> **** **** **** 1111
158
+ 4111111111111111 -> ************1111
159
159
  """
160
160
 
161
161
  def mask_card_match(match):
@@ -182,7 +182,7 @@ class FinancialPIIFilter(logging.Filter):
182
182
  Mask ABA routing numbers with checksum validation.
183
183
 
184
184
  Example:
185
- 021000021 ******021
185
+ 021000021 -> ******021
186
186
  """
187
187
 
188
188
  def mask_routing_match(match):
@@ -207,7 +207,7 @@ class FinancialPIIFilter(logging.Filter):
207
207
  Mask bank account numbers.
208
208
 
209
209
  Example:
210
- 1234567890 ******7890
210
+ 1234567890 -> ******7890
211
211
  """
212
212
 
213
213
  def mask_account_match(match):
@@ -231,7 +231,7 @@ class FinancialPIIFilter(logging.Filter):
231
231
  Mask CVV codes (context-dependent).
232
232
 
233
233
  Example:
234
- CVV: 123 CVV: ***
234
+ CVV: 123 -> CVV: ***
235
235
  """
236
236
 
237
237
  def mask_cvv_match(match):
@@ -255,7 +255,7 @@ class FinancialPIIFilter(logging.Filter):
255
255
  Mask email addresses.
256
256
 
257
257
  Example:
258
- user@example.com u***@example.com
258
+ user@example.com -> u***@example.com
259
259
  """
260
260
 
261
261
  def mask_email_match(match):
@@ -272,7 +272,7 @@ class FinancialPIIFilter(logging.Filter):
272
272
  Mask phone numbers.
273
273
 
274
274
  Example:
275
- (555) 123-4567 (***) ***-4567
275
+ (555) 123-4567 -> (***) ***-4567
276
276
  """
277
277
 
278
278
  def mask_phone_match(match):
@@ -5,7 +5,6 @@ Database operations for encrypted provider tokens.
5
5
  """
6
6
 
7
7
  from datetime import datetime
8
- from typing import Optional
9
8
 
10
9
  from sqlalchemy import Column, DateTime, String, Text, select, update
11
10
  from sqlalchemy.dialects.postgresql import UUID
@@ -48,8 +47,8 @@ async def store_provider_token(
48
47
  provider: str,
49
48
  token: str,
50
49
  encryption: ProviderTokenEncryption,
51
- expires_at: Optional[datetime] = None,
52
- key_id: Optional[str] = None,
50
+ expires_at: datetime | None = None,
51
+ key_id: str | None = None,
53
52
  ) -> ProviderTokenMetadata:
54
53
  """
55
54
  Store encrypted provider token in database.
fin_infra/settings.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from functools import lru_cache
4
+
4
5
  from pydantic import Field
5
6
  from pydantic_settings import BaseSettings, SettingsConfigDict
6
7
 
@@ -33,5 +34,5 @@ class Settings(BaseSettings):
33
34
 
34
35
 
35
36
  @lru_cache
36
- def get_settings() -> "Settings":
37
+ def get_settings() -> Settings:
37
38
  return Settings()
fin_infra/tax/__init__.py CHANGED
@@ -33,7 +33,7 @@ Example:
33
33
  import os
34
34
 
35
35
  from fin_infra.providers.base import TaxProvider
36
- from fin_infra.providers.tax import MockTaxProvider, IRSProvider, TaxBitProvider
36
+ from fin_infra.providers.tax import IRSProvider, MockTaxProvider, TaxBitProvider
37
37
  from fin_infra.tax.add import add_tax_data
38
38
  from fin_infra.tax.tlh import (
39
39
  TLHOpportunity,
fin_infra/tax/add.py CHANGED
@@ -17,7 +17,8 @@ Example:
17
17
  """
18
18
 
19
19
  from decimal import Decimal
20
- from fastapi import FastAPI, Query, Body
20
+
21
+ from fastapi import Body, FastAPI, Query
21
22
  from pydantic import BaseModel
22
23
 
23
24
  from fin_infra.providers.base import TaxProvider
@@ -89,8 +90,8 @@ def add_tax_data(
89
90
  >>> # POST /tax/tax-liability
90
91
  """
91
92
  # Use svc-infra user_router for authentication (tax data is user-specific and sensitive)
92
- from svc_infra.api.fastapi.dual.protected import user_router
93
93
  from svc_infra.api.fastapi.docs.scoped import add_prefixed_docs
94
+ from svc_infra.api.fastapi.dual.protected import user_router
94
95
 
95
96
  # Initialize provider
96
97
  if provider is None:
@@ -278,7 +279,7 @@ def add_tax_data(
278
279
  that can be sold to offset capital gains. Suggests replacement securities
279
280
  to maintain market exposure without triggering wash sale rules.
280
281
 
281
- ⚠️ **DISCLAIMER**: Not a substitute for professional tax or financial advice.
282
+ [!] **DISCLAIMER**: Not a substitute for professional tax or financial advice.
282
283
  Consult a certified tax professional before executing TLH trades.
283
284
 
284
285
  Args:
@@ -343,7 +344,7 @@ def add_tax_data(
343
344
  Projects the outcome of executing provided TLH opportunities, including
344
345
  total tax savings, portfolio impact, and risk assessment.
345
346
 
346
- ⚠️ **DISCLAIMER**: Not a substitute for professional tax or financial advice.
347
+ [!] **DISCLAIMER**: Not a substitute for professional tax or financial advice.
347
348
  Consult a certified tax professional before executing TLH trades.
348
349
 
349
350
  Args:
fin_infra/tax/tlh.py CHANGED
@@ -55,7 +55,7 @@ Cost Considerations:
55
55
 
56
56
  from __future__ import annotations
57
57
 
58
- from datetime import datetime, timezone
58
+ from datetime import UTC, datetime
59
59
  from decimal import Decimal
60
60
  from typing import TYPE_CHECKING
61
61
 
@@ -412,9 +412,9 @@ def _assess_wash_sale_risk(symbol: str, last_purchase_date: datetime | None) ->
412
412
  return "none"
413
413
 
414
414
  # Calculate days since last purchase
415
- now = datetime.now(timezone.utc)
415
+ now = datetime.now(UTC)
416
416
  if last_purchase_date.tzinfo is None:
417
- last_purchase_date = last_purchase_date.replace(tzinfo=timezone.utc)
417
+ last_purchase_date = last_purchase_date.replace(tzinfo=UTC)
418
418
 
419
419
  days_since = (now - last_purchase_date).days
420
420
 
@@ -456,7 +456,7 @@ def _suggest_replacement(symbol: str, asset_class: str) -> str:
456
456
  # Simple rule-based suggestions (v1)
457
457
  # TODO: Replace with ai-infra LLM for intelligent suggestions
458
458
  replacements = {
459
- # Tech stocks sector ETFs
459
+ # Tech stocks -> sector ETFs
460
460
  "AAPL": "VGT", # Tech ETF
461
461
  "MSFT": "VGT",
462
462
  "GOOGL": "VGT",
@@ -464,19 +464,19 @@ def _suggest_replacement(symbol: str, asset_class: str) -> str:
464
464
  "META": "VGT",
465
465
  "NVDA": "SOXX", # Semiconductor ETF
466
466
  "AMD": "SOXX",
467
- # Auto/EV sector alternatives
467
+ # Auto/EV -> sector alternatives
468
468
  "TSLA": "ARKK", # Innovation ETF
469
469
  "F": "XLI", # Industrials ETF
470
470
  "GM": "XLI",
471
- # Finance sector ETF
471
+ # Finance -> sector ETF
472
472
  "JPM": "XLF", # Financials ETF
473
473
  "BAC": "XLF",
474
474
  "GS": "XLF",
475
- # Healthcare sector ETF
475
+ # Healthcare -> sector ETF
476
476
  "JNJ": "XLV", # Healthcare ETF
477
477
  "PFE": "XLV",
478
478
  "MRNA": "XBI", # Biotech ETF
479
- # Crypto broad exposure
479
+ # Crypto -> broad exposure
480
480
  "BTC": "ETH", # Ethereum (different asset)
481
481
  "ETH": "BTC", # Bitcoin (different asset)
482
482
  }
@@ -506,8 +506,8 @@ def _generate_tlh_recommendations(
506
506
  recommendations = []
507
507
 
508
508
  # Timing recommendations
509
- now = datetime.now(timezone.utc)
510
- days_until_year_end = (datetime(now.year, 12, 31, tzinfo=timezone.utc) - now).days
509
+ now = datetime.now(UTC)
510
+ days_until_year_end = (datetime(now.year, 12, 31, tzinfo=UTC) - now).days
511
511
 
512
512
  if days_until_year_end < 30:
513
513
  recommendations.append(
@@ -10,8 +10,22 @@ and should be imported from there:
10
10
 
11
11
  For async retry with exponential backoff:
12
12
  from fin_infra.utils.retry import retry_async, RetryError
13
+
14
+ For deprecation utilities:
15
+ from fin_infra.utils.deprecation import deprecated, deprecated_parameter
13
16
  """
14
17
 
18
+ from fin_infra.utils.deprecation import (
19
+ DeprecatedWarning,
20
+ deprecated,
21
+ deprecated_parameter,
22
+ )
15
23
  from fin_infra.utils.retry import RetryError, retry_async
16
24
 
17
- __all__ = ["RetryError", "retry_async"]
25
+ __all__ = [
26
+ "RetryError",
27
+ "retry_async",
28
+ "deprecated",
29
+ "deprecated_parameter",
30
+ "DeprecatedWarning",
31
+ ]
@@ -0,0 +1,161 @@
1
+ """Deprecation utilities for fin-infra.
2
+
3
+ This module provides decorators and functions for marking features as deprecated,
4
+ following the deprecation policy defined in DEPRECATION.md.
5
+
6
+ Example:
7
+ >>> from fin_infra.utils.deprecation import deprecated
8
+ >>>
9
+ >>> @deprecated(
10
+ ... version="1.2.0",
11
+ ... reason="Use new_function() instead",
12
+ ... removal_version="1.4.0"
13
+ ... )
14
+ ... def old_function():
15
+ ... pass
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import functools
21
+ import warnings
22
+ from collections.abc import Callable
23
+ from typing import Any, TypeVar
24
+
25
+ __all__ = [
26
+ "deprecated",
27
+ "deprecated_parameter",
28
+ "DeprecatedWarning",
29
+ ]
30
+
31
+ F = TypeVar("F", bound=Callable[..., Any])
32
+
33
+
34
+ class DeprecatedWarning(DeprecationWarning):
35
+ """Custom deprecation warning for fin-infra.
36
+
37
+ This warning is used to distinguish fin-infra deprecations from
38
+ Python's built-in DeprecationWarning.
39
+ """
40
+
41
+ pass
42
+
43
+
44
+ def deprecated(
45
+ version: str,
46
+ reason: str,
47
+ removal_version: str | None = None,
48
+ *,
49
+ stacklevel: int = 2,
50
+ ) -> Callable[[F], F]:
51
+ """Decorator to mark a function or class as deprecated.
52
+
53
+ The decorated function/class will emit a DeprecationWarning when called/instantiated.
54
+
55
+ Args:
56
+ version: The version in which the feature was deprecated (e.g., "1.2.0").
57
+ reason: The reason for deprecation and recommended alternative.
58
+ removal_version: The version in which the feature will be removed (e.g., "1.4.0").
59
+ stacklevel: Stack level for the warning (default 2 for immediate caller).
60
+
61
+ Returns:
62
+ A decorator that wraps the function/class with deprecation warning.
63
+
64
+ Example:
65
+ >>> @deprecated(
66
+ ... version="1.2.0",
67
+ ... reason="Use new_function() instead",
68
+ ... removal_version="1.4.0"
69
+ ... )
70
+ ... def old_function():
71
+ ... return "result"
72
+ >>>
73
+ >>> old_function() # Emits DeprecationWarning
74
+ 'result'
75
+ """
76
+
77
+ def decorator(func: F) -> F:
78
+ # Build the warning message
79
+ name = getattr(func, "__qualname__", getattr(func, "__name__", str(func)))
80
+ message = f"{name} is deprecated since version {version}."
81
+
82
+ if removal_version:
83
+ message += f" It will be removed in version {removal_version}."
84
+
85
+ message += f" {reason}"
86
+
87
+ if isinstance(func, type):
88
+ # Handle class deprecation
89
+ original_init = func.__init__ # type: ignore[misc]
90
+
91
+ @functools.wraps(original_init)
92
+ def new_init(self: Any, *args: Any, **kwargs: Any) -> None:
93
+ warnings.warn(message, DeprecatedWarning, stacklevel=stacklevel)
94
+ original_init(self, *args, **kwargs)
95
+
96
+ func.__init__ = new_init # type: ignore[misc]
97
+
98
+ # Add deprecation info to docstring
99
+ if func.__doc__:
100
+ func.__doc__ = f".. deprecated:: {version}\n {reason}\n\n{func.__doc__}"
101
+ else:
102
+ func.__doc__ = f".. deprecated:: {version}\n {reason}"
103
+
104
+ return func # type: ignore[return-value]
105
+ else:
106
+ # Handle function deprecation
107
+ @functools.wraps(func)
108
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
109
+ warnings.warn(message, DeprecatedWarning, stacklevel=stacklevel)
110
+ return func(*args, **kwargs)
111
+
112
+ # Add deprecation info to docstring
113
+ if wrapper.__doc__:
114
+ wrapper.__doc__ = f".. deprecated:: {version}\n {reason}\n\n{wrapper.__doc__}"
115
+ else:
116
+ wrapper.__doc__ = f".. deprecated:: {version}\n {reason}"
117
+
118
+ return wrapper # type: ignore[return-value]
119
+
120
+ return decorator
121
+
122
+
123
+ def deprecated_parameter(
124
+ name: str,
125
+ version: str,
126
+ reason: str,
127
+ removal_version: str | None = None,
128
+ *,
129
+ stacklevel: int = 2,
130
+ ) -> None:
131
+ """Emit a deprecation warning for a deprecated parameter.
132
+
133
+ Call this function when a deprecated parameter is used. This should be
134
+ called at the beginning of a function that has deprecated parameters.
135
+
136
+ Args:
137
+ name: The name of the deprecated parameter.
138
+ version: The version in which the parameter was deprecated.
139
+ reason: The reason for deprecation and recommended alternative.
140
+ removal_version: The version in which the parameter will be removed.
141
+ stacklevel: Stack level for the warning (default 2 for immediate caller).
142
+
143
+ Example:
144
+ >>> def my_function(new_param: str, old_param: str | None = None):
145
+ ... if old_param is not None:
146
+ ... deprecated_parameter(
147
+ ... name="old_param",
148
+ ... version="1.2.0",
149
+ ... reason="Use new_param instead"
150
+ ... )
151
+ ... new_param = old_param
152
+ ... return new_param
153
+ """
154
+ message = f"Parameter '{name}' is deprecated since version {version}."
155
+
156
+ if removal_version:
157
+ message += f" It will be removed in version {removal_version}."
158
+
159
+ message += f" {reason}"
160
+
161
+ warnings.warn(message, DeprecatedWarning, stacklevel=stacklevel + 1)
fin_infra/utils/http.py CHANGED
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- import httpx
4
3
  from typing import Any, cast
5
- from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
4
+
5
+ import httpx
6
+ from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_exponential
6
7
 
7
8
  _DEFAULT_TIMEOUT = httpx.Timeout(20.0)
8
9
 
@@ -17,4 +18,4 @@ async def aget_json(url: str, **kwargs) -> dict[Any, Any]:
17
18
  async with httpx.AsyncClient(timeout=_DEFAULT_TIMEOUT) as client:
18
19
  r = await client.get(url, **kwargs)
19
20
  r.raise_for_status()
20
- return cast(dict[Any, Any], r.json())
21
+ return cast("dict[Any, Any]", r.json())
fin_infra/utils/retry.py CHANGED
@@ -2,7 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import random
5
- from typing import Awaitable, Callable, Iterable, TypeVar
5
+ from collections.abc import Awaitable, Callable, Iterable
6
+ from typing import TypeVar
6
7
 
7
8
  from fin_infra.exceptions import RetryError
8
9
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: fin-infra
3
- Version: 0.1.69
3
+ Version: 0.4.0
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
@@ -19,41 +19,55 @@ Classifier: Programming Language :: Python :: 3 :: Only
19
19
  Classifier: Topic :: Office/Business :: Financial
20
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Classifier: Typing :: Typed
22
+ Provides-Extra: all
23
+ Provides-Extra: banking
24
+ Provides-Extra: crypto
25
+ Provides-Extra: markets
26
+ Provides-Extra: plaid
27
+ Provides-Extra: yahoo
22
28
  Requires-Dist: ai-infra (>=0.1.142)
23
29
  Requires-Dist: cashews[redis] (>=7.0)
24
- Requires-Dist: ccxt (>=4.0.0)
30
+ Requires-Dist: ccxt (>=4.0.0) ; extra == "markets" or extra == "crypto" or extra == "all"
31
+ Requires-Dist: fastapi-users (>=15.0.2,<16.0.0)
25
32
  Requires-Dist: httpx (>=0.25.0)
33
+ Requires-Dist: langchain-core (>=1.2.5,<2.0.0)
26
34
  Requires-Dist: loguru (>=0.7.0)
27
35
  Requires-Dist: numpy (>=1.24.0)
28
36
  Requires-Dist: numpy-financial (>=1.0.0)
29
- Requires-Dist: plaid-python (>=25.0.0)
37
+ Requires-Dist: plaid-python (>=25.0.0) ; extra == "plaid" or extra == "banking" or extra == "all"
30
38
  Requires-Dist: pydantic (>=2.0)
31
39
  Requires-Dist: pydantic-settings (>=2.0)
32
40
  Requires-Dist: python-dotenv (>=1.0.0)
33
41
  Requires-Dist: tenacity (>=8.0.0)
34
42
  Requires-Dist: typing-extensions (>=4.0)
35
- Requires-Dist: yahooquery (>=2.3.0)
36
- Project-URL: Documentation, https://github.com/your-org/fin-infra#readme
37
- Project-URL: Homepage, https://github.com/your-org/fin-infra
38
- Project-URL: Issues, https://github.com/your-org/fin-infra/issues
39
- Project-URL: Repository, https://github.com/your-org/fin-infra
43
+ Requires-Dist: yahooquery (>=2.3.0) ; extra == "markets" or extra == "yahoo" or extra == "all"
44
+ Project-URL: Documentation, https://nfrax.com/fin-infra
45
+ Project-URL: Homepage, https://github.com/nfraxlab/fin-infra
46
+ Project-URL: Issues, https://github.com/nfraxlab/fin-infra/issues
47
+ Project-URL: Repository, https://github.com/nfraxlab/fin-infra
40
48
  Description-Content-Type: text/markdown
41
49
 
42
- <div align="center">
43
-
44
50
  # fin-infra
45
51
 
46
- ### Financial data infrastructure for fintech apps
52
+ **Financial data infrastructure for fintech apps.**
47
53
 
48
- [![PyPI](https://img.shields.io/pypi/v/fin-infra.svg)](https://pypi.org/project/fin-infra/) [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) [![Downloads](https://static.pepy.tech/badge/fin-infra/month)](https://pepy.tech/project/fin-infra)
54
+ [![PyPI](https://img.shields.io/pypi/v/fin-infra.svg)](https://pypi.org/project/fin-infra/)
55
+ [![CI](https://github.com/nfraxlab/fin-infra/actions/workflows/ci.yml/badge.svg)](https://github.com/nfraxlab/fin-infra/actions/workflows/ci.yml)
56
+ [![Python](https://img.shields.io/pypi/pyversions/fin-infra.svg)](https://pypi.org/project/fin-infra/)
57
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
49
58
 
50
- **Banking, investments, market data, credit scores, and financial calculations in one toolkit.**
59
+ ## Overview
51
60
 
52
- [Documentation](docs/) · [Examples](examples/) · [PyPI](https://pypi.org/project/fin-infra/)
61
+ Banking, investments, market data, credit scores, and financial calculations in one toolkit.
53
62
 
54
- </div>
63
+ ### Key Features
55
64
 
56
- ---
65
+ - **Banking** - Plaid/Teller integration, accounts, transactions
66
+ - **Investments** - Holdings, portfolio data, real P/L with cost basis
67
+ - **Market Data** - Stocks, crypto, forex quotes and history
68
+ - **Credit** - Credit scores and monitoring
69
+ - **Analytics** - Cash flow, savings rate, spending insights
70
+ - **Cashflows** - NPV, IRR, loan amortization calculations
57
71
 
58
72
  ## Why fin-infra?
59
73