From 08e1de9e3197afd65cd021612c5897123dcfbd83 Mon Sep 17 00:00:00 2001 From: Sivan Becker Date: Thu, 24 Oct 2019 10:28:12 +0300 Subject: [PATCH 01/25] drop support for python < 3.6 --- .travis.yml | 3 +-- backslash/api.py | 8 ++++---- backslash/api_object.py | 6 +++--- backslash/archiveable.py | 2 +- backslash/client.py | 8 ++++---- backslash/commentable.py | 6 +++--- backslash/compatibility.py | 4 ++-- backslash/contrib/keepalive_thread.py | 2 +- backslash/contrib/slash_plugin.py | 16 ++++++++-------- backslash/contrib/utils.py | 6 +++--- backslash/error_container.py | 2 +- backslash/field_filters.py | 10 +++++----- backslash/lazy_query.py | 8 ++++---- backslash/metadata_holder.py | 2 +- backslash/related_entity_container.py | 2 +- backslash/session.py | 4 ++-- backslash/test.py | 4 ++-- backslash/timing_container.py | 4 ++-- backslash/utils.py | 2 +- backslash/warning_container.py | 2 +- docs/changelog.rst | 1 + setup.cfg | 12 ++++-------- setup.py | 1 + tests/test_api_object.py | 4 ++-- tests/test_lazy_query.py | 9 ++++----- tests/test_slash_plugin.py | 2 +- 26 files changed, 63 insertions(+), 67 deletions(-) diff --git a/.travis.yml b/.travis.yml index 98463d9..5c3de6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,7 @@ language: python python: -- 2.7 -- 3.5 - 3.6 +- 3.7 install: - pip install -U pip setuptools - pip install -e .[testing] diff --git a/backslash/api.py b/backslash/api.py index 5d91b42..19fadbf 100644 --- a/backslash/api.py +++ b/backslash/api.py @@ -44,10 +44,10 @@ _MAX_PARAMS_UNCOMPRESSED_SIZE = 10 * 1024 * 1024 # 10Mb -class API(object): +class API(): def __init__(self, client, url, runtoken, timeout_seconds=60, headers=None): - super(API, self).__init__() + super().__init__() self.client = client self.url = URL(url) self.runtoken = runtoken @@ -177,10 +177,10 @@ def _compress(self, data): return s.getvalue() -class CallProxy(object): +class CallProxy(): def __init__(self, api): - super(CallProxy, self).__init__() + super().__init__() self._api = api def __getattr__(self, attr): diff --git a/backslash/api_object.py b/backslash/api_object.py index c0f04f7..dc3f03a 100644 --- a/backslash/api_object.py +++ b/backslash/api_object.py @@ -1,8 +1,8 @@ -class APIObject(object): +class APIObject(): def __init__(self, client, json_data): - super(APIObject, self).__init__() + super().__init__() self.client = client self._data = json_data @@ -38,7 +38,7 @@ def _fetch(self): return self.client.api.get(self.api_path, raw=True)[self._data['type']] def __repr__(self): - return ''.format(data=self._data) + return f'' def without_fields(self, field_names): new_data = dict((field_name, field_value) diff --git a/backslash/archiveable.py b/backslash/archiveable.py index 85a9ac0..1d8754f 100644 --- a/backslash/archiveable.py +++ b/backslash/archiveable.py @@ -1,4 +1,4 @@ -class Archiveable(object): +class Archiveable(): def toggle_archived(self): self.client.api.call_function('toggle_archived', {self._get_id_key(): self.id}) diff --git a/backslash/client.py b/backslash/client.py index 1a6a54c..e35d4a8 100644 --- a/backslash/client.py +++ b/backslash/client.py @@ -12,12 +12,12 @@ _logger = logbook.Logger(__name__) -class Backslash(object): +class Backslash(): def __init__(self, url, runtoken, headers=None): - super(Backslash, self).__init__() + super().__init__() if not url.startswith('http'): - url = 'http://{0}'.format(url) + url = f'http://{url}' self._url = URL(url) self.api = API(self, url, runtoken, headers=headers) @@ -33,7 +33,7 @@ def get_ui_url(self, fragment=None): fragment = '/' elif not fragment.startswith('/'): fragment = '/' + fragment - returned += '#{}'.format(fragment) + returned += f'#{fragment}' return returned diff --git a/backslash/commentable.py b/backslash/commentable.py index a979fb4..8337b29 100644 --- a/backslash/commentable.py +++ b/backslash/commentable.py @@ -1,13 +1,13 @@ from .lazy_query import LazyQuery -class Commentable(object): +class Commentable(): def post_comment(self, comment): return self.client.api.call_function('post_comment', { 'comment': comment, - '{}_id'.format(self.type): self.id + f'{self.type}_id': self.id }) def get_comments(self): - return LazyQuery(self.client, '/rest/comments', query_params={'{}_id'.format(self.type): self.id}) + return LazyQuery(self.client, '/rest/comments', query_params={f'{self.type}_id': self.id}) diff --git a/backslash/compatibility.py b/backslash/compatibility.py index 25d9fd1..6c5759a 100644 --- a/backslash/compatibility.py +++ b/backslash/compatibility.py @@ -1,5 +1,5 @@ -class Compatibility(object): +class Compatibility(): def __init__(self, client): - super(Compatibility, self).__init__() + super().__init__() self.client = client diff --git a/backslash/contrib/keepalive_thread.py b/backslash/contrib/keepalive_thread.py index 94e3fe9..b3127a7 100644 --- a/backslash/contrib/keepalive_thread.py +++ b/backslash/contrib/keepalive_thread.py @@ -8,7 +8,7 @@ class KeepaliveThread(threading.Thread): def __init__(self, client, session, interval, error_callback=None): - super(KeepaliveThread, self).__init__() + super().__init__() self._client = client self._session = session self._interval = interval / 2.0 diff --git a/backslash/contrib/slash_plugin.py b/backslash/contrib/slash_plugin.py index 3260056..1412827 100644 --- a/backslash/contrib/slash_plugin.py +++ b/backslash/contrib/slash_plugin.py @@ -68,7 +68,7 @@ class BackslashPlugin(PluginInterface): def __init__(self, url=None, keepalive_interval=None, runtoken=None, propagate_exceptions=False, config_filename=_DEFAULT_CONFIG_FILENAME): - super(BackslashPlugin, self).__init__() + super().__init__() self._url = url self._repo_cache = {} self._config_filename = config_filename @@ -98,7 +98,7 @@ def session_webapp_url(self): session = slash.context.session if session is None or self.client is None: return None - return self.client.get_ui_url('sessions/{}'.format(session.id)) + return self.client.get_ui_url(f'sessions/{session.id}') def _handle_exception(self, exc_info): pass @@ -144,7 +144,7 @@ def _get_default_headers(self): def deactivate(self): if self._keepalive_thread is not None: self._keepalive_thread.stop() - super(BackslashPlugin, self).deactivate() + super().deactivate() def _notify_session_start(self): metadata = self._get_initial_session_metadata() @@ -298,7 +298,7 @@ def test_skip(self, reason=None): @slash.plugins.registers_on(None) def is_session_exist(self, session_id): try: - self.client.api.get('/rest/sessions/{0}'.format(session_id)) + self.client.api.get(f'/rest/sessions/{session_id}') return True except HTTPError as e: if e.response.status_code == 404: @@ -392,10 +392,10 @@ def _calculate_file_hash(self, filename): data = f.read() h = hashlib.sha1() h.update('blob '.encode('utf-8')) - h.update('{0}\0'.format(len(data)).encode('utf-8')) + h.update(f'{len(data)}\0'.encode('utf-8')) h.update(data) except IOError as e: - _logger.debug('Ignoring IOError {0!r} when calculating file hash for {1}', e, filename) + _logger.debug(f'Ignoring IOError {e!r} when calculating file hash for {filename}') returned = None else: returned = h.hexdigest() @@ -452,7 +452,7 @@ def _session_report_end(self, hook_name): self.session.report_end(**kwargs) self._started = False except Exception: # pylint: disable=broad-except - _logger.error('Exception ignored in {}'.format(hook_name), exc_info=True) + _logger.error(f'Exception ignored in {hook_name}', exc_info=True) @handle_exceptions def error_added(self, result, error): @@ -588,7 +588,7 @@ def fetch_token(self, username, password): headers={'Content-type': 'application/json'})\ .raise_for_status() - s.post(URL(self._get_backslash_url()).add_path('/runtoken/request/{}/complete'.format(request_id)))\ + s.post(URL(self._get_backslash_url()).add_path(f'/runtoken/request/{request_id}/complete'))\ .raise_for_status() resp = s.get(response_url) diff --git a/backslash/contrib/utils.py b/backslash/contrib/utils.py index a31c3a5..345ed29 100644 --- a/backslash/contrib/utils.py +++ b/backslash/contrib/utils.py @@ -103,7 +103,7 @@ def _unwrap_object_variable(var_name, var_value): return for attr, value in _iter_distilled_object_attributes(var_value): - yield 'self.{}'.format(attr), value + yield f'self.{attr}', value def _iter_distilled_object_attributes(obj): try: @@ -162,12 +162,12 @@ def _nested_assign(dictionary, key, value): def _safe_repr(value, repr_blacklisted_types, truncate=True): if isinstance(value, repr_blacklisted_types): - returned = "<{!r} object {:x}>".format(type(value).__name__, id(value)) + returned = f"<{type(value).__name__!r} object {id(value):x}>" try: returned = repr(value) except Exception: # pylint: disable=broad-except - return "[Unprintable {0!r} object]".format(type(value).__name__) + return f"[Unprintable {type(value).__name__!r} object]" if truncate and len(returned) > _MAX_VARIABLE_VALUE_LENGTH: returned = returned[:_MAX_VARIABLE_VALUE_LENGTH - 3] + '...' diff --git a/backslash/error_container.py b/backslash/error_container.py index 9873be3..521440c 100644 --- a/backslash/error_container.py +++ b/backslash/error_container.py @@ -11,7 +11,7 @@ _logger = logbook.Logger(__name__) -class ErrorContainer(object): +class ErrorContainer(): def add_error(self, message, exception_type=NOTHING, traceback=NOTHING, timestamp=NOTHING, is_failure=NOTHING, exception_attrs=NOTHING, is_interruption=NOTHING, is_fatal=NOTHING): diff --git a/backslash/field_filters.py b/backslash/field_filters.py index 3808a7d..0a04164 100644 --- a/backslash/field_filters.py +++ b/backslash/field_filters.py @@ -1,13 +1,13 @@ -class FieldFilter(object): +class FieldFilter(): def __init__(self, field_name): - super(FieldFilter, self).__init__() + super().__init__() self.field_name = field_name self._filters = [] def add_to_url(self, url): for operator_name, value in self._filters: - url = url.add_query_param(self.field_name, '{0}:{1}'.format(operator_name, value)) + url = url.add_query_param(self.field_name, f'{operator_name}:{value}') return url def _add_field_proxy_operator_method(operator_name): @@ -15,14 +15,14 @@ def _add_field_proxy_operator_method(operator_name): def method(self, other): self._filters.append((operator_name, other)) # pylint: disable=protected-access return self - method_name = method.__name__ = '__{0}__'.format(operator_name) + method_name = method.__name__ = f'__{operator_name}__' setattr(FieldFilter, method_name, method) for _operator_name in ['eq', 'ne', 'lt', 'le', 'gt', 'ge']: _add_field_proxy_operator_method(_operator_name) -class _Fields(object): +class _Fields(): def __getattr__(self, name): return FieldFilter(name) diff --git a/backslash/lazy_query.py b/backslash/lazy_query.py index 65120ae..87279b1 100644 --- a/backslash/lazy_query.py +++ b/backslash/lazy_query.py @@ -7,10 +7,10 @@ from .utils import raise_for_status -class LazyQuery(object): +class LazyQuery(): def __init__(self, client, path=None, url=None, query_params=None, page_size=100): - super(LazyQuery, self).__init__() + super().__init__() self._client = client if url is None: url = client.api.url @@ -37,7 +37,7 @@ def filter(self, *filter_objects, **fields): return LazyQuery(self._client, url=returned_url, page_size=self._page_size) def __repr__(self): - return ''.format(str(self._url)) + return f'' def __iter__(self): for i in itertools.count(): @@ -77,7 +77,7 @@ def _fetch_page(self, page_index): raise RuntimeError('Multiple keys returned') [obj_typename] = keys if self._typename is not None and obj_typename != self._typename: - raise RuntimeError('Got different typename in query: {!r} != {!r}'.format(obj_typename, self._typename)) + raise RuntimeError(f'Got different typename in query: {obj_typename!r} != {self._typename!r}') self._typename = obj_typename for index, json_obj in enumerate(response_data[self._typename]): diff --git a/backslash/metadata_holder.py b/backslash/metadata_holder.py index 10340f2..8fc6fac 100644 --- a/backslash/metadata_holder.py +++ b/backslash/metadata_holder.py @@ -1,4 +1,4 @@ -class MetadataHolder(object): +class MetadataHolder(): def set_metadata(self, key, value): self.client.api.call_function('set_metadata', { diff --git a/backslash/related_entity_container.py b/backslash/related_entity_container.py index 2a350ec..82e7e68 100644 --- a/backslash/related_entity_container.py +++ b/backslash/related_entity_container.py @@ -1,4 +1,4 @@ -class RelatedEntityContainer(object): +class RelatedEntityContainer(): def add_related_entity(self, entity_type, entity_name): # pylint: disable=no-member diff --git a/backslash/session.py b/backslash/session.py index d3e7303..f9e068c 100644 --- a/backslash/session.py +++ b/backslash/session.py @@ -17,7 +17,7 @@ class Session(APIObject, MetadataHolder, ErrorContainer, WarningContainer, Archi @property def ui_url(self): - return self.client.get_ui_url('/sessions/{}'.format(self.logical_id or self.id)) + return self.client.get_ui_url(f'/sessions/{self.logical_id or self.id}') def report_end(self, duration=NOTHING, has_fatal_errors=NOTHING): @@ -100,7 +100,7 @@ def query_tests(self, include_planned=False): params = None if include_planned: params = {'show_planned':'true'} - return LazyQuery(self.client, '/rest/sessions/{0}/tests'.format(self.id), query_params=params) + return LazyQuery(self.client, f'/rest/sessions/{self.id}/tests', query_params=params) def query_errors(self): """Queries tests of the current session diff --git a/backslash/test.py b/backslash/test.py index 9de1600..6807b8c 100644 --- a/backslash/test.py +++ b/backslash/test.py @@ -14,7 +14,7 @@ class Test(APIObject, MetadataHolder, ErrorContainer, WarningContainer, Commenta @property def ui_url(self): - return self.client.get_ui_url('sessions/{}/tests/{}'.format(self.session_display_id, self.logical_id or self.id)) + return self.client.get_ui_url(f'sessions/{self.session_display_id}/tests/{self.logical_id or self.id}') def report_end(self, duration=NOTHING): self.client.api.call_function('report_test_end', {'id': self.id, 'duration': duration}) @@ -33,7 +33,7 @@ def query_errors(self): return LazyQuery(self.client, '/rest/errors', query_params={'test_id': self.id}) def get_session(self): - return self.client.api.get('/rest/sessions/{0}'.format(self.session_id)) + return self.client.api.get(f'/rest/sessions/{self.session_id}') def get_parent(self): return self.get_session() diff --git a/backslash/timing_container.py b/backslash/timing_container.py index de67717..6a885d2 100644 --- a/backslash/timing_container.py +++ b/backslash/timing_container.py @@ -1,4 +1,4 @@ -class TimingContainer(object): +class TimingContainer(): def report_timing_start(self, name): self._report('start', name) @@ -9,7 +9,7 @@ def report_timing_end(self, name): def _report(self, start_stop, name): kwargs = {'name': name} kwargs.update(self._get_identity_kwargs()) - self.client.api.call_function('report_timing_{}'.format(start_stop), kwargs) # pylint: disable=no-member + self.client.api.call_function(f'report_timing_{start_stop}', kwargs) # pylint: disable=no-member def _get_identity_kwargs(self): if self.type.lower() == 'session': # pylint: disable=no-member diff --git a/backslash/utils.py b/backslash/utils.py index 3ed9e15..01f0b9c 100644 --- a/backslash/utils.py +++ b/backslash/utils.py @@ -33,5 +33,5 @@ def raise_for_status(resp): resp.raise_for_status() except HTTPError as e: raise HTTPError( - '{0.request.method} {0.request.url}: {0.status_code}\n\n{0.content}'.format(e.response), + f'{e.response.request.method} {e.response.request.url}: {e.response.status_code}\n\n{e.response.content}', response=resp, request=resp.request) diff --git a/backslash/warning_container.py b/backslash/warning_container.py index 23008da..be21555 100644 --- a/backslash/warning_container.py +++ b/backslash/warning_container.py @@ -4,7 +4,7 @@ from .lazy_query import LazyQuery -class WarningContainer(object): +class WarningContainer(): def add_warning(self, message, filename=NOTHING, lineno=NOTHING, timestamp=NOTHING): return self.client.api.call_function('add_warning', {self._get_id_key(): self.id, diff --git a/docs/changelog.rst b/docs/changelog.rst index e1fcd08..6424a0b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* :feature:`104` Drop support for python version < 3.6 * :release:`2.39.0 <03-07-2019>` * :feature:`101` Report if error is fatal * :feature:`91` Allow passing custom default headers to server diff --git a/setup.cfg b/setup.cfg index d4429c0..581d834 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,9 +1,8 @@ [metadata] name = backslash classifiers = - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 description = Client library for the Backslash test reporting service license = BSD author = Rotem Yaari @@ -16,12 +15,9 @@ testing = slash>=1.5.0 Flask Flask-Loopback - pylint~=1.9.0; python_version<'3.0' - pylint~=2.2.0; python_version>='3.0' - pytest; python_version < '3.0' - pytest>4.0; python_version >= '3.0' - pytest-cov<2.6; python_version < '3.0' - pytest-cov>=2.6; python_version >= '3.0' + pylint~=2.2.0 + pytest>4.0 + pytest-cov>=2.6 URLObject weber-utils diff --git a/setup.py b/setup.py index 8bee739..5bc87b0 100644 --- a/setup.py +++ b/setup.py @@ -5,4 +5,5 @@ setup( setup_requires=['pbr>=3.0', 'setuptools>=17.1'], pbr=True, + python_requires=">=3.6.*", ) diff --git a/tests/test_api_object.py b/tests/test_api_object.py index 9f92d53..fe9c309 100644 --- a/tests/test_api_object.py +++ b/tests/test_api_object.py @@ -49,9 +49,9 @@ def test_ui_url(client, object_type, logical_id, use_logical): url = obj.ui_url display_id = logical_id if use_logical else id if object_type is test.Test: - assert url == client.url + '/#/sessions/{}/tests/{}'.format(data['session_display_id'], display_id) + assert url == client.url + f"/#/sessions/{data['session_display_id']}/tests/{display_id}" else: - assert url == client.url + '/#/{}s/{}'.format(object_type.__name__.lower(), display_id) + assert url == client.url + f'/#/{object_type.__name__.lower()}s/{display_id}' @pytest.fixture diff --git a/tests/test_lazy_query.py b/tests/test_lazy_query.py index 9f66ccf..8ab511c 100644 --- a/tests/test_lazy_query.py +++ b/tests/test_lazy_query.py @@ -44,8 +44,7 @@ def test_querying_simple_equality(query): def test_querying_with_field_queries(query, field_value, operator_name, operator_func): query = query.filter(operator_func(FIELDS.field_name, field_value)) - assert query._url.query == 'field_name={0}%3A{1}'.format( # pylint: disable=protected-access - operator_name, field_value) + assert query._url.query == f'field_name={operator_name}%3A{field_value}' # pylint: disable=protected-access def test_querying_between(query): assert query.filter(1 <= FIELDS.x <= 2)._url.query == 'x=ge%3A1&x=le%3A2' # pylint: disable=protected-access @@ -75,7 +74,7 @@ def operator_func(operator_name): @pytest.fixture def url(request, flask_app): address = str(uuid1()) - returned = URL('http://{0}'.format(address)) + returned = URL(f'http://{address}') webapp = FlaskLoopback(flask_app) webapp.activate_address((address, 80)) @@ -117,10 +116,10 @@ def test_fake_cursor_count(): assert FakeCursor(lst).offset(30).limit(50000).count() == 970 -class FakeCursor(object): +class FakeCursor(): def __init__(self, lst): - super(FakeCursor, self).__init__() + super().__init__() self._lst = lst self._iterated = False diff --git a/tests/test_slash_plugin.py b/tests/test_slash_plugin.py index 4cf8deb..45e3b62 100644 --- a/tests/test_slash_plugin.py +++ b/tests/test_slash_plugin.py @@ -50,7 +50,7 @@ def test_session_webapp_url_with_session(installed_plugin, server_url): installed_plugin.activate() with slash.Session() as s: url = installed_plugin.session_webapp_url - assert url == '{}/#/sessions/{}'.format(server_url, s.id) + assert url == f'{server_url}/#/sessions/{s.id}' @pytest.fixture From 34751abdd150a045597cad184958b5887933149d Mon Sep 17 00:00:00 2001 From: David Sternlicht Date: Sun, 16 Feb 2020 13:45:37 +0200 Subject: [PATCH 02/25] Fix repr of api object --- backslash/api_object.py | 2 +- tests/test_api_object.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/backslash/api_object.py b/backslash/api_object.py index dc3f03a..9101d0d 100644 --- a/backslash/api_object.py +++ b/backslash/api_object.py @@ -38,7 +38,7 @@ def _fetch(self): return self.client.api.get(self.api_path, raw=True)[self._data['type']] def __repr__(self): - return f'' + return f"" def without_fields(self, field_names): new_data = dict((field_name, field_value) diff --git a/tests/test_api_object.py b/tests/test_api_object.py index fe9c309..2b61e6d 100644 --- a/tests/test_api_object.py +++ b/tests/test_api_object.py @@ -32,6 +32,13 @@ def test_object_api_url(client): obj = APIObject(client, data) assert obj.api_url == 'http://127.0.0.1:12345/rest/objects/1' + +def test_object_api_repr(client): + data = {'id': 1, 'type': 'test'} + obj = APIObject(client, data) + assert repr(obj) == '' + + def test_object_ui_url(client): obj = APIObject(client, {}) with pytest.raises(NotImplementedError): From d9e37e3196df5a05a963fc9ff1715f8f5804eee5 Mon Sep 17 00:00:00 2001 From: Oren Epshtain Date: Sun, 27 Dec 2020 13:49:38 +0200 Subject: [PATCH 03/25] INFRADEV-14640: type hint api.py --- backslash/api.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/backslash/api.py b/backslash/api.py index 19fadbf..b114641 100644 --- a/backslash/api.py +++ b/backslash/api.py @@ -22,6 +22,9 @@ from .user import User from .utils import raise_for_status, compute_memory_usage from .warning import Warning +from backslash.client import Backslash + +from typing import Optional, Union, Dict, Tuple, Any, Iterator _RETRY_STATUS_CODES = frozenset([ requests.codes.bad_gateway, @@ -46,7 +49,11 @@ class API(): - def __init__(self, client, url, runtoken, timeout_seconds=60, headers=None): + def __init__(self, client: Backslash, + url: str, + runtoken: str, + timeout_seconds: int=60, + headers: Optional[Dict[str, str]]=None) -> None: super().__init__() self.client = client self.url = URL(url) @@ -62,7 +69,7 @@ def __init__(self, client, url, runtoken, timeout_seconds=60, headers=None): self._cached_info = None self._timeout = timeout_seconds - def __del__(self): + def __del__(self) -> None: if self.session is not None: self.session.close() @@ -75,7 +82,7 @@ def info(self): self._cached_info = munchify(resp.json()) return copy.deepcopy(self._cached_info) - def call_function(self, name, params=None): + def call_function(self, name, params: Optional[Dict[str, Optional[Union[str, int]]]]=None): is_compressed, data = self._serialize_params(params) headers = {'Content-type': 'application/json'} if is_compressed: @@ -99,7 +106,7 @@ def call_function(self, name, params=None): return self._normalize_return_value(resp) - def _iter_retries(self, timeout=30, sleep_range=(3, 10)): + def _iter_retries(self, timeout: int=30, sleep_range: Tuple[int, int]=(3, 10)) -> Iterator: start_time = time.time() end_time = start_time + timeout while True: @@ -107,7 +114,7 @@ def _iter_retries(self, timeout=30, sleep_range=(3, 10)): if time.time() < end_time: time.sleep(random.randrange(*sleep_range)) - def get(self, path, raw=False, params=None): + def get(self, path: str, raw: bool=False, params: Optional[Dict[str, Any]]=None): resp = self.session.get(self.url.add_path(path), params=params, timeout=self._timeout) raise_for_status(resp) if raw: @@ -115,12 +122,12 @@ def get(self, path, raw=False, params=None): else: return self._normalize_return_value(resp) - def delete(self, path, params=None): + def delete(self, path: str, params=None) -> requests.Response: resp = self.session.delete(self.url.add_path(path), params=params, timeout=self._timeout) raise_for_status(resp) return resp - def _normalize_return_value(self, response): + def _normalize_return_value(self, response: requests.Response): json_res = response.json() if json_res is None: return None @@ -135,17 +142,18 @@ def _normalize_return_value(self, response): return self.build_api_object(result) return result - def build_api_object(self, result): + def build_api_object(self, result: Dict[str, Any]): objtype = self._get_objtype(result) if objtype is None: return result return objtype(self.client, result) - def _get_objtype(self, json_object): + def _get_objtype(self, json_object: Dict[str, Any])\ + -> Union[Session, Test, Error, Warning, Comment, Suite, User]: typename = json_object['type'] return _TYPES_BY_TYPENAME.get(typename) - def _serialize_params(self, params): + def _serialize_params(self, params: Dict[str, Any]) -> Tuple[bool, bytes]: if params is None: params = {} @@ -167,7 +175,7 @@ def _serialize_params(self, params): raise ParamsTooLarge() return compressed, returned - def _compress(self, data): + def _compress(self, data: str) -> bytes: s = BytesIO() with gzip.GzipFile(fileobj=s, mode='wb') as f: @@ -179,11 +187,11 @@ def _compress(self, data): class CallProxy(): - def __init__(self, api): + def __init__(self, api: API) -> None: super().__init__() self._api = api - def __getattr__(self, attr): + def __getattr__(self, attr: str): if attr.startswith('_'): raise AttributeError(attr) From 1427bfd0a7b7196f594856171dc1e252beb40247 Mon Sep 17 00:00:00 2001 From: Oren Epshtain Date: Sun, 27 Dec 2020 15:52:52 +0200 Subject: [PATCH 04/25] INFRADEV-14640: fix type hint api.py --- backslash/api.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/backslash/api.py b/backslash/api.py index b114641..f52a476 100644 --- a/backslash/api.py +++ b/backslash/api.py @@ -22,9 +22,8 @@ from .user import User from .utils import raise_for_status, compute_memory_usage from .warning import Warning -from backslash.client import Backslash -from typing import Optional, Union, Dict, Tuple, Any, Iterator +from typing import Optional, Union, Dict, Tuple, Any, Iterator, Type _RETRY_STATUS_CODES = frozenset([ requests.codes.bad_gateway, @@ -49,7 +48,7 @@ class API(): - def __init__(self, client: Backslash, + def __init__(self, client, #: Backslash url: str, runtoken: str, timeout_seconds: int=60, @@ -82,7 +81,7 @@ def info(self): self._cached_info = munchify(resp.json()) return copy.deepcopy(self._cached_info) - def call_function(self, name, params: Optional[Dict[str, Optional[Union[str, int]]]]=None): + def call_function(self, name, params: Dict[str, Any]=None): is_compressed, data = self._serialize_params(params) headers = {'Content-type': 'application/json'} if is_compressed: @@ -153,7 +152,7 @@ def _get_objtype(self, json_object: Dict[str, Any])\ typename = json_object['type'] return _TYPES_BY_TYPENAME.get(typename) - def _serialize_params(self, params: Dict[str, Any]) -> Tuple[bool, bytes]: + def _serialize_params(self, params: Optional[Dict[str, Any]]) -> Tuple[bool, Dict[Any, Any]]: if params is None: params = {} @@ -167,10 +166,10 @@ def _serialize_params(self, params: Dict[str, Any]) -> Tuple[bool, bytes]: continue returned[param_name] = param_value compressed = False - returned = json.dumps(returned, default=repr) + returned: str = json.dumps(returned, default=repr) if len(returned) > _COMPRESS_THRESHOLD: compressed = True - returned = self._compress(returned) + returned: bytes = self._compress(returned) if len(returned) > _MAX_PARAMS_COMPRESSED_SIZE: raise ParamsTooLarge() return compressed, returned From 62483b8798e64606c1a70610d80d5fd070290508 Mon Sep 17 00:00:00 2001 From: Oren Epshtain Date: Sun, 27 Dec 2020 16:23:45 +0200 Subject: [PATCH 05/25] INFRADEV-14640: type hint api_object.py --- backslash/api_object.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/backslash/api_object.py b/backslash/api_object.py index 9101d0d..98be563 100644 --- a/backslash/api_object.py +++ b/backslash/api_object.py @@ -1,28 +1,32 @@ +from __future__ import annotations + +from typing import Dict, Optional, Union +from urlobject.urlobject import URLObject class APIObject(): - def __init__(self, client, json_data): + def __init__(self, client, json_data: Dict[str, Optional[Union[int, str]]]) -> None: super().__init__() self.client = client self._data = json_data @property - def api_url(self): + def api_url(self) -> URLObject: return self.client.url.add_path(self.api_path) @property def ui_url(self): raise NotImplementedError() # pragma: no cover - def __eq__(self, other): + def __eq__(self, other: APIObject) -> bool: if not isinstance(other, APIObject): return NotImplemented return self.client is other.client and self._data == other._data # pylint: disable=protected-access - def __ne__(self, other): + def __ne__(self, other: APIObject) -> bool: return not (self == other) # pylint: disable=superfluous-parens - def __getattr__(self, name): + def __getattr__(self, name: str) -> Optional[Union[int, str]]: try: return self.__dict__['_data'][name] except KeyError: @@ -37,7 +41,7 @@ def refresh(self): def _fetch(self): return self.client.api.get(self.api_path, raw=True)[self._data['type']] - def __repr__(self): + def __repr__(self) -> str: return f"" def without_fields(self, field_names): From 1f8da1d45fbbbb8fe333199a7364f3d0b88643b6 Mon Sep 17 00:00:00 2001 From: Oren Epshtain Date: Sun, 27 Dec 2020 17:08:36 +0200 Subject: [PATCH 06/25] INFRADEV-14640: type hint client.py and fix type hint in app.py --- backslash/api.py | 2 +- backslash/client.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/backslash/api.py b/backslash/api.py index f52a476..4fa2213 100644 --- a/backslash/api.py +++ b/backslash/api.py @@ -81,7 +81,7 @@ def info(self): self._cached_info = munchify(resp.json()) return copy.deepcopy(self._cached_info) - def call_function(self, name, params: Dict[str, Any]=None): + def call_function(self, name: str, params: Dict[str, Any]=None): is_compressed, data = self._serialize_params(params) headers = {'Content-type': 'application/json'} if is_compressed: diff --git a/backslash/client.py b/backslash/client.py index e35d4a8..865ecdd 100644 --- a/backslash/client.py +++ b/backslash/client.py @@ -7,6 +7,8 @@ from .api import API from .lazy_query import LazyQuery +from typing import Optional, Union +from urlobject.urlobject import URLObject _logger = logbook.Logger(__name__) @@ -14,7 +16,7 @@ class Backslash(): - def __init__(self, url, runtoken, headers=None): + def __init__(self, url: Union[str, URLObject], runtoken: str, headers: None=None) -> None: super().__init__() if not url.startswith('http'): url = f'http://{url}' @@ -22,10 +24,10 @@ def __init__(self, url, runtoken, headers=None): self.api = API(self, url, runtoken, headers=headers) @property - def url(self): + def url(self) -> URLObject: return self._url - def get_ui_url(self, fragment=None): + def get_ui_url(self, fragment: Optional[str]=None) -> str: returned = str(self.url) if not returned.endswith('/'): returned += '/' @@ -43,7 +45,7 @@ def toggle_user_role(self, user_id, role): def get_user_run_tokens(self, user_id): return self.api.call_function('get_user_run_tokens', {'user_id': user_id}) - def delete_comment(self, comment_id): + def delete_comment(self, comment_id) -> None: self.api.call_function('delete_comment', {'comment_id': comment_id}) def report_session_start(self, logical_id=NOTHING, @@ -87,19 +89,19 @@ def report_session_start(self, logical_id=NOTHING, returned = self.api.call_function('report_session_start', params) return returned - def query_sessions(self): + def query_sessions(self) -> LazyQuery: """Queries sessions stored on the server :rtype: A lazy query object """ return LazyQuery(self, '/rest/sessions') - def query_tests(self): + def query_tests(self) -> LazyQuery: """Queries tests stored on the server (directly, not via a session) :rtype: A lazy query object """ return LazyQuery(self, '/rest/tests') - def query(self, path, **kwargs): + def query(self, path, **kwargs) -> LazyQuery: return LazyQuery(self, path, **kwargs) From 60cf064d2b28261b299031f04c45846bc45eaf00 Mon Sep 17 00:00:00 2001 From: Oren Epshtain Date: Sun, 27 Dec 2020 17:46:45 +0200 Subject: [PATCH 07/25] INFRADEV-14640: type hint field_filters.py --- backslash/field_filters.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/backslash/field_filters.py b/backslash/field_filters.py index 0a04164..9477593 100644 --- a/backslash/field_filters.py +++ b/backslash/field_filters.py @@ -1,16 +1,20 @@ +from urlobject import URLObject + +from typing import List, Tuple + class FieldFilter(): - def __init__(self, field_name): + def __init__(self, field_name: str) -> None: super().__init__() self.field_name = field_name - self._filters = [] + self._filters: List[Tuple[str, str]] = [] - def add_to_url(self, url): + def add_to_url(self, url: URLObject) -> URLObject: for operator_name, value in self._filters: url = url.add_query_param(self.field_name, f'{operator_name}:{value}') return url -def _add_field_proxy_operator_method(operator_name): +def _add_field_proxy_operator_method(operator_name: str) -> None: def method(self, other): self._filters.append((operator_name, other)) # pylint: disable=protected-access @@ -24,7 +28,7 @@ def method(self, other): class _Fields(): - def __getattr__(self, name): + def __getattr__(self, name: str) -> FieldFilter: return FieldFilter(name) FIELDS = _Fields() From fbb7a88ce9d1d55ea1d188d0ad004a9e054e71f3 Mon Sep 17 00:00:00 2001 From: Oren Epshtain Date: Sun, 27 Dec 2020 18:01:20 +0200 Subject: [PATCH 08/25] INFRADEV-14640: type hint session.py --- backslash/session.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/backslash/session.py b/backslash/session.py index f9e068c..3c64f41 100644 --- a/backslash/session.py +++ b/backslash/session.py @@ -11,20 +11,22 @@ from .metadata_holder import MetadataHolder from .timing_container import TimingContainer +from typing import Dict, Any + APPEND_UPCOMING_TESTS_STR = 'append_upcoming_tests' class Session(APIObject, MetadataHolder, ErrorContainer, WarningContainer, Archiveable, Commentable, RelatedEntityContainer, TimingContainer): @property - def ui_url(self): + def ui_url(self) -> str: return self.client.get_ui_url(f'/sessions/{self.logical_id or self.id}') - def report_end(self, duration=NOTHING, has_fatal_errors=NOTHING): + def report_end(self, duration=NOTHING, has_fatal_errors=NOTHING) -> None: kwargs = {'id': self.id, 'duration': duration, 'has_fatal_errors': has_fatal_errors} self.client.api.call_function('report_session_end', kwargs) - def send_keepalive(self): + def send_keepalive(self) -> None: self.client.api.call_function('send_keepalive', {'session_id': self.id}) def report_test_start(self, name, file_name=NOTHING, class_name=NOTHING, test_logical_id=NOTHING, scm=NOTHING, @@ -65,26 +67,26 @@ def report_test_start(self, name, file_name=NOTHING, class_name=NOTHING, test_lo return returned - def report_test_distributed(self, test_logical_id): + def report_test_distributed(self, test_logical_id) -> None: self.client.api.call_function('report_test_distributed', {'session_id': self.id, 'test_logical_id': test_logical_id}) - def report_upcoming_tests(self, tests): + def report_upcoming_tests(self, tests) -> None: self.client.api.call_function(APPEND_UPCOMING_TESTS_STR, {'tests':tests, 'session_id':self.id} ) - def report_in_pdb(self): + def report_in_pdb(self) -> None: self.client.api.call_function('report_in_pdb', {'session_id': self.id}) - def report_not_in_pdb(self): + def report_not_in_pdb(self) -> None: self.client.api.call_function('report_not_in_pdb', {'session_id': self.id}) - def report_interrupted(self): + def report_interrupted(self) -> None: if 'report_session_interrupted' in self.client.api.info().endpoints: self.client.api.call_function('report_session_interrupted', {'id': self.id}) - def add_subject(self, name, product=NOTHING, version=NOTHING, revision=NOTHING): + def add_subject(self, name: str, product=NOTHING, version=NOTHING, revision=NOTHING): return self.client.api.call_function( 'add_subject', {'session_id': self.id, 'name': name, 'product': product, 'version': version, 'revision': revision}) @@ -92,7 +94,7 @@ def add_subject(self, name, product=NOTHING, version=NOTHING, revision=NOTHING): def edit_status(self, status): return self.client.api.call_function('edit_session_status', {'id': self.id, 'status': status}) - def query_tests(self, include_planned=False): + def query_tests(self, include_planned: bool=False) -> LazyQuery: """Queries tests of the current session :rtype: A lazy query object @@ -102,7 +104,7 @@ def query_tests(self, include_planned=False): params = {'show_planned':'true'} return LazyQuery(self.client, f'/rest/sessions/{self.id}/tests', query_params=params) - def query_errors(self): + def query_errors(self) -> LazyQuery: """Queries tests of the current session :rtype: A lazy query object @@ -112,11 +114,11 @@ def query_errors(self): def toggle_investigated(self): return self.client.api.call_function('toggle_investigated', {'session_id': self.id}) - def get_parent(self): + def get_parent(self) -> None: return None -def _sanitize_params(params, max_length=100): +def _sanitize_params(params: Dict[str, Any], max_length: int=100) -> Dict[str, Any]: if params is NOTHING: return params From 1ea5f00e1c110cf63821fa5e82f91c84798f59d2 Mon Sep 17 00:00:00 2001 From: Oren Epshtain Date: Sun, 27 Dec 2020 18:19:20 +0200 Subject: [PATCH 09/25] INFRADEV-14640: type hint test.py --- backslash/test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backslash/test.py b/backslash/test.py index 6807b8c..e62935f 100644 --- a/backslash/test.py +++ b/backslash/test.py @@ -13,19 +13,19 @@ class Test(APIObject, MetadataHolder, ErrorContainer, WarningContainer, Commentable, RelatedEntityContainer, TimingContainer): @property - def ui_url(self): + def ui_url(self) -> str: return self.client.get_ui_url(f'sessions/{self.session_display_id}/tests/{self.logical_id or self.id}') - def report_end(self, duration=NOTHING): + def report_end(self, duration=NOTHING) -> None: self.client.api.call_function('report_test_end', {'id': self.id, 'duration': duration}) - def mark_skipped(self, reason=None): + def mark_skipped(self, reason=None) -> None: self.client.api.call_function('report_test_skipped', {'id': self.id, 'reason': reason}) - def report_interrupted(self): + def report_interrupted(self) -> None: self.client.api.call_function('report_test_interrupted', {'id': self.id}) - def query_errors(self): + def query_errors(self) -> LazyQuery: """Queries tests of the current session :rtype: A lazy query object @@ -38,5 +38,5 @@ def get_session(self): def get_parent(self): return self.get_session() - def update_status_description(self, description): + def update_status_description(self, description: str): return self.client.api.call_function('update_status_description', {'test_id': self.id, 'description': description}) From 8ce3a47a40d3f4f5bd498557412961993cf6c3bb Mon Sep 17 00:00:00 2001 From: Oren Epshtain Date: Sun, 27 Dec 2020 18:25:56 +0200 Subject: [PATCH 10/25] INFRADEV-14640: type hint utils.py --- backslash/utils.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/backslash/utils.py b/backslash/utils.py index 01f0b9c..07a894c 100644 --- a/backslash/utils.py +++ b/backslash/utils.py @@ -1,15 +1,19 @@ import os from sys import getsizeof -from requests import HTTPError +from typing import TypeVar +from requests import HTTPError, Response -def ensure_dir(path): +T = TypeVar('T') + + +def ensure_dir(path: str) -> None: if not os.path.isdir(path): os.makedirs(path) -def compute_memory_usage(obj): +def compute_memory_usage(obj: T) -> int: seen = set() stack = [obj] returned = 0 @@ -28,7 +32,7 @@ def compute_memory_usage(obj): return returned -def raise_for_status(resp): +def raise_for_status(resp: Response) -> None: try: resp.raise_for_status() except HTTPError as e: From 632e523585d459e70fb39d3905e1b7fb32da5696 Mon Sep 17 00:00:00 2001 From: Oren Epshtain Date: Mon, 28 Dec 2020 11:12:39 +0200 Subject: [PATCH 11/25] INFRADEV-14640: fix type hinting in api.py --- backslash/api.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/backslash/api.py b/backslash/api.py index 4fa2213..7833122 100644 --- a/backslash/api.py +++ b/backslash/api.py @@ -23,7 +23,12 @@ from .utils import raise_for_status, compute_memory_usage from .warning import Warning -from typing import Optional, Union, Dict, Tuple, Any, Iterator, Type +from typing import Optional, Union, Dict, Tuple, Any, Iterator, Type, TYPE_CHECKING + +if TYPE_CHECKING: + from .client import Backslash + +ObjectType = Union[Session, Test, Error, Warning, Comment, Suite, User] _RETRY_STATUS_CODES = frozenset([ requests.codes.bad_gateway, @@ -48,7 +53,7 @@ class API(): - def __init__(self, client, #: Backslash + def __init__(self, client: "Backslash", url: str, runtoken: str, timeout_seconds: int=60, @@ -126,7 +131,7 @@ def delete(self, path: str, params=None) -> requests.Response: raise_for_status(resp) return resp - def _normalize_return_value(self, response: requests.Response): + def _normalize_return_value(self, response: requests.Response) -> Optional[Union[Dict[str, Any], ObjectType]]: json_res = response.json() if json_res is None: return None @@ -141,14 +146,13 @@ def _normalize_return_value(self, response: requests.Response): return self.build_api_object(result) return result - def build_api_object(self, result: Dict[str, Any]): + def build_api_object(self, result: Dict[str, Any]) -> Union[Dict[str, Any], ObjectType]: objtype = self._get_objtype(result) if objtype is None: return result return objtype(self.client, result) - def _get_objtype(self, json_object: Dict[str, Any])\ - -> Union[Session, Test, Error, Warning, Comment, Suite, User]: + def _get_objtype(self, json_object: Dict[str, Any]) -> ObjectType: typename = json_object['type'] return _TYPES_BY_TYPENAME.get(typename) From b7e6a85475b985b9dbdc30a37f6703a7d78f369d Mon Sep 17 00:00:00 2001 From: Oren Epshtain Date: Mon, 28 Dec 2020 12:24:30 +0200 Subject: [PATCH 12/25] INFRADEV-14640: remove future annotations line from imports. --- backslash/api_object.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/backslash/api_object.py b/backslash/api_object.py index 98be563..d54dd6d 100644 --- a/backslash/api_object.py +++ b/backslash/api_object.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Dict, Optional, Union from urlobject.urlobject import URLObject From 674d656b199586fe9cd60ddbf1165c1e7ff50624 Mon Sep 17 00:00:00 2001 From: Oren Epshtain Date: Mon, 28 Dec 2020 12:32:32 +0200 Subject: [PATCH 13/25] INFRADEV-14640: fix type hints in api_object.py --- backslash/api_object.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backslash/api_object.py b/backslash/api_object.py index d54dd6d..2931ab1 100644 --- a/backslash/api_object.py +++ b/backslash/api_object.py @@ -16,12 +16,12 @@ def api_url(self) -> URLObject: def ui_url(self): raise NotImplementedError() # pragma: no cover - def __eq__(self, other: APIObject) -> bool: + def __eq__(self, other: "APIObject") -> bool: if not isinstance(other, APIObject): return NotImplemented return self.client is other.client and self._data == other._data # pylint: disable=protected-access - def __ne__(self, other: APIObject) -> bool: + def __ne__(self, other: "APIObject") -> bool: return not (self == other) # pylint: disable=superfluous-parens def __getattr__(self, name: str) -> Optional[Union[int, str]]: From 0577f37dbab06aeab447a0376cf4d84185ec8d76 Mon Sep 17 00:00:00 2001 From: Ayala Shachar Date: Fri, 10 Nov 2023 11:38:53 +0200 Subject: [PATCH 14/25] Fix pylint errors --- .pylintrc | 2 +- backslash/_compat.py | 1 + backslash/api.py | 2 +- backslash/api_object.py | 2 +- backslash/contrib/slash_plugin.py | 11 ++++++----- backslash/utils.py | 2 +- setup.cfg | 2 +- tests/test_backslash.py | 2 +- tests/test_slash_plugin.py | 4 ++-- 9 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.pylintrc b/.pylintrc index 62520ea..0b70789 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,5 +1,5 @@ [MESSAGES CONTROL] -disable=R,attribute-defined-outside-init,bad-continuation,bad-option-value,bare-except,invalid-name,locally-disabled,missing-docstring,redefined-builtin,ungrouped-imports,wrong-import-order,wrong-import-position +disable=R,attribute-defined-outside-init,bad-continuation,bad-option-value,bare-except,invalid-name,locally-disabled,missing-docstring,redefined-builtin,ungrouped-imports,wrong-import-order,wrong-import-position,superfluous-parens [REPORTS] reports=no diff --git a/backslash/_compat.py b/backslash/_compat.py index d50f405..fd99ffa 100644 --- a/backslash/_compat.py +++ b/backslash/_compat.py @@ -9,6 +9,7 @@ #pylint: disable=unused-argument #pylint: disable=unused-import #pylint: disable=exec-used +#pylint: disable=unnecessary-lambda-assignment import sys from contextlib import contextmanager diff --git a/backslash/api.py b/backslash/api.py index 7833122..e075adf 100644 --- a/backslash/api.py +++ b/backslash/api.py @@ -23,7 +23,7 @@ from .utils import raise_for_status, compute_memory_usage from .warning import Warning -from typing import Optional, Union, Dict, Tuple, Any, Iterator, Type, TYPE_CHECKING +from typing import Optional, Union, Dict, Tuple, Any, Iterator, TYPE_CHECKING if TYPE_CHECKING: from .client import Backslash diff --git a/backslash/api_object.py b/backslash/api_object.py index 2931ab1..56093dc 100644 --- a/backslash/api_object.py +++ b/backslash/api_object.py @@ -28,7 +28,7 @@ def __getattr__(self, name: str) -> Optional[Union[int, str]]: try: return self.__dict__['_data'][name] except KeyError: - raise AttributeError(name) + raise AttributeError(name) from None def refresh(self): prev_id = self.id diff --git a/backslash/contrib/slash_plugin.py b/backslash/contrib/slash_plugin.py index 1412827..cca49bc 100644 --- a/backslash/contrib/slash_plugin.py +++ b/backslash/contrib/slash_plugin.py @@ -17,7 +17,7 @@ try: import git -except Exception as e: # pylint: disable=broad-except +except Exception: # pylint: disable=broad-except pass import slash @@ -38,6 +38,7 @@ from ..__version__ import __version__ as BACKSLASH_CLIENT_VERSION _DEFAULT_CONFIG_FILENAME = os.path.expanduser('~/.backslash/config.json') +_GET_TOKEN_TIMEOUT_SEC = 30 _logger = logbook.Logger(__name__) @@ -388,7 +389,7 @@ def _calculate_file_hash(self, filename): returned = self._file_hash_cache.get(filename) if returned is None: try: - with open(filename, 'rb') as f: + with open(filename, 'rb', encoding='utf-8') as f: data = f.read() h = hashlib.sha1() h.update('blob '.encode('utf-8')) @@ -561,7 +562,7 @@ def _get_existing_tokens(self): def _get_config(self): if not os.path.isfile(self._config_filename): return {} - with open(self._config_filename) as f: + with open(self._config_filename, encoding='utf-8') as f: return json.load(f) def _save_token(self, token): @@ -571,7 +572,7 @@ def _save_token(self, token): ensure_dir(os.path.dirname(tmp_filename)) - with open(tmp_filename, 'w') as f: + with open(tmp_filename, 'w', encoding='utf-8') as f: json.dump(cfg, f, indent=2) os.rename(tmp_filename, self._config_filename) @@ -611,7 +612,7 @@ def _fetch_token_via_browser(self): opened_browser = False url = self._get_token_request_url() for retry in itertools.count(): - resp = requests.get(url) + resp = requests.get(url, timeout=_GET_TOKEN_TIMEOUT_SEC) resp.raise_for_status() data = resp.json() if retry == 0: diff --git a/backslash/utils.py b/backslash/utils.py index 07a894c..45fc8ab 100644 --- a/backslash/utils.py +++ b/backslash/utils.py @@ -38,4 +38,4 @@ def raise_for_status(resp: Response) -> None: except HTTPError as e: raise HTTPError( f'{e.response.request.method} {e.response.request.url}: {e.response.status_code}\n\n{e.response.content}', - response=resp, request=resp.request) + response=resp, request=resp.request) from None diff --git a/setup.cfg b/setup.cfg index 581d834..eb32818 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,7 @@ testing = slash>=1.5.0 Flask Flask-Loopback - pylint~=2.2.0 + pylint pytest>4.0 pytest-cov>=2.6 URLObject diff --git a/tests/test_backslash.py b/tests/test_backslash.py index 77948e5..d96394e 100644 --- a/tests/test_backslash.py +++ b/tests/test_backslash.py @@ -1,4 +1,4 @@ # py.test style tests here def test_import(): - import backslash # pylint: disable=trailing-newlines, unused-variable, unused-import + import backslash # pylint: disable=unused-import, import-outside-toplevel diff --git a/tests/test_slash_plugin.py b/tests/test_slash_plugin.py index 45e3b62..3eb03a6 100644 --- a/tests/test_slash_plugin.py +++ b/tests/test_slash_plugin.py @@ -9,7 +9,7 @@ def test_importing_slash_plugin(): - from backslash.contrib import slash_plugin # pylint: disable=unused-variable,unused-import + from backslash.contrib import slash_plugin # pylint: disable=unused-variable,unused-import,import-outside-toplevel def test_exception_distilling(traceback): @@ -75,7 +75,7 @@ def test_failing(): @pytest.fixture def installed_plugin(request, server_url): - from backslash.contrib import slash_plugin + from backslash.contrib import slash_plugin # pylint: disable=import-outside-toplevel plugin = slash_plugin.BackslashPlugin(url=str(server_url), runtoken='blap') @request.addfinalizer From be542db5cbb2aa74f81ca544071f5e44bdefb604 Mon Sep 17 00:00:00 2001 From: Ayala Shachar Date: Fri, 10 Nov 2023 11:40:51 +0200 Subject: [PATCH 15/25] Update Makefile --- Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 6dd9e3c..b324e3f 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,10 @@ default: test test: env - .env/bin/py.test -x tests --cov=backslash --cov-report=html + .env/bin/pytest -x tests --cov=backslash --cov-report=html + +pylint: env + .env/bin/pylint --rcfile .pylintrc backslash tests doc: env .env/bin/python setup.py build_sphinx -a -E @@ -10,7 +13,7 @@ env: .env/.up-to-date .env/.up-to-date: setup.py Makefile setup.cfg - virtualenv --no-site-packages .env + python3 -m venv .env .env/bin/pip install -e .[testing] touch $@ From 7fc924fcca431ca0cde9a7da4f26bdf9198779c4 Mon Sep 17 00:00:00 2001 From: Ayala Shachar Date: Fri, 10 Nov 2023 11:45:46 +0200 Subject: [PATCH 16/25] Add github actions configuration (remove TravisCI) --- .github/workflows/main.yml | 29 +++++++++++++++++++++++++++++ .travis.yml | 19 ------------------- 2 files changed, 29 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..a93f6ce --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,29 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + # manually triggered + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: make env + - name: Lint with pylint + run: make pylint + - name: Test with pytest + run: make test + - name: Documentation + run: make doc diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5c3de6b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: python -python: -- 3.6 -- 3.7 -install: -- pip install -U pip setuptools -- pip install -e .[testing] -script: -- pylint -j $(nproc) --rcfile=.pylintrc backslash tests -- py.test tests -deploy: - provider: pypi - user: vmalloc - password: - secure: QERgKUrFAiCgfUswgqg1kNJpAE9giHeYkvoeMRXkucQ9Eq3z3v8rZPm63o4idXrQAR5i3PbkPix2pZ4h+Jc7EbmihSECXxVuivs7LtEUY34fNIDMBlpHUkvJClS9unMIzXipLzTJxQR0ZMbggz1bTwX9NaM44ZvX+22pLHYEom8= - on: - tags: true - repo: getslash/backslash-python - python: "3.6" From 2f7453dd70920c702248f1d4e68b28d89f745895 Mon Sep 17 00:00:00 2001 From: Ayala Shachar Date: Fri, 10 Nov 2023 12:08:36 +0200 Subject: [PATCH 17/25] Update docs configuration --- Makefile | 4 ++-- docs/conf.py | 2 +- docs/requirements.txt | 3 --- setup.cfg | 5 +++++ 4 files changed, 8 insertions(+), 6 deletions(-) delete mode 100644 docs/requirements.txt diff --git a/Makefile b/Makefile index b324e3f..356f51e 100644 --- a/Makefile +++ b/Makefile @@ -7,13 +7,13 @@ pylint: env .env/bin/pylint --rcfile .pylintrc backslash tests doc: env - .env/bin/python setup.py build_sphinx -a -E + .env/bin/sphinx-build -a -W -E docs build/sphinx/html env: .env/.up-to-date .env/.up-to-date: setup.py Makefile setup.cfg python3 -m venv .env - .env/bin/pip install -e .[testing] + .env/bin/pip install -e .[testing,doc] touch $@ diff --git a/docs/conf.py b/docs/conf.py index 7d7b004..939fb73 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -70,7 +70,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +#language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 2058c08..0000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -alabaster -releases -Sphinx diff --git a/setup.cfg b/setup.cfg index eb32818..d97aa94 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,6 +21,11 @@ testing = URLObject weber-utils +doc = + alabaster + releases + Sphinx + sentry = raven From ed4a6d749eb10039722a1c5b6147993ea8a1804c Mon Sep 17 00:00:00 2001 From: Ayala Shachar Date: Fri, 10 Nov 2023 12:17:06 +0200 Subject: [PATCH 18/25] Update README --- README.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2d32b9c..625fab6 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,13 @@ +# Backslash -![Build Status](https://secure.travis-ci.org/getslash/backslash-python.png) -![Downloads](https://img.shields.io/pypi/dm/backslash.svg) +| | | +|-----------------------|-----------------------------------------------------------------------------------------| +| Build Status | ![Build Status](https://github.com/getslash/backslash-python/actions/workflows/main.yml/badge.svg?branch=develop) | +| Supported Versions | ![Supported Versions](https://img.shields.io/pypi/pyversions/backslash.svg) | +| Latest Version | ![Latest Version](https://img.shields.io/pypi/v/backslash.svg) | -![Version](https://img.shields.io/pypi/v/backslash.svg) - -Overview -======== - - -Licence -======= +# Licence BSD3 From 2d980338b860f4b9148c54840411a37d75063c8a Mon Sep 17 00:00:00 2001 From: Ayala Shachar Date: Fri, 10 Nov 2023 12:21:43 +0200 Subject: [PATCH 19/25] Use pyproject.toml for package configuration --- Makefile | 2 +- pyproject.toml | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 7 ------- setup.cfg | 33 -------------------------------- setup.py | 9 --------- 5 files changed, 51 insertions(+), 50 deletions(-) create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/Makefile b/Makefile index 356f51e..ae2b4de 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ doc: env env: .env/.up-to-date -.env/.up-to-date: setup.py Makefile setup.cfg +.env/.up-to-date: Makefile pyproject.toml python3 -m venv .env .env/bin/pip install -e .[testing,doc] touch $@ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7ffa961 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +[build-system] +requires = ["hatchling>=0.25.1", "hatch-vcs"] +build-backend = "hatchling.build" + +[project] +name = "backslash" +description = "Client library for the Backslash test reporting service" +readme = "README.md" +requires-python = ">=3.7" +license = { text = "BSD 3-Clause License" } + +classifiers = ["Programming Language :: Python :: 3.7"] +dependencies = [ + "GitPython", + "Logbook", + "munch", + "requests", + "sentinels", + "URLObject", + "vintage", +] + +dynamic = ["version"] + +authors = [{ name = "Rotem Yaari", email = "vmalloc@gmail.com" }] + +[project.urls] +"Homepage" = "http://getslash.github.io/" +"GitHub" = "https://github.com/getslash/backslash" + +[project.optional-dependencies] +testing = [ + "slash>=1.5.0", + "Flask", + "Flask-Loopback", + "pylint", + "pytest>4.0", + "pytest-cov>=2.6", + "URLObject", + "weber-utils", +] +doc = ["alabaster", "releases", "Sphinx"] + +[tool.hatch.version] +source = "vcs" + +[tool.pytest] +testpaths = "tests" +timeout_method = "signal" +addopts = "-ra -W error::DeprecationWarning" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 0949502..0000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -GitPython -Logbook -munch -requests -sentinels -URLObject -vintage diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index d97aa94..0000000 --- a/setup.cfg +++ /dev/null @@ -1,33 +0,0 @@ -[metadata] -name = backslash -classifiers = - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 -description = Client library for the Backslash test reporting service -license = BSD -author = Rotem Yaari -author_email = vmalloc@gmail.com -url = http://github.com/getslash/backslash-python/ - - -[extras] -testing = - slash>=1.5.0 - Flask - Flask-Loopback - pylint - pytest>4.0 - pytest-cov>=2.6 - URLObject - weber-utils - -doc = - alabaster - releases - Sphinx - -sentry = - raven - -[tool:pytest] -testpaths = tests diff --git a/setup.py b/setup.py deleted file mode 100644 index 5bc87b0..0000000 --- a/setup.py +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python -from setuptools import setup - - -setup( - setup_requires=['pbr>=3.0', 'setuptools>=17.1'], - pbr=True, - python_requires=">=3.6.*", -) From 037ce5e39bfbdf6e15f415ad23743e3fe7bcdc6f Mon Sep 17 00:00:00 2001 From: Ayala Shachar Date: Fri, 10 Nov 2023 12:24:38 +0200 Subject: [PATCH 20/25] Update supported python versions --- .github/workflows/main.yml | 2 +- backslash/__version__.py | 5 +- backslash/_compat.py | 80 ------------------------------- backslash/api.py | 4 +- backslash/contrib/slash_plugin.py | 6 +-- backslash/contrib/utils.py | 7 --- backslash/error_container.py | 2 +- backslash/lazy_query.py | 3 +- docs/changelog.rst | 2 + pyproject.toml | 10 +++- 10 files changed, 21 insertions(+), 100 deletions(-) delete mode 100644 backslash/_compat.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a93f6ce..7b9c194 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 diff --git a/backslash/__version__.py b/backslash/__version__.py index 670fce2..19a479a 100644 --- a/backslash/__version__.py +++ b/backslash/__version__.py @@ -1,3 +1,4 @@ -import pkg_resources +import importlib.metadata -__version__ = pkg_resources.get_distribution('backslash').version + +__version__ = importlib.metadata.distribution("backslash").version diff --git a/backslash/_compat.py b/backslash/_compat.py deleted file mode 100644 index fd99ffa..0000000 --- a/backslash/_compat.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 -*- - -# Based on logbook.helpers, licensed under BSD. See https://github.com/mitsuhiko/logbook/blob/0.4.2/logbook/compat.py for copyright information - -#pylint: disable=import-error -#pylint: disable=maybe-no-member -#pylint: disable=no-name-in-module -#pylint: disable=undefined-variable -#pylint: disable=unused-argument -#pylint: disable=unused-import -#pylint: disable=exec-used -#pylint: disable=unnecessary-lambda-assignment -import sys -from contextlib import contextmanager - -PY2 = sys.version_info[0] == 2 - -if PY2: - from cStringIO import StringIO as cStringIO - from cStringIO import StringIO as BytesIO - from pipes import quote as shellquote - - @contextmanager - def TextIOWrapper(f): - yield f - -else: - from io import StringIO as cStringIO - from io import BytesIO, TextIOWrapper - from shlex import quote as shellquote - -if PY2: - import __builtin__ as _builtins -else: - import builtins as _builtins - -try: - import json -except ImportError: - import simplejson as json - -if PY2: - from cStringIO import StringIO - iteritems = lambda d: d.iteritems() # not dict.iteritems!!! we support ordered dicts as well - itervalues = lambda d: d.itervalues() - from itertools import imap - reduce = _builtins.reduce - from itertools import izip - from itertools import izip_longest - xrange = _builtins.xrange -else: - from io import StringIO - izip = _builtins.zip - imap = _builtins.map - from functools import reduce - xrange = range - iteritems = lambda d: iter(d.items()) # not dict.items!!! See above - itervalues = lambda d: iter(d.values()) - from itertools import zip_longest as izip_longest - -_IDENTITY = lambda obj: obj - -if PY2: - integer_types = (int, long) - string_types = (basestring,) -else: - integer_types = (int,) - string_types = (str,) - -if PY2: - #Yucky, but apparently that's the only way to do this - exec(""" -def reraise(tp, value, tb=None): - raise tp, value, tb -""", locals(), globals()) -else: - def reraise(tp, value, tb=None): - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value diff --git a/backslash/api.py b/backslash/api.py index e075adf..41d26bd 100644 --- a/backslash/api.py +++ b/backslash/api.py @@ -12,7 +12,7 @@ from urlobject import URLObject as URL from .__version__ import __version__ as BACKSLASH_CLIENT_VERSION -from ._compat import BytesIO, TextIOWrapper, iteritems +from io import BytesIO, TextIOWrapper from .comment import Comment from .error import Error from .exceptions import BackslashClientException, ParamsTooLarge @@ -165,7 +165,7 @@ def _serialize_params(self, params: Optional[Dict[str, Any]]) -> Tuple[bool, Dic if compute_memory_usage(params) > _MAX_PARAMS_UNCOMPRESSED_SIZE: raise ParamsTooLarge() - for param_name, param_value in iteritems(params): + for param_name, param_value in params.items(): if param_value is NOTHING: continue returned[param_name] = param_value diff --git a/backslash/contrib/slash_plugin.py b/backslash/contrib/slash_plugin.py index cca49bc..cf316d0 100644 --- a/backslash/contrib/slash_plugin.py +++ b/backslash/contrib/slash_plugin.py @@ -5,7 +5,6 @@ import itertools import json import os -import pkg_resources import socket import sys import time @@ -27,7 +26,8 @@ from slash.utils.conf_utils import Cmdline, Doc from urlobject import URLObject as URL from requests import HTTPError -from .._compat import shellquote +from shlex import quote as shellquote +from packaging.version import parse as parse_version from ..client import Backslash as BackslashClient from ..exceptions import ParamsTooLarge from ..utils import ensure_dir @@ -327,7 +327,7 @@ def get_tests_to_resume(self, session_id, filters_dict): def _get_test_info(self, test): if test.__slash__.is_interactive() and \ - pkg_resources.parse_version(slash.__version__) < pkg_resources.parse_version('1.6.0'): + parse_version(slash.__version__) < parse_version('1.6.0'): returned = { 'file_name': '', 'class_name': '', diff --git a/backslash/contrib/utils.py b/backslash/contrib/utils.py index 345ed29..db9f9cf 100644 --- a/backslash/contrib/utils.py +++ b/backslash/contrib/utils.py @@ -3,8 +3,6 @@ import os import types -from .._compat import PY2 - try: from slash import config as slash_config except ImportError: @@ -16,14 +14,9 @@ _HERE = os.path.abspath('.') _ALLOWED_ATTRIBUTE_TYPES = [int, str, float] -if PY2: - _ALLOWED_ATTRIBUTE_TYPES.append(long) # pylint: disable=undefined-variable _ALLOWED_ATTRIBUTE_TYPES = tuple(_ALLOWED_ATTRIBUTE_TYPES) _FILTERED_MEMBER_TYPES = [types.MethodType, types.FunctionType, type] -if PY2: - _FILTERED_MEMBER_TYPES.append(types.UnboundMethodType) # pylint: disable=no-member - _FILTERED_MEMBER_TYPES.append(types.ClassType) # pylint: disable=no-member _FILTERED_MEMBER_TYPES = tuple(_FILTERED_MEMBER_TYPES) _MAX_VARIABLE_VALUE_LENGTH = 100 diff --git a/backslash/error_container.py b/backslash/error_container.py index 521440c..83f4ed4 100644 --- a/backslash/error_container.py +++ b/backslash/error_container.py @@ -2,7 +2,7 @@ import json import tempfile -from ._compat import TextIOWrapper +from io import TextIOWrapper import logbook from sentinels import NOTHING diff --git a/backslash/lazy_query.py b/backslash/lazy_query.py index 87279b1..d310b9b 100644 --- a/backslash/lazy_query.py +++ b/backslash/lazy_query.py @@ -3,7 +3,6 @@ from sentinels import NOTHING -from ._compat import iteritems from .utils import raise_for_status @@ -32,7 +31,7 @@ def filter(self, *filter_objects, **fields): returned_url = self._url for filter_object in filter_objects: returned_url = filter_object.add_to_url(returned_url) - for field_name, field_value in iteritems(fields): + for field_name, field_value in fields.items(): returned_url = returned_url.add_query_param(field_name, str(field_value)) return LazyQuery(self._client, url=returned_url, page_size=self._page_size) diff --git a/docs/changelog.rst b/docs/changelog.rst index 6424a0b..0ac17d2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,8 @@ Changelog ========= +* :feature:`-` Support python versions 3.8 to 3.12 +* :feature:`-` Use pyproject.toml for project configuration * :feature:`104` Drop support for python version < 3.6 * :release:`2.39.0 <03-07-2019>` * :feature:`101` Report if error is fatal diff --git a/pyproject.toml b/pyproject.toml index 7ffa961..7a5bc22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,10 +6,16 @@ build-backend = "hatchling.build" name = "backslash" description = "Client library for the Backslash test reporting service" readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.8" license = { text = "BSD 3-Clause License" } -classifiers = ["Programming Language :: Python :: 3.7"] +classifiers = [ + "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", +] dependencies = [ "GitPython", "Logbook", From cc1086ccc3bdf467d1c9ff6e957c8de343e8d9a2 Mon Sep 17 00:00:00 2001 From: Ayala Shachar Date: Fri, 11 Oct 2024 11:01:12 +0300 Subject: [PATCH 21/25] Use UV instead of pip --- .github/workflows/main.yml | 16 +++++++++++----- Makefile | 16 ++++++---------- pyproject.toml | 8 +++++++- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7b9c194..32e88c0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,13 +12,19 @@ jobs: strategy: matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + env: + UV_PYTHON: ${{ matrix.python-version }} steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} + - name: Checkout the repository + uses: actions/checkout@main + - name: Install the default version of uv + id: setup-uv + uses: astral-sh/setup-uv@v3 + - name: Print the installed version + run: echo "Installed uv version is ${{ steps.setup-uv.outputs.uv-version }}" + - name: Install Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} - name: Install dependencies run: make env - name: Lint with pylint diff --git a/Makefile b/Makefile index ae2b4de..90cee10 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,15 @@ default: test test: env - .env/bin/pytest -x tests --cov=backslash --cov-report=html + .venv/bin/pytest -x tests --cov=backslash --cov-report=html pylint: env - .env/bin/pylint --rcfile .pylintrc backslash tests + .venv/bin/pylint --rcfile .pylintrc backslash tests doc: env - .env/bin/sphinx-build -a -W -E docs build/sphinx/html + .venv/bin/sphinx-build -a -W -E docs build/sphinx/html -env: .env/.up-to-date - - -.env/.up-to-date: Makefile pyproject.toml - python3 -m venv .env - .env/bin/pip install -e .[testing,doc] - touch $@ +env: + uv venv + uv pip install -e .[testing,doc] diff --git a/pyproject.toml b/pyproject.toml index 7a5bc22..23bf685 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,10 @@ testing = [ "pytest-cov>=2.6", "URLObject", "weber-utils", + # Slash still using pkg_resources, installing setuptools as temporary workaround + # so this repo can be installed with UV + # Should be removed once a new version of slash will be released. + "setuptools<81", ] doc = ["alabaster", "releases", "Sphinx"] @@ -53,4 +57,6 @@ source = "vcs" [tool.pytest] testpaths = "tests" timeout_method = "signal" -addopts = "-ra -W error::DeprecationWarning" +# Current slash version uses pkg_resources, deprecated module, which emits warnings. +# This option (of consider warnings as errors) should be comment out, until a new slash version will be released. +# addopts = "-ra -W error::DeprecationWarning" From fbbc0b8e45c6d1a091410255dcf7e897718dad5a Mon Sep 17 00:00:00 2001 From: Ayala Shachar Date: Fri, 11 Oct 2024 11:08:30 +0300 Subject: [PATCH 22/25] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 625fab6..32e3630 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ | | | |-----------------------|-----------------------------------------------------------------------------------------| -| Build Status | ![Build Status](https://github.com/getslash/backslash-python/actions/workflows/main.yml/badge.svg?branch=develop) | +| Build Status | ![Build Status](https://github.com/getslash/backslash-python/actions/workflows/main.yml/badge.svg?branch=master) | | Supported Versions | ![Supported Versions](https://img.shields.io/pypi/pyversions/backslash.svg) | | Latest Version | ![Latest Version](https://img.shields.io/pypi/v/backslash.svg) | -# Licence +# License BSD3 From 7c4af29e7dbdc8ded6fff6b27a25a0b4c0eae355 Mon Sep 17 00:00:00 2001 From: Ayala Shachar Date: Fri, 11 Oct 2024 11:09:37 +0300 Subject: [PATCH 23/25] Update supported python versions: >= 3.8, <= 3.13 --- .github/workflows/main.yml | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 32e88c0..a3ffd03 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] env: UV_PYTHON: ${{ matrix.python-version }} diff --git a/pyproject.toml b/pyproject.toml index 23bf685..41832aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dependencies = [ "GitPython", From 68af6261b90551ff381cf95b1756b12742eec261 Mon Sep 17 00:00:00 2001 From: Ayala Shachar Date: Fri, 3 Oct 2025 09:56:18 +0300 Subject: [PATCH 24/25] Update changelog --- docs/changelog.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0ac17d2..d6b718e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,7 +1,8 @@ Changelog ========= -* :feature:`-` Support python versions 3.8 to 3.12 +* :feature:`-` Use UV +* :feature:`-` Support python versions 3.8 to 3.13 * :feature:`-` Use pyproject.toml for project configuration * :feature:`104` Drop support for python version < 3.6 * :release:`2.39.0 <03-07-2019>` From e3e0a66d165be90ba3e9bf4e6dffde6a162a3aec Mon Sep 17 00:00:00 2001 From: Ayala Shachar Date: Fri, 3 Oct 2025 09:55:07 +0300 Subject: [PATCH 25/25] CI: Building env only once, run sphinx for specific python only & Add publish job --- .github/workflows/main.yml | 52 ++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a3ffd03..10e4bc8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ on: # manually triggered jobs: - build: + test: runs-on: ubuntu-latest strategy: matrix: @@ -26,10 +26,52 @@ jobs: - name: Install Python ${{ matrix.python-version }} run: uv python install ${{ matrix.python-version }} - name: Install dependencies - run: make env + run: | + uv venv + uv pip install ".[testing]" - name: Lint with pylint - run: make pylint + run: .venv/bin/pylint --rcfile .pylintrc backslash tests - name: Test with pytest - run: make test - - name: Documentation + run: .venv/bin/pytest tests --cov=backslash --cov-report=html + + docs: + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@main + - name: Install the default version of uv + id: setup-uv + uses: astral-sh/setup-uv@v3 + - name: Building docs run: make doc + + publish: + if: startsWith(github.ref, 'refs/tags/') + needs: test + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install hatch + run: pip install hatch + + - name: Build package + run: hatch build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + attestations: true + skip-existing: true