From 30e7c0854160db9706028067abb9e28f5e8c71e2 Mon Sep 17 00:00:00 2001 From: 2O4 <35725720+2O4@users.noreply.github.com> Date: Wed, 2 Mar 2022 19:03:35 +0100 Subject: [PATCH] version 1.0.0 --- .editorconfig | 14 + .github/workflows/python-publish.yml | 36 ++ .gitignore | 129 +++++++ LICENSE | 21 ++ MANIFEST.in | 7 + README.md | 31 ++ dev_requirements.txt | 5 + nomics/__init__.py | 1 + nomics/nomics.py | 518 +++++++++++++++++++++++++++ nomics/utils.py | 30 ++ pyproject.toml | 10 + setup.py | 26 ++ test/__init__.py | 0 test/nomics_test.py | 192 ++++++++++ test/utils_test.py | 33 ++ 15 files changed, 1053 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/workflows/python-publish.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 dev_requirements.txt create mode 100644 nomics/__init__.py create mode 100644 nomics/nomics.py create mode 100644 nomics/utils.py create mode 100644 pyproject.toml create mode 100644 setup.py create mode 100644 test/__init__.py create mode 100644 test/nomics_test.py create mode 100644 test/utils_test.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..74be54e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# https://editorconfig.org/ + +root = true + +[*] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +end_of_line = lf +charset = utf-8 + +# Docstrings and comments use max_line_length = 79 +[*.py] +max_line_length = 80 diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..3bfabfc --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,36 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +on: + release: + types: [published] + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5269766 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 nlnsaoadc + +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. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..84a806b --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +include LICENSE +include README.md + +recursive-include tests * + +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa7c82d --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# [Nomics REST API](https://nomics.com/) wrapper + +[![py-nomics-client-pypi](https://img.shields.io/pypi/v/py-nomics-client.svg)](https://pypi.python.org/pypi/py-nomics-client) + +Nomics REST API Doc: https://nomics.com/docs/ + +## Install + +```bash +pip install py-nomics-client +``` + +## Usage + +```python +from nomics import Nomics + +n = Nomics(key="") +n.get_market(base="BTC", quote="USD") +``` + +## Testing + +```bash +virtualenv venv +source ./venv/bin/activate +pip install -r dev_requirements.txt +deactivate +source ./venv/bin/activate +pytest +``` diff --git a/dev_requirements.txt b/dev_requirements.txt new file mode 100644 index 0000000..b865a23 --- /dev/null +++ b/dev_requirements.txt @@ -0,0 +1,5 @@ +black +isort +pytest +pytest-cov +requests diff --git a/nomics/__init__.py b/nomics/__init__.py new file mode 100644 index 0000000..27f7be7 --- /dev/null +++ b/nomics/__init__.py @@ -0,0 +1 @@ +from .nomics import Nomics diff --git a/nomics/nomics.py b/nomics/nomics.py new file mode 100644 index 0000000..dcec1b9 --- /dev/null +++ b/nomics/nomics.py @@ -0,0 +1,518 @@ +import logging +from typing import Any, Dict, List, Optional + +import requests + +from .utils import clean_params + +logger = logging.getLogger(__name__) + + +class KeyTypeError(Exception): + def __init__(self, function, message=""): + super().__init__(message) + self.func_name = function.__name__ + self.message = message + logger.error(self.__str__()) + + def __str__(self): + return f"'{self.func_name}' is unavailable without a paid plans." + + +def requires_paid_plans(func): + def wrapper_func(self, *args, **kwargs): + if not self.paid_plans: + raise KeyTypeError(func) + func(self, *args, **kwargs) + + return wrapper_func + + +class NomicsAPIError(Exception): + def __init__(self, response, message=""): + super().__init__(message) + self.response = response + self.message = message + + def __str__(self): + return f"{self.response.status_code} {self.response.content.decode()}" + + +class Nomics: + """Nomics API wrapper. + + Web: https://nomics.com/ + Doc: https://nomics.com/docs/ + """ + + BASE_URL = "https://api.nomics.com/" + + def __init__( + self, key: str, paid_plans: bool = False, fail_silently: bool = False + ) -> None: + self.key = key + self.paid_plans = paid_plans + self.fail_silently = fail_silently + + def _get( + self, + path: str, + params: Dict[str, Any] = dict(), + ) -> Any: + params.update({"key": self.key}) + + r = requests.get( + url=self.BASE_URL + path, + params=clean_params(params), + ) + + if r.status_code == 200: + return r.json() + + self._fail(r) + + return None + + def _fail(self, r): + details = r.content.decode() + try: + details = r.json() + except Exception: + pass + + if not self.fail_silently: + logger.warning( + f"Nomics API error {r.status_code} on {r.url}: {details}" + ) + raise NomicsAPIError(response=r) + + logger.info( + f"Nomics API silent error {r.status_code} on {r.url}: " f"{details}" + ) + + def get_currencies_ticker( + self, + ids: List[str], + interval: List[str], + convert: str = "USD", + status: Optional[str] = None, + filter_param: Optional[str] = None, + sort: Optional[str] = None, + include_transparency: Optional[str] = None, + per_page: int = 100, + page: int = 1, + ): + return self._get( + "v1/currencies/ticker", + params={ + "ids": ids, + "interval": interval, + "convert": convert, + "status": status, + "filter": filter_param, + "sort": sort, + "include-transparency": include_transparency, + "per-page": per_page, + "page": page, + }, + ) + + def get_currencies_sparkline( + self, + ids: str, + start: str, + end: Optional[str] = None, + convert: Optional[str] = None, + ): + return self._get( + "v1/currencies/sparkline", + params={ + "ids": ids, + "start": start, + "end": end, + "convert": convert, + }, + ) + + def get_market( + self, + exchange: Optional[str] = None, + base: Optional[str] = None, + quote: Optional[str] = None, + format: Optional[str] = None, + ): + return self._get( + "v1/markets", + params={ + "exchange": exchange, + "base": base, + "quote": quote, + "format": format, + }, + ) + + def get_marketcap_history( + self, + start: str, + end: Optional[str] = None, + convert: Optional[str] = None, + format: Optional[str] = None, + include_transparency: Optional[bool] = None, + ): + return self._get( + "v1/market-cap/history", + params={ + "start": start, + "end": end, + "convert": convert, + "format": format, + "include_transparency": include_transparency, + }, + ) + + def get_global_volume_history( + self, + start: Optional[str] = None, + end: Optional[str] = None, + convert: Optional[str] = None, + format: Optional[str] = None, + include_transparency: Optional[bool] = None, + ): + return self._get( + "v1/volume/history", + params={ + "start": start, + "end": end, + "convert": convert, + "format": format, + "include_transparency": include_transparency, + }, + ) + + def get_exchange_rates(self): + return self._get("v1/exchange-rates") + + def get_exchange_rates_history(self): + return self._get("v1/exchange-rates/history") + + def get_global_ticker(self, convert): + return self._get("v1/global-ticker", params={"convert": convert}) + + def get_currency_highlights( + self, + currency: str, + convert: Optional[str] = None, + interval: Optional[str] = None, + ): + return self._get( + "v1/currencies/highlights", + params={ + "currency": currency, + "convert": convert, + "interval": interval, + }, + ) + + def get_supply_history( + self, + currency: str, + start: str, + end: Optional[str] = None, + format: Optional[str] = None, + ): + return self._get( + "/v1/supplies/history", + params={ + "currency": currency, + "start": start, + "end": end, + "format": format, + }, + ) + + def get_exchange_highlights( + self, + exchange: str, + convert: Optional[str] = None, + interval: Optional[str] = None, + ): + return self._get( + "/v1/exchanges/highlights", + params={ + "exchange": exchange, + "convert": convert, + "interval": interval, + }, + ) + + def get_exchanges_ticker( + self, + ids: Optional[str] = None, + interval: Optional[str] = None, + convert: Optional[str] = None, + status: Optional[str] = None, + type: Optional[str] = None, + centralized: Optional[str] = None, + decentralized: Optional[str] = None, + per_page: Optional[int] = None, + page: Optional[int] = None, + sort: Optional[str] = None, + ): + return self._get( + "v1/exchanges/ticker", + params={ + "ids": ids, + "interval": interval, + "convert": convert, + "status": status, + "type": type, + "centralized": centralized, + "decentralized": decentralized, + "per-page": per_page, + "page": page, + "sort": sort, + }, + ) + + def get_exchanges_volume_history( + self, + exchange: str, + start: str, + end: Optional[str] = None, + convert: Optional[str] = None, + format: Optional[str] = None, + include_transparency: Optional[bool] = None, + ): + return self._get( + "v1/exchanges/volume/history", + params={ + "exchange": exchange, + "start": start, + "end": end, + "convert": convert, + "format": format, + "include_transparency": include_transparency, + }, + ) + + def get_exchange_metadata( + self, + ids: Optional[str] = None, + attribute: Optional[str] = None, + centralized: Optional[str] = None, + decentralized: Optional[str] = None, + format: Optional[str] = None, + ): + return self._get( + "v1/exchanges", + params={ + "ids": ids, + "attribute": attribute, + "centralized": centralized, + "decentralized": decentralized, + "format": format, + }, + ) + + def get_market_highlights( + self, + base: str, + quote: str, + convert: Optional[str] = None, + interval: Optional[str] = None, + ): + return self._get( + "v1/exchange-markets/highlights", + params={ + "base": base, + "quote": quote, + "convert": convert, + "interval": interval, + }, + ) + + @requires_paid_plans + def get_exchange_markets_ticker( + self, + interval: Optional[str] = None, + currency: Optional[str] = None, + base: Optional[str] = None, + quote: Optional[str] = None, + exchange: Optional[str] = None, + market: Optional[str] = None, + convert: Optional[str] = None, + status: Optional[str] = None, + search: Optional[str] = None, + per_page: Optional[str] = None, + page: Optional[int] = None, + sort: Optional[str] = None, + ): + return self._get( + "v1/exchange-markets/ticker", + params={ + "interval": interval, + "currency": currency, + "base": base, + "quote": quote, + "exchange": exchange, + "market": market, + "convert": convert, + "status": status, + "search": search, + "per_page": per_page, + "page": page, + "sort": sort, + }, + ) + + @requires_paid_plans + def get_aggregated_ohlcv_candles( + self, + interval: str, + currency: str, + start: Optional[str] = None, + end: Optional[str] = None, + format: Optional[str] = None, + ): + return self._get( + "v1/candles", + params={ + "interval": interval, + "currency": currency, + "start": start, + "end": end, + "format": format, + }, + ) + + @requires_paid_plans + def get_exchange_ohlcv_candles( + self, + interval: str, + exchange: str, + market: str, + start: Optional[str] = None, + end: Optional[str] = None, + format: Optional[str] = None, + ): + return self._get( + "v1/exchange_candles", + params={ + "interval": interval, + "exchange": exchange, + "market": market, + "start": start, + "end": end, + "format": format, + }, + ) + + @requires_paid_plans + def get_aggregated_pair_ohlcv_candles( + self, + interval: str, + base: str, + quote: str, + start: Optional[str] = None, + end: Optional[str] = None, + format: Optional[str] = None, + ): + return self._get( + "v1/exchange_candles", + params={ + "interval": interval, + "base": base, + "quote": quote, + "start": start, + "end": end, + "format": format, + }, + ) + + @requires_paid_plans + def get_trades( + self, + exchange: str, + market: str, + limit: Optional[int] = None, + order: Optional[str] = None, + from_timestamp: Optional[str] = None, + format: Optional[str] = None, + ): + return self._get( + "v1/trades", + params={ + "exchange": exchange, + "market": market, + "limit": limit, + "order": order, + "from": from_timestamp, + "format": format, + }, + ) + + @requires_paid_plans + def get_order_book_snapshot( + self, + exchange: str, + market: str, + at: Optional[str] = None, + format: Optional[str] = None, + ): + return self._get( + "v1/orders/snapshot", + params={ + "exchange": exchange, + "market": market, + "at": at, + "format": format, + }, + ) + + @requires_paid_plans + def get_order_book_batches( + self, exchange: str, market: str, date: Optional[str] = None + ): + return self._get( + "v1/orders/batches", + params={"exchange": exchange, "market": market, "date": date}, + ) + + @requires_paid_plans + def get_currency_predictions_ticker(self, ids: Optional[str] = None): + return self._get( + "v1/currencies/predictions/ticker", params={"ids": ids} + ) + + @requires_paid_plans + def get_currency_predictions_history( + self, + id: Optional[str] = None, + interval: Optional[str] = None, + ): + return self._get( + "v1/currencies/predictions/history", + params={"id": id, "interval": interval}, + ) + + def get_currencies( + self, + ids: List[str], + attributes: List[str], + format_param: Optional[str] = None, + ): + return self._get( + "v1/currencies", + params={ + "ids": ids, + "attributes": attributes, + "format": format_param, + }, + ) + + @requires_paid_plans + def get_currencies_predictions_ticket(self, ids: List[str]): + return self._get( + "/v1/currencies/predictions/ticker", + params={"ids": ids}, + ) diff --git a/nomics/utils.py b/nomics/utils.py new file mode 100644 index 0000000..634ccda --- /dev/null +++ b/nomics/utils.py @@ -0,0 +1,30 @@ +from typing import Any, Dict, List, Optional, Union + + +def remove_empty_dict_values(dic: Dict[str, Any]) -> Dict[str, Any]: + """Remove empty values inside a dict.""" + return {k: v for k, v in dic.items() if v is not None} + + +def clean_dict_values(dic: Dict[str, Any]) -> Dict[str, Any]: + """Convert booleans and lists to strings in a dict.""" + for key, value in dic.items(): + + if isinstance(value, bool): + # convert a boolean to a string + dic[key] = str(value).lower() + + elif isinstance(value, list): + # convert a list to a string + dic[key] = ",".join([str(i) for i in value]) + + return dic + + +def clean_params(params: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: + """Clean requests params removing empty values.""" + if not params: + return None + params = remove_empty_dict_values(params) + params = clean_dict_values(params) + return params diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9e23844 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,10 @@ +[tool.black] +line-length = 80 + +[tool.isort] +skip_gitignore = true +profile = "black" +line_length = 80 + +[tool.pytest.ini_options] +addopts = "--cov=nomics --no-cov-on-fail" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..5049626 --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +import pathlib + +from setuptools import setup + +ROOT = pathlib.Path(__file__).parent + +README = (ROOT / "README.md").read_text() + +setup( + name="py-nomics-client", + version="1.0.0", + description="Nomics API wrapper", + long_description=README, + long_description_content_type="text/markdown", + url="https://github.com/nlnsaoadc/py-nomics", + author="nlnsaoadc", + license="MIT", + classifiers=[ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + ], + packages=["nomics"], + include_package_data=True, + install_requires=["requests"], +) diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/nomics_test.py b/test/nomics_test.py new file mode 100644 index 0000000..1334d8d --- /dev/null +++ b/test/nomics_test.py @@ -0,0 +1,192 @@ +from unittest import TestCase, mock + +from nomics.nomics import KeyTypeError, Nomics + + +class NomicsTestCase(TestCase): + def setUp(self): + self.api = Nomics(key="123test", paid_plans=True) + self.api_unpaid_plans = Nomics(key="123test", paid_plans=False) + + @mock.patch( + "requests.get", return_value=mock.Mock(status_code=200, json=lambda: {}) + ) + def test_get(self, mock_get): + self.api._get("test") + mock_get.assert_called_once_with( + url="https://api.nomics.com/test", + params={"key": "123test"}, + ) + + @mock.patch("nomics.nomics.logger.warning") + @mock.patch( + "requests.get", + return_value=mock.Mock( + status_code=404, + json=lambda: {"message": "Not Found"}, + content=b"404 Not Found Message", + ), + ) + def test_get_404_status(self, mock_get, mock_log): + with self.assertRaises(Exception) as context: + self.api._get("test") + self.assertEqual( + "404 404 Not Found Message", + str(context.exception), + ) + mock_log.assert_called_once() + + @mock.patch("nomics.nomics.logger.info") + @mock.patch( + "requests.get", + return_value=mock.Mock( + status_code=404, + json=mock.Mock(side_effect=Exception("")), + content=b"404 Not Found Message", + ), + ) + def test_get_404_status_fail_silently(self, mock_get, mock_log): + self.api.fail_silently = True + self.assertEqual(self.api._get("test"), None) + mock_log.assert_called_once() + + @mock.patch("nomics.nomics.logger.error") + @mock.patch("nomics.nomics.Nomics._get") + def test_wrong_key_type(self, mock_get, mock_log): + try: + self.api_unpaid_plans.get_exchange_markets_ticker() + except KeyTypeError as error: + self.assertEqual(type(str(error)), str) + mock_log.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_currencies_ticker(self, mock_get): + self.api.get_currencies_ticker(ids=[""], interval=[""], convert="USD") + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_currencies_sparkline(self, mock_get): + self.api.get_currencies_sparkline(ids="", start="") + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_market(self, mock_get): + self.api.get_market() + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_marketcap_history(self, mock_get): + self.api.get_marketcap_history(start="") + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_global_volume_history(self, mock_get): + self.api.get_global_volume_history() + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_exchange_rates(self, mock_get): + self.api.get_exchange_rates() + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_exchange_rates_history(self, mock_get): + self.api.get_exchange_rates_history() + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_global_ticker(self, mock_get): + self.api.get_global_ticker(convert=True) + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_currency_highlights(self, mock_get): + self.api.get_currency_highlights(currency="") + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_supply_history(self, mock_get): + self.api.get_supply_history(currency="", start="") + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_exchange_highlights(self, mock_get): + self.api.get_exchange_highlights(exchange="") + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_exchanges_ticker(self, mock_get): + self.api.get_exchanges_ticker() + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_exchanges_volume_history(self, mock_get): + self.api.get_exchanges_volume_history(exchange="", start="") + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_exchange_metadata(self, mock_get): + self.api.get_exchange_metadata() + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_market_highlights(self, mock_get): + self.api.get_market_highlights(base="", quote="") + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_exchange_markets_ticker(self, mock_get): + self.api.get_exchange_markets_ticker() + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_aggregated_ohlcv_candles(self, mock_get): + self.api.get_aggregated_ohlcv_candles(interval="", currency="") + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_exchange_ohlcv_candles(self, mock_get): + self.api.get_exchange_ohlcv_candles(interval="", exchange="", market="") + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_aggregated_pair_ohlcv_candles(self, mock_get): + self.api.get_aggregated_pair_ohlcv_candles( + interval="", base="", quote="" + ) + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_trades(self, mock_get): + self.api.get_trades(exchange="", market="") + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_order_book_snapshot(self, mock_get): + self.api.get_order_book_snapshot(exchange="", market="") + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_order_book_batches(self, mock_get): + self.api.get_order_book_batches(exchange="", market="") + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_currency_predictions_ticker(self, mock_get): + self.api.get_currency_predictions_ticker() + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_currency_predictions_history(self, mock_get): + self.api.get_currency_predictions_history() + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_currencies(self, mock_get): + self.api.get_currencies(ids=[""], attributes=[""]) + mock_get.assert_called_once() + + @mock.patch("nomics.nomics.Nomics._get") + def test_get_currencies_predictions_ticket(self, mock_get): + self.api.get_currencies_predictions_ticket(ids=[""]) + mock_get.assert_called_once() diff --git a/test/utils_test.py b/test/utils_test.py new file mode 100644 index 0000000..79c4e29 --- /dev/null +++ b/test/utils_test.py @@ -0,0 +1,33 @@ +from nomics.utils import ( + clean_dict_values, + clean_params, + remove_empty_dict_values, +) + + +def test_remove_empty_dict_values(): + dict_with_empty_values = {"a": None, "b": 123, "c": "foo", "d": None} + new_dict = remove_empty_dict_values(dict_with_empty_values) + for value in new_dict.values(): + assert value is not None + + +def test_clean_dict_values(): + input_dict = {"c": "foo", "e": True, "f": ["foo", "bar"]} + new_dict = clean_dict_values(input_dict) + assert isinstance(new_dict["e"], str) + assert isinstance(new_dict["f"], str) + assert new_dict["e"] == "true" + assert new_dict["f"] == "foo,bar" + + +def test_clean_params(): + new_dict = clean_params({"a": None, "b": ["foo", "bar"], "c": True}) + assert "a" not in new_dict + assert new_dict["b"] == "foo,bar" + assert new_dict["c"] == "true" + + +def test_clean_params_empty(): + new_dict = clean_params(None) + assert new_dict is None