Skip to content

Commit

Permalink
ci: add all checks (#8)
Browse files Browse the repository at this point in the history
* ci: add all checks

* adjust test ci

* add permissions

* requirements-dev

* fix

* fix lint

* remove docs

* format

* lazy logging

* lazy logging

* more custom exceptions

* lint

* lint

* lint

* public methods

* public methods

* remove old ci

* Potential fix for code scanning alert no. 10: Clear-text logging of sensitive information

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
  • Loading branch information
1 parent d06727d commit ab7a995
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 110 deletions.
38 changes: 0 additions & 38 deletions .github/workflows/ci-tests.yml

This file was deleted.

133 changes: 133 additions & 0 deletions .github/workflows/static-code-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
name: Static Code Checks
permissions:
contents: read
pull-requests: write

on:
workflow_dispatch:
push:
branches:
- main
pull_request:
branches:
- main
release:
types: [ created ]

jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ 3.9, '3.10', 3.11, 3.12, 3.13 ]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
- name: Lint
run: |
pylint deribit_wrapper
check:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ 3.9, '3.10', 3.11, 3.12, 3.13 ]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
- name: Check
run: |
pyflakes
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ 3.9, '3.10', 3.11, 3.12, 3.13 ]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
- name: Test
env:
TEST_CLIENT_ID: ${{ secrets.TEST_CLIENT_ID }}
TEST_CLIENT_SECRET: ${{ secrets.TEST_CLIENT_SECRET }}
run: |
pytest
docs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ 3.9, '3.10', 3.11, 3.12, 3.13 ]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
# TODO: improve documentation
# - name: Docs style
# run: |
# pydocstyle

may-merge:
if: always()
needs: [ 'lint', 'check', 'test', 'docs' ]
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ 3.9, '3.10', 3.11, 3.12, 3.13 ]
steps:
- name: Cleared for merging
run: |
if [ "${{ needs.lint.result }}" == "success" ] && [ "${{ needs.check.result }}" == "success" ] && [ "${{ needs.test.result }}" == "success" ] && [ "${{ needs.docs.result }}" == "success" ]; then
echo "This PR is cleared for merging"
else
echo "This PR is not cleared for merging"
exit 1
fi
36 changes: 36 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[MAIN]

# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=
dev_scripts,
tests,

[MESSAGES-CONTROL]

# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=
missing-function-docstring,
missing-module-docstring,
missing-class-docstring,

[FORMAT]

# Maximum number of characters on a single line.
max-line-length=120

[DESIGN]

# Maximum number of arguments for function / method.
max-args=10

# Maximum number of public methods for a class (see R0904).
max-public-methods=30
19 changes: 7 additions & 12 deletions deribit_wrapper/account_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from dev_scripts.utilities_dev import create_multilevel_df
from .exceptions import SubaccountNameAlreadyTaken, SubaccountNameWrongFormat, SubaccountError, WaitRequiredError, \
SubaccountNotRemovable, SubaccountAlreadyRemoved, InvalidMarginModelError, InvalidParameterError
SubaccountNotRemovable, SubaccountAlreadyRemoved, InvalidMarginModelError, InvalidParameterForRequest
from .market_data import MarketData
from .utilities import DEFAULT_END, DEFAULT_START, MarginModelType, MarketOrderType, from_dt_to_ts, seconds_to_hms

Expand Down Expand Up @@ -148,10 +148,9 @@ def change_subaccount_name(self, subaccount_id: int, name: str) -> dict:
data = r.get('data')
if data == 'already_taken':
raise SubaccountNameAlreadyTaken(f"Subaccount name '{name}' is already taken.")
elif data == 'wrong_format':
if data == 'wrong_format':
raise SubaccountNameWrongFormat(f"Subaccount name '{name}' has the wrong format.")
else:
raise SubaccountError(f"Error changing subaccount name to '{name}': {data}.")
raise SubaccountError(f"Error changing subaccount name to '{name}': {data}.")

return r

Expand Down Expand Up @@ -185,8 +184,7 @@ def remove_subaccount(self, subaccount_id: int, wait_if_over_limit: bool = False
reason = error_data.get('reason')
if reason == 'already_removed':
raise SubaccountAlreadyRemoved(f'Subaccount {subaccount_id} already removed.')
else:
raise SubaccountError(f'Unauthorized to remove subaccount {subaccount_id}: {reason}.')
raise SubaccountError(f'Unauthorized to remove subaccount {subaccount_id}: {reason}.')

return r

Expand All @@ -210,8 +208,7 @@ def _change_margin_model(self, margin_model: MarginModelType, subaccount_id: int
reason = error_data.get('reason')
if param == 'margin_model':
raise InvalidMarginModelError(f'Invalid margin model {margin_model}: {reason}')
else:
raise InvalidParameterError(f'Invalid params for request {uri} with param {param}: {reason}')
raise InvalidParameterForRequest(f'Invalid params for request {uri} with param {param}: {reason}')

df = create_multilevel_df(r)
return df
Expand Down Expand Up @@ -258,10 +255,8 @@ def get_transaction_log(self, start: str | datetime = None, end: str | datetime
currency = self.currencies
elif not isinstance(currency, list):
currency = [currency]
start_ts = from_dt_to_ts(pd.to_datetime(start, utc=True))
end_ts = from_dt_to_ts(pd.to_datetime(end, utc=True))
params['start_timestamp'] = start_ts
params['end_timestamp'] = end_ts
params['start_timestamp'] = from_dt_to_ts(pd.to_datetime(start, utc=True))
params['end_timestamp'] = from_dt_to_ts(pd.to_datetime(end, utc=True))
results = pd.DataFrame()
for q in query:
if q is not None:
Expand Down
95 changes: 52 additions & 43 deletions deribit_wrapper/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from urllib3.util.retry import Retry

from .base import DeribitBase
from .exceptions import DeribitClientWarning
from .exceptions import DeribitClientWarning, ServiceUnavailable, RequestError
from .utilities import ParamsType, ScopeType, seconds_to_hms


Expand Down Expand Up @@ -84,49 +84,9 @@ def _request(self, uri: str, params: ParamsType, give_results: bool = True) -> d
ret = ret['error']
error_code = ret.get('code')
error_data = ret.get('data', {})

# Error 10028: too many requests
if error_code == 10028:
wait = error_data.get('wait', 1)
print(f'Too many requests for URI {uri}. Waiting {seconds_to_hms(wait)}...')
for i in range(wait):
time.sleep(1)
print(f"Wait {seconds_to_hms(wait - i)}...", end='\r', flush=True)
print()
ret = self._request(uri, params, give_results=give_results)

# Error 13009: unauthorized
elif error_code == 13009:
reason = error_data.get('reason')
if reason == 'invalid_token':
max_attempts = 3
for i in range(max_attempts):
print(f'Invalid token. Trying to get a new one. Attempt {i + 1} of {max_attempts}...')
ret = self._request(uri, params, give_results=give_results)

# Error 13028: temporarily unavailable
elif error_code == 13028:
max_attempts = 60
for i in range(max_attempts):
print(f'Temporarily unavailable. Waiting 1 minute [{i + 1}/{max_attempts}]...')
time.sleep(60)
ret = self._request(uri, params, give_results=give_results)
if ret.get('code') != 13028:
break
if ret.get('code') == 13028:
raise Exception('Service temporarily unavailable.')

# Error -32602: invalid params
elif error_code == -32602:
param = error_data.get('param')
reason = error_data.get('reason')
print(f'Invalid params for request {uri} with param {param}: {reason}')

else:
print(f'Error code {error_code} for request {uri} with params {params}.')
print(ret)
return self._handle_error(uri, params, error_code, error_data, give_results=give_results)
else:
raise Exception(ret)
raise RequestError(ret)

if not isinstance(ret, dict) and not isinstance(ret, list):
ret = {'result': ret}
Expand All @@ -136,6 +96,55 @@ def _request(self, uri: str, params: ParamsType, give_results: bool = True) -> d

return ret

def _handle_error(self, uri: str, params: ParamsType, error_code: int, error_data: dict,
give_results: bool) -> dict:
if error_code == 10028:
return self._handle_too_many_requests(uri, params, error_data, give_results=give_results)
if error_code == 13009:
return self._handle_unauthorised(uri, params, error_data, give_results=give_results)
if error_code == 13028:
return self._handle_temporarily_unavailable(uri, params, give_results=give_results)
if error_code == -32602:
self._handle_invalid_params(uri, error_data)
else:
sanitized_params = {k: (v if k != 'client_secret' else '***') for k, v in params.items()}
print(f'Error code {error_code} for request {uri} with params {sanitized_params}.')
print(error_data)
return {}

def _handle_too_many_requests(self, uri: str, params: ParamsType, error_data: dict, give_results: bool) -> dict:
wait = error_data.get('wait', 1)
print(f'Too many requests for URI {uri}. Waiting {seconds_to_hms(wait)}...')
for i in range(wait):
time.sleep(1)
print(f"Wait {seconds_to_hms(wait - i)}...", end='\r', flush=True)
print()
return self._request(uri, params, give_results=give_results)

def _handle_unauthorised(self, uri: str, params: ParamsType, error_data: dict, give_results: bool) -> dict:
reason = error_data.get('reason')
if reason == 'invalid_token':
max_attempts = 3
for i in range(max_attempts):
print(f'Invalid token. Trying to get a new one. Attempt {i + 1} of {max_attempts}...')
return self._request(uri, params, give_results=give_results)
return {}

def _handle_temporarily_unavailable(self, uri: str, params: ParamsType, give_results: bool) -> dict:
max_attempts = 60
for i in range(max_attempts):
print(f'Temporarily unavailable. Waiting 1 minute [{i + 1}/{max_attempts}]...')
time.sleep(60)
ret = self._request(uri, params, give_results=give_results)
if ret.get('code') != 13028:
return ret
raise ServiceUnavailable('Service temporarily unavailable.')

def _handle_invalid_params(self, uri: str, error_data: dict):
param = error_data.get('param')
reason = error_data.get('reason')
print(f'Invalid params for request {uri}: param={param}, reason={reason}')

@property
def access_token(self) -> str:
self.refresh_token_if_expired()
Expand Down
Loading

0 comments on commit ab7a995

Please sign in to comment.