Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(SPT-27226, SPT-12911): add python3.12 support and retry support #200

Merged
merged 38 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
35b3331
fix: update pendulum requirement to support python3.12
koushik-kiran-kumar Jun 24, 2024
00f4fa7
fix: update setup
koushik-kiran-kumar Jun 24, 2024
bc61d44
fix: version
koushik-kiran-kumar Jun 24, 2024
0b9bf84
fix: github action
koushik-kiran-kumar Jun 24, 2024
9e0f53a
fix: github action
koushik-kiran-kumar Jun 24, 2024
6d43b8e
fix: test requirements
koushik-kiran-kumar Jun 24, 2024
a518a6a
fix: python tests
koushik-kiran-kumar Jun 24, 2024
6958b9a
fix: workflows
koushik-kiran-kumar Jun 24, 2024
fd77c45
fix: workflows
koushik-kiran-kumar Jun 24, 2024
badc467
fix: workflows
koushik-kiran-kumar Jun 24, 2024
a28a6e9
fix: workflows
koushik-kiran-kumar Jun 24, 2024
13c2b48
fix: workflows
koushik-kiran-kumar Jun 24, 2024
a2c76bc
fix: workflows
koushik-kiran-kumar Jun 24, 2024
73689b5
fix: workflows
koushik-kiran-kumar Jun 24, 2024
a8fec87
fix: workflows
koushik-kiran-kumar Jun 24, 2024
081c987
fix: workflows
koushik-kiran-kumar Jun 24, 2024
0739cb2
fix: workflows
koushik-kiran-kumar Jun 24, 2024
06027c0
fix: workflows
koushik-kiran-kumar Jun 24, 2024
bf976cb
fix: workflows
koushik-kiran-kumar Jun 24, 2024
b3d4909
fix: workflows
koushik-kiran-kumar Jun 24, 2024
aadd973
fix: workflows
koushik-kiran-kumar Jun 24, 2024
b82a64b
fix: workflows
koushik-kiran-kumar Jun 24, 2024
12d99b1
fix: workflows
koushik-kiran-kumar Jun 25, 2024
5cace02
fix: requirements
koushik-kiran-kumar Jun 25, 2024
1709dda
fix: requirements
koushik-kiran-kumar Jun 25, 2024
acbbf52
fix: requirements
koushik-kiran-kumar Jun 25, 2024
f507c8e
fix: requirements
koushik-kiran-kumar Jun 25, 2024
4204323
fix: requirements
koushik-kiran-kumar Jun 25, 2024
2f9e50c
fix: requirements
koushik-kiran-kumar Jun 25, 2024
2c73e4a
fix: requirements
koushik-kiran-kumar Jun 25, 2024
223452f
fix: requirements
koushik-kiran-kumar Jun 25, 2024
459c66b
fix: pytest.ini
koushik-kiran-kumar Jun 25, 2024
4c8a1d7
feat: add retry support for api calls
koushik-kiran-kumar Jun 25, 2024
950e2ab
feat: add retry support for api calls
koushik-kiran-kumar Jun 25, 2024
c196f9f
feat: add retry support for api calls
koushik-kiran-kumar Jun 25, 2024
f486a4e
feat: add retry support for api calls
koushik-kiran-kumar Jun 25, 2024
5845758
feat: add retry support for api calls
koushik-kiran-kumar Jun 25, 2024
6a27f76
feat: add retry support for api calls
koushik-kiran-kumar Jun 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions .github/workflows/build_test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "BuildAndTest"
name: "Build And Test"

on:
push:
Expand All @@ -8,16 +8,16 @@ on:

jobs:
build:
name: Build and test driver
name: Build and Test Driver
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: [3.6]
python-version: [3.6, 3.7, 3.12]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand All @@ -32,9 +32,6 @@ jobs:
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Run Tests
run: py.test -v --cov=swimlane --cov-report=xml
- name: Run Codacy Coverage
env:
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
run: python-codacy-coverage -r coverage.xml
run: |
python -m pytest

2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "CodeQL"
name: "CodeQL Analysis"

on:
push:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This workflow is responsible for publishing packages to PyPi
# and creating offline packages

name: "Release new version"
name: "Release New Version"

on:
- workflow_call
Expand All @@ -12,12 +12,12 @@ jobs:
name: Publish to PyPi
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Setup python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.7
python-version: 3.12

- name: Update PIP tools
run: |
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,6 @@ functional_tests/pydriver-report.html

# virtual environment
venv3/
venv/
venv/

test-script.py
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ docs:
cd docs/ && make html

release:
python setup.py sdist bdist_wheel upload -r swimlane
python3 setup.py sdist bdist_wheel upload -r swimlane

build-offline-installer:
python2.7 offline_installer/build_installer.py
python3 offline_installer/build_installer.py
21 changes: 11 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,27 +63,28 @@ Install the required PIP packages with the command
## Executing

The test suite allows for overriding the target server and user parameters via the following arguments:

--url default="https://localhost"
--user default="admin"
--pass This is the password for the user defined above.
--skipverify This is for allowing the version of PyDriver to not match the version of Swimlane.
::
pytest
--url default="https://localhost"
--user default="admin"
--pass This is the password for the user defined above.
--skipverify This is for allowing the version of PyDriver to not match the version of Swimlane.

To run a specific test and skip the version verification:

pytest driver_tests/test_app_adaptor.py --skipverify
::
pytest driver_tests/test_app_adaptor.py --skipverify

To run all the tests against 10.20.30.40:

pytest --url "https://10.20.30.40"

::
pytest --url "https://10.20.30.40"

.. NOTE::
All of the data created for testing purposes is cleaned up.

No preset data is needed beyond the base user.

These tests are Python 2 and 3 compatible.
These tests are Python 3.6+ compatible.

Issues
------
Expand Down
45 changes: 41 additions & 4 deletions docs/examples/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,46 @@ additional requests made by using the client automatically.

The `verify_ssl` parameter is ignored when connecting over HTTP.

Retry Requests
^^^^^^^^^^^^^

Initial client connection and all failed requests are retried upon recieving :class:`HTTP 5XX` errors (server errors) if the :class:`retry` parameter is enabled.
The default retry options are set as following:
- retry = True
- max_retries = 5
- retry_interval = 5 (in seconds)

To override the default retry options used by all library methods, provide them during client instantiation.

.. code-block:: python

from swimlane import Swimlane

swimlane = Swimlane(
'192.168.1.1',
'username',
'password',
retry=True,
max_retries=3,
retry_interval=10 # in seconds
)
The :meth:`swimlane.Swimlane.request` method can also accept the optional retry options that will override the
global defaults for the single request.

.. code-block:: python

from swimlane import Swimlane

swimlane = Swimlane('192.168.1.1', 'username', 'password')

response = swimlane.request(
'post',
'some/endpoint',
...,
retry=True,
max_retries=3,
retry_interval=10 # in seconds
)

Resource Caching
^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -200,7 +240,7 @@ All provided keyword arguments will be passed to the underlying :meth:`requests.

.. note::

Any 400/500 responses will raise :class:`requests.HTTPError` automatically.
Any 400/500 responses will raise :class:`requests.HTTPError` automatically after Max Retry attempts are exceeded.


Request Timeouts
Expand Down Expand Up @@ -270,9 +310,6 @@ disabled by setting `verify_server_version=False`.
'password',
verify_server_version=False
)



Available Adapters
------------------

Expand Down
4 changes: 0 additions & 4 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ Install/upgrade to latest release::

pip install -U swimlane

Install/upgrade to latest release for platform v2.x::

pip install -U "swimlane>=2,<3"


Offline Installer
^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 1 addition & 1 deletion functional_tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Faker==0.8.15
pytest>=3.5.0
pytest-html>=1.22.1
pytest-html>=1.22.1
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
testpaths = tests
5 changes: 3 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
cachetools>=2.0,<2.1
cachetools>=4.2.4
certifi>=2017
pendulum==2.1.2
pendulum==2.1.2; python_version<='3.7'
pendulum==3.0.0; python_version>='3.8'
pyjwt>=2.4.0
pyuri>=0.3,<0.4
requests[security]>=2,<3
Expand Down
4 changes: 0 additions & 4 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,3 @@ test=pytest

[wheel]
universal=1

[tool:pytest]
python_paths = .
testpaths = tests
12 changes: 8 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ def parse_requirements(requirement_file):


setup(
version="10.14.0",
version="10.18.0",
name="swimlane",
author="Swimlane",
author_email="[email protected]",
url="https://github.com/swimlane/swimlane-python",
python_requires=">=3.6",
packages=find_packages(exclude=('tests', 'tests.*')),
description="Python driver for the Swimlane API",
long_description=long_description,
Expand All @@ -29,9 +30,12 @@ def parse_requirements(requirement_file):
"License :: OSI Approved :: GNU Affero General Public License v3",
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7"
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12"
]
)
60 changes: 48 additions & 12 deletions swimlane/core/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import jwt
import pendulum
import requests
import time
from pyuri import URI
from requests.compat import json
from requests.packages import urllib3
from requests.structures import CaseInsensitiveDict
from requests.exceptions import ConnectionError
from six.moves.urllib.parse import urljoin

from swimlane.core.adapters import GroupAdapter, UserAdapter, AppAdapter, HelperAdapter
Expand Down Expand Up @@ -46,6 +48,9 @@ class Swimlane(object):
caching. Disabled by default
access_token (str): Authentication token, used in lieu of a username and password
write_to_read_only (bool): Enable the ability to write to Read-only fields
retry (bool): Retry request when error code is >= 500
max_retries (int): Maximum number of retry attempts
retry_interval (int): Time interval (in seconds) between two retry attempts

Attributes:
host (pyuri.URI): Full RFC-1738 URL pointing to Swimlane host
Expand Down Expand Up @@ -92,7 +97,10 @@ def __init__(
verify_server_version=True,
resource_cache_size=0,
access_token=None,
write_to_read_only=False
write_to_read_only: bool=False,
retry: bool=True,
max_retries: int=5,
retry_interval: int=5
):
self.__verify_auth_params(username, password, access_token)

Expand All @@ -111,6 +119,10 @@ def __init__(

self._session = WrappedSession()
self._session.verify = verify_ssl

self.retry = retry
self.max_retries = max_retries
self.retry_interval = retry_interval

if username is not None and password is not None:
self._session.auth = SwimlaneJwtAuth(
Expand Down Expand Up @@ -215,17 +227,41 @@ def request(self, method, api_endpoint, **kwargs):
kwargs['headers'] = headers

kwargs['data'] = json.dumps(json_data, sort_keys=True, separators=(',', ':'))

response = self._session.request(method, urljoin(str(self.host) + self._api_root, api_endpoint), **kwargs)

# Roll 400 errors up into SwimlaneHTTP400Errors with specific Swimlane error code support
try:
response.raise_for_status()
except requests.HTTPError as error:
if error.response.status_code == 400:
raise SwimlaneHTTP400Error(error)
else:
raise error

# Retry logic
req_retry = kwargs.pop('retry', self.retry)

req_max_retries = kwargs.pop('max_retries', self.max_retries)
if not isinstance(req_max_retries, int):
raise TypeError('max_retries should be an integer')
if req_max_retries <= 0:
raise ValueError('max_retries should be a positive integer')

req_retry_interval = kwargs.pop('retry_interval', self.retry_interval)
if not isinstance(req_retry_interval, int):
raise TypeError('retry_interval should be an integer')
if req_retry_interval <= 0:
raise ValueError('retry_interval should be a positive integer')

while not req_max_retries<0:
response = self._session.request(method, urljoin(str(self.host) + self._api_root, api_endpoint), **kwargs)

# Roll 400 errors up into SwimlaneHTTP400Errors with specific Swimlane error code support
try:
response.raise_for_status()
# Exit loop on successful request
req_max_retries = -1
except requests.HTTPError as error:
if error.response.status_code == 400:
raise SwimlaneHTTP400Error(error)
else:
if req_retry and req_max_retries>0 and error.response.status_code>=500:
req_max_retries -= 1
time.sleep(req_retry_interval)
continue
elif req_max_retries == 0:
raise ConnectionError(f'Max retries exceeded. Caused by ({error})')
raise error

return response

Expand Down
9 changes: 6 additions & 3 deletions swimlane/core/resources/usergroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ def _evaluate(self):
yield element
else:
for user_id in self.__user_ids:
element = self._swimlane.users.get(id=user_id)
self._elements.append(element)
yield element
try:
element = self._swimlane.users.get(id=user_id)
self._elements.append(element)
yield element
except StopIteration:
return
5 changes: 2 additions & 3 deletions test-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
coverage
faker
mock
pytest==3.10.1
pytest-cov==2.8.1
pytest>=6.2.5
pytest-pythonpath
tox
pytz
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py27,py34,py35,py36
envlist = py36,py37,py38,py39,py310,py311,py312

[testenv]
passenv = LANG
Expand Down