diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4ce265b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ + +# Change Log + +### Version 2.0.0 + +* Changed configuration implementation.
+In version 1, you could pass arguments directly into the @client.configure() decorator and the server.start() method. +In version 2, the configuration can now be accessed or changed ***only*** through the dedicated 'config' singleton, which can be imported the same way as the 'client' and 'server' objects. +Note: it is not necessary to use the 'config' object, if you wish to run YFrake with the default settings. + + +* Trying to import YFrake with Python version less than 3.10 will throw a RuntimeError exception.
+YFrake uses the new feature of defining union types as 'X | Y' (PEP 604), which is only available from Python 3.10 and upwards. + + +* The YFrake module itself is now directly runnable, which is used to run the server from the command line with: 'python -m yfrake args'. + + +* Updated the documentation to reflect the changes above. \ No newline at end of file diff --git a/docs/source/client/examples/async_mode_examples.rst b/docs/source/client/examples/async_mode_examples.rst index 940e46a..001095b 100644 --- a/docs/source/client/examples/async_mode_examples.rst +++ b/docs/source/client/examples/async_mode_examples.rst @@ -19,7 +19,7 @@ The following example loops at line 4 ``while`` the response has not yet arrived :emphasize-lines: 4 :linenos: - @client.configure() + @client.session async def main(): resp = client.get('quote_type', symbol='msft') while resp.pending(): @@ -32,7 +32,7 @@ The following example blocks at line 4 until the response has arrived: :emphasize-lines: 4 :linenos: - @client.configure() + @client.session async def main(): resp = client.get('quote_type', symbol='msft') await resp.wait() @@ -54,7 +54,7 @@ The following example waits until all of the responses have arrived before runni :emphasize-lines: 9 :linenos: - @client.configure() + @client.session async def main(): queries = [ dict(endpoint='quote_type', symbol='msft'), @@ -72,7 +72,7 @@ The following example starts yielding the responses into the ``async for`` loop :emphasize-lines: 9 :linenos: - @client.configure() + @client.session async def main(): queries = [ dict(endpoint='quote_type', symbol='msft'), @@ -99,7 +99,7 @@ The following example loops while all the available data about a symbol is being :emphasize-lines: 4 :linenos: - @client.configure() + @client.session async def main(): results = client.get_all(symbol='msft') while results.pending(): @@ -112,7 +112,7 @@ The following example blocks while all the available data about a symbol is bein :emphasize-lines: 4 :linenos: - @client.configure() + @client.session async def main(): results = client.get_all(symbol='aapl') await results.wait() diff --git a/docs/source/client/examples/decorator_examples.rst b/docs/source/client/examples/decorator_examples.rst deleted file mode 100644 index a1f5293..0000000 --- a/docs/source/client/examples/decorator_examples.rst +++ /dev/null @@ -1,46 +0,0 @@ -Decorator Examples -================== - -The following examples open a session with the default settings: - -.. code-block:: python - :emphasize-lines: 1 - :linenos: - - @client.configure() - def main() - # do some stuff - - -.. code-block:: python - :emphasize-lines: 1 - :linenos: - - @client.configure() - async def main() - # do some stuff - - -.. raw:: html - -
- - -The following examples open a session with custom settings: - -.. code-block:: python - :emphasize-lines: 1 - :linenos: - - @client.configure(limit=128, timeout=8) - def main() - # do some stuff - - -.. code-block:: python - :emphasize-lines: 1 - :linenos: - - @client.configure(limit=128, timeout=8) - async def main() - # do some stuff diff --git a/docs/source/client/examples/index.rst b/docs/source/client/examples/index.rst index 9ceeaa8..eece758 100644 --- a/docs/source/client/examples/index.rst +++ b/docs/source/client/examples/index.rst @@ -5,7 +5,6 @@ Examples :maxdepth: 2 :caption: Examples - decorator_examples.rst async_mode_examples.rst sync_mode_examples.rst various_examples.rst diff --git a/docs/source/client/examples/sync_mode_examples.rst b/docs/source/client/examples/sync_mode_examples.rst index 2ebfef5..956d5e6 100644 --- a/docs/source/client/examples/sync_mode_examples.rst +++ b/docs/source/client/examples/sync_mode_examples.rst @@ -19,7 +19,7 @@ The following example loops at line 4 ``while`` the response has not yet arrived :emphasize-lines: 4 :linenos: - @client.configure() + @client.session def main(): resp = client.get('quote_type', symbol='msft') while resp.pending(): @@ -32,7 +32,7 @@ The following example blocks at line 4 until the response has arrived: :emphasize-lines: 4 :linenos: - @client.configure() + @client.session def main(): resp = client.get('quote_type', symbol='msft') resp.wait() @@ -54,7 +54,7 @@ The following example waits until all of the responses have arrived before runni :emphasize-lines: 9 :linenos: - @client.configure() + @client.session def main(): queries = [ dict(endpoint='quote_type', symbol='msft'), @@ -72,7 +72,7 @@ The following example starts yielding the responses into the ``for`` loop as soo :emphasize-lines: 9 :linenos: - @client.configure() + @client.session def main(): queries = [ dict(endpoint='quote_type', symbol='msft'), @@ -99,7 +99,7 @@ The following example loops while all the available data about a symbol is being :emphasize-lines: 4 :linenos: - @client.configure() + @client.session def main(): results = client.get_all(symbol='msft') while results.pending(): @@ -112,7 +112,7 @@ The following example blocks while all the available data about a symbol is bein :emphasize-lines: 4 :linenos: - @client.configure() + @client.session def main(): results = client.get_all(symbol='aapl') results.wait() diff --git a/docs/source/client/examples/various_examples.rst b/docs/source/client/examples/various_examples.rst index b9acf0a..2646378 100644 --- a/docs/source/client/examples/various_examples.rst +++ b/docs/source/client/examples/various_examples.rst @@ -10,7 +10,7 @@ The following example prints out the names of all the endpoints queried: from yfrake import client import asyncio - @client.configure() + @client.session async def main(): results = client.get_all(symbol='msft') async for resp in results.gather(): @@ -28,7 +28,7 @@ The following example prints out either the ``error`` or the ``data`` property o from yfrake import client import asyncio - @client.configure() + @client.session async def main(): queries = [ dict(endpoint='quote_type', symbol='msft'), @@ -54,7 +54,7 @@ The following example creates a batch request of 3 endpoints for 3 symbols: from yfrake import client - @client.configure() + @client.session def main(): all_queries = list() for symbol in ['msft', 'aapl', 'tsla']: @@ -87,7 +87,7 @@ The following example demonstrates the usage of the ``get`` method inside a non- resp.wait() return resp - @client.configure() + @client.session def main(): resp = make_the_request('msft') print(f'Data: {resp.data}') diff --git a/docs/source/client/overview.rst b/docs/source/client/overview.rst index 83c1003..ebec5e5 100644 --- a/docs/source/client/overview.rst +++ b/docs/source/client/overview.rst @@ -25,7 +25,7 @@ about a single symbol from all symbol-specific endpoints at once. Client Decorators +++++++++++++++++ -The ``client`` object has a decorator named ``configure``, which opens a session to the Yahoo Finance API servers and +The ``client`` object has a single decorator named ``session``, which opens a session to the Yahoo Finance API servers and inspects the concurrency mode of your program to adjust its behaviour accordingly. This enables YFrake to work in async and sync (threaded) modes out-of-the-box. diff --git a/docs/source/client/reference/client_reference.rst b/docs/source/client/reference/client_reference.rst index 6603640..025f2e3 100644 --- a/docs/source/client/reference/client_reference.rst +++ b/docs/source/client/reference/client_reference.rst @@ -13,21 +13,15 @@ Client Reference Public Decorators ----------------- -.. py:decorator:: configure(limit=64, timeout=2) +.. py:decorator:: session | Manages the connection session for the YFrake client singleton. | Needs to be active when methods of the client object are being called. - - :param limit: The total number of concurrent requests to the Yahoo Finance API servers. - :type limit: int - - :param timeout: The maximum time allowed for a request to fetch data from the Yahoo Finance API servers, in seconds. - :type timeout: int + | The server object uses this decorator internally to manage its session + | to the Yahoo Finance API servers. :raises RuntimeError: if a configuration is already active. - :return: None - .. raw:: html @@ -49,7 +43,7 @@ Public Methods :param kwargs: Variable keyword arguments, which depend on the endpoint requirements. Values can be either *str*, *int* or *bool*. :type kwargs: unpacked dict - :raises RuntimeError: if a configuration is not active. + :raises RuntimeError: if the session decorator is not in use. :raises NameError: if an invalid endpoint name has been provided. :raises KeyError: if an invalid query parameter has been provided. :raises TypeError: if the datatype of a query parameter is invalid. @@ -66,11 +60,10 @@ Public Methods :param queries: Collection of query dicts. :type queries: list - :raises RuntimeError: if a configuration is not active. + :raises RuntimeError: if the session decorator is not in use. :raises NameError: if an invalid endpoint name has been provided. :raises KeyError: if an invalid query parameter has been provided. :raises TypeError: if the datatype of a query parameter is invalid. - :raises TypeError: if an element in the queries list is not a dict. :return: List-like collection object :rtype: AsyncResults or ThreadResults @@ -87,11 +80,10 @@ Public Methods :param symbol: Security identifier. :type symbol: str - :raises RuntimeError: if a configuration is not active. + :raises RuntimeError: if the session decorator is not in use. :raises NameError: if an invalid endpoint name has been provided. :raises KeyError: if an invalid query parameter has been provided. :raises TypeError: if the datatype of a query parameter is invalid. - :raises TypeError: if an element in the queries list is not a dict. :return: List-like collection object :rtype: AsyncResults or ThreadResults diff --git a/docs/source/config/config_examples.rst b/docs/source/config/config_examples.rst new file mode 100644 index 0000000..23190c7 --- /dev/null +++ b/docs/source/config/config_examples.rst @@ -0,0 +1,138 @@ +Examples +======== + + +.. contents:: Contents + +.. raw:: html + +
+
+ + +Correct Usage Examples +---------------------- + +| No config object usage is required to use the default settings: + +.. code-block:: python + :emphasize-lines: 1 + :linenos: + + from yfrake import client + + @client.session + def main(): + # do stuff + + main() + + +| Assigning a custom config file in the **Current Working Directory**. +| If the file doesn't exist, it will be created with the default settings. + +.. code-block:: python + :emphasize-lines: 3 + :linenos: + + from yfrake import client, config + + config.file = config.HERE + + @client.session + def main(): + # do stuff + + main() + + +Assigning a custom config file in the specified path: + +.. code-block:: python + :emphasize-lines: 3 + :linenos: + + from yfrake import client, config + + config.file = "C:/Users/username/Projects/Project Name/yfrake_settings.ini" + + @client.session + def main(): + # do stuff + + main() + + +Assigning a custom config file before the server is started: + +.. code-block:: python + :emphasize-lines: 3 + :linenos: + + from yfrake import server, config + + config.file = Path("C:/Users/username/Projects/Project Name/yfrake_settings.ini") + server.start() + + # defined behaviour + + server.stop() + + +.. raw:: html + +
+
+ + +Incorrect Usage Examples +------------------------ + +Trying to assign a custom config file in the **Current Working Directory**. + +.. code-block:: python + :emphasize-lines: 5 + :linenos: + + from yfrake import client, config + + @client.session + def main(): + config.file = config.HERE + + # will raise an exception + + main() + + +Trying to assign a custom custom config file in the specified path: + +.. code-block:: python + :emphasize-lines: 5 + :linenos: + + from yfrake import client, config + + @client.session + def main(): + config.file = "C:/Users/username/Projects/Project Name/yfrake_settings.ini" + + # will raise an exception + + main() + + +Assigning a custom config file after the server has started: + +.. code-block:: python + :emphasize-lines: 4 + :linenos: + + from yfrake import server, config + + server.start() + config.file = Path("C:/Users/username/Projects/Project Name/yfrake_settings.ini") + + # undefined behaviour + + server.stop() diff --git a/docs/source/config/config_overview.rst b/docs/source/config/config_overview.rst new file mode 100644 index 0000000..0b63f41 --- /dev/null +++ b/docs/source/config/config_overview.rst @@ -0,0 +1,22 @@ +Overview +======== + +Configuration settings for YFrake are stored in a file named ``yfrake_settings.ini``. +The ``config`` singleton reads the settings from that file and configures the ``client`` and the ``server`` objects. +It is not necessary to use the ``config`` object, if you want to run YFrake with the default settings. + +The config has one property named ``file``, which is used to access **or modify** the path to the config file, +a few other properties to **only access** the values of the currently loaded configuration, and +a method to check if the ``client.session`` decorator is in use (active). + +All the properties of the ``config`` object can be accessed at any time, but the ``file`` property +can be modified **only** when the ``client.session`` decorator is **not** in use (active). +The ``file`` property can accept either a pathlib.Path or a string object, which contains a full path to a config file. +Modifying the ``file`` property after the ``server`` has started has undefined behaviour and is therefore **not recommended**. + + +The ``config`` object also has an attribute named ``HERE``, which points to an abstract config file in the **Current Working Directory**. +Assigning the ``HERE`` attribute to the ``file`` property will create the config file in the **CWD** with the default settings, if it doesn't exist. + +*NOTE:* The configuration was moved to a separate object, because future versions of YFrake will have caching, +which will use individual TTL values for each endpoint, therefore a file-based config implementation is more suitable in this case. diff --git a/docs/source/config/config_reference.rst b/docs/source/config/config_reference.rst new file mode 100644 index 0000000..849b737 --- /dev/null +++ b/docs/source/config/config_reference.rst @@ -0,0 +1,105 @@ +Reference +========= + + +.. contents:: Contents + +.. raw:: html + +
+
+ + +Public Methods +-------------- + +.. py:classmethod:: is_locked() + + | Helper method which is used to check if the configuration is + | being used by the ``client.session`` decorator. Any attempt + | to change the configuration while the session is open will cause + | a ``RuntimeError`` to be thrown. + + :return: Value of the config lock status. + :rtype: bool + + +.. raw:: html + +
+
+ + +Public Properties +----------------- + +.. py:property:: file + :classmethod: + + | The full path to the configuration file which should be used by the client and the server objects. + | Can be assigned either a ``pathlib.Path`` or a ``str`` object. + + :raises TypeError: on attempt to delete the property. + + :return: Full path to the config file to be used. + :rtype: pathlib.Path + + +.. py:property:: limit + :classmethod: + + | The total number of concurrent requests to the Yahoo Finance API servers. + | *READ ONLY.* + + :raises TypeError: on attempt to modify the property. + :raises TypeError: on attempt to delete the property. + + :rtype: int + + +.. py:property:: timeout + :classmethod: + + | The maximum time allowed for a request to fetch data, in seconds. + | *READ ONLY.* + + :raises TypeError: on attempt to modify the property. + :raises TypeError: on attempt to delete the property. + + :rtype: int + + +.. py:property:: host + :classmethod: + + | The web address of the host. + | *READ ONLY.* + + :raises TypeError: on attempt to modify the property. + :raises TypeError: on attempt to delete the property. + + :rtype: str + + +.. py:property:: port + :classmethod: + + | The port number of the host. + | *READ ONLY.* + + :raises TypeError: on attempt to modify the property. + :raises TypeError: on attempt to delete the property. + + :rtype: int + + +.. py:property:: backlog + :classmethod: + + | A number of connections on-hold before the system refuses new connections. + | *READ ONLY.* + + :raises TypeError: on attempt to modify the property. + :raises TypeError: on attempt to delete the property. + + :rtype: int diff --git a/docs/source/index.rst b/docs/source/index.rst index be726fc..ccdcdba 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -23,3 +23,11 @@ Welcome to YFrake's documentation! server/server_overview.rst server/server_reference.rst server/server_examples.rst + +.. toctree:: + :maxdepth: 2 + :caption: Config + + config/config_overview.rst + config/config_reference.rst + config/config_examples.rst diff --git a/docs/source/server/server_examples.rst b/docs/source/server/server_examples.rst index 11d07a3..a34b74f 100644 --- a/docs/source/server/server_examples.rst +++ b/docs/source/server/server_examples.rst @@ -2,12 +2,14 @@ Examples ======== -Running the server programmatically with the default settings: +Running the server programmatically: .. code-block:: python - :emphasize-lines: 2 + :emphasize-lines: 4 :linenos: + from yfrake import server + if not server.is_running() server.start() @@ -16,39 +18,20 @@ Running the server programmatically with the default settings: if server.is_running() server.stop() +| +| Creating the 'yfrake_settings.ini' file to the *CWD* if it doesn't exist, without running the server: +| ``$ python -m yfrake --config-file here`` +| +| +| **Running the server from the terminal:** -Running the server programmatically with custom settings: - -.. code-block:: python - :emphasize-lines: 10 - :linenos: - - settings = dict( - host='localhost', - port=8888, - limit=64, - timeout=2, - backlog=128 - ) - - if not server.is_running() - server.start(**settings) - - # do other stuff - - if server.is_running() - server.stop() - +| 1) With the default configuration: +| ``$ python -m yfrake --run-server`` -Running the server from the command line or terminal: +| 2) With 'yfrake_settings.ini' in the *CWD*: +| ``$ python -m yfrake --run-server --config-file here`` -``$ python "/path/to/python/Lib/site-packages/yfrake/server/runner.py" args`` +| 3) With the config file in a custom directory: +| ``$ python -m yfrake --run-server --config-file "/path/to/'yfrake_settings.ini"`` -| It is not necessary to provide any args, if you want to run the server with the default settings. -| The default values for the available args are as follows: -``--host 'localhost'`` -``--port 8888`` -``--limit 64`` -``--timeout 2`` -``--backlog 128`` diff --git a/docs/source/server/server_overview.rst b/docs/source/server/server_overview.rst index 4dd406d..534ec5f 100644 --- a/docs/source/server/server_overview.rst +++ b/docs/source/server/server_overview.rst @@ -1,17 +1,31 @@ Overview ======== -The standardized interface of the YFrake server simplifies the process of acquiring stock market data from other applications, +The standardized interface of the YFrake server simplifies the process of acquiring stock market data for other applications, which can use their own networking libraries to make web requests to the YFrake server. -There are two ways how you can run the server: you can either control it from within your Python program or -you can run the ``runner.py`` module located in the ``/Libs/site-packages/yfrake/server`` folder of your Python distribution. +There are two ways how you can run the server: you can either control it from within your Python program +through the ``server`` singleton or you can directly call the YFrake module in the terminal with ``python -m yfrake args``. +When running the server from the terminal without any args, then nothing will happen. +The optional args are ``--run-server`` and ``--config-file /path``, which can be used independently from each other. + +The arg ``--config-file`` accepts as its only parameter either a full path to the config file or the special keyword ``here``, +which will have the server look for the config file in the **Current Working Directory**. +When using the keyword ``here``, if the file does not exist, it will be created with the default settings. +If the parameter is a full path to a config file, then the file must exist, otherwise an exception will be thrown. +In all cases, the config file must be named ``yfrake_settings.ini``. + +When ``--run-server`` is used without the ``--config-file`` arg, then the server is run with the default settings. +Using ``--config-file here`` without the ``--run-server`` arg is useful for getting a copy of the config file with the default settings to the **CWD**. You can access the built-in Swagger documentation by running the server and -navigating to the servers root address in your web browser (default: ``localhost:8888``). +navigating to the servers root address in your web browser (default: ``http://localhost:8888``). + +You can perform queries to the endpoints either directly through the Swagger Docs UI, +or by navigating to the appropriate URL-s in the address bar of your web browser. -Each endpoint has a path name like ``/market_summary``, so to request data from that endpoint, -in your address bar you would write: ``localhost:8888/market_summary``. +When accessing endpoints through their URL-s, each endpoint has a path name like ``/market_summary``. +To request data from that endpoint, in your address bar you would write: ``http://localhost:8888/market_summary``. If an endpoint like ``/company_overview`` requires query parameters, then you would write in your address bar: -``localhost:8888/company_overview?symbol=msft``. +``http://localhost:8888/company_overview?symbol=msft``. diff --git a/docs/source/server/server_reference.rst b/docs/source/server/server_reference.rst index 9d62e71..1cd56ad 100644 --- a/docs/source/server/server_reference.rst +++ b/docs/source/server/server_reference.rst @@ -1,25 +1,10 @@ Reference ========= -.. py:classmethod:: server.start(host='localhost', port=8888, limit=64, timeout=2, backlog=128) +.. py:classmethod:: server.start() Starts the YFrake server. Only one server can be active *per process* at any time. - :param host: The web address of the host. - :type host: str - - :param port: The port number of the host. - :type port: int - - :param limit: The maximum number of concurrent requests opened to the Yahoo Finance servers. - :type limit: int - - :param timeout: Maximum allowed time per request to fetch data from the Yahoo Finance servers, in seconds. - :type timeout: int - - :param backlog: A number of unaccepted connections that the system will allow before refusing new connections. - :type backlog: int - :raises RuntimeError: if the server is already running. :return: None diff --git a/docs/source/yfrake/getting_started.rst b/docs/source/yfrake/getting_started.rst index 30cfce8..02e4d8a 100644 --- a/docs/source/yfrake/getting_started.rst +++ b/docs/source/yfrake/getting_started.rst @@ -3,24 +3,28 @@ Getting Started **Install the package by executing:** -``pip install yfrake`` +.. code-block:: python + + pip install yfrake **Import the public objects with:** .. code-block:: python - from yfrake import client, server + from yfrake import client, server, config .. raw:: html
-Both the ``client`` and ``server`` objects are singletons and instances of the corresponding internal ``Client`` and ``Server`` classes. -All public methods of these objects are **classmethods**, so there is no need to create more client or server objects. -The classes have been instantiated internally beforehand to provide the user with lower-case object name identifiers. + +| The ``client``, ``server``, and ``config`` objects are all singletons, which have public **classmethods**. +| The classes have been instantiated internally beforehand to provide the user with lower-case object name identifiers. You can get the list of valid endpoints and their arguments by running the server and reading the Swagger documentation. -To convert the servers endpoint path param names for use with the client object, just strip the ``/`` character from the -beginning of the endpoint string: ``/company_overview`` becomes ``company_overview``. +To convert the servers endpoint path param names for use with the client object, just omit the ``/`` character from the +beginning of the endpoint name: ``/company_overview`` becomes ``company_overview``. + +**NB!** Trying to import yfrake objects in Python version less than **3.10** will throw an exception! \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index afc0822..c3f7436 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "poetry.core.masonry.api" # ==================================================================================== # [tool.poetry] name = "YFrake" -version = "1.1.0" +version = "2.0.0" description = "A fast and flexible stock market data provider." readme = "README.md" license = "MIT License" diff --git a/tests/test_config/config_files/bad_field_type.ini b/tests/test_config/config_files/bad_field_type.ini index b9fe2fa..5d66e0a 100644 --- a/tests/test_config/config_files/bad_field_type.ini +++ b/tests/test_config/config_files/bad_field_type.ini @@ -32,45 +32,6 @@ timeout: 2 [SERVER] host: localhost port: 8888 -backlog: 128 -[OTHER] # Bad type, should be int -cache_size_mb: True - -[CACHE_TTL] -historical_prices: 0 -options: 0 -insights: 0 -quotes_overview: 0 -esg_chart: 0 -quote_type: 0 -news: 0 -recommendations: 0 -shares_outstanding: 0 -validate_symbols: 0 -market_summary: 0 -trending_symbols: 0 -currencies: 0 -esg_scores: 0 -purchase_activity: 0 -earnings: 0 -price_overview: 0 -calendar_events: 0 -company_overview: 0 -sec_filings: 0 -detailed_summary: 0 -financials: 0 -recommendation_trend: 0 -ratings_history: 0 -earnings_history: 0 -earnings_trend: 0 -key_statistics: 0 -income_statements: 0 -cashflow_statements: 0 -balance_statements: 0 -institution_ownership: 0 -fund_ownership: 0 -major_holders: 0 -insider_transactions: 0 -insider_holders: 0 +backlog: True diff --git a/tests/test_config/config_files/good_config.ini b/tests/test_config/config_files/good_config.ini index 40e4448..0b35660 100644 --- a/tests/test_config/config_files/good_config.ini +++ b/tests/test_config/config_files/good_config.ini @@ -33,43 +33,3 @@ timeout: 2 host: localhost port: 8888 backlog: 128 - -[OTHER] -cache_size_mb: 64 - -[CACHE_TTL] -historical_prices: 0 -options: 0 -insights: 0 -quotes_overview: 0 -esg_chart: 0 -quote_type: 0 -news: 0 -recommendations: 0 -shares_outstanding: 0 -validate_symbols: 0 -market_summary: 0 -trending_symbols: 0 -currencies: 0 -esg_scores: 0 -purchase_activity: 0 -earnings: 0 -price_overview: 0 -calendar_events: 0 -company_overview: 0 -sec_filings: 0 -detailed_summary: 0 -financials: 0 -recommendation_trend: 0 -ratings_history: 0 -earnings_history: 0 -earnings_trend: 0 -key_statistics: 0 -income_statements: 0 -cashflow_statements: 0 -balance_statements: 0 -institution_ownership: 0 -fund_ownership: 0 -major_holders: 0 -insider_transactions: 0 -insider_holders: 0 diff --git a/tests/test_config/config_files/missing_field.ini b/tests/test_config/config_files/missing_field.ini index d6dbd9d..cf4ca69 100644 --- a/tests/test_config/config_files/missing_field.ini +++ b/tests/test_config/config_files/missing_field.ini @@ -33,43 +33,3 @@ timeout: 2 host: localhost port: 8888 # No 'backlog' field - -[OTHER] -cache_size_mb: 64 - -[CACHE_TTL] -historical_prices: 0 -options: 0 -insights: 0 -quotes_overview: 0 -esg_chart: 0 -quote_type: 0 -news: 0 -recommendations: 0 -shares_outstanding: 0 -validate_symbols: 0 -market_summary: 0 -trending_symbols: 0 -currencies: 0 -esg_scores: 0 -purchase_activity: 0 -earnings: 0 -price_overview: 0 -calendar_events: 0 -company_overview: 0 -sec_filings: 0 -detailed_summary: 0 -financials: 0 -recommendation_trend: 0 -ratings_history: 0 -earnings_history: 0 -earnings_trend: 0 -key_statistics: 0 -income_statements: 0 -cashflow_statements: 0 -balance_statements: 0 -institution_ownership: 0 -fund_ownership: 0 -major_holders: 0 -insider_transactions: 0 -insider_holders: 0 diff --git a/tests/test_config/config_files/missing_section.ini b/tests/test_config/config_files/missing_section.ini index 047dbbe..af279f8 100644 --- a/tests/test_config/config_files/missing_section.ini +++ b/tests/test_config/config_files/missing_section.ini @@ -29,12 +29,4 @@ limit: 64 timeout: 2 -[SERVER] -host: localhost -port: 8888 -backlog: 128 - -[OTHER] -cache_size_mb: 64 - -# [CACHE_TTL] section is missing +# [SERVER] section is missing diff --git a/tests/test_config/test_base_config.py b/tests/test_config/test_base_config.py index 332d798..f4412ff 100644 --- a/tests/test_config/test_base_config.py +++ b/tests/test_config/test_base_config.py @@ -45,21 +45,3 @@ def test_base_config_backlog(): config.backlog = 'asdfg' with pytest.raises(TypeError): del config.backlog - - -def test_base_config_cache_size_mb(): - cache_size_mb = config.cache_size_mb - assert isinstance(cache_size_mb, int) - with pytest.raises(TypeError): - config.cache_size_mb = 'asdfg' - with pytest.raises(TypeError): - del config.cache_size_mb - - -def test_base_config_cache_ttl(): - cache_ttl = config.cache_ttl - assert isinstance(cache_ttl, dict) - with pytest.raises(TypeError): - config.cache_ttl = 'asdfg' - with pytest.raises(TypeError): - del config.cache_ttl diff --git a/yfrake/client/cache.py b/yfrake/client/cache.py deleted file mode 100644 index 497ac00..0000000 --- a/yfrake/client/cache.py +++ /dev/null @@ -1,77 +0,0 @@ -# ==================================================================================== # -# cache.py - This file is part of the YFrake package. # -# ------------------------------------------------------------------------------------ # -# # -# MIT License # -# # -# Copyright (c) 2022 Mattias Aabmets # -# # -# Permission is hereby granted, free of charge, to any person obtaining a copy # -# of this software and associated documentation files (the "Software"), to deal # -# in the Software without restriction, including without limitation the rights # -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # -# copies of the Software, and to permit persons to whom the Software is # -# furnished to do so, subject to the following conditions: # -# # -# The above copyright notice and this permission notice shall be included in all # -# copies or substantial portions of the Software. # -# # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # -# SOFTWARE. # -# # -# ==================================================================================== # -from collections import OrderedDict -import asyncio -import hashlib -import pickle - - -# ==================================================================================== # -class CacheSingleton: - __cache__ = None - __instance__ = None - - # Singleton pattern - # ------------------------------------------------------------------------------------ # - def __new__(cls): - if not cls.__instance__: - cls.__instance__ = super(CacheSingleton, cls).__new__(cls) - return cls.__instance__ - - # ------------------------------------------------------------------------------------ # - def __init__(self): - if not self.__cache__: - self.__cache__ = OrderedDict() - self._cache_max_size_mb = 0 - self._cache_size_mb = 0 - - # ------------------------------------------------------------------------------------ # - @staticmethod - def calculate_hex_digest(*args) -> str: - jar = pickle.dumps(args) - key = hashlib.sha3_256(jar) - return key.hexdigest() - - # ------------------------------------------------------------------------------------ # - @classmethod - async def get(cls, endpoint: str, params: dict) -> dict | None: - digest: str = cls.calculate_hex_digest(endpoint, params) - jar = await cls.__cache__.get(key=digest, default=None) - return pickle.loads(jar) if jar else None - - # ------------------------------------------------------------------------------------ # - @classmethod - async def set(cls, endpoint: str, params: dict, result: dict) -> None: - digest: str = cls.calculate_hex_digest(endpoint, params) - jar = pickle.dumps(result) - ttl = cls.calculate_ttl(endpoint, params) - - # ------------------------------------------------------------------------------------ # - @staticmethod - async def calculate_ttl(endpoint: str, params: dict) -> None: - pass diff --git a/yfrake/client/client.py b/yfrake/client/client.py index b8d68f3..b7e02d6 100644 --- a/yfrake/client/client.py +++ b/yfrake/client/client.py @@ -31,7 +31,6 @@ from .return_types.thread_results import ThreadResults from .validators import validate_request from .session import SessionSingleton -from .cache import CacheSingleton from . import endpoints import asyncio @@ -39,7 +38,6 @@ # ==================================================================================== # class ClientSingleton(Decorator): _err_not_open = 'Session has not been opened! (YFrake)' - _cache = CacheSingleton() __instance__ = None # Singleton pattern @@ -51,11 +49,9 @@ def __new__(cls): # ------------------------------------------------------------------------------------ # @classmethod - async def _wrapper(cls, endpoint, params, func, resp) -> ClientResponse: - result = await cls._cache.get(endpoint, params) - if not result: - result = await func(endpoint, params) - await cls._cache.set(endpoint, params, result) + async def _wrapper(cls, endpoint, params, resp) -> ClientResponse: + attr = 'get_' + endpoint + func = getattr(endpoints, attr) result = await func(endpoint, params) setattr(resp, '_endpoint', result['endpoint']) setattr(resp, '_error', result['error']) @@ -76,17 +72,15 @@ def get(cls, endpoint: str = '', **kwargs) -> ClientResponse: raise RuntimeError(cls._err_not_open) validate_request(endpoint, kwargs) - attr = 'get_' + endpoint - func = getattr(endpoints, attr) resp = ClientResponse(cls._async_mode) - if cls._async_mode: future = asyncio.create_task( - cls._wrapper(endpoint, kwargs, func, resp)) + cls._wrapper(endpoint, kwargs, resp)) else: ss = SessionSingleton() future = asyncio.run_coroutine_threadsafe( - cls._wrapper(endpoint, kwargs, func, resp), ss.loop) + cls._wrapper(endpoint, kwargs, resp), ss.loop) + setattr(resp, '_future', future) return resp diff --git a/yfrake/config/__init__.py b/yfrake/config/__init__.py index f6d9973..5f80423 100644 --- a/yfrake/config/__init__.py +++ b/yfrake/config/__init__.py @@ -35,48 +35,52 @@ 'host': str(), 'port': int(), 'backlog': int() - }, - 'cache_max_size': { - 'num_of_items': int(), - 'memory_megs': int() - }, - 'cache_ttl_variable': { - 'historical_prices': str(), - }, - 'cache_ttl_constant': { - 'options': int(), - 'insights': int(), - 'quotes_overview': int(), - 'esg_chart': int(), - 'quote_type': int(), - 'news': int(), - 'recommendations': int(), - 'shares_outstanding': int(), - 'validate_symbols': int(), - 'market_summary': int(), - 'trending_symbols': int(), - 'currencies': int(), - 'esg_scores': int(), - 'purchase_activity': int(), - 'earnings': int(), - 'price_overview': int(), - 'calendar_events': int(), - 'company_overview': int(), - 'sec_filings': int(), - 'detailed_summary': int(), - 'financials': int(), - 'recommendation_trend': int(), - 'ratings_history': int(), - 'earnings_history': int(), - 'earnings_trend': int(), - 'key_statistics': int(), - 'income_statements': int(), - 'cashflow_statements': int(), - 'balance_statements': int(), - 'institution_ownership': int(), - 'fund_ownership': int(), - 'major_holders': int(), - 'insider_transactions': int(), - 'insider_holders': int() } } + +# NOT IMPLEMENTED YET / WORK IN PROGRESS +# +# 'cache_max_size': { +# 'num_of_items': int(), +# 'memory_megs': int() +# }, +# 'cache_ttl_variable': { +# 'historical_prices': str(), +# }, +# 'cache_ttl_constant': { +# 'options': int(), +# 'insights': int(), +# 'quotes_overview': int(), +# 'esg_chart': int(), +# 'quote_type': int(), +# 'news': int(), +# 'recommendations': int(), +# 'shares_outstanding': int(), +# 'validate_symbols': int(), +# 'market_summary': int(), +# 'trending_symbols': int(), +# 'currencies': int(), +# 'esg_scores': int(), +# 'purchase_activity': int(), +# 'earnings': int(), +# 'price_overview': int(), +# 'calendar_events': int(), +# 'company_overview': int(), +# 'sec_filings': int(), +# 'detailed_summary': int(), +# 'financials': int(), +# 'recommendation_trend': int(), +# 'ratings_history': int(), +# 'earnings_history': int(), +# 'earnings_trend': int(), +# 'key_statistics': int(), +# 'income_statements': int(), +# 'cashflow_statements': int(), +# 'balance_statements': int(), +# 'institution_ownership': int(), +# 'fund_ownership': int(), +# 'major_holders': int(), +# 'insider_transactions': int(), +# 'insider_holders': int() +# } +# } diff --git a/yfrake/config/base_config.py b/yfrake/config/base_config.py index 985dc14..c6d7dd0 100644 --- a/yfrake/config/base_config.py +++ b/yfrake/config/base_config.py @@ -115,29 +115,3 @@ def backlog(self, _) -> None: @backlog.deleter def backlog(self) -> None: raise TypeError(self._err_not_available) - - # ------------------------------------------------------------------------------------ # - @property - def cache_size_mb(self) -> int: - return self._config['other']['cache_size_mb'] - - @cache_size_mb.setter - def cache_size_mb(self, _) -> None: - raise TypeError(self._err_not_available) - - @cache_size_mb.deleter - def cache_size_mb(self) -> None: - raise TypeError(self._err_not_available) - - # ------------------------------------------------------------------------------------ # - @property - def cache_ttl(self) -> dict: - return dict(self._config['cache_ttl']) - - @cache_ttl.setter - def cache_ttl(self, _) -> None: - raise TypeError(self._err_not_available) - - @cache_ttl.deleter - def cache_ttl(self) -> None: - raise TypeError(self._err_not_available) diff --git a/yfrake/config/yfrake_settings.ini b/yfrake/config/yfrake_settings.ini index da41273..ccae59f 100644 --- a/yfrake/config/yfrake_settings.ini +++ b/yfrake/config/yfrake_settings.ini @@ -34,45 +34,48 @@ host: localhost port: 8888 backlog: 128 -[CACHE_MAX_SIZE] -num_of_items: 1024 -memory_megs: 64 -[CACHE_TTL_VARIABLE] -historical_prices: !variable! - -[CACHE_TTL_CONSTANT] -options: 0 -insights: 0 -quotes_overview: 0 -esg_chart: 0 -quote_type: 0 -news: 0 -recommendations: 0 -shares_outstanding: 0 -validate_symbols: 0 -market_summary: 0 -trending_symbols: 0 -currencies: 0 -esg_scores: 0 -purchase_activity: 0 -earnings: 0 -price_overview: 0 -calendar_events: 0 -company_overview: 0 -sec_filings: 0 -detailed_summary: 0 -financials: 0 -recommendation_trend: 0 -ratings_history: 0 -earnings_history: 0 -earnings_trend: 0 -key_statistics: 0 -income_statements: 0 -cashflow_statements: 0 -balance_statements: 0 -institution_ownership: 0 -fund_ownership: 0 -major_holders: 0 -insider_transactions: 0 -insider_holders: 0 +; NOT IMPLEMENTED YET / WORK IN PROGRESS +; +;[CACHE_MAX_SIZE] +;num_of_items: 1024 +;memory_megs: 64 +; +;[CACHE_TTL_VARIABLE] +;historical_prices: !variable! +; +;[CACHE_TTL_CONSTANT] +;options: 0 +;insights: 0 +;quotes_overview: 0 +;esg_chart: 0 +;quote_type: 0 +;news: 0 +;recommendations: 0 +;shares_outstanding: 0 +;validate_symbols: 0 +;market_summary: 0 +;trending_symbols: 0 +;currencies: 0 +;esg_scores: 0 +;purchase_activity: 0 +;earnings: 0 +;price_overview: 0 +;calendar_events: 0 +;company_overview: 0 +;sec_filings: 0 +;detailed_summary: 0 +;financials: 0 +;recommendation_trend: 0 +;ratings_history: 0 +;earnings_history: 0 +;earnings_trend: 0 +;key_statistics: 0 +;income_statements: 0 +;cashflow_statements: 0 +;balance_statements: 0 +;institution_ownership: 0 +;fund_ownership: 0 +;major_holders: 0 +;insider_transactions: 0 +;insider_holders: 0 diff --git a/yfrake/openapi/yfrake_spec.yaml b/yfrake/openapi/yfrake_spec.yaml index 55d3ef6..bb08c02 100644 --- a/yfrake/openapi/yfrake_spec.yaml +++ b/yfrake/openapi/yfrake_spec.yaml @@ -29,7 +29,7 @@ info: name: MIT License url: https://github.com/aspenforest/yfrake/blob/main/LICENSE title: YFrake - version: 1.1.0 + version: 2.0.0 openapi: 3.0.0 paths: /balance_statements: