eulerpool 1.0.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 (64) hide show
  1. eulerpool/__init__.py +281 -0
  2. eulerpool/_client.py +250 -0
  3. eulerpool/_version.py +3 -0
  4. eulerpool/errors.py +46 -0
  5. eulerpool/py.typed +0 -0
  6. eulerpool/resources/__init__.py +0 -0
  7. eulerpool/resources/_base.py +34 -0
  8. eulerpool/resources/aaq.py +18 -0
  9. eulerpool/resources/alternative.py +116 -0
  10. eulerpool/resources/analytics.py +50 -0
  11. eulerpool/resources/backtest.py +66 -0
  12. eulerpool/resources/bonds.py +36 -0
  13. eulerpool/resources/calendar.py +78 -0
  14. eulerpool/resources/certificates.py +30 -0
  15. eulerpool/resources/charting.py +42 -0
  16. eulerpool/resources/commodity.py +64 -0
  17. eulerpool/resources/crypto.py +30 -0
  18. eulerpool/resources/crypto_extended.py +246 -0
  19. eulerpool/resources/data.py +24 -0
  20. eulerpool/resources/datasets.py +100 -0
  21. eulerpool/resources/deals.py +28 -0
  22. eulerpool/resources/derivatives.py +68 -0
  23. eulerpool/resources/dex.py +54 -0
  24. eulerpool/resources/earning_calls.py +24 -0
  25. eulerpool/resources/ecb.py +34 -0
  26. eulerpool/resources/economic_forecasts.py +28 -0
  27. eulerpool/resources/energy.py +94 -0
  28. eulerpool/resources/equity.py +420 -0
  29. eulerpool/resources/equity_extended.py +150 -0
  30. eulerpool/resources/etf.py +60 -0
  31. eulerpool/resources/fair_value.py +18 -0
  32. eulerpool/resources/fixed_income.py +46 -0
  33. eulerpool/resources/forex.py +24 -0
  34. eulerpool/resources/fundamentals.py +60 -0
  35. eulerpool/resources/funds.py +32 -0
  36. eulerpool/resources/government.py +42 -0
  37. eulerpool/resources/ice_swap.py +18 -0
  38. eulerpool/resources/index.py +34 -0
  39. eulerpool/resources/institutional.py +60 -0
  40. eulerpool/resources/interest_rates.py +34 -0
  41. eulerpool/resources/macro.py +156 -0
  42. eulerpool/resources/market.py +214 -0
  43. eulerpool/resources/mutual_fund.py +42 -0
  44. eulerpool/resources/news.py +18 -0
  45. eulerpool/resources/nft.py +42 -0
  46. eulerpool/resources/partner.py +18 -0
  47. eulerpool/resources/patents.py +24 -0
  48. eulerpool/resources/peer_comparison.py +58 -0
  49. eulerpool/resources/portfolio.py +96 -0
  50. eulerpool/resources/portfolio_risk.py +58 -0
  51. eulerpool/resources/private_markets.py +24 -0
  52. eulerpool/resources/research.py +30 -0
  53. eulerpool/resources/risk_models.py +52 -0
  54. eulerpool/resources/screener.py +46 -0
  55. eulerpool/resources/sentiment.py +60 -0
  56. eulerpool/resources/shipping.py +58 -0
  57. eulerpool/resources/singapore.py +82 -0
  58. eulerpool/resources/transcripts.py +40 -0
  59. eulerpool/resources/trends.py +18 -0
  60. eulerpool/resources/vendor.py +28 -0
  61. eulerpool-1.0.0.dist-info/METADATA +146 -0
  62. eulerpool-1.0.0.dist-info/RECORD +64 -0
  63. eulerpool-1.0.0.dist-info/WHEEL +4 -0
  64. eulerpool-1.0.0.dist-info/licenses/LICENSE +21 -0
eulerpool/__init__.py ADDED
@@ -0,0 +1,281 @@
1
+ """Eulerpool Financial Data API SDK for Python."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any, Optional
7
+
8
+ from ._client import AsyncHttpClient, HttpClient
9
+ from ._version import __version__
10
+ from .errors import (
11
+ AuthenticationError,
12
+ BadRequestError,
13
+ EulerpoolError,
14
+ NotFoundError,
15
+ RateLimitError,
16
+ ServerError,
17
+ )
18
+ from .resources.aaq import Aaq, AsyncAaq
19
+ from .resources.alternative import Alternative, AsyncAlternative
20
+ from .resources.analytics import Analytics, AsyncAnalytics
21
+ from .resources.backtest import Backtest, AsyncBacktest
22
+ from .resources.bonds import Bonds, AsyncBonds
23
+ from .resources.calendar import Calendar, AsyncCalendar
24
+ from .resources.certificates import Certificates, AsyncCertificates
25
+ from .resources.charting import Charting, AsyncCharting
26
+ from .resources.commodity import Commodity, AsyncCommodity
27
+ from .resources.crypto import Crypto, AsyncCrypto
28
+ from .resources.crypto_extended import CryptoExtended, AsyncCryptoExtended
29
+ from .resources.data import Data, AsyncData
30
+ from .resources.datasets import Datasets, AsyncDatasets
31
+ from .resources.deals import Deals, AsyncDeals
32
+ from .resources.derivatives import Derivatives, AsyncDerivatives
33
+ from .resources.dex import Dex, AsyncDex
34
+ from .resources.earning_calls import EarningCalls, AsyncEarningCalls
35
+ from .resources.ecb import Ecb, AsyncEcb
36
+ from .resources.economic_forecasts import EconomicForecasts, AsyncEconomicForecasts
37
+ from .resources.energy import Energy, AsyncEnergy
38
+ from .resources.equity import Equity, AsyncEquity
39
+ from .resources.equity_extended import EquityExtended, AsyncEquityExtended
40
+ from .resources.etf import Etf, AsyncEtf
41
+ from .resources.fair_value import FairValue, AsyncFairValue
42
+ from .resources.fixed_income import FixedIncome, AsyncFixedIncome
43
+ from .resources.forex import Forex, AsyncForex
44
+ from .resources.fundamentals import Fundamentals, AsyncFundamentals
45
+ from .resources.funds import Funds, AsyncFunds
46
+ from .resources.government import Government, AsyncGovernment
47
+ from .resources.ice_swap import IceSwap, AsyncIceSwap
48
+ from .resources.index import Index, AsyncIndex
49
+ from .resources.institutional import Institutional, AsyncInstitutional
50
+ from .resources.interest_rates import InterestRates, AsyncInterestRates
51
+ from .resources.macro import Macro, AsyncMacro
52
+ from .resources.market import Market, AsyncMarket
53
+ from .resources.mutual_fund import MutualFund, AsyncMutualFund
54
+ from .resources.news import News, AsyncNews
55
+ from .resources.nft import Nft, AsyncNft
56
+ from .resources.partner import Partner, AsyncPartner
57
+ from .resources.patents import Patents, AsyncPatents
58
+ from .resources.peer_comparison import PeerComparison, AsyncPeerComparison
59
+ from .resources.portfolio import Portfolio, AsyncPortfolio
60
+ from .resources.portfolio_risk import PortfolioRisk, AsyncPortfolioRisk
61
+ from .resources.private_markets import PrivateMarkets, AsyncPrivateMarkets
62
+ from .resources.research import Research, AsyncResearch
63
+ from .resources.risk_models import RiskModels, AsyncRiskModels
64
+ from .resources.screener import Screener, AsyncScreener
65
+ from .resources.sentiment import Sentiment, AsyncSentiment
66
+ from .resources.shipping import Shipping, AsyncShipping
67
+ from .resources.singapore import Singapore, AsyncSingapore
68
+ from .resources.transcripts import Transcripts, AsyncTranscripts
69
+ from .resources.trends import Trends, AsyncTrends
70
+ from .resources.vendor import Vendor, AsyncVendor
71
+
72
+ __all__ = [
73
+ "Eulerpool",
74
+ "AsyncEulerpool",
75
+ "EulerpoolError",
76
+ "AuthenticationError",
77
+ "NotFoundError",
78
+ "RateLimitError",
79
+ "BadRequestError",
80
+ "ServerError",
81
+ "__version__",
82
+ ]
83
+
84
+
85
+ def _require_key(api_key: Optional[str]) -> str:
86
+ key = api_key or os.environ.get("EULERPOOL_API_KEY")
87
+ if not key:
88
+ raise AuthenticationError(
89
+ "Pass an API key or set the EULERPOOL_API_KEY environment variable. "
90
+ "Get a free key at https://eulerpool.com/developers/register"
91
+ )
92
+ return key
93
+
94
+
95
+ class Eulerpool:
96
+ """Synchronous client for the Eulerpool Financial Data API."""
97
+
98
+ def __init__(
99
+ self,
100
+ api_key: Optional[str] = None,
101
+ *,
102
+ base_url: Optional[str] = None,
103
+ use_auth_header: bool = False,
104
+ max_retries: int = 2,
105
+ timeout: float = 30.0,
106
+ ) -> None:
107
+ self._client = HttpClient(
108
+ _require_key(api_key),
109
+ base_url=base_url,
110
+ use_auth_header=use_auth_header,
111
+ max_retries=max_retries,
112
+ timeout=timeout,
113
+ )
114
+ self.aaq = Aaq(self._client)
115
+ self.alternative = Alternative(self._client)
116
+ self.analytics = Analytics(self._client)
117
+ self.backtest = Backtest(self._client)
118
+ self.bonds = Bonds(self._client)
119
+ self.calendar = Calendar(self._client)
120
+ self.certificates = Certificates(self._client)
121
+ self.charting = Charting(self._client)
122
+ self.commodity = Commodity(self._client)
123
+ self.crypto = Crypto(self._client)
124
+ self.crypto_extended = CryptoExtended(self._client)
125
+ self.data = Data(self._client)
126
+ self.datasets = Datasets(self._client)
127
+ self.deals = Deals(self._client)
128
+ self.derivatives = Derivatives(self._client)
129
+ self.dex = Dex(self._client)
130
+ self.earning_calls = EarningCalls(self._client)
131
+ self.ecb = Ecb(self._client)
132
+ self.economic_forecasts = EconomicForecasts(self._client)
133
+ self.energy = Energy(self._client)
134
+ self.equity = Equity(self._client)
135
+ self.equity_extended = EquityExtended(self._client)
136
+ self.etf = Etf(self._client)
137
+ self.fair_value = FairValue(self._client)
138
+ self.fixed_income = FixedIncome(self._client)
139
+ self.forex = Forex(self._client)
140
+ self.fundamentals = Fundamentals(self._client)
141
+ self.funds = Funds(self._client)
142
+ self.government = Government(self._client)
143
+ self.ice_swap = IceSwap(self._client)
144
+ self.index = Index(self._client)
145
+ self.institutional = Institutional(self._client)
146
+ self.interest_rates = InterestRates(self._client)
147
+ self.macro = Macro(self._client)
148
+ self.market = Market(self._client)
149
+ self.mutual_fund = MutualFund(self._client)
150
+ self.news = News(self._client)
151
+ self.nft = Nft(self._client)
152
+ self.partner = Partner(self._client)
153
+ self.patents = Patents(self._client)
154
+ self.peer_comparison = PeerComparison(self._client)
155
+ self.portfolio = Portfolio(self._client)
156
+ self.portfolio_risk = PortfolioRisk(self._client)
157
+ self.private_markets = PrivateMarkets(self._client)
158
+ self.research = Research(self._client)
159
+ self.risk_models = RiskModels(self._client)
160
+ self.screener = Screener(self._client)
161
+ self.sentiment = Sentiment(self._client)
162
+ self.shipping = Shipping(self._client)
163
+ self.singapore = Singapore(self._client)
164
+ self.transcripts = Transcripts(self._client)
165
+ self.trends = Trends(self._client)
166
+ self.vendor = Vendor(self._client)
167
+ self.aaqs = self.aaq
168
+
169
+ def get(self, path: str, **params: Any) -> Any:
170
+ """Call any GET endpoint by path, e.g. client.get("/equity/profile/AAPL")."""
171
+ if not path.startswith("/"):
172
+ path = "/" + path
173
+ return self._client.get(path, params or None)
174
+
175
+ def post(self, path: str, body: Any = None, **params: Any) -> Any:
176
+ if not path.startswith("/"):
177
+ path = "/" + path
178
+ return self._client.post(path, body, params or None)
179
+
180
+ def close(self) -> None:
181
+ self._client.close()
182
+
183
+ def __enter__(self) -> "Eulerpool":
184
+ return self
185
+
186
+ def __exit__(self, *exc: object) -> None:
187
+ self.close()
188
+
189
+
190
+ class AsyncEulerpool:
191
+ """Async client for the Eulerpool Financial Data API."""
192
+
193
+ def __init__(
194
+ self,
195
+ api_key: Optional[str] = None,
196
+ *,
197
+ base_url: Optional[str] = None,
198
+ use_auth_header: bool = False,
199
+ max_retries: int = 2,
200
+ timeout: float = 30.0,
201
+ ) -> None:
202
+ self._client = AsyncHttpClient(
203
+ _require_key(api_key),
204
+ base_url=base_url,
205
+ use_auth_header=use_auth_header,
206
+ max_retries=max_retries,
207
+ timeout=timeout,
208
+ )
209
+ self.aaq = AsyncAaq(self._client)
210
+ self.alternative = AsyncAlternative(self._client)
211
+ self.analytics = AsyncAnalytics(self._client)
212
+ self.backtest = AsyncBacktest(self._client)
213
+ self.bonds = AsyncBonds(self._client)
214
+ self.calendar = AsyncCalendar(self._client)
215
+ self.certificates = AsyncCertificates(self._client)
216
+ self.charting = AsyncCharting(self._client)
217
+ self.commodity = AsyncCommodity(self._client)
218
+ self.crypto = AsyncCrypto(self._client)
219
+ self.crypto_extended = AsyncCryptoExtended(self._client)
220
+ self.data = AsyncData(self._client)
221
+ self.datasets = AsyncDatasets(self._client)
222
+ self.deals = AsyncDeals(self._client)
223
+ self.derivatives = AsyncDerivatives(self._client)
224
+ self.dex = AsyncDex(self._client)
225
+ self.earning_calls = AsyncEarningCalls(self._client)
226
+ self.ecb = AsyncEcb(self._client)
227
+ self.economic_forecasts = AsyncEconomicForecasts(self._client)
228
+ self.energy = AsyncEnergy(self._client)
229
+ self.equity = AsyncEquity(self._client)
230
+ self.equity_extended = AsyncEquityExtended(self._client)
231
+ self.etf = AsyncEtf(self._client)
232
+ self.fair_value = AsyncFairValue(self._client)
233
+ self.fixed_income = AsyncFixedIncome(self._client)
234
+ self.forex = AsyncForex(self._client)
235
+ self.fundamentals = AsyncFundamentals(self._client)
236
+ self.funds = AsyncFunds(self._client)
237
+ self.government = AsyncGovernment(self._client)
238
+ self.ice_swap = AsyncIceSwap(self._client)
239
+ self.index = AsyncIndex(self._client)
240
+ self.institutional = AsyncInstitutional(self._client)
241
+ self.interest_rates = AsyncInterestRates(self._client)
242
+ self.macro = AsyncMacro(self._client)
243
+ self.market = AsyncMarket(self._client)
244
+ self.mutual_fund = AsyncMutualFund(self._client)
245
+ self.news = AsyncNews(self._client)
246
+ self.nft = AsyncNft(self._client)
247
+ self.partner = AsyncPartner(self._client)
248
+ self.patents = AsyncPatents(self._client)
249
+ self.peer_comparison = AsyncPeerComparison(self._client)
250
+ self.portfolio = AsyncPortfolio(self._client)
251
+ self.portfolio_risk = AsyncPortfolioRisk(self._client)
252
+ self.private_markets = AsyncPrivateMarkets(self._client)
253
+ self.research = AsyncResearch(self._client)
254
+ self.risk_models = AsyncRiskModels(self._client)
255
+ self.screener = AsyncScreener(self._client)
256
+ self.sentiment = AsyncSentiment(self._client)
257
+ self.shipping = AsyncShipping(self._client)
258
+ self.singapore = AsyncSingapore(self._client)
259
+ self.transcripts = AsyncTranscripts(self._client)
260
+ self.trends = AsyncTrends(self._client)
261
+ self.vendor = AsyncVendor(self._client)
262
+ self.aaqs = self.aaq
263
+
264
+ async def get(self, path: str, **params: Any) -> Any:
265
+ if not path.startswith("/"):
266
+ path = "/" + path
267
+ return await self._client.get(path, params or None)
268
+
269
+ async def post(self, path: str, body: Any = None, **params: Any) -> Any:
270
+ if not path.startswith("/"):
271
+ path = "/" + path
272
+ return await self._client.post(path, body, params or None)
273
+
274
+ async def close(self) -> None:
275
+ await self._client.close()
276
+
277
+ async def __aenter__(self) -> "AsyncEulerpool":
278
+ return self
279
+
280
+ async def __aexit__(self, *exc: object) -> None:
281
+ await self.close()
eulerpool/_client.py ADDED
@@ -0,0 +1,250 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from typing import Any, Dict, Optional
5
+
6
+ import httpx
7
+
8
+ from ._version import __version__
9
+ from .errors import (
10
+ AuthenticationError,
11
+ BadRequestError,
12
+ EulerpoolError,
13
+ NotFoundError,
14
+ RateLimitError,
15
+ ServerError,
16
+ )
17
+
18
+ _PARAM_ALIASES = {"from_": "from", "class_": "class", "global_": "global"}
19
+
20
+
21
+ def _normalize_params(params: Optional[Dict[str, Any]]) -> Dict[str, Any]:
22
+ q: Dict[str, Any] = {}
23
+ if not params:
24
+ return q
25
+ for key, value in params.items():
26
+ if value is None:
27
+ continue
28
+ q[_PARAM_ALIASES.get(key, key)] = value
29
+ return q
30
+
31
+
32
+ class HttpClient:
33
+ """Low-level synchronous HTTP client for the Eulerpool API."""
34
+
35
+ _BASE_URL = "https://api.eulerpool.com/api/1"
36
+
37
+ def __init__(
38
+ self,
39
+ api_key: str,
40
+ *,
41
+ base_url: Optional[str] = None,
42
+ use_auth_header: bool = False,
43
+ max_retries: int = 2,
44
+ timeout: float = 30.0,
45
+ ) -> None:
46
+ self._api_key = api_key
47
+ self._base_url = (base_url or self._BASE_URL).rstrip("/")
48
+ self._use_auth_header = use_auth_header
49
+ self._max_retries = max_retries
50
+ self._http = httpx.Client(
51
+ timeout=timeout,
52
+ follow_redirects=True,
53
+ headers={
54
+ "Accept": "application/json",
55
+ "User-Agent": f"eulerpool-python/{__version__}",
56
+ },
57
+ )
58
+
59
+ def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
60
+ return self._request("GET", path, params=params)
61
+
62
+ def post(self, path: str, body: Any = None, params: Optional[Dict[str, Any]] = None) -> Any:
63
+ return self._request("POST", path, params=params, json_body=body)
64
+
65
+ def delete(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
66
+ return self._request("DELETE", path, params=params)
67
+
68
+ def close(self) -> None:
69
+ self._http.close()
70
+
71
+ def __enter__(self) -> "HttpClient":
72
+ return self
73
+
74
+ def __exit__(self, *exc: Any) -> None:
75
+ self.close()
76
+
77
+ def _request(
78
+ self,
79
+ method: str,
80
+ path: str,
81
+ *,
82
+ params: Optional[Dict[str, Any]] = None,
83
+ json_body: Any = None,
84
+ ) -> Any:
85
+ url = f"{self._base_url}{path}"
86
+ query = self._build_query(params)
87
+ headers: Dict[str, str] = {
88
+ "Authorization": f"Bearer {self._api_key}",
89
+ }
90
+
91
+ last_exc: Optional[Exception] = None
92
+ for attempt in range(self._max_retries + 1):
93
+ if attempt > 0:
94
+ time.sleep(min(2 ** (attempt - 1), 8))
95
+ try:
96
+ resp = self._http.request(
97
+ method,
98
+ url,
99
+ params=query,
100
+ json=json_body,
101
+ headers=headers,
102
+ )
103
+ if resp.is_success:
104
+ if not resp.content:
105
+ return None
106
+ try:
107
+ return resp.json()
108
+ except Exception:
109
+ return resp.text
110
+ error = self._make_error(resp)
111
+ if not self._is_retryable(resp.status_code):
112
+ raise error
113
+ last_exc = error
114
+ except EulerpoolError:
115
+ raise
116
+ except Exception as exc:
117
+ last_exc = exc
118
+
119
+ raise last_exc or EulerpoolError("Request failed after retries.")
120
+
121
+ def _build_query(self, params: Optional[Dict[str, Any]]) -> Dict[str, Any]:
122
+ q: Dict[str, Any] = {}
123
+ if not self._use_auth_header:
124
+ q["token"] = self._api_key
125
+ q.update(_normalize_params(params))
126
+ return q
127
+
128
+ @staticmethod
129
+ def _make_error(resp: httpx.Response) -> EulerpoolError:
130
+ try:
131
+ body = resp.json()
132
+ msg = body.get("message") or body.get("error") or resp.reason_phrase
133
+ except Exception:
134
+ msg = resp.text or resp.reason_phrase
135
+ status = resp.status_code
136
+ if status == 400:
137
+ return BadRequestError(msg)
138
+ if status == 401:
139
+ return AuthenticationError(msg)
140
+ if status == 404:
141
+ return NotFoundError(msg)
142
+ if status == 429:
143
+ retry = resp.headers.get("Retry-After")
144
+ return RateLimitError(msg, retry_after=int(retry) if retry else None)
145
+ if status >= 500:
146
+ return ServerError(msg)
147
+ return EulerpoolError(msg, status=status)
148
+
149
+ @staticmethod
150
+ def _is_retryable(status: int) -> bool:
151
+ return status == 429 or status >= 500
152
+
153
+
154
+ class AsyncHttpClient:
155
+ """Low-level async HTTP client for the Eulerpool API."""
156
+
157
+ _BASE_URL = "https://api.eulerpool.com/api/1"
158
+
159
+ def __init__(
160
+ self,
161
+ api_key: str,
162
+ *,
163
+ base_url: Optional[str] = None,
164
+ use_auth_header: bool = False,
165
+ max_retries: int = 2,
166
+ timeout: float = 30.0,
167
+ ) -> None:
168
+ self._api_key = api_key
169
+ self._base_url = (base_url or self._BASE_URL).rstrip("/")
170
+ self._use_auth_header = use_auth_header
171
+ self._max_retries = max_retries
172
+ self._http = httpx.AsyncClient(
173
+ timeout=timeout,
174
+ follow_redirects=True,
175
+ headers={
176
+ "Accept": "application/json",
177
+ "User-Agent": f"eulerpool-python/{__version__}",
178
+ },
179
+ )
180
+
181
+ async def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
182
+ return await self._request("GET", path, params=params)
183
+
184
+ async def post(self, path: str, body: Any = None, params: Optional[Dict[str, Any]] = None) -> Any:
185
+ return await self._request("POST", path, params=params, json_body=body)
186
+
187
+ async def delete(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
188
+ return await self._request("DELETE", path, params=params)
189
+
190
+ async def close(self) -> None:
191
+ await self._http.aclose()
192
+
193
+ async def __aenter__(self) -> "AsyncHttpClient":
194
+ return self
195
+
196
+ async def __aexit__(self, *exc: Any) -> None:
197
+ await self.close()
198
+
199
+ async def _request(
200
+ self,
201
+ method: str,
202
+ path: str,
203
+ *,
204
+ params: Optional[Dict[str, Any]] = None,
205
+ json_body: Any = None,
206
+ ) -> Any:
207
+ import asyncio
208
+
209
+ url = f"{self._base_url}{path}"
210
+ query = self._build_query(params)
211
+ headers: Dict[str, str] = {
212
+ "Authorization": f"Bearer {self._api_key}",
213
+ }
214
+
215
+ last_exc: Optional[Exception] = None
216
+ for attempt in range(self._max_retries + 1):
217
+ if attempt > 0:
218
+ await asyncio.sleep(min(2 ** (attempt - 1), 8))
219
+ try:
220
+ resp = await self._http.request(
221
+ method,
222
+ url,
223
+ params=query,
224
+ json=json_body,
225
+ headers=headers,
226
+ )
227
+ if resp.is_success:
228
+ if not resp.content:
229
+ return None
230
+ try:
231
+ return resp.json()
232
+ except Exception:
233
+ return resp.text
234
+ error = HttpClient._make_error(resp)
235
+ if not HttpClient._is_retryable(resp.status_code):
236
+ raise error
237
+ last_exc = error
238
+ except EulerpoolError:
239
+ raise
240
+ except Exception as exc:
241
+ last_exc = exc
242
+
243
+ raise last_exc or EulerpoolError("Request failed after retries.")
244
+
245
+ def _build_query(self, params: Optional[Dict[str, Any]]) -> Dict[str, Any]:
246
+ q: Dict[str, Any] = {}
247
+ if not self._use_auth_header:
248
+ q["token"] = self._api_key
249
+ q.update(_normalize_params(params))
250
+ return q
eulerpool/_version.py ADDED
@@ -0,0 +1,3 @@
1
+ """Eulerpool SDK version."""
2
+
3
+ __version__ = "1.0.0"
eulerpool/errors.py ADDED
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ class EulerpoolError(Exception):
4
+ """Base exception for Eulerpool API errors."""
5
+
6
+ def __init__(self, message: str, status: int = 0, code: str = "api_error"):
7
+ super().__init__(message)
8
+ self.message = message
9
+ self.status = status
10
+ self.code = code
11
+
12
+
13
+ class AuthenticationError(EulerpoolError):
14
+ """Raised when the API key is invalid or missing."""
15
+
16
+ def __init__(self, message: str = "Invalid or missing API key."):
17
+ super().__init__(message, status=401, code="authentication_error")
18
+
19
+
20
+ class NotFoundError(EulerpoolError):
21
+ """Raised when the requested resource is not found."""
22
+
23
+ def __init__(self, message: str = "The requested resource was not found."):
24
+ super().__init__(message, status=404, code="not_found")
25
+
26
+
27
+ class RateLimitError(EulerpoolError):
28
+ """Raised when rate limit is exceeded."""
29
+
30
+ def __init__(self, message: str = "Rate limit exceeded.", retry_after=None):
31
+ super().__init__(message, status=429, code="rate_limit_exceeded")
32
+ self.retry_after = retry_after
33
+
34
+
35
+ class BadRequestError(EulerpoolError):
36
+ """Raised on 400 bad request."""
37
+
38
+ def __init__(self, message: str = "Bad request."):
39
+ super().__init__(message, status=400, code="bad_request")
40
+
41
+
42
+ class ServerError(EulerpoolError):
43
+ """Raised on 5xx server errors."""
44
+
45
+ def __init__(self, message: str = "Internal server error."):
46
+ super().__init__(message, status=500, code="server_error")
eulerpool/py.typed ADDED
File without changes
File without changes
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Optional, TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from .._client import AsyncHttpClient, HttpClient
7
+
8
+
9
+ class SyncResource:
10
+ def __init__(self, client: "HttpClient") -> None:
11
+ self._client = client
12
+
13
+ def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
14
+ return self._client.get(path, params)
15
+
16
+ def _post(self, path: str, body: Any = None, params: Optional[Dict[str, Any]] = None) -> Any:
17
+ return self._client.post(path, body, params)
18
+
19
+ def _delete(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
20
+ return self._client.delete(path, params)
21
+
22
+
23
+ class AsyncResource:
24
+ def __init__(self, client: "AsyncHttpClient") -> None:
25
+ self._client = client
26
+
27
+ async def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
28
+ return await self._client.get(path, params)
29
+
30
+ async def _post(self, path: str, body: Any = None, params: Optional[Dict[str, Any]] = None) -> Any:
31
+ return await self._client.post(path, body, params)
32
+
33
+ async def _delete(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
34
+ return await self._client.delete(path, params)
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Optional
4
+ from urllib.parse import quote
5
+
6
+ from ._base import AsyncResource, SyncResource
7
+
8
+
9
+ class Aaq(SyncResource):
10
+ def by_isin(self, identifier: str, **params: Any) -> Any:
11
+ """AAQS Score"""
12
+ return self._get(f"/aaqs/by-isin/{quote(str(identifier))}", params)
13
+
14
+
15
+ class AsyncAaq(AsyncResource):
16
+ async def by_isin(self, identifier: str, **params: Any) -> Any:
17
+ """AAQS Score"""
18
+ return await self._get(f"/aaqs/by-isin/{quote(str(identifier))}", params)