fmp-stable-api 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Vimal Seshadri Raguraman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include FMP/fmp_endpoints.json
2
+ include pyproject.toml
3
+ include setup.py
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.2
2
+ Name: fmp-stable-api
3
+ Version: 0.0.1
4
+ Summary: Python client library for the Financial Modeling Prep API
5
+ Home-page: https://github.com/Vimal-Seshadri-Raguraman/FMP
6
+ Author: Vimal Seshadri Raguraman
7
+ Project-URL: GitHub, https://github.com/Vimal-Seshadri-Raguraman/FMP
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: Topic :: Office/Business :: Financial
14
+ Requires-Python: >=3.6
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE.txt
17
+ Requires-Dist: requests
18
+ Requires-Dist: ratelimit
19
+ Requires-Dist: websockets
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest; extra == "dev"
22
+ Requires-Dist: twine; extra == "dev"
23
+ Dynamic: author
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: project-url
29
+ Dynamic: provides-extra
30
+ Dynamic: requires-dist
31
+ Dynamic: requires-python
32
+ Dynamic: summary
33
+
34
+ # FMP Client Library
35
+ A Python client library for the [Financial Modeling Prep (FMP) API](https://financialmodelingprep.com/). This library provides both synchronous REST API and asynchronous WebSocket support, all in a modular, easy-to-use package.
36
+
37
+ The client is built to work with the [**Stable** version](https://site.financialmodelingprep.com/developer/docs/stable) of the Financial Modeling Prep (FMP) API. All endpoints are configured to use the stable base URL:
38
+ ```arduino
39
+ https://financialmodelingprep.com/stable/
40
+ ```
41
+ The endpoints available in this stable version are defined in the [fmp_endpoints.json](https://github.com/Vimal-Seshadri-Raguraman/FMP/blob/main/FMP/fmp_endpoints.json) file
42
+
43
+ ## Features
44
+
45
+ - **Unified API Client:**
46
+ Dynamically access FMP endpoints using Pythonic method names.
47
+
48
+ - **Rate-Limited HTTP Session:**
49
+ Built-in rate limiting using the `ratelimit` library and robust error handling with logging.
50
+
51
+ - **Dynamic Endpoint Methods:**
52
+ Automatically attaches methods based on your configuration file (`fmp_endpoints.json`).
53
+
54
+ - **Asynchronous WebSocket Clients:**
55
+ Separate clients for stocks, crypto, and forex data available in the `fmp_websockets` subpackage.
56
+
57
+ - **Flexible Configuration and Logging:**
58
+ Easily configure your endpoints with a JSON file and log messages to a file in the same directory as your main script.
59
+
60
+ ## Installation
61
+
62
+ Clone the repository and install the dependencies:
63
+
64
+ ```bash
65
+ git clone https://github.com/Vimal-Seshadri-Raguraman/FMP.git
66
+ cd FMP
67
+ pip install -r requirements.txt
68
+ python setup.py install
69
+ ```
70
+ Alternatively, install directly from Github:
71
+ ```bash
72
+ pip install git+https://github.com/Vimal-Seshadri-Raguraman/FMP.git
73
+ ```
74
+
75
+ ## Usage
76
+ ### API Client
77
+ Initialize the client with your API Key and call any endpoint dynmically:
78
+ ```python
79
+ from fmp import FMP
80
+
81
+ # Initialize client with your API Key
82
+ client = fmp(api_key = "YOUR_API_KEY")
83
+
84
+ # Call an endpoint (e.g., "Profile")
85
+ response = client.profile(symbol = "AAPL")
86
+ print("Status Code:", response.status_code)
87
+ print("Profile Data:", response.json())
88
+ ```
89
+ #### Dynmic Methods and Help
90
+ The client automatically attaches methods based on the JSON configuration. To see available endpoints and their parameters:
91
+ ```python
92
+ # General help for all endpoints
93
+ print(client.help())
94
+
95
+ # Detailed help for a specific endpoint (e.g., "profile")
96
+ print(client.help("profile"))
97
+
98
+ # Generate a manual page of endpoints
99
+ man_doc = client.man_page() # Create a text file
100
+ print(man_doc)
101
+ ```
102
+ ### WebSocket Clients
103
+ The library includes asynchronous WebSocket clients for real-time data.
104
+ #### Stock WebSocket Example
105
+ ```python
106
+ import asyncio
107
+ from fmp import StockWebsockets
108
+
109
+ async def run_stock_ws():
110
+ stock_ws = StockWebsockets(tickers=["AAPL", "MSFT"], api_key="YOUR_API_KEY")
111
+ async for message in stock_ws.run():
112
+ print("Stock WebSocket Message:", message)
113
+
114
+ asyncio.run(run_stock_ws())
115
+ ```
116
+ #### Crypto and Forex WebSocket Example
117
+ ```python
118
+ import asyncio
119
+ from fmp import CryptoWebsockets, ForexWebsockets
120
+
121
+ async def run_crypto_ws():
122
+ crypto_ws = CryptoWebsockets(tickers=["BTCUSD", "ETHUSD"], api_key="YOUR_API_KEY")
123
+ async for message in crypto_ws.run():
124
+ print("Crypto WebSocket Message:", message)
125
+
126
+ async def run_forex_ws():
127
+ forex_ws = ForexWebsockets(pairs=["EURUSD", "GBPUSD"], api_key="YOUR_API_KEY")
128
+ async for message in forex_ws.run():
129
+ print("Forex WebSocket Message:", message)
130
+
131
+ # Run one example at a time:
132
+ asyncio.run(run_crypto_ws())
133
+ # asyncio.run(run_forex_ws())
134
+ ```
135
+ ## Configuration
136
+ By default, the client loads endpoint configuration from ```fmp_endpoints.json``` located in the same directory as ```fmp_client.py```. To use a custom configuration file:
137
+ ```python
138
+ client = FMP(api_key="YOUR_API_KEY", config_file="path/to/your_config.json")
139
+ ```
140
+ If you update the configuration file during runtime, force a reload with:
141
+ ```python
142
+ client.config_manager.reload()
143
+ ```
144
+ ## Logging
145
+ Logging is enabled by default and creates a log file (```fmp.log```) in the directory where your main script is located. To disable logging:
146
+ ```python
147
+ client = FMP(api_key="YOUR_API_KEY", log_enabled=False)
148
+ ```
149
+ ## Contributing
150
+ Contributions, bug reports, and feature requests are welcome. Please open an issue or submit a pull request.
151
+ ## License
152
+ This project is licensed under the MIT License. See the [```LICENSE.md```](https://github.com/Vimal-Seshadri-Raguraman/FMP/blob/main/LICENSE) file for details.
@@ -0,0 +1,119 @@
1
+ # FMP Client Library
2
+ A Python client library for the [Financial Modeling Prep (FMP) API](https://financialmodelingprep.com/). This library provides both synchronous REST API and asynchronous WebSocket support, all in a modular, easy-to-use package.
3
+
4
+ The client is built to work with the [**Stable** version](https://site.financialmodelingprep.com/developer/docs/stable) of the Financial Modeling Prep (FMP) API. All endpoints are configured to use the stable base URL:
5
+ ```arduino
6
+ https://financialmodelingprep.com/stable/
7
+ ```
8
+ The endpoints available in this stable version are defined in the [fmp_endpoints.json](https://github.com/Vimal-Seshadri-Raguraman/FMP/blob/main/FMP/fmp_endpoints.json) file
9
+
10
+ ## Features
11
+
12
+ - **Unified API Client:**
13
+ Dynamically access FMP endpoints using Pythonic method names.
14
+
15
+ - **Rate-Limited HTTP Session:**
16
+ Built-in rate limiting using the `ratelimit` library and robust error handling with logging.
17
+
18
+ - **Dynamic Endpoint Methods:**
19
+ Automatically attaches methods based on your configuration file (`fmp_endpoints.json`).
20
+
21
+ - **Asynchronous WebSocket Clients:**
22
+ Separate clients for stocks, crypto, and forex data available in the `fmp_websockets` subpackage.
23
+
24
+ - **Flexible Configuration and Logging:**
25
+ Easily configure your endpoints with a JSON file and log messages to a file in the same directory as your main script.
26
+
27
+ ## Installation
28
+
29
+ Clone the repository and install the dependencies:
30
+
31
+ ```bash
32
+ git clone https://github.com/Vimal-Seshadri-Raguraman/FMP.git
33
+ cd FMP
34
+ pip install -r requirements.txt
35
+ python setup.py install
36
+ ```
37
+ Alternatively, install directly from Github:
38
+ ```bash
39
+ pip install git+https://github.com/Vimal-Seshadri-Raguraman/FMP.git
40
+ ```
41
+
42
+ ## Usage
43
+ ### API Client
44
+ Initialize the client with your API Key and call any endpoint dynmically:
45
+ ```python
46
+ from fmp import FMP
47
+
48
+ # Initialize client with your API Key
49
+ client = fmp(api_key = "YOUR_API_KEY")
50
+
51
+ # Call an endpoint (e.g., "Profile")
52
+ response = client.profile(symbol = "AAPL")
53
+ print("Status Code:", response.status_code)
54
+ print("Profile Data:", response.json())
55
+ ```
56
+ #### Dynmic Methods and Help
57
+ The client automatically attaches methods based on the JSON configuration. To see available endpoints and their parameters:
58
+ ```python
59
+ # General help for all endpoints
60
+ print(client.help())
61
+
62
+ # Detailed help for a specific endpoint (e.g., "profile")
63
+ print(client.help("profile"))
64
+
65
+ # Generate a manual page of endpoints
66
+ man_doc = client.man_page() # Create a text file
67
+ print(man_doc)
68
+ ```
69
+ ### WebSocket Clients
70
+ The library includes asynchronous WebSocket clients for real-time data.
71
+ #### Stock WebSocket Example
72
+ ```python
73
+ import asyncio
74
+ from fmp import StockWebsockets
75
+
76
+ async def run_stock_ws():
77
+ stock_ws = StockWebsockets(tickers=["AAPL", "MSFT"], api_key="YOUR_API_KEY")
78
+ async for message in stock_ws.run():
79
+ print("Stock WebSocket Message:", message)
80
+
81
+ asyncio.run(run_stock_ws())
82
+ ```
83
+ #### Crypto and Forex WebSocket Example
84
+ ```python
85
+ import asyncio
86
+ from fmp import CryptoWebsockets, ForexWebsockets
87
+
88
+ async def run_crypto_ws():
89
+ crypto_ws = CryptoWebsockets(tickers=["BTCUSD", "ETHUSD"], api_key="YOUR_API_KEY")
90
+ async for message in crypto_ws.run():
91
+ print("Crypto WebSocket Message:", message)
92
+
93
+ async def run_forex_ws():
94
+ forex_ws = ForexWebsockets(pairs=["EURUSD", "GBPUSD"], api_key="YOUR_API_KEY")
95
+ async for message in forex_ws.run():
96
+ print("Forex WebSocket Message:", message)
97
+
98
+ # Run one example at a time:
99
+ asyncio.run(run_crypto_ws())
100
+ # asyncio.run(run_forex_ws())
101
+ ```
102
+ ## Configuration
103
+ By default, the client loads endpoint configuration from ```fmp_endpoints.json``` located in the same directory as ```fmp_client.py```. To use a custom configuration file:
104
+ ```python
105
+ client = FMP(api_key="YOUR_API_KEY", config_file="path/to/your_config.json")
106
+ ```
107
+ If you update the configuration file during runtime, force a reload with:
108
+ ```python
109
+ client.config_manager.reload()
110
+ ```
111
+ ## Logging
112
+ Logging is enabled by default and creates a log file (```fmp.log```) in the directory where your main script is located. To disable logging:
113
+ ```python
114
+ client = FMP(api_key="YOUR_API_KEY", log_enabled=False)
115
+ ```
116
+ ## Contributing
117
+ Contributions, bug reports, and feature requests are welcome. Please open an issue or submit a pull request.
118
+ ## License
119
+ This project is licensed under the MIT License. See the [```LICENSE.md```](https://github.com/Vimal-Seshadri-Raguraman/FMP/blob/main/LICENSE) file for details.
@@ -0,0 +1,35 @@
1
+ """
2
+ Financial Modeling Prep Client Library
3
+
4
+ Exposes:
5
+ FMP: Unified REST API client.
6
+ Logger: Logging utility.
7
+ ConfigManager: Configuration loader.
8
+ Session: HTTP session with rate limiting.
9
+ StockWebsockets: WebSocket client for stock data.
10
+ CryptoWebsockets: WebSocket client for crypto data.
11
+ ForexWebsockets: WebSocket client for forex data.
12
+ """
13
+
14
+ from .logger import Logger
15
+ from .config_manager import ConfigManager
16
+ from .dynamic import create_endpoint_method, attach_dynamic_functions
17
+ from .session import Session
18
+ from .fmp_client import FMP
19
+
20
+ from .fmp_websockets.stock_websocket import StockWebsockets
21
+ from .fmp_websockets.crypto_websocket import CryptoWebsockets
22
+ from .fmp_websockets.forex_websocket import ForexWebsockets
23
+
24
+ __all__ = [
25
+ "FMP",
26
+ "Logger",
27
+ "ConfigManager",
28
+ "Session",
29
+ "StockWebsockets",
30
+ "CryptoWebsockets",
31
+ "ForexWebsockets",
32
+ ]
33
+
34
+ __version__ = "0.0.1"
35
+ __author__ = "Vimal Seshadri Raguraman"
@@ -0,0 +1,34 @@
1
+ import json
2
+ from typing import Dict, Any, Optional
3
+ import importlib.resources as pkg_resources
4
+
5
+ class ConfigManager:
6
+ """
7
+ Loads and caches a JSON configuration file.
8
+ If no config_file is provided, loads the default configuration
9
+ from the package resource fmp_endpoints.json.
10
+ """
11
+ def __init__(self, config_file: Optional[str] = None) -> None:
12
+ self.config_file = config_file
13
+ self._config: Optional[Dict[str, Any]] = None
14
+
15
+ def get(self) -> Dict[str, Any]:
16
+ if self._config is None:
17
+ if self.config_file is None:
18
+ # Load default configuration from package resources.
19
+ with pkg_resources.open_text("fmp", "fmp_endpoints.json") as f:
20
+ self._config = json.load(f)
21
+ else:
22
+ with open(self.config_file, "r") as f:
23
+ self._config = json.load(f)
24
+ return self._config
25
+
26
+ def reload(self) -> Dict[str, Any]:
27
+ """Force reload of the configuration file."""
28
+ if self.config_file is None:
29
+ with pkg_resources.open_text("fmp", "fmp_endpoints.json") as f:
30
+ self._config = json.load(f)
31
+ else:
32
+ with open(self.config_file, "r") as f:
33
+ self._config = json.load(f)
34
+ return self._config
@@ -0,0 +1,26 @@
1
+ from typing import Callable, Any
2
+
3
+ def create_endpoint_method(ep: str) -> Callable:
4
+ """
5
+ Returns an endpoint method bound to a specific API endpoint.
6
+ Renames parameters (e.g. 'start_date' to 'from') before calling the client.
7
+ """
8
+ def method(self, *args, **kwargs):
9
+ if 'start_date' in kwargs:
10
+ kwargs['from'] = kwargs.pop('start_date')
11
+ elif 'from_' in kwargs:
12
+ kwargs['from'] = kwargs.pop('from_')
13
+ if 'end_date' in kwargs:
14
+ kwargs['to'] = kwargs.pop('end_date')
15
+ elif 'to_' in kwargs:
16
+ kwargs['to'] = kwargs.pop('to_')
17
+ return self.call(ep, **kwargs)
18
+ return method
19
+
20
+ def attach_dynamic_functions(client: Any) -> None:
21
+ """
22
+ Attaches dynamic endpoint methods to the client.
23
+ """
24
+ for ep in client.endpoints.keys():
25
+ func_name = ep.replace("-", "_")
26
+ setattr(client.__class__, func_name, create_endpoint_method(ep))
@@ -0,0 +1,118 @@
1
+ import os
2
+ import sys
3
+ import urllib.parse
4
+ from typing import Optional, Dict, Any
5
+ import requests
6
+
7
+ from .config_manager import ConfigManager
8
+ from .logger import Logger
9
+ from .session import Session
10
+ from .dynamic import attach_dynamic_functions
11
+
12
+ class FMP:
13
+ """
14
+ Unified Financial Modeling Prep API client.
15
+
16
+ Combines configuration management, HTTP session handling, dynamic endpoint methods,
17
+ and helper functions into one class.
18
+
19
+ Example:
20
+ client = FMP(api_key="YOUR_API_KEY")
21
+ response = client.profile(symbol="AAPL")
22
+ print(response.json())
23
+ """
24
+ def __init__(self,
25
+ api_key: str,
26
+ config_file: Optional[str] = None,
27
+ base_url: Optional[str] = None,
28
+ logger: Optional[Logger] = None,
29
+ log_enabled: bool = True) -> None:
30
+ self.api_key = api_key
31
+ # Let ConfigManager load the default config from package resources if config_file is None.
32
+ self.config_manager = ConfigManager(config_file)
33
+ self.config = self.config_manager.get()
34
+ self.base_url = base_url or self.config.get("base_url")
35
+ if logger is None:
36
+ logger = Logger("FMP", enabled=log_enabled)
37
+ self.logger = logger
38
+ self.session = Session(api_key, logger=logger)
39
+ self.endpoints: Dict[str, Any] = self.config.get("endpoints", {})
40
+ attach_dynamic_functions(self)
41
+
42
+ def call(self, endpoint_name: str, **kwargs) -> requests.Response:
43
+ if endpoint_name not in self.endpoints:
44
+ raise ValueError(f"Endpoint '{endpoint_name}' not found in configuration.")
45
+ endpoint_info = self.endpoints[endpoint_name]
46
+ url = urllib.parse.urljoin(self.base_url, endpoint_info["path"])
47
+ allowed = endpoint_info.get("params", {}).keys()
48
+ params = {k: v for k, v in kwargs.items() if k in allowed}
49
+ params["apikey"] = self.api_key
50
+ return self.session.get(url, params=params)
51
+
52
+ def help(self, endpoint: Optional[str] = None) -> str:
53
+ """
54
+ Returns help information for endpoints.
55
+ """
56
+ doc = ("FMP API Client Help\n\nUsage:\n client.endpoint_name(param1=value1, ...)\n"
57
+ "Example:\n client.profile(symbol='AAPL')\n\n")
58
+ if endpoint:
59
+ target = next((key for key in self.endpoints if key.replace("-", "_") == endpoint), None)
60
+ if not target:
61
+ doc += f"Endpoint '{endpoint}' not found.\n"
62
+ else:
63
+ config = self.endpoints[target]
64
+ doc += f"Help for endpoint '{target}':\n Path: {config.get('path')}\n"
65
+ params = config.get("params", {})
66
+ if params:
67
+ doc += " Allowed parameters:\n"
68
+ for param, details in params.items():
69
+ required = details.get("required", False)
70
+ param_type = details.get("type", "unknown")
71
+ example = details.get("example", "")
72
+ doc += f" - {param} (required: {required}, type: {param_type}, example: {example})\n"
73
+ else:
74
+ doc += " No parameters defined.\n"
75
+ else:
76
+ for ep, config in self.endpoints.items():
77
+ func_name = ep.replace("-", "_")
78
+ doc += f"\nEndpoint: {func_name}\n Path: {config.get('path')}\n"
79
+ params = config.get("params", {})
80
+ if params:
81
+ doc += " Allowed parameters:\n"
82
+ for param, details in params.items():
83
+ required = details.get("required", False)
84
+ param_type = details.get("type", "unknown")
85
+ example = details.get("example", "")
86
+ doc += f" - {param} (required: {required}, type: {param_type}, example: {example})\n"
87
+ else:
88
+ doc += " No parameters defined.\n"
89
+ doc += "\n"
90
+ return doc
91
+
92
+ def man_page(self, just_name: bool = False, filename: str = "FMP_man_page.txt") -> str:
93
+ main_dir = (os.path.dirname(sys.modules["__main__"].__file__)
94
+ if "__main__" in sys.modules and hasattr(sys.modules["__main__"], "__file__")
95
+ else os.getcwd())
96
+ doc_file = os.path.join(main_dir, filename)
97
+ if os.path.exists(doc_file):
98
+ os.remove(doc_file)
99
+ output = "Manual Page for FMP API Client Endpoints:\n"
100
+ for ep, config in self.endpoints.items():
101
+ func_name = ep.replace("-", "_")
102
+ if just_name:
103
+ output += f"\n\t{func_name}"
104
+ else:
105
+ output += f"\n\nEndpoint: {func_name}\n Path: {config.get('path')}\n"
106
+ params = config.get("params", {})
107
+ if params:
108
+ output += " Allowed parameters:\n"
109
+ for param, details in params.items():
110
+ required = details.get("required", False)
111
+ param_type = details.get("type", "unknown")
112
+ example = details.get("example", "")
113
+ output += f" - {param} (required: {required}, type: {param_type}, example: {example})\n"
114
+ else:
115
+ output += " No parameters defined.\n"
116
+ with open(doc_file, "w") as f:
117
+ f.write(output)
118
+ return output
@@ -0,0 +1,9 @@
1
+ from .stock_websocket import StockWebsockets
2
+ from .crypto_websocket import CryptoWebsockets
3
+ from .forex_websocket import ForexWebsockets
4
+
5
+ __all__ = [
6
+ "StockWebsockets",
7
+ "CryptoWebsockets",
8
+ "ForexWebsockets",
9
+ ]
@@ -0,0 +1,58 @@
1
+ import json
2
+ import asyncio
3
+ from typing import List, Optional, AsyncGenerator
4
+ import websockets
5
+
6
+ class BaseWebsocketClient:
7
+ """
8
+ Base class for FinancialModelingPrep WebSocket clients.
9
+ Handles connection, login, subscription, and yields messages.
10
+ """
11
+ def __init__(self, tickers: List[str], api_key: str, uri: str) -> None:
12
+ self.tickers = tickers if isinstance(tickers, list) else [tickers]
13
+ self.api_key = api_key
14
+ self.uri = uri
15
+ self.websocket: Optional[websockets.WebSocketClientProtocol] = None
16
+
17
+ async def connect(self) -> None:
18
+ self.websocket = await websockets.connect(self.uri)
19
+ print(f"Connected to {self.uri}")
20
+
21
+ async def login(self) -> None:
22
+ login_msg = {"event": "login", "data": {"apiKey": self.api_key}}
23
+ await self.websocket.send(json.dumps(login_msg))
24
+ print("Sent login message")
25
+
26
+ async def subscribe(self) -> None:
27
+ subscribe_msg = {"event": "subscribe", "data": {"ticker": self.tickers}}
28
+ await self.websocket.send(json.dumps(subscribe_msg))
29
+ print(f"Subscribed to tickers: {self.tickers}")
30
+
31
+ async def run(self) -> AsyncGenerator[dict, None]:
32
+ """
33
+ Connects, logs in, waits for confirmation, subscribes,
34
+ and yields messages continuously.
35
+ """
36
+ await self.connect()
37
+ await self.login()
38
+ while True:
39
+ response = await self.websocket.recv()
40
+ message = json.loads(response)
41
+ if message.get("event") == "login":
42
+ if message.get("status") == 200:
43
+ print("Login successful")
44
+ break
45
+ else:
46
+ raise Exception("Login failed: " + message.get("message", "Unknown error"))
47
+ else:
48
+ print("Received (before login confirmation):", message)
49
+ await self.subscribe()
50
+ try:
51
+ while True:
52
+ response = await self.websocket.recv()
53
+ data = json.loads(response)
54
+ yield data
55
+ except websockets.ConnectionClosed:
56
+ print("Connection closed by the server.")
57
+ finally:
58
+ await self.websocket.close()
@@ -0,0 +1,7 @@
1
+ from typing import List
2
+ from .base_websocket_client import BaseWebsocketClient
3
+
4
+ class CryptoWebsockets(BaseWebsocketClient):
5
+ def __init__(self, tickers: List[str], api_key: str,
6
+ uri: str = "wss://crypto.financialmodelingprep.com") -> None:
7
+ super().__init__(tickers, api_key, uri)
@@ -0,0 +1,7 @@
1
+ from typing import List
2
+ from .base_websocket_client import BaseWebsocketClient
3
+
4
+ class ForexWebsockets(BaseWebsocketClient):
5
+ def __init__(self, pairs: List[str], api_key: str,
6
+ uri: str = "wss://forex.financialmodelingprep.com") -> None:
7
+ super().__init__(pairs, api_key, uri)
@@ -0,0 +1,7 @@
1
+ from typing import List
2
+ from .base_websocket_client import BaseWebsocketClient
3
+
4
+ class StockWebsockets(BaseWebsocketClient):
5
+ def __init__(self, tickers: List[str], api_key: str,
6
+ uri: str = "wss://websockets.financialmodelingprep.com") -> None:
7
+ super().__init__(tickers, api_key, uri)
@@ -0,0 +1,29 @@
1
+ import os
2
+ import sys
3
+ import logging
4
+ from typing import Optional
5
+
6
+ class Logger:
7
+ def __init__(self, name: str = "FMPLogger", log_file: Optional[str] = None,
8
+ level: int = logging.INFO, enabled: bool = True) -> None:
9
+ self.logger = logging.getLogger(name)
10
+ self.logger.setLevel(level)
11
+ if log_file is None:
12
+ if "__main__" in sys.modules and hasattr(sys.modules["__main__"], "__file__"):
13
+ main_dir = os.path.dirname(sys.modules["__main__"].__file__)
14
+ else:
15
+ main_dir = os.getcwd()
16
+ log_file = os.path.join(main_dir, "fmp.log")
17
+ if enabled:
18
+ handler = logging.FileHandler(log_file)
19
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
20
+ handler.setFormatter(formatter)
21
+ self.logger.addHandler(handler)
22
+ else:
23
+ self.logger.addHandler(logging.NullHandler())
24
+
25
+ def info(self, message: str) -> None:
26
+ self.logger.info(message)
27
+
28
+ def error(self, message: str) -> None:
29
+ self.logger.error(message)
@@ -0,0 +1,30 @@
1
+ import requests
2
+ from typing import Optional, Dict, Any
3
+ from ratelimit import limits, sleep_and_retry
4
+ from concurrent.futures import ThreadPoolExecutor
5
+ from .logger import Logger
6
+
7
+ REQUEST_RATE = {"calls": 3000, "seconds": 60}
8
+
9
+ class Session:
10
+ """
11
+ Provides a persistent HTTP session with rate limiting.
12
+ """
13
+ def __init__(self, api_key: str, logger: Optional[Logger] = None) -> None:
14
+ self.api_key = api_key
15
+ self.session = requests.Session()
16
+ self.logger = logger if logger is not None else Logger("FMP.Session", enabled=True)
17
+ self.executor = ThreadPoolExecutor(max_workers=5)
18
+
19
+ @sleep_and_retry
20
+ @limits(calls=REQUEST_RATE["calls"], period=REQUEST_RATE["seconds"])
21
+ def get(self, url: str, params: Optional[Dict[str, Any]] = None) -> requests.Response:
22
+ self.logger.info(f"HTTP GET: {url} with params {params}")
23
+ try:
24
+ response = self.session.get(url, params=params)
25
+ response.raise_for_status()
26
+ self.logger.info(f"Response: {response.status_code}")
27
+ return response
28
+ except Exception as e:
29
+ self.logger.error(f"HTTP GET error: {e}")
30
+ raise
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.2
2
+ Name: fmp-stable-api
3
+ Version: 0.0.1
4
+ Summary: Python client library for the Financial Modeling Prep API
5
+ Home-page: https://github.com/Vimal-Seshadri-Raguraman/FMP
6
+ Author: Vimal Seshadri Raguraman
7
+ Project-URL: GitHub, https://github.com/Vimal-Seshadri-Raguraman/FMP
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: Topic :: Office/Business :: Financial
14
+ Requires-Python: >=3.6
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE.txt
17
+ Requires-Dist: requests
18
+ Requires-Dist: ratelimit
19
+ Requires-Dist: websockets
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest; extra == "dev"
22
+ Requires-Dist: twine; extra == "dev"
23
+ Dynamic: author
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: project-url
29
+ Dynamic: provides-extra
30
+ Dynamic: requires-dist
31
+ Dynamic: requires-python
32
+ Dynamic: summary
33
+
34
+ # FMP Client Library
35
+ A Python client library for the [Financial Modeling Prep (FMP) API](https://financialmodelingprep.com/). This library provides both synchronous REST API and asynchronous WebSocket support, all in a modular, easy-to-use package.
36
+
37
+ The client is built to work with the [**Stable** version](https://site.financialmodelingprep.com/developer/docs/stable) of the Financial Modeling Prep (FMP) API. All endpoints are configured to use the stable base URL:
38
+ ```arduino
39
+ https://financialmodelingprep.com/stable/
40
+ ```
41
+ The endpoints available in this stable version are defined in the [fmp_endpoints.json](https://github.com/Vimal-Seshadri-Raguraman/FMP/blob/main/FMP/fmp_endpoints.json) file
42
+
43
+ ## Features
44
+
45
+ - **Unified API Client:**
46
+ Dynamically access FMP endpoints using Pythonic method names.
47
+
48
+ - **Rate-Limited HTTP Session:**
49
+ Built-in rate limiting using the `ratelimit` library and robust error handling with logging.
50
+
51
+ - **Dynamic Endpoint Methods:**
52
+ Automatically attaches methods based on your configuration file (`fmp_endpoints.json`).
53
+
54
+ - **Asynchronous WebSocket Clients:**
55
+ Separate clients for stocks, crypto, and forex data available in the `fmp_websockets` subpackage.
56
+
57
+ - **Flexible Configuration and Logging:**
58
+ Easily configure your endpoints with a JSON file and log messages to a file in the same directory as your main script.
59
+
60
+ ## Installation
61
+
62
+ Clone the repository and install the dependencies:
63
+
64
+ ```bash
65
+ git clone https://github.com/Vimal-Seshadri-Raguraman/FMP.git
66
+ cd FMP
67
+ pip install -r requirements.txt
68
+ python setup.py install
69
+ ```
70
+ Alternatively, install directly from Github:
71
+ ```bash
72
+ pip install git+https://github.com/Vimal-Seshadri-Raguraman/FMP.git
73
+ ```
74
+
75
+ ## Usage
76
+ ### API Client
77
+ Initialize the client with your API Key and call any endpoint dynmically:
78
+ ```python
79
+ from fmp import FMP
80
+
81
+ # Initialize client with your API Key
82
+ client = fmp(api_key = "YOUR_API_KEY")
83
+
84
+ # Call an endpoint (e.g., "Profile")
85
+ response = client.profile(symbol = "AAPL")
86
+ print("Status Code:", response.status_code)
87
+ print("Profile Data:", response.json())
88
+ ```
89
+ #### Dynmic Methods and Help
90
+ The client automatically attaches methods based on the JSON configuration. To see available endpoints and their parameters:
91
+ ```python
92
+ # General help for all endpoints
93
+ print(client.help())
94
+
95
+ # Detailed help for a specific endpoint (e.g., "profile")
96
+ print(client.help("profile"))
97
+
98
+ # Generate a manual page of endpoints
99
+ man_doc = client.man_page() # Create a text file
100
+ print(man_doc)
101
+ ```
102
+ ### WebSocket Clients
103
+ The library includes asynchronous WebSocket clients for real-time data.
104
+ #### Stock WebSocket Example
105
+ ```python
106
+ import asyncio
107
+ from fmp import StockWebsockets
108
+
109
+ async def run_stock_ws():
110
+ stock_ws = StockWebsockets(tickers=["AAPL", "MSFT"], api_key="YOUR_API_KEY")
111
+ async for message in stock_ws.run():
112
+ print("Stock WebSocket Message:", message)
113
+
114
+ asyncio.run(run_stock_ws())
115
+ ```
116
+ #### Crypto and Forex WebSocket Example
117
+ ```python
118
+ import asyncio
119
+ from fmp import CryptoWebsockets, ForexWebsockets
120
+
121
+ async def run_crypto_ws():
122
+ crypto_ws = CryptoWebsockets(tickers=["BTCUSD", "ETHUSD"], api_key="YOUR_API_KEY")
123
+ async for message in crypto_ws.run():
124
+ print("Crypto WebSocket Message:", message)
125
+
126
+ async def run_forex_ws():
127
+ forex_ws = ForexWebsockets(pairs=["EURUSD", "GBPUSD"], api_key="YOUR_API_KEY")
128
+ async for message in forex_ws.run():
129
+ print("Forex WebSocket Message:", message)
130
+
131
+ # Run one example at a time:
132
+ asyncio.run(run_crypto_ws())
133
+ # asyncio.run(run_forex_ws())
134
+ ```
135
+ ## Configuration
136
+ By default, the client loads endpoint configuration from ```fmp_endpoints.json``` located in the same directory as ```fmp_client.py```. To use a custom configuration file:
137
+ ```python
138
+ client = FMP(api_key="YOUR_API_KEY", config_file="path/to/your_config.json")
139
+ ```
140
+ If you update the configuration file during runtime, force a reload with:
141
+ ```python
142
+ client.config_manager.reload()
143
+ ```
144
+ ## Logging
145
+ Logging is enabled by default and creates a log file (```fmp.log```) in the directory where your main script is located. To disable logging:
146
+ ```python
147
+ client = FMP(api_key="YOUR_API_KEY", log_enabled=False)
148
+ ```
149
+ ## Contributing
150
+ Contributions, bug reports, and feature requests are welcome. Please open an issue or submit a pull request.
151
+ ## License
152
+ This project is licensed under the MIT License. See the [```LICENSE.md```](https://github.com/Vimal-Seshadri-Raguraman/FMP/blob/main/LICENSE) file for details.
@@ -0,0 +1,21 @@
1
+ LICENSE.txt
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ setup.py
6
+ fmp/__init__.py
7
+ fmp/config_manager.py
8
+ fmp/dynamic.py
9
+ fmp/fmp_client.py
10
+ fmp/logger.py
11
+ fmp/session.py
12
+ fmp/fmp_websockets/__init__.py
13
+ fmp/fmp_websockets/base_websocket_client.py
14
+ fmp/fmp_websockets/crypto_websocket.py
15
+ fmp/fmp_websockets/forex_websocket.py
16
+ fmp/fmp_websockets/stock_websocket.py
17
+ fmp_stable_api.egg-info/PKG-INFO
18
+ fmp_stable_api.egg-info/SOURCES.txt
19
+ fmp_stable_api.egg-info/dependency_links.txt
20
+ fmp_stable_api.egg-info/requires.txt
21
+ fmp_stable_api.egg-info/top_level.txt
@@ -0,0 +1,7 @@
1
+ requests
2
+ ratelimit
3
+ websockets
4
+
5
+ [dev]
6
+ pytest
7
+ twine
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,36 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="fmp-stable-api",
5
+ version="0.0.1",
6
+ description="Python client library for the Financial Modeling Prep API",
7
+ long_description=open("README.md").read(),
8
+ long_description_content_type="text/markdown",
9
+ author="Vimal Seshadri Raguraman",
10
+ url="https://github.com/Vimal-Seshadri-Raguraman/FMP",
11
+ packages=find_packages(),
12
+ include_package_data=True,
13
+ install_requires=[
14
+ "requests",
15
+ "ratelimit",
16
+ "websockets",
17
+ ],
18
+ classifiers=[
19
+ "Development Status :: 3 - Alpha",
20
+ "Programming Language :: Python :: 3",
21
+ "Operating System :: OS Independent",
22
+ "Intended Audience :: Developers",
23
+ "Intended Audience :: Financial and Insurance Industry",
24
+ "Topic :: Office/Business :: Financial"
25
+ ],
26
+ extras_require={
27
+ "dev": [
28
+ "pytest",
29
+ "twine"
30
+ ]
31
+ },
32
+ python_requires='>=3.6',
33
+ project_urls={
34
+ "GitHub":"https://github.com/Vimal-Seshadri-Raguraman/FMP",
35
+ }
36
+ )