aress-sdk 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.
aress_sdk/__init__.py ADDED
@@ -0,0 +1,278 @@
1
+ """Aress SDK — official Python client for the Aress Data API.
2
+
3
+ Fetch instruments, bonds, currencies and (preview) funds from the Iranian
4
+ capital market over the Aress Data HTTP API.
5
+
6
+ Quickstart::
7
+
8
+ from aress_sdk import AressClient
9
+
10
+ with AressClient("<your api key>") as client:
11
+ for currency in client.currencies.list():
12
+ print(currency.symbol, currency.farsi_name)
13
+
14
+ See :class:`AressClient` / :class:`AsyncAressClient` for the full surface.
15
+ """
16
+
17
+ from ._version import __version__
18
+ from .client import AressClient, AsyncAressClient
19
+ from .config import (
20
+ API_KEY_HEADER,
21
+ DEFAULT_BACKOFF_FACTOR,
22
+ DEFAULT_BASE_URL,
23
+ DEFAULT_MAX_RETRIES,
24
+ DEFAULT_TIMEOUT,
25
+ ClientConfig,
26
+ )
27
+ from .exceptions import (
28
+ AressAPIStatusError,
29
+ AressConnectionError,
30
+ AressError,
31
+ AressTimeoutError,
32
+ AressTransportError,
33
+ AuthenticationError,
34
+ BadRequestError,
35
+ NotFoundError,
36
+ PermissionDeniedError,
37
+ RateLimitError,
38
+ ServerError,
39
+ UnprocessableEntityError,
40
+ )
41
+
42
+ # Domain models — re-exported for convenient typing/imports.
43
+ from .bonds import Bond, BondDailyTradeHistory
44
+ from .currencies import Currency, CurrencyDailyHistory
45
+ from .funds import (
46
+ CategoryAnnualPeriodAnalysis,
47
+ CategoryIssueRedeemSeriesItem,
48
+ CategoryIssueRedeemAnalysisItem,
49
+ CategoryMonthlyPeriodAnalysis,
50
+ CategoryPeriodAnalysis,
51
+ SubFund,
52
+ FundManager,
53
+ FundAnalysis,
54
+ FundNavAndReturnHistoryItem,
55
+ IndexReturnHistoryItem,
56
+ FundAnnualPeriodAnalysis,
57
+ FundAssetAllocation,
58
+ FundCategory,
59
+ FundFinancialRegime,
60
+ FundIssueRedeemSeriesItem,
61
+ FundMonthlyPeriodAnalysis,
62
+ FundNavHistoryItem,
63
+ FundPeriodAnalysis,
64
+ FundRecentAdjustedNavHistoryItem,
65
+ FundSummaryAnalysis,
66
+ FundType,
67
+ SubFundWithSummaryAnalysis,
68
+ SubFundFlowsSummary,
69
+ AumFlowPoint,
70
+ SubFundDailySeriesPoint,
71
+ FundMonthlyReturn,
72
+ RollingPeriodAnalysis,
73
+ RollingPeriodAnalysisVsBenchmark,
74
+ RollingPeriodDecomposition,
75
+ RollingPeriodWindowDecomposition,
76
+ RollingPeriodWindowAnalysis,
77
+ RollingPeriodWindowAnalysisVsBenchmark,
78
+ RollingWindow,
79
+ TotalIndexAnnualPeriodAnalysis,
80
+ TotalIndexMonthlyPeriodAnalysis,
81
+ )
82
+ from .instruments import (
83
+ DailyClientTypeHistory,
84
+ DailyIndexHistory,
85
+ DailyTradeHistory,
86
+ Index,
87
+ IndexReturnHistoryPoint,
88
+ Instrument,
89
+ OptionMarketLive,
90
+ OptionMarketLiveEntry,
91
+ OptionMarketLiveLeg,
92
+ PriceAdjustment,
93
+ RealtimeClientType,
94
+ RealtimeIndex,
95
+ RealtimeInstrument,
96
+ RealtimeMarket,
97
+ RealtimeOrderBook,
98
+ RealtimeOrderBookRow,
99
+ Sector,
100
+ ShareChange,
101
+ SubSector,
102
+ UnderlyingOptionMarketLive,
103
+ )
104
+ from .ime import (
105
+ ImeCdcContract,
106
+ ImeCdcDailyTradeHistory,
107
+ ImeOptionContract,
108
+ ImeCdcLive,
109
+ ImeClientType,
110
+ ImeListedInstrumentLive,
111
+ ImeMarketLive,
112
+ ImeOptionChainLive,
113
+ ImeOptionExpirationLive,
114
+ ImeOptionLegLive,
115
+ ImeOptionStrikeLive,
116
+ ImeOrderBookRow,
117
+ ImeUnderlyingOptionChainLive,
118
+ )
119
+ from .market import (
120
+ MarketListWindow,
121
+ RealtimeStockFlow,
122
+ RealtimeStockFlows,
123
+ SectorTradeValue,
124
+ SectorTradeValues,
125
+ StockPriceChange,
126
+ StockPriceChanges,
127
+ )
128
+ from .tradingview import (
129
+ TradingViewBar,
130
+ TradingViewConfig,
131
+ TradingViewExchange,
132
+ TradingViewHistory,
133
+ TradingViewSearchResult,
134
+ TradingViewSymbolInfo,
135
+ TradingViewSymbolType,
136
+ )
137
+ from .reports import (
138
+ AppliedFilter,
139
+ ReportBatchResultItem,
140
+ ReportCalculationResultType,
141
+ ReportCategory,
142
+ ReportFilter,
143
+ ReportFilterOption,
144
+ ReportMetadata,
145
+ ReportRequest,
146
+ ReportResult,
147
+ )
148
+
149
+ __all__ = [
150
+ "__version__",
151
+ # clients
152
+ "AressClient",
153
+ "AsyncAressClient",
154
+ # config
155
+ "ClientConfig",
156
+ "API_KEY_HEADER",
157
+ "DEFAULT_BASE_URL",
158
+ "DEFAULT_TIMEOUT",
159
+ "DEFAULT_MAX_RETRIES",
160
+ "DEFAULT_BACKOFF_FACTOR",
161
+ # exceptions
162
+ "AressError",
163
+ "AressTransportError",
164
+ "AressConnectionError",
165
+ "AressTimeoutError",
166
+ "AressAPIStatusError",
167
+ "BadRequestError",
168
+ "AuthenticationError",
169
+ "PermissionDeniedError",
170
+ "NotFoundError",
171
+ "UnprocessableEntityError",
172
+ "RateLimitError",
173
+ "ServerError",
174
+ # instruments
175
+ "Sector",
176
+ "SubSector",
177
+ "Instrument",
178
+ "PriceAdjustment",
179
+ "ShareChange",
180
+ "DailyTradeHistory",
181
+ "DailyClientTypeHistory",
182
+ "Index",
183
+ "DailyIndexHistory",
184
+ "IndexReturnHistoryPoint",
185
+ # realtime
186
+ "RealtimeOrderBookRow",
187
+ "RealtimeOrderBook",
188
+ "RealtimeClientType",
189
+ "RealtimeInstrument",
190
+ "RealtimeIndex",
191
+ "RealtimeMarket",
192
+ "OptionMarketLiveLeg",
193
+ "OptionMarketLiveEntry",
194
+ "UnderlyingOptionMarketLive",
195
+ "OptionMarketLive",
196
+ # IME
197
+ "ImeCdcContract",
198
+ "ImeOptionContract",
199
+ "ImeCdcDailyTradeHistory",
200
+ "ImeOrderBookRow",
201
+ "ImeClientType",
202
+ "ImeCdcLive",
203
+ "ImeListedInstrumentLive",
204
+ "ImeMarketLive",
205
+ "ImeOptionLegLive",
206
+ "ImeOptionStrikeLive",
207
+ "ImeOptionExpirationLive",
208
+ "ImeUnderlyingOptionChainLive",
209
+ "ImeOptionChainLive",
210
+ # tradingview
211
+ "TradingViewExchange",
212
+ "TradingViewSymbolType",
213
+ "TradingViewConfig",
214
+ "TradingViewSearchResult",
215
+ "TradingViewSymbolInfo",
216
+ "TradingViewBar",
217
+ "TradingViewHistory",
218
+ # market lists
219
+ "MarketListWindow",
220
+ "StockPriceChange",
221
+ "StockPriceChanges",
222
+ "SectorTradeValue",
223
+ "SectorTradeValues",
224
+ "RealtimeStockFlow",
225
+ "RealtimeStockFlows",
226
+ # bonds
227
+ "Bond",
228
+ "BondDailyTradeHistory",
229
+ # currencies
230
+ "Currency",
231
+ "CurrencyDailyHistory",
232
+ # funds
233
+ "SubFund",
234
+ "FundType",
235
+ "FundCategory",
236
+ "FundFinancialRegime",
237
+ "RollingWindow",
238
+ "FundNavHistoryItem",
239
+ "FundAnalysis",
240
+ "FundNavAndReturnHistoryItem",
241
+ "IndexReturnHistoryItem",
242
+ "FundSummaryAnalysis",
243
+ "FundAssetAllocation",
244
+ "SubFundWithSummaryAnalysis",
245
+ "FundManager",
246
+ "SubFundFlowsSummary",
247
+ "AumFlowPoint",
248
+ "SubFundDailySeriesPoint",
249
+ "FundMonthlyReturn",
250
+ "FundRecentAdjustedNavHistoryItem",
251
+ "RollingPeriodWindowAnalysis",
252
+ "RollingPeriodAnalysis",
253
+ "RollingPeriodWindowAnalysisVsBenchmark",
254
+ "RollingPeriodAnalysisVsBenchmark",
255
+ "RollingPeriodDecomposition",
256
+ "RollingPeriodWindowDecomposition",
257
+ "FundIssueRedeemSeriesItem",
258
+ "CategoryIssueRedeemSeriesItem",
259
+ "CategoryIssueRedeemAnalysisItem",
260
+ "FundPeriodAnalysis",
261
+ "FundMonthlyPeriodAnalysis",
262
+ "FundAnnualPeriodAnalysis",
263
+ "CategoryPeriodAnalysis",
264
+ "CategoryMonthlyPeriodAnalysis",
265
+ "CategoryAnnualPeriodAnalysis",
266
+ "TotalIndexMonthlyPeriodAnalysis",
267
+ "TotalIndexAnnualPeriodAnalysis",
268
+ # reports
269
+ "ReportMetadata",
270
+ "ReportCategory",
271
+ "ReportFilter",
272
+ "ReportFilterOption",
273
+ "ReportRequest",
274
+ "ReportResult",
275
+ "AppliedFilter",
276
+ "ReportBatchResultItem",
277
+ "ReportCalculationResultType",
278
+ ]
aress_sdk/_resource.py ADDED
@@ -0,0 +1,40 @@
1
+ """Base classes for resource clients.
2
+
3
+ Resources are thin: they build the request path/params, delegate the HTTP call
4
+ to a transport, and validate the JSON response into pydantic models. The model
5
+ parsing is shared here so the sync and async resources stay symmetric.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, List, Type, TypeVar
11
+
12
+ from pydantic import TypeAdapter
13
+
14
+ from ._transport import AsyncTransport, SyncTransport
15
+ from ._types import AressModel
16
+
17
+ M = TypeVar("M", bound=AressModel)
18
+
19
+
20
+ def parse_one(model: Type[M], data: Any) -> M:
21
+ return model.model_validate(data)
22
+
23
+
24
+ def parse_list(model: Type[M], data: Any) -> List[M]:
25
+ adapter: TypeAdapter = TypeAdapter(List[model]) # type: ignore[valid-type]
26
+ return adapter.validate_python(data)
27
+
28
+
29
+ class SyncResource:
30
+ """Base for synchronous resource clients."""
31
+
32
+ def __init__(self, transport: SyncTransport) -> None:
33
+ self._t = transport
34
+
35
+
36
+ class AsyncResource:
37
+ """Base for asynchronous resource clients."""
38
+
39
+ def __init__(self, transport: AsyncTransport) -> None:
40
+ self._t = transport
@@ -0,0 +1,271 @@
1
+ """HTTP transport: thin wrappers over ``httpx`` clients.
2
+
3
+ Two transports share one retry/error-mapping policy:
4
+
5
+ * :class:`SyncTransport` — wraps :class:`httpx.Client`.
6
+ * :class:`AsyncTransport` — wraps :class:`httpx.AsyncClient`.
7
+
8
+ Resources call ``transport.get(path, params=...)`` and get back parsed JSON
9
+ (``dict``/``list``); non-2xx responses are turned into
10
+ :class:`~aress_sdk.exceptions.AressAPIStatusError` subclasses, and transport
11
+ failures into :class:`~aress_sdk.exceptions.AressTransportError` subclasses.
12
+ Retries (with exponential backoff) cover connection errors, timeouts, 5xx and
13
+ 429.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import time
19
+ from typing import Any, Mapping, Optional
20
+
21
+ import httpx
22
+
23
+ from .config import ClientConfig
24
+ from .exceptions import (
25
+ AressConnectionError,
26
+ AressTimeoutError,
27
+ AressTransportError,
28
+ error_from_response,
29
+ )
30
+
31
+ # Status codes worth retrying (the request is read-only, so this is always safe).
32
+ _RETRYABLE_STATUS = frozenset({429, 500, 502, 503, 504})
33
+
34
+
35
+ def _clean_params(params: Optional[Mapping[str, Any]]) -> Optional[dict]:
36
+ """Drop ``None`` values so optional filters become "not sent"."""
37
+ if not params:
38
+ return None
39
+ return {key: value for key, value in params.items() if value is not None}
40
+
41
+
42
+ def _should_retry_status(status_code: int) -> bool:
43
+ return status_code in _RETRYABLE_STATUS
44
+
45
+
46
+ def _backoff_seconds(config: ClientConfig, attempt: int) -> float:
47
+ """Exponential backoff: factor * 2**attempt (attempt is 0-based)."""
48
+ return config.backoff_factor * (2 ** attempt)
49
+
50
+
51
+ def _wrap_transport_error(exc: httpx.HTTPError) -> AressTransportError:
52
+ request = getattr(exc, "request", None)
53
+ if isinstance(exc, httpx.TimeoutException):
54
+ return AressTimeoutError(f"Request timed out: {exc}", request=request)
55
+ if isinstance(exc, httpx.ConnectError):
56
+ return AressConnectionError(f"Failed to connect to the API: {exc}", request=request)
57
+ return AressConnectionError(f"Transport error: {exc}", request=request)
58
+
59
+
60
+ class _BaseTransport:
61
+ def __init__(self, config: ClientConfig) -> None:
62
+ self._config = config
63
+
64
+ @property
65
+ def base_url(self) -> str:
66
+ return self._config.normalized_base_url()
67
+
68
+ @property
69
+ def config(self) -> ClientConfig:
70
+ """The client configuration, so resources can read per-endpoint budgets."""
71
+ return self._config
72
+
73
+
74
+ class SyncTransport(_BaseTransport):
75
+ """Synchronous transport backed by :class:`httpx.Client`."""
76
+
77
+ def __init__(
78
+ self,
79
+ config: ClientConfig,
80
+ *,
81
+ http_client: Optional[httpx.Client] = None,
82
+ ) -> None:
83
+ super().__init__(config)
84
+ self._owns_client = http_client is None
85
+ self._client = http_client or httpx.Client(
86
+ base_url=config.normalized_base_url(),
87
+ timeout=config.timeout,
88
+ headers=config.default_headers(),
89
+ )
90
+
91
+ def get(self, path: str, *, params: Optional[Mapping[str, Any]] = None) -> Any:
92
+ clean = _clean_params(params)
93
+ last_exc: Optional[AressTransportError] = None
94
+
95
+ for attempt in range(self._config.max_retries + 1):
96
+ try:
97
+ response = self._client.get(
98
+ path, params=clean, headers=self._config.auth_headers()
99
+ )
100
+ except httpx.HTTPError as exc:
101
+ last_exc = _wrap_transport_error(exc)
102
+ if attempt < self._config.max_retries:
103
+ time.sleep(_backoff_seconds(self._config, attempt))
104
+ continue
105
+ raise last_exc
106
+
107
+ if response.is_success:
108
+ return _decode(response)
109
+
110
+ if _should_retry_status(response.status_code) and attempt < self._config.max_retries:
111
+ time.sleep(_backoff_seconds(self._config, attempt))
112
+ continue
113
+
114
+ raise error_from_response(response)
115
+
116
+ # Unreachable, but keeps type checkers happy.
117
+ assert last_exc is not None
118
+ raise last_exc
119
+
120
+ def post(
121
+ self,
122
+ path: str,
123
+ *,
124
+ json: Optional[Any] = None,
125
+ timeout: Optional[float] = None,
126
+ ) -> Any:
127
+ """POST a JSON body and return the parsed JSON response.
128
+
129
+ Same retry/error policy as :meth:`get`. The reports calculation
130
+ endpoints are read-only despite using POST (the body carries filter
131
+ selections), so retrying transient failures is safe.
132
+
133
+ ``timeout`` overrides the client-level timeout for this request only,
134
+ and applies **per attempt** — slow endpoints (report calculations) pass
135
+ their own budget rather than inflating it for the whole client.
136
+ """
137
+ last_exc: Optional[AressTransportError] = None
138
+ request_timeout = self._config.timeout if timeout is None else timeout
139
+
140
+ for attempt in range(self._config.max_retries + 1):
141
+ try:
142
+ response = self._client.post(
143
+ path,
144
+ json=json,
145
+ timeout=request_timeout,
146
+ headers=self._config.auth_headers(),
147
+ )
148
+ except httpx.HTTPError as exc:
149
+ last_exc = _wrap_transport_error(exc)
150
+ if attempt < self._config.max_retries:
151
+ time.sleep(_backoff_seconds(self._config, attempt))
152
+ continue
153
+ raise last_exc
154
+
155
+ if response.is_success:
156
+ return _decode(response)
157
+
158
+ if _should_retry_status(response.status_code) and attempt < self._config.max_retries:
159
+ time.sleep(_backoff_seconds(self._config, attempt))
160
+ continue
161
+
162
+ raise error_from_response(response)
163
+
164
+ assert last_exc is not None
165
+ raise last_exc
166
+
167
+ def close(self) -> None:
168
+ if self._owns_client:
169
+ self._client.close()
170
+
171
+
172
+ class AsyncTransport(_BaseTransport):
173
+ """Asynchronous transport backed by :class:`httpx.AsyncClient`."""
174
+
175
+ def __init__(
176
+ self,
177
+ config: ClientConfig,
178
+ *,
179
+ http_client: Optional[httpx.AsyncClient] = None,
180
+ ) -> None:
181
+ super().__init__(config)
182
+ self._owns_client = http_client is None
183
+ self._client = http_client or httpx.AsyncClient(
184
+ base_url=config.normalized_base_url(),
185
+ timeout=config.timeout,
186
+ headers=config.default_headers(),
187
+ )
188
+
189
+ async def get(self, path: str, *, params: Optional[Mapping[str, Any]] = None) -> Any:
190
+ import asyncio
191
+
192
+ clean = _clean_params(params)
193
+ last_exc: Optional[AressTransportError] = None
194
+
195
+ for attempt in range(self._config.max_retries + 1):
196
+ try:
197
+ response = await self._client.get(
198
+ path, params=clean, headers=self._config.auth_headers()
199
+ )
200
+ except httpx.HTTPError as exc:
201
+ last_exc = _wrap_transport_error(exc)
202
+ if attempt < self._config.max_retries:
203
+ await asyncio.sleep(_backoff_seconds(self._config, attempt))
204
+ continue
205
+ raise last_exc
206
+
207
+ if response.is_success:
208
+ return _decode(response)
209
+
210
+ if _should_retry_status(response.status_code) and attempt < self._config.max_retries:
211
+ await asyncio.sleep(_backoff_seconds(self._config, attempt))
212
+ continue
213
+
214
+ raise error_from_response(response)
215
+
216
+ assert last_exc is not None
217
+ raise last_exc
218
+
219
+ async def post(
220
+ self,
221
+ path: str,
222
+ *,
223
+ json: Optional[Any] = None,
224
+ timeout: Optional[float] = None,
225
+ ) -> Any:
226
+ """Async counterpart of :meth:`SyncTransport.post`; ``timeout``
227
+ overrides the client-level timeout for this request only, per attempt."""
228
+ import asyncio
229
+
230
+ last_exc: Optional[AressTransportError] = None
231
+ request_timeout = self._config.timeout if timeout is None else timeout
232
+
233
+ for attempt in range(self._config.max_retries + 1):
234
+ try:
235
+ response = await self._client.post(
236
+ path,
237
+ json=json,
238
+ timeout=request_timeout,
239
+ headers=self._config.auth_headers(),
240
+ )
241
+ except httpx.HTTPError as exc:
242
+ last_exc = _wrap_transport_error(exc)
243
+ if attempt < self._config.max_retries:
244
+ await asyncio.sleep(_backoff_seconds(self._config, attempt))
245
+ continue
246
+ raise last_exc
247
+
248
+ if response.is_success:
249
+ return _decode(response)
250
+
251
+ if _should_retry_status(response.status_code) and attempt < self._config.max_retries:
252
+ await asyncio.sleep(_backoff_seconds(self._config, attempt))
253
+ continue
254
+
255
+ raise error_from_response(response)
256
+
257
+ assert last_exc is not None
258
+ raise last_exc
259
+
260
+ async def aclose(self) -> None:
261
+ if self._owns_client:
262
+ await self._client.aclose()
263
+
264
+
265
+ def _decode(response: httpx.Response) -> Any:
266
+ try:
267
+ return response.json()
268
+ except ValueError as exc:
269
+ raise AressConnectionError(
270
+ f"Expected JSON from {response.request.url} but got non-JSON body"
271
+ ) from exc
aress_sdk/_types.py ADDED
@@ -0,0 +1,71 @@
1
+ """Shared field types and the base response model.
2
+
3
+ The API serializes Jalali (Persian) dates as ISO ``YYYY-MM-DD`` strings (e.g.
4
+ ``"1403-01-15"``). On the SDK side we parse them back into
5
+ :class:`jdatetime.date` so callers get real date objects with calendar
6
+ arithmetic, and serialize them back to the same ISO string.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Annotated, Any
12
+
13
+ import jdatetime
14
+ from pydantic import BaseModel, ConfigDict, GetCoreSchemaHandler
15
+ from pydantic.json_schema import GetJsonSchemaHandler, JsonSchemaValue
16
+ from pydantic_core import core_schema
17
+
18
+
19
+ class _JalaliDatePydanticAnnotation:
20
+ """Pydantic v2 support for :class:`jdatetime.date`.
21
+
22
+ Accepts a ``jdatetime.date`` or an ISO ``YYYY-MM-DD`` Jalali string and
23
+ serializes to the ISO string form.
24
+ """
25
+
26
+ @classmethod
27
+ def __get_pydantic_core_schema__(
28
+ cls, _source_type: Any, _handler: GetCoreSchemaHandler
29
+ ) -> core_schema.CoreSchema:
30
+ def validate(value: Any) -> jdatetime.date:
31
+ if isinstance(value, jdatetime.date):
32
+ return value
33
+ if isinstance(value, str):
34
+ try:
35
+ year, month, day = (int(part) for part in value.split("-"))
36
+ return jdatetime.date(year, month, day)
37
+ except (ValueError, TypeError) as exc:
38
+ raise ValueError(f"Invalid Jalali date: {value!r}") from exc
39
+ raise ValueError(f"Invalid Jalali date: {value!r}")
40
+
41
+ return core_schema.no_info_plain_validator_function(
42
+ validate,
43
+ serialization=core_schema.plain_serializer_function_ser_schema(
44
+ lambda value: value.isoformat(),
45
+ return_schema=core_schema.str_schema(),
46
+ ),
47
+ )
48
+
49
+ @classmethod
50
+ def __get_pydantic_json_schema__(
51
+ cls, _schema: core_schema.CoreSchema, _handler: GetJsonSchemaHandler
52
+ ) -> JsonSchemaValue:
53
+ return {"type": "string", "format": "date", "example": "1403-01-15"}
54
+
55
+
56
+ #: A Jalali calendar date (``jdatetime.date``) usable as a pydantic field type.
57
+ JalaliDate = Annotated[jdatetime.date, _JalaliDatePydanticAnnotation]
58
+
59
+
60
+ class AressModel(BaseModel):
61
+ """Base class for all response models returned by the SDK.
62
+
63
+ Configured to ignore unknown fields, so the SDK keeps working when the API
64
+ adds new fields to a response.
65
+ """
66
+
67
+ model_config = ConfigDict(
68
+ extra="ignore",
69
+ frozen=False,
70
+ populate_by_name=True,
71
+ )
aress_sdk/_version.py ADDED
@@ -0,0 +1,3 @@
1
+ """Single source of truth for the SDK version."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,11 @@
1
+ """Bonds domain: bonds and their daily trade history."""
2
+
3
+ from .models import Bond, BondDailyTradeHistory
4
+ from .resource import AsyncBondsResource, BondsResource
5
+
6
+ __all__ = [
7
+ "Bond",
8
+ "BondDailyTradeHistory",
9
+ "BondsResource",
10
+ "AsyncBondsResource",
11
+ ]