fmp-data 0.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 (84) hide show
  1. fmp_data/__init__.py +138 -0
  2. fmp_data/alternative/__init__.py +34 -0
  3. fmp_data/alternative/client.py +150 -0
  4. fmp_data/alternative/endpoints.py +533 -0
  5. fmp_data/alternative/mapping.py +737 -0
  6. fmp_data/alternative/models.py +296 -0
  7. fmp_data/alternative/schema.py +165 -0
  8. fmp_data/base.py +316 -0
  9. fmp_data/client.py +269 -0
  10. fmp_data/company/__init__.py +40 -0
  11. fmp_data/company/client.py +238 -0
  12. fmp_data/company/endpoints.py +711 -0
  13. fmp_data/company/hints.py +77 -0
  14. fmp_data/company/mapping.py +1361 -0
  15. fmp_data/company/models.py +543 -0
  16. fmp_data/company/schema.py +132 -0
  17. fmp_data/config.py +207 -0
  18. fmp_data/economics/__init__.py +16 -0
  19. fmp_data/economics/client.py +52 -0
  20. fmp_data/economics/endpoints.py +164 -0
  21. fmp_data/economics/mapping.py +641 -0
  22. fmp_data/economics/models.py +75 -0
  23. fmp_data/economics/schema.py +91 -0
  24. fmp_data/exceptions.py +54 -0
  25. fmp_data/fundamental/__init__.py +40 -0
  26. fmp_data/fundamental/client.py +87 -0
  27. fmp_data/fundamental/endpoints.py +403 -0
  28. fmp_data/fundamental/mapping.py +867 -0
  29. fmp_data/fundamental/models.py +913 -0
  30. fmp_data/fundamental/schema.py +40 -0
  31. fmp_data/institutional/__init__.py +26 -0
  32. fmp_data/institutional/client.py +141 -0
  33. fmp_data/institutional/endpoints.py +321 -0
  34. fmp_data/institutional/mapping.py +749 -0
  35. fmp_data/institutional/models.py +301 -0
  36. fmp_data/institutional/schema.py +99 -0
  37. fmp_data/intelligence/__init__.py +58 -0
  38. fmp_data/intelligence/client.py +331 -0
  39. fmp_data/intelligence/endpoints.py +788 -0
  40. fmp_data/intelligence/mapping.py +1677 -0
  41. fmp_data/intelligence/models.py +707 -0
  42. fmp_data/intelligence/schema.py +57 -0
  43. fmp_data/investment/__init__.py +24 -0
  44. fmp_data/investment/client.py +104 -0
  45. fmp_data/investment/endpoints.py +241 -0
  46. fmp_data/investment/mapping.py +658 -0
  47. fmp_data/investment/models.py +220 -0
  48. fmp_data/investment/schema.py +106 -0
  49. fmp_data/lc/__init__.py +256 -0
  50. fmp_data/lc/config.py +88 -0
  51. fmp_data/lc/embedding.py +140 -0
  52. fmp_data/lc/hints.py +66 -0
  53. fmp_data/lc/mapping.py +107 -0
  54. fmp_data/lc/models.py +98 -0
  55. fmp_data/lc/registry.py +693 -0
  56. fmp_data/lc/utils.py +35 -0
  57. fmp_data/lc/validation.py +267 -0
  58. fmp_data/lc/vector_store.py +592 -0
  59. fmp_data/logger.py +358 -0
  60. fmp_data/market/__init__.py +18 -0
  61. fmp_data/market/client.py +106 -0
  62. fmp_data/market/endpoints.py +358 -0
  63. fmp_data/market/hints.py +22 -0
  64. fmp_data/market/mapping.py +854 -0
  65. fmp_data/market/models.py +310 -0
  66. fmp_data/market/schema.py +186 -0
  67. fmp_data/mcp/__init__.py +0 -0
  68. fmp_data/mcp/server.py +101 -0
  69. fmp_data/mcp/tool_loader.py +74 -0
  70. fmp_data/mcp/tools_manifest.py +17 -0
  71. fmp_data/models.py +265 -0
  72. fmp_data/rate_limit.py +136 -0
  73. fmp_data/schema.py +158 -0
  74. fmp_data/technical/__init__.py +28 -0
  75. fmp_data/technical/client.py +214 -0
  76. fmp_data/technical/endpoints.py +102 -0
  77. fmp_data/technical/mapping.py +452 -0
  78. fmp_data/technical/models.py +87 -0
  79. fmp_data/technical/schema.py +261 -0
  80. fmp_data-0.0.0.dist-info/METADATA +732 -0
  81. fmp_data-0.0.0.dist-info/RECORD +84 -0
  82. fmp_data-0.0.0.dist-info/WHEEL +4 -0
  83. fmp_data-0.0.0.dist-info/entry_points.txt +10 -0
  84. fmp_data-0.0.0.dist-info/licenses/LICENSE +21 -0
fmp_data/base.py ADDED
@@ -0,0 +1,316 @@
1
+ # fmp_data/base.py
2
+ import json
3
+ import logging
4
+ import time
5
+ import warnings
6
+ from typing import Any, TypeVar
7
+
8
+ import httpx
9
+ from pydantic import BaseModel
10
+ from tenacity import (
11
+ after_log,
12
+ before_sleep_log,
13
+ retry,
14
+ retry_if_exception_type,
15
+ stop_after_attempt,
16
+ wait_exponential,
17
+ )
18
+
19
+ from fmp_data.config import ClientConfig
20
+ from fmp_data.exceptions import (
21
+ AuthenticationError,
22
+ FMPError,
23
+ RateLimitError,
24
+ ValidationError,
25
+ )
26
+ from fmp_data.logger import FMPLogger, log_api_call
27
+ from fmp_data.models import Endpoint
28
+ from fmp_data.rate_limit import FMPRateLimiter, QuotaConfig
29
+
30
+ T = TypeVar("T", bound=BaseModel)
31
+
32
+ logger = FMPLogger().get_logger(__name__)
33
+
34
+
35
+ class BaseClient:
36
+ def __init__(self, config: ClientConfig) -> None:
37
+ """
38
+ Initialize the BaseClient with the provided configuration.
39
+ """
40
+ self.config = config
41
+ self.logger = FMPLogger().get_logger(__name__)
42
+ self.max_rate_limit_retries = getattr(config, "max_rate_limit_retries", 3)
43
+ self._rate_limit_retry_count = 0
44
+
45
+ # Configure logging based on config
46
+ FMPLogger().configure(self.config.logging)
47
+
48
+ self._setup_http_client()
49
+ self.logger.info(
50
+ "Initializing API client",
51
+ extra={"base_url": self.config.base_url, "timeout": self.config.timeout},
52
+ )
53
+
54
+ # Initialize rate limiter
55
+ self._rate_limiter = FMPRateLimiter(
56
+ QuotaConfig(
57
+ daily_limit=self.config.rate_limit.daily_limit,
58
+ requests_per_second=self.config.rate_limit.requests_per_second,
59
+ requests_per_minute=self.config.rate_limit.requests_per_minute,
60
+ )
61
+ )
62
+
63
+ def _setup_http_client(self) -> None:
64
+ """
65
+ Setup HTTP client with default configuration.
66
+ """
67
+ self.client = httpx.Client(
68
+ timeout=self.config.timeout,
69
+ follow_redirects=True,
70
+ headers={
71
+ "User-Agent": "FMP-Python-Client/1.0",
72
+ "Accept": "application/json",
73
+ },
74
+ )
75
+
76
+ def close(self) -> None:
77
+ """
78
+ Clean up resources (close the httpx client).
79
+ """
80
+ if hasattr(self, "client") and self.client is not None:
81
+ self.client.close()
82
+
83
+ def _handle_rate_limit(self, wait_time: float) -> None:
84
+ """
85
+ Handle rate limiting by waiting or raising an exception based on retry count.
86
+ """
87
+ self._rate_limit_retry_count += 1
88
+
89
+ if self._rate_limit_retry_count > self.max_rate_limit_retries:
90
+ self._rate_limit_retry_count = 0 # Reset for next request
91
+ raise RateLimitError(
92
+ f"Rate limit exceeded after "
93
+ f"{self.max_rate_limit_retries} retries. "
94
+ f"Please wait {wait_time:.1f} seconds",
95
+ retry_after=wait_time,
96
+ )
97
+
98
+ self.logger.warning(
99
+ f"Rate limit reached "
100
+ f"(attempt {self._rate_limit_retry_count}/{self.max_rate_limit_retries}), "
101
+ f"waiting {wait_time:.1f} seconds before retrying"
102
+ )
103
+ time.sleep(wait_time)
104
+
105
+ @retry(
106
+ stop=stop_after_attempt(3),
107
+ wait=wait_exponential(multiplier=1, min=4, max=10),
108
+ retry=retry_if_exception_type(
109
+ (httpx.TimeoutException, httpx.NetworkError, httpx.HTTPStatusError)
110
+ ),
111
+ before_sleep=before_sleep_log(logger, logging.WARNING),
112
+ after=after_log(logger, logging.INFO),
113
+ )
114
+ @log_api_call()
115
+ def request(self, endpoint: Endpoint[T], **kwargs: Any) -> T | list[T]:
116
+ """
117
+ Make request with rate limiting and retry logic.
118
+
119
+ Args:
120
+ endpoint: The Endpoint object describing the request (method, path, etc.).
121
+ **kwargs: Arbitrary keyword arguments passed as request parameters.
122
+
123
+ Returns:
124
+ Either a single Pydantic model of type T or a list of T.
125
+ """
126
+ # First, check if we're already over the rate limit
127
+ if not self._rate_limiter.should_allow_request():
128
+ wait_time = self._rate_limiter.get_wait_time()
129
+ raise RateLimitError(
130
+ f"Rate limit exceeded. Please wait {wait_time:.1f} seconds",
131
+ retry_after=wait_time,
132
+ )
133
+
134
+ self._rate_limit_retry_count = 0 # Reset counter at start of new request
135
+
136
+ try:
137
+ self._rate_limiter.record_request()
138
+
139
+ # Validate and process parameters
140
+ validated_params = endpoint.validate_params(kwargs)
141
+
142
+ # Build URL
143
+ url = endpoint.build_url(self.config.base_url, validated_params)
144
+
145
+ # Extract query parameters and add API key
146
+ query_params = endpoint.get_query_params(validated_params)
147
+ query_params["apikey"] = self.config.api_key
148
+
149
+ self.logger.debug(
150
+ f"Making request to {endpoint.name}",
151
+ extra={
152
+ "url": url,
153
+ "endpoint": endpoint.name,
154
+ "method": endpoint.method.value,
155
+ },
156
+ )
157
+
158
+ response = self.client.request(
159
+ endpoint.method.value, url, params=query_params
160
+ )
161
+
162
+ # Handle 429 responses from the API
163
+ if response.status_code == 429:
164
+ self._rate_limiter.handle_response(response.status_code, response.text)
165
+ wait_time = self._rate_limiter.get_wait_time()
166
+ raise RateLimitError(
167
+ f"Rate limit exceeded. Please wait {wait_time:.1f} seconds",
168
+ retry_after=wait_time,
169
+ )
170
+
171
+ data = self.handle_response(response)
172
+ return self._process_response(endpoint, data)
173
+
174
+ except Exception as e:
175
+ self.logger.error(
176
+ f"Request failed: {str(e)}",
177
+ extra={"endpoint": endpoint.name, "error": str(e)},
178
+ exc_info=True,
179
+ )
180
+ raise
181
+
182
+ def handle_response(self, response: httpx.Response) -> dict[str, Any] | list[Any]:
183
+ """
184
+ Handle API response and errors, returning dict or list from JSON.
185
+
186
+ Raises:
187
+ RateLimitError: If status is 429
188
+ AuthenticationError: If status is 401
189
+ ValidationError: If status is 400
190
+ FMPError: For other 4xx/5xx errors or invalid JSON
191
+ """
192
+ try:
193
+ response.raise_for_status()
194
+ data = response.json()
195
+ if not isinstance(data, dict | list):
196
+ raise FMPError(
197
+ f"Unexpected response type: {type(data)}. Expected dict or list.",
198
+ response={"data": data},
199
+ )
200
+ return data # Now mypy knows this is dict[str, Any] | list[Any]
201
+ except httpx.HTTPStatusError as e:
202
+ error_details: dict[str, Any] = {}
203
+ try:
204
+ error_details = e.response.json()
205
+ except json.JSONDecodeError:
206
+ error_details["raw_content"] = e.response.content.decode()
207
+
208
+ if e.response.status_code == 429:
209
+ wait_time = self._rate_limiter.get_wait_time()
210
+ raise RateLimitError(
211
+ f"Rate limit exceeded. Please wait {wait_time:.1f} seconds",
212
+ status_code=429,
213
+ response=error_details,
214
+ retry_after=wait_time,
215
+ ) from e
216
+ elif e.response.status_code == 401:
217
+ raise AuthenticationError(
218
+ "Invalid API key or authentication failed",
219
+ status_code=401,
220
+ response=error_details,
221
+ ) from e
222
+ elif e.response.status_code == 400:
223
+ raise ValidationError(
224
+ f"Invalid request parameters: {error_details}",
225
+ status_code=400,
226
+ response=error_details,
227
+ ) from e
228
+ else:
229
+ raise FMPError(
230
+ f"HTTP {e.response.status_code} error occurred: {error_details}",
231
+ status_code=e.response.status_code,
232
+ response=error_details,
233
+ ) from e
234
+ except json.JSONDecodeError as e:
235
+ raise FMPError(
236
+ f"Invalid JSON response from API: {str(e)}",
237
+ response={"raw_content": response.content.decode()},
238
+ ) from e
239
+
240
+ @staticmethod
241
+ def _process_response(endpoint: Endpoint[T], data: Any) -> T | list[T]:
242
+ """
243
+ Process the response data with warnings, returning T or list[T].
244
+ """
245
+ if isinstance(data, dict):
246
+ # Check for error messages
247
+ if "Error Message" in data:
248
+ raise FMPError(data["Error Message"])
249
+ if "message" in data:
250
+ raise FMPError(data["message"])
251
+ if "error" in data:
252
+ raise FMPError(data["error"])
253
+
254
+ if isinstance(data, list):
255
+ processed_items: list[T] = []
256
+ for item in data:
257
+ with warnings.catch_warnings(record=True) as w:
258
+ warnings.simplefilter("always")
259
+ if isinstance(item, dict):
260
+ processed_item = endpoint.response_model.model_validate(item)
261
+ else:
262
+ # If it's not a dict, try to feed it into the first field
263
+ model = endpoint.response_model
264
+ try:
265
+ first_field = next(iter(model.__annotations__))
266
+ field_info = model.model_fields[first_field]
267
+ field_name = field_info.alias or first_field
268
+ processed_item = model.model_validate({field_name: item})
269
+ except (StopIteration, KeyError, AttributeError) as exc:
270
+ raise ValueError(
271
+ f"Invalid model structure for {model.__name__}"
272
+ ) from exc
273
+ for warning in w:
274
+ logger.warning(f"Validation warning: {warning.message}")
275
+ processed_items.append(processed_item)
276
+ return processed_items
277
+ return endpoint.response_model.model_validate(data)
278
+
279
+ async def request_async(self, endpoint: Endpoint[T], **kwargs: Any) -> T | list[T]:
280
+ """
281
+ Make async request with rate limiting, returning T or list[T].
282
+ """
283
+ validated_params = endpoint.validate_params(kwargs)
284
+ url = endpoint.build_url(self.config.base_url, validated_params)
285
+ query_params = endpoint.get_query_params(validated_params)
286
+ query_params["apikey"] = self.config.api_key
287
+
288
+ try:
289
+ async with httpx.AsyncClient(
290
+ timeout=self.config.timeout,
291
+ follow_redirects=True,
292
+ headers={
293
+ "User-Agent": "FMP-Python-Client/1.0",
294
+ "Accept": "application/json",
295
+ },
296
+ ) as client:
297
+ response = await client.request(
298
+ endpoint.method.value, url, params=query_params
299
+ )
300
+ data = self.handle_response(response)
301
+ return self._process_response(endpoint, data)
302
+ except Exception as e:
303
+ self.logger.error(f"Async request failed: {str(e)}")
304
+ raise
305
+
306
+
307
+ class EndpointGroup:
308
+ """Abstract base class for endpoint groups"""
309
+
310
+ def __init__(self, client: BaseClient) -> None:
311
+ self._client = client
312
+
313
+ @property
314
+ def client(self) -> BaseClient:
315
+ """Get the client instance."""
316
+ return self._client
fmp_data/client.py ADDED
@@ -0,0 +1,269 @@
1
+ # client.py
2
+ import logging
3
+ import types
4
+ import warnings
5
+
6
+ from pydantic import ValidationError as PydanticValidationError
7
+
8
+ from fmp_data.alternative import AlternativeMarketsClient
9
+ from fmp_data.base import BaseClient
10
+ from fmp_data.company.client import CompanyClient
11
+ from fmp_data.config import ClientConfig, LoggingConfig, LogHandlerConfig
12
+ from fmp_data.economics import EconomicsClient
13
+ from fmp_data.exceptions import ConfigError
14
+ from fmp_data.fundamental import FundamentalClient
15
+ from fmp_data.institutional import InstitutionalClient
16
+ from fmp_data.intelligence import MarketIntelligenceClient
17
+ from fmp_data.investment import InvestmentClient
18
+ from fmp_data.logger import FMPLogger
19
+ from fmp_data.market import MarketClient
20
+ from fmp_data.technical import TechnicalClient
21
+
22
+
23
+ class FMPDataClient(BaseClient):
24
+ """Main client for FMP Data API"""
25
+
26
+ def __init__(
27
+ self,
28
+ api_key: str | None = None,
29
+ timeout: int = 30,
30
+ max_retries: int = 3,
31
+ base_url: str = "https://financialmodelingprep.com/api",
32
+ config: ClientConfig | None = None,
33
+ debug: bool = False,
34
+ ):
35
+ self._initialized: bool = False
36
+ self._logger: logging.Logger | None = None
37
+ self._company: CompanyClient | None = None
38
+ self._market: MarketClient | None = None
39
+ self._fundamental: FundamentalClient | None = None
40
+ self._technical: TechnicalClient | None = None
41
+ self._intelligence: MarketIntelligenceClient | None = None
42
+ self._institutional: InstitutionalClient | None = None
43
+ self._investment: InvestmentClient | None = None
44
+ self._alternative: AlternativeMarketsClient | None = None
45
+ self._economics: EconomicsClient | None = None
46
+
47
+ if not api_key and (config is None or not config.api_key):
48
+ raise ConfigError("Invalid client configuration: API key is required")
49
+
50
+ try:
51
+ if config is not None:
52
+ self._config = config
53
+ else:
54
+ logging_config = LoggingConfig(
55
+ level="DEBUG" if debug else "INFO",
56
+ handlers={
57
+ "console": LogHandlerConfig(
58
+ class_name="StreamHandler",
59
+ level="DEBUG" if debug else "INFO",
60
+ format=(
61
+ "%(asctime)s - %(levelname)s -"
62
+ " %(name)s - %(message)s"
63
+ ),
64
+ )
65
+ },
66
+ )
67
+
68
+ try:
69
+ self._config = ClientConfig(
70
+ api_key=api_key or "", # Handle None case
71
+ timeout=timeout,
72
+ max_retries=max_retries,
73
+ base_url=base_url,
74
+ logging=logging_config,
75
+ )
76
+ except PydanticValidationError as e:
77
+ raise ConfigError("Invalid client configuration") from e
78
+
79
+ FMPLogger().configure(self._config.logging)
80
+ self._logger = FMPLogger().get_logger(__name__)
81
+
82
+ super().__init__(self._config)
83
+ self._initialized = True
84
+
85
+ except Exception as e:
86
+ if not hasattr(self, "_logger") or self._logger is None:
87
+ self._logger = FMPLogger().get_logger(__name__)
88
+ self._logger.error(f"Failed to initialize client: {str(e)}")
89
+ raise
90
+
91
+ @classmethod
92
+ def from_env(cls, debug: bool = False) -> "FMPDataClient":
93
+ """
94
+ Create client instance from environment variables
95
+
96
+ Args:
97
+ debug: Enable debug logging if True
98
+ """
99
+ config = ClientConfig.from_env()
100
+ if debug:
101
+ config.logging.level = "DEBUG"
102
+ if "console" in config.logging.handlers:
103
+ config.logging.handlers["console"].level = "DEBUG"
104
+
105
+ return cls(config=config)
106
+
107
+ def __enter__(self) -> "FMPDataClient":
108
+ """Context manager enter"""
109
+ if not self._initialized:
110
+ self._setup_http_client()
111
+ return self
112
+
113
+ def __exit__(
114
+ self,
115
+ exc_type: type[BaseException] | None,
116
+ exc_val: BaseException | None,
117
+ exc_tb: types.TracebackType | None,
118
+ ) -> None:
119
+ """
120
+ Context manager exit
121
+
122
+ Args:
123
+ exc_type: Exception type if an error occurred
124
+ exc_val: Exception value if an error occurred
125
+ exc_tb: Exception traceback if an error occurred
126
+ """
127
+ self.close()
128
+ if exc_type is not None and exc_val is not None and self.logger:
129
+ self.logger.error(
130
+ "Error in context manager",
131
+ extra={"error_type": exc_type.__name__, "error": str(exc_val)},
132
+ exc_info=True, # Use True instead of tuple to let logger handle it
133
+ )
134
+
135
+ def close(self) -> None:
136
+ """Clean up resources"""
137
+ try:
138
+ if hasattr(self, "client") and self.client is not None:
139
+ self.client.close()
140
+ if hasattr(self, "_initialized") and self._initialized:
141
+ logger = getattr(self, "_logger", None)
142
+ if logger is not None:
143
+ logger.info("FMP Data client closed")
144
+ except Exception as e:
145
+ # Log if possible, but don't raise
146
+ logger = getattr(self, "_logger", None)
147
+ if logger is not None:
148
+ logger.error(f"Error during cleanup: {str(e)}")
149
+
150
+ def __del__(self) -> None:
151
+ """Destructor that ensures resources are cleaned up"""
152
+ try:
153
+ if hasattr(self, "_initialized") and self._initialized:
154
+ self.close()
155
+ except (Exception, BaseException) as e:
156
+ # Suppress any errors during cleanup
157
+ warnings.warn(
158
+ f"Error during FMPDataClient cleanup: {str(e)}",
159
+ ResourceWarning,
160
+ stacklevel=2,
161
+ )
162
+
163
+ @property
164
+ def company(self) -> CompanyClient:
165
+ """Get or create the company client instance"""
166
+ if not self._initialized:
167
+ raise RuntimeError("Client not properly initialized")
168
+
169
+ if self._company is None:
170
+ if self.logger:
171
+ self.logger.debug("Initializing company client")
172
+ self._company = CompanyClient(self)
173
+ return self._company
174
+
175
+ @property
176
+ def market(self) -> MarketClient:
177
+ """Get or create the market data client instance"""
178
+ if not self._initialized:
179
+ raise RuntimeError("Client not properly initialized")
180
+
181
+ if self._market is None:
182
+ if self.logger:
183
+ self.logger.debug("Initializing market client")
184
+ self._market = MarketClient(self)
185
+ return self._market
186
+
187
+ @property
188
+ def fundamental(self) -> FundamentalClient:
189
+ """Get or create the fundamental client instance"""
190
+ if not self._initialized:
191
+ raise RuntimeError("Client not properly initialized")
192
+
193
+ if self._fundamental is None:
194
+ if self.logger:
195
+ self.logger.debug("Initializing fundamental client")
196
+ self._fundamental = FundamentalClient(self)
197
+ return self._fundamental
198
+
199
+ @property
200
+ def technical(self) -> TechnicalClient:
201
+ """Get or create the technical analysis client instance"""
202
+ if not self._initialized:
203
+ raise RuntimeError("Client not properly initialized")
204
+
205
+ if self._technical is None:
206
+ if self.logger:
207
+ self.logger.debug("Initializing technical analysis client")
208
+ self._technical = TechnicalClient(self)
209
+ return self._technical
210
+
211
+ @property
212
+ def intelligence(self) -> MarketIntelligenceClient:
213
+ """Get or create the market intelligence client instance"""
214
+ if not self._initialized:
215
+ raise RuntimeError("Client not properly initialized")
216
+
217
+ if self._intelligence is None:
218
+ if self.logger:
219
+ self.logger.debug("Initializing market intelligence client")
220
+ self._intelligence = MarketIntelligenceClient(self)
221
+ return self._intelligence
222
+
223
+ @property
224
+ def institutional(self) -> InstitutionalClient:
225
+ """Get or create the institutional activity client instance"""
226
+ if not self._initialized:
227
+ raise RuntimeError("Client not properly initialized")
228
+
229
+ if self._institutional is None:
230
+ if self.logger:
231
+ self.logger.debug("Initializing institutional activity client")
232
+ self._institutional = InstitutionalClient(self)
233
+ return self._institutional
234
+
235
+ @property
236
+ def investment(self) -> InvestmentClient:
237
+ """Get or create the investment products client instance"""
238
+ if not self._initialized:
239
+ raise RuntimeError("Client not properly initialized")
240
+
241
+ if self._investment is None:
242
+ if self.logger:
243
+ self.logger.debug("Initializing investment products client")
244
+ self._investment = InvestmentClient(self)
245
+ return self._investment
246
+
247
+ @property
248
+ def alternative(self) -> AlternativeMarketsClient:
249
+ """Get or create the alternative markets client instance"""
250
+ if not self._initialized:
251
+ raise RuntimeError("Client not properly initialized")
252
+
253
+ if self._alternative is None:
254
+ if self.logger:
255
+ self.logger.debug("Initializing alternative markets client")
256
+ self._alternative = AlternativeMarketsClient(self)
257
+ return self._alternative
258
+
259
+ @property
260
+ def economics(self) -> EconomicsClient:
261
+ """Get or create the economics data client instance"""
262
+ if not self._initialized:
263
+ raise RuntimeError("Client not properly initialized")
264
+
265
+ if self._economics is None:
266
+ if self.logger:
267
+ self.logger.debug("Initializing economics data client")
268
+ self._economics = EconomicsClient(self)
269
+ return self._economics
@@ -0,0 +1,40 @@
1
+ # company/__init__.py
2
+ from __future__ import annotations
3
+
4
+ from fmp_data.company.client import CompanyClient
5
+ from fmp_data.company.models import (
6
+ CompanyCoreInformation,
7
+ CompanyExecutive,
8
+ CompanyNote,
9
+ CompanyProfile,
10
+ EmployeeCount,
11
+ ExecutiveCompensation,
12
+ GeographicRevenueSegment,
13
+ HistoricalPrice,
14
+ HistoricalShareFloat,
15
+ IntradayPrice,
16
+ ProductRevenueSegment,
17
+ Quote,
18
+ ShareFloat,
19
+ SimpleQuote,
20
+ SymbolChange,
21
+ )
22
+
23
+ __all__ = [
24
+ "Quote",
25
+ "SimpleQuote",
26
+ "HistoricalPrice",
27
+ "IntradayPrice",
28
+ "CompanyClient",
29
+ "CompanyProfile",
30
+ "CompanyCoreInformation",
31
+ "CompanyExecutive",
32
+ "CompanyNote",
33
+ "EmployeeCount",
34
+ "ExecutiveCompensation",
35
+ "ShareFloat",
36
+ "HistoricalShareFloat",
37
+ "GeographicRevenueSegment",
38
+ "ProductRevenueSegment",
39
+ "SymbolChange",
40
+ ]