From 3cb86c8cf6b0040f2bf3e3c72f8e63012ff049fe Mon Sep 17 00:00:00 2001 From: mcarans Date: Wed, 28 Oct 2026 17:18:11 +1300 Subject: [PATCH] Upgrade ruff, pre-commit hooks and GitHub Actions; fix new ruff lint violations - actions/checkout v7.0.1, astral-sh/setup-uv v10.3.2 - ruff 0.17.3, uv-pre-commit 0.13.6 - hdx-python-utilities >= 4.2.6 - Fix violations from ruff 0.17's expanded default rules - Default today to None instead of now_utc() evaluated at import --- .github/workflows/publish.yaml | 4 +- .github/workflows/run-python-tests.yaml | 4 +- .pre-commit-config.yaml | 4 +- documentation/explanation/facades.md | 2 +- documentation/explanation/history.md | 2 +- documentation/explanation/information.md | 2 +- documentation/how-to/api-key.md | 2 +- documentation/how-to/docker.md | 2 +- documentation/how-to/framework.md | 2 +- documentation/how-to/install.md | 2 +- documentation/how-to/logging.md | 2 +- documentation/how-to/objects.md | 2 +- .../operate-datasets/dataset-resources.md | 2 +- .../how-to/operate-datasets/location.md | 2 +- .../how-to/operate-datasets/maintainer.md | 2 +- .../how-to/operate-datasets/organization.md | 2 +- documentation/how-to/operate-datasets/tags.md | 2 +- .../how-to/operate-datasets/time-period.md | 2 +- .../operate-datasets/update-frequency.md | 2 +- documentation/reference/changelog.md | 2 +- documentation/tutorials/getting-started.md | 2 +- pyproject.toml | 4 +- src/hdx/api/configuration.py | 2 +- src/hdx/api/locations.py | 0 src/hdx/api/utilities/date_helper.py | 4 +- src/hdx/api/utilities/filestore_helper.py | 0 src/hdx/api/utilities/hdx_error_handler.py | 10 +- src/hdx/data/__init__.py | 0 src/hdx/data/dataset.py | 91 +++++++++---------- src/hdx/data/hdxobject.py | 6 +- src/hdx/data/location.py | 7 +- src/hdx/data/organization.py | 5 +- src/hdx/data/resource.py | 10 +- src/hdx/data/resource_matcher.py | 17 ++-- src/hdx/data/resource_view.py | 0 src/hdx/data/showcase.py | 10 +- src/hdx/data/user.py | 4 +- src/hdx/data/vocabulary.py | 6 +- src/hdx/facades/__init__.py | 0 src/hdx/facades/infer_arguments.py | 3 +- src/hdx/facades/keyword_arguments.py | 0 src/hdx/facades/simple.py | 0 tests/hdx/__init__.py | 0 tests/hdx/api/test_ckan.py | 2 +- tests/hdx/api/test_configuration.py | 0 tests/hdx/api/test_locations.py | 0 .../api/utilities/test_hdx_error_handler.py | 77 ++++++++-------- tests/hdx/conftest.py | 0 tests/hdx/data/test_activity.py | 3 +- tests/hdx/data/test_dataset_core.py | 7 +- tests/hdx/data/test_dataset_noncore.py | 5 +- tests/hdx/data/test_organization.py | 0 tests/hdx/data/test_resource.py | 36 ++++---- tests/hdx/data/test_resource_view.py | 3 +- tests/hdx/data/test_showcase.py | 3 +- .../hdx/data/test_update_dataset_resources.py | 5 +- tests/hdx/data/test_user.py | 64 +++++++------ tests/hdx/data/test_vocabulary.py | 3 +- tests/hdx/facades/__init__.py | 0 tests/hdx/facades/test_infer_arguments.py | 0 tests/hdx/facades/test_keyword_arguments.py | 0 tests/hdx/facades/test_simple.py | 0 uv.lock | 65 +++++++------ workingexample/my_code.py | 0 workingexample/run.py | 0 65 files changed, 245 insertions(+), 255 deletions(-) mode change 100755 => 100644 src/hdx/api/configuration.py mode change 100755 => 100644 src/hdx/api/locations.py mode change 100755 => 100644 src/hdx/api/utilities/date_helper.py mode change 100755 => 100644 src/hdx/api/utilities/filestore_helper.py mode change 100755 => 100644 src/hdx/data/__init__.py mode change 100755 => 100644 src/hdx/data/dataset.py mode change 100755 => 100644 src/hdx/data/hdxobject.py mode change 100755 => 100644 src/hdx/data/organization.py mode change 100755 => 100644 src/hdx/data/resource.py mode change 100755 => 100644 src/hdx/data/resource_matcher.py mode change 100755 => 100644 src/hdx/data/resource_view.py mode change 100755 => 100644 src/hdx/data/showcase.py mode change 100755 => 100644 src/hdx/data/user.py mode change 100755 => 100644 src/hdx/data/vocabulary.py mode change 100755 => 100644 src/hdx/facades/__init__.py mode change 100755 => 100644 src/hdx/facades/infer_arguments.py mode change 100755 => 100644 src/hdx/facades/keyword_arguments.py mode change 100755 => 100644 src/hdx/facades/simple.py mode change 100755 => 100644 tests/hdx/__init__.py mode change 100755 => 100644 tests/hdx/api/test_configuration.py mode change 100755 => 100644 tests/hdx/api/test_locations.py mode change 100755 => 100644 tests/hdx/conftest.py mode change 100755 => 100644 tests/hdx/data/test_dataset_core.py mode change 100755 => 100644 tests/hdx/data/test_dataset_noncore.py mode change 100755 => 100644 tests/hdx/data/test_organization.py mode change 100755 => 100644 tests/hdx/data/test_resource.py mode change 100755 => 100644 tests/hdx/data/test_resource_view.py mode change 100755 => 100644 tests/hdx/data/test_showcase.py mode change 100755 => 100644 tests/hdx/data/test_user.py mode change 100755 => 100644 tests/hdx/data/test_vocabulary.py mode change 100755 => 100644 tests/hdx/facades/__init__.py mode change 100755 => 100644 tests/hdx/facades/test_infer_arguments.py mode change 100755 => 100644 tests/hdx/facades/test_keyword_arguments.py mode change 100755 => 100644 tests/hdx/facades/test_simple.py mode change 100755 => 100644 workingexample/my_code.py mode change 100755 => 100644 workingexample/run.py diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 9606a97f..718b47f8 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -17,7 +17,7 @@ jobs: contents: read steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Get history and tags for versioning to work run: | @@ -25,7 +25,7 @@ jobs: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2 - name: Build with uv run: uv build diff --git a/.github/workflows/run-python-tests.yaml b/.github/workflows/run-python-tests.yaml index 60e40b95..0db4d3a5 100644 --- a/.github/workflows/run-python-tests.yaml +++ b/.github/workflows/run-python-tests.yaml @@ -16,10 +16,10 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2 with: enable-cache: true python-version: "3.13" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 47fa2760..d21944f2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: - id: check-ast - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.14 + rev: v0.17.3 hooks: # Run the linter. - id: ruff-check @@ -21,7 +21,7 @@ repos: - id: ruff-format - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.9.25 + rev: 0.13.6 hooks: # Ensure the lockfile is up-to-date with pyproject.toml - id: uv-lock diff --git a/documentation/explanation/facades.md b/documentation/explanation/facades.md index 9d1c12c3..6a6aa883 100644 --- a/documentation/explanation/facades.md +++ b/documentation/explanation/facades.md @@ -11,4 +11,4 @@ project configuration is used for any configuration specific to your project. The default logging configuration reads a configuration file internal to the library that sets up an coloured console handler outputting at INFO level and a file handler -writing to errors.log at ERROR level. \ No newline at end of file +writing to errors.log at ERROR level. diff --git a/documentation/explanation/history.md b/documentation/explanation/history.md index c3b2a577..a5be3695 100644 --- a/documentation/explanation/history.md +++ b/documentation/explanation/history.md @@ -15,4 +15,4 @@ resolve issues as quickly as possible. Static metadata can be held in human read files so if it needs to be modified, it is straightforward. The HDX Python library has expanded over time as needs have arisen and is used for a -range of tasks involving interaction with the HDX platform. \ No newline at end of file +range of tasks involving interaction with the HDX platform. diff --git a/documentation/explanation/information.md b/documentation/explanation/information.md index e37aaed2..6cb3e216 100644 --- a/documentation/explanation/information.md +++ b/documentation/explanation/information.md @@ -8,4 +8,4 @@ This library is part of the [Humanitarian Data Exchange](https://data.humdata.or HDX. The code for the library is [here](https://github.com/OCHA-DAP/hdx-python-api). -The library has detailed API documentation which can be found in the menu at the top. \ No newline at end of file +The library has detailed API documentation which can be found in the menu at the top. diff --git a/documentation/how-to/api-key.md b/documentation/how-to/api-key.md index 5ad2f990..567a7049 100644 --- a/documentation/how-to/api-key.md +++ b/documentation/how-to/api-key.md @@ -21,4 +21,4 @@ the drop down menu **.hdx_configuration.yaml** in the current user's home directory. Then put in the YAML file: - hdx_key: "HDX API KEY" \ No newline at end of file + hdx_key: "HDX API KEY" diff --git a/documentation/how-to/docker.md b/documentation/how-to/docker.md index 4e26e416..de0198ec 100644 --- a/documentation/how-to/docker.md +++ b/documentation/how-to/docker.md @@ -3,4 +3,4 @@ The library is also available set up and ready to go in a Docker image: docker pull public.ecr.aws/unocha/hdx-scraper-baseimage:stable - docker run -i -t public.ecr.aws/unocha/hdx-scraper-baseimage:stable python3 \ No newline at end of file + docker run -i -t public.ecr.aws/unocha/hdx-scraper-baseimage:stable python3 diff --git a/documentation/how-to/framework.md b/documentation/how-to/framework.md index 6d6b4838..9d2ddc96 100644 --- a/documentation/how-to/framework.md +++ b/documentation/how-to/framework.md @@ -54,4 +54,4 @@ Create a file **my_code.py** and copy into it the code below: """ logger.debug("Generating dataset!") -You can then fill out the function **generate_dataset** as required. \ No newline at end of file +You can then fill out the function **generate_dataset** as required. diff --git a/documentation/how-to/install.md b/documentation/how-to/install.md index 1d3b0623..b741ee68 100644 --- a/documentation/how-to/install.md +++ b/documentation/how-to/install.md @@ -15,4 +15,4 @@ package that are missing eg. for Ubuntu: python-dev, libffi-dev and libssl-dev. If you get import or other errors, then please either recreate your virtualenv if you are using one or uninstall hdx-python-api, hdx-python-country and hdx-python-utilities using **pip uninstall**, then install hdx-python-api (which will pull in the other -dependencies). \ No newline at end of file +dependencies). diff --git a/documentation/how-to/logging.md b/documentation/how-to/logging.md index 2481d58b..a8dea4ee 100644 --- a/documentation/how-to/logging.md +++ b/documentation/how-to/logging.md @@ -23,4 +23,4 @@ Then use the logger like this: logger.info("INFORMATION message") logger.warning("WARNING message") logger.error("ERROR message") - logger.critical("CRITICAL error message") \ No newline at end of file + logger.critical("CRITICAL error message") diff --git a/documentation/how-to/objects.md b/documentation/how-to/objects.md index 1346843a..ff1d36f0 100644 --- a/documentation/how-to/objects.md +++ b/documentation/how-to/objects.md @@ -99,4 +99,4 @@ You can delete HDX objects using **delete_from_hdx** and update an object that already exists in HDX with the method **update_in_hdx**. These take various boolean parameters that all have defaults and are documented in the API docs. They do not return anything and they throw exceptions for failures like the -object to update not existing. \ No newline at end of file +object to update not existing. diff --git a/documentation/how-to/operate-datasets/dataset-resources.md b/documentation/how-to/operate-datasets/dataset-resources.md index d31d30aa..11292e9a 100644 --- a/documentation/how-to/operate-datasets/dataset-resources.md +++ b/documentation/how-to/operate-datasets/dataset-resources.md @@ -106,4 +106,4 @@ dataset is updated from the result: dataset.set_qa_completed(True) dataset.qa_revise_resources("pii", "false") - dataset.reset_fs_check() \ No newline at end of file + dataset.reset_fs_check() diff --git a/documentation/how-to/operate-datasets/location.md b/documentation/how-to/operate-datasets/location.md index 46b2b6c6..2596313d 100644 --- a/documentation/how-to/operate-datasets/location.md +++ b/documentation/how-to/operate-datasets/location.md @@ -38,4 +38,4 @@ If you want to add any other kind of location (which must be in this [list of valid locations](https://data.humdata.org/api/action/group_list?all_fields=true)), you do as shown below. - dataset.add_other_location("LOCATION") \ No newline at end of file + dataset.add_other_location("LOCATION") diff --git a/documentation/how-to/operate-datasets/maintainer.md b/documentation/how-to/operate-datasets/maintainer.md index 5bc59a14..ce228d12 100644 --- a/documentation/how-to/operate-datasets/maintainer.md +++ b/documentation/how-to/operate-datasets/maintainer.md @@ -10,4 +10,4 @@ If you want to set the maintainer, you do it like this: dataset.set_maintainer(USER) -USER is either a string id, dictionary or a User object. \ No newline at end of file +USER is either a string id, dictionary or a User object. diff --git a/documentation/how-to/operate-datasets/organization.md b/documentation/how-to/operate-datasets/organization.md index 51d89bfb..985094dd 100644 --- a/documentation/how-to/operate-datasets/organization.md +++ b/documentation/how-to/operate-datasets/organization.md @@ -11,4 +11,4 @@ If you want to set the organization, you do it like this: dataset.set_organization(ORGANIZATION) ORGANIZATION is either a string id, dictionary or an Organization -object. \ No newline at end of file +object. diff --git a/documentation/how-to/operate-datasets/tags.md b/documentation/how-to/operate-datasets/tags.md index be12eb86..8a8669f8 100644 --- a/documentation/how-to/operate-datasets/tags.md +++ b/documentation/how-to/operate-datasets/tags.md @@ -21,4 +21,4 @@ If you want to add a list of tags, you do it as follows: To obtain the predefined set of approved tags: - approved_tags = Vocabulary.approved_tags() \ No newline at end of file + approved_tags = Vocabulary.approved_tags() diff --git a/documentation/how-to/operate-datasets/time-period.md b/documentation/how-to/operate-datasets/time-period.md index a9628be8..881ab791 100644 --- a/documentation/how-to/operate-datasets/time-period.md +++ b/documentation/how-to/operate-datasets/time-period.md @@ -28,4 +28,4 @@ The method below allows you to set the time period using a year range. The start end year can be supplied as integers or strings. If no end year is supplied then the range will be from the beginning of the start year to the end of that year. - dataset.set_time_period_year_range(START YEAR, END YEAR) \ No newline at end of file + dataset.set_time_period_year_range(START YEAR, END YEAR) diff --git a/documentation/how-to/operate-datasets/update-frequency.md b/documentation/how-to/operate-datasets/update-frequency.md index b3437f60..c067785e 100644 --- a/documentation/how-to/operate-datasets/update-frequency.md +++ b/documentation/how-to/operate-datasets/update-frequency.md @@ -38,4 +38,4 @@ A list of valid update frequencies can be found using: Transforming backwards and forwards between representations can be achieved with this function: - update_frequency = Dataset.transform_update_frequency("UPDATE_FREQUENCY") \ No newline at end of file + update_frequency = Dataset.transform_update_frequency("UPDATE_FREQUENCY") diff --git a/documentation/reference/changelog.md b/documentation/reference/changelog.md index 63d9a42c..72a920dc 100644 --- a/documentation/reference/changelog.md +++ b/documentation/reference/changelog.md @@ -70,4 +70,4 @@ List[str]] (Tuple containing list of added tags and list of deleted tags and tag added). From 3.7.1, the list of tags must be from a set of approved tags (see -[Set Tags](../how-to/operate-datasets/tags.md)). \ No newline at end of file +[Set Tags](../how-to/operate-datasets/tags.md)). diff --git a/documentation/tutorials/getting-started.md b/documentation/tutorials/getting-started.md index 23d351c1..ec4d69ed 100644 --- a/documentation/tutorials/getting-started.md +++ b/documentation/tutorials/getting-started.md @@ -130,4 +130,4 @@ virtualenv if not installed: On other OSs: - rm -rf test \ No newline at end of file + rm -rf test diff --git a/pyproject.toml b/pyproject.toml index 4baa129a..45aafe0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ dependencies = [ "defopt>=7.0.0", "email_validator", "hdx-python-country>=4.2.6", - "hdx-python-utilities>=4.2.5", + "hdx-python-utilities>=4.2.6", "makefun", "requests", ] @@ -55,7 +55,7 @@ dev = [ "pytest-cov", "pytest-mock", "pre-commit", - "ruff==0.14.14", + "ruff==0.17.3", ] [project.urls] diff --git a/src/hdx/api/configuration.py b/src/hdx/api/configuration.py old mode 100755 new mode 100644 index f96dd2ae..a4c4c174 --- a/src/hdx/api/configuration.py +++ b/src/hdx/api/configuration.py @@ -200,7 +200,7 @@ def __init__(self, **kwargs: Any) -> None: if hdx_read_only is None: hdx_read_only = False self.hdx_read_only = hdx_read_only - logger.info(f"Read only access to HDX: {str(self.hdx_read_only)}") + logger.info(f"Read only access to HDX: {self.hdx_read_only!s}") if self.hdx_site == "hdx_custom_site": hdx_key = None diff --git a/src/hdx/api/locations.py b/src/hdx/api/locations.py old mode 100755 new mode 100644 diff --git a/src/hdx/api/utilities/date_helper.py b/src/hdx/api/utilities/date_helper.py old mode 100755 new mode 100644 index 063569cf..6445a9c8 --- a/src/hdx/api/utilities/date_helper.py +++ b/src/hdx/api/utilities/date_helper.py @@ -63,7 +63,7 @@ def get_hdx_date( def get_time_period_info( hdx_time_period: str, date_format: str | None = None, - today: datetime = now_utc(), + today: datetime | None = None, ) -> dict: """Get time period as datetimes and strings in specified format. If no format is supplied, the ISO 8601 format is used. Returns a @@ -80,6 +80,8 @@ def get_time_period_info( Returns: Dictionary of date information """ + if today is None: + today = now_utc() result = {} if hdx_time_period: if hdx_time_period[0] == "[" and hdx_time_period[-1] == "]": diff --git a/src/hdx/api/utilities/filestore_helper.py b/src/hdx/api/utilities/filestore_helper.py old mode 100755 new mode 100644 diff --git a/src/hdx/api/utilities/hdx_error_handler.py b/src/hdx/api/utilities/hdx_error_handler.py index 56aefdb7..f31bbd21 100644 --- a/src/hdx/api/utilities/hdx_error_handler.py +++ b/src/hdx/api/utilities/hdx_error_handler.py @@ -1,6 +1,7 @@ import logging from collections.abc import Sequence from os import getenv +from types import TracebackType from typing import Any from hdx.utilities.dictandlist import dict_of_sets_add @@ -32,7 +33,7 @@ def __init__( ): super().__init__(should_exit_on_error) if write_to_hdx is None: - write_to_hdx = getenv("ERR_TO_HDX", True) + write_to_hdx = getenv("ERR_TO_HDX", "True") if write_to_hdx in (False, 0, "false", "False", "FALSE", "N", "n", ""): self._write_to_hdx = False logger.info("Errors won't be written to HDX") @@ -210,7 +211,12 @@ def output_errors(self) -> None: if self._write_to_hdx: self.write_errors_to_hdx() - def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: if self._write_to_hdx: self.write_errors_to_hdx() super().__exit__(exc_type, exc_value, traceback) diff --git a/src/hdx/data/__init__.py b/src/hdx/data/__init__.py old mode 100755 new mode 100644 diff --git a/src/hdx/data/dataset.py b/src/hdx/data/dataset.py old mode 100755 new mode 100644 index 64a36074..cb2d193d --- a/src/hdx/data/dataset.py +++ b/src/hdx/data/dataset.py @@ -10,6 +10,7 @@ from typing import ( TYPE_CHECKING, Any, + ClassVar, Optional, Union, ) @@ -19,7 +20,6 @@ from hdx.utilities.dateparse import ( default_date, default_enddate, - now_utc, now_utc_notz, parse_date, parse_date_range, @@ -34,12 +34,11 @@ import hdx.data.organization as org_module import hdx.data.resource as res_module import hdx.data.showcase as sc_module -import hdx.data.user as user -import hdx.data.vocabulary as vocabulary from hdx.api.configuration import Configuration from hdx.api.locations import Locations from hdx.api.utilities.date_helper import DateHelper from hdx.api.utilities.filestore_helper import FilestoreHelper +from hdx.data import user, vocabulary from hdx.data.activity import Activity from hdx.data.hdxobject import HDXError, HDXObject from hdx.data.resource_matcher import ResourceMatcher @@ -67,7 +66,7 @@ class Dataset(HDXObject): max_attempts = 5 max_int = sys.maxsize - update_frequencies = { + update_frequencies: ClassVar[dict[str, str]] = { "-2": "As needed", "-1": "Never", "0": "Live", @@ -224,7 +223,7 @@ def save_to_json( session = hdx.api.utilities.url_utils.get_ckan_ready_session( self.configuration ) - for resource in dataset_dict.get("resources", tuple()): + for resource in dataset_dict.get("resources", ()): resource["url"] = hdx.api.utilities.url_utils.follow_url( resource["url"], session=session ) @@ -295,11 +294,10 @@ def add_update_resource( The resource that was added after matching with any existing resource """ resource = self._get_resource_from_obj(resource) - if "package_id" in resource: - if not ignore_datasetid: - raise HDXError( - f"Resource {resource['name']} being added already has a dataset id!" - ) + if "package_id" in resource and not ignore_datasetid: + raise HDXError( + f"Resource {resource['name']} being added already has a dataset id!" + ) resource.check_both_url_filetoupload() resource_index = ResourceMatcher.match_resource_list(self._resources, resource) if resource_index is None: @@ -331,11 +329,10 @@ def add_update_resources( resource_objects = [] for resource in resources: resource = self._get_resource_from_obj(resource) - if "package_id" in resource: - if not ignore_datasetid: - raise HDXError( - f"Resource {resource['name']} being added already has a dataset id!" - ) + if "package_id" in resource and not ignore_datasetid: + raise HDXError( + f"Resource {resource['name']} being added already has a dataset id!" + ) resource.check_both_url_filetoupload() resource_objects.append(resource) ( @@ -371,9 +368,8 @@ def delete_resource( Returns: True if resource removed or False if not """ - if isinstance(resource, str): - if is_valid_uuid(resource) is False: - raise HDXError(f"{resource} is not a valid resource id!") + if isinstance(resource, str) and is_valid_uuid(resource) is False: + raise HDXError(f"{resource} is not a valid resource id!") return self._remove_hdxobject(self._resources, resource, delete=delete) def get_resources(self) -> list["Resource"]: @@ -593,8 +589,8 @@ def check_required_fields( def revise( match: dict[str, Any], filter: Sequence[str] = (), - update: dict[str, Any] = {}, - files_to_upload: dict[str, str] = {}, + update: dict[str, Any] | None = None, + files_to_upload: dict[str, str] | None = None, configuration: Configuration | None = None, **kwargs: Any, ) -> "Dataset": @@ -611,6 +607,10 @@ def revise( Returns: Dataset object """ + if files_to_upload is None: + files_to_upload = {} + if update is None: + update = {} separators = (",", ":") data = {"match": json.dumps(match, separators=separators)} if filter: @@ -750,11 +750,11 @@ def _revise_files_to_upload_resource_deletions( for resource_index in resources_to_delete: del resources_to_update[resource_index] new_fsresources = {} - for index in filestore_resources: + for index, fsresource in filestore_resources.items(): if index > resource_index: - new_fsresources[index - 1] = filestore_resources[index] + new_fsresources[index - 1] = fsresource else: - new_fsresources[index] = filestore_resources[index] + new_fsresources[index] = fsresource filestore_resources = new_fsresources for ( resource_index, @@ -1182,15 +1182,13 @@ def create_in_hdx( """ self.check_resources_url_filetoupload() loadedid = self.data.get("id") - if loadedid is not None: - if not self._dataset_load_from_hdx(loadedid): - logger.warning(f"Failed to load dataset with id {loadedid}") - loadedid = None + if loadedid is not None and not self._dataset_load_from_hdx(loadedid): + logger.warning(f"Failed to load dataset with id {loadedid}") + loadedid = None if not loadedid: loadedid = self.data.get("name") - if loadedid is not None: - if not self._dataset_load_from_hdx(loadedid): - loadedid = None + if loadedid is not None and not self._dataset_load_from_hdx(loadedid): + loadedid = None if loadedid: statuses, _ = self._dataset_hdx_update( allow_no_resources=allow_no_resources, @@ -1433,8 +1431,7 @@ def get_all_resources( """ resources = [] for dataset in datasets: - for resource in dataset.get_resources(): - resources.append(resource) + resources.extend(dataset.get_resources()) return resources def get_activities(self, all_pages: bool = False, **kwargs: Any) -> list[Activity]: @@ -1479,7 +1476,7 @@ def autocomplete( def get_time_period( self, date_format: str | None = None, - today: datetime = now_utc(), + today: datetime | None = None, ) -> dict: """Get dataset date as datetimes and strings in specified format. If no format is supplied, the ISO 8601 format is used. Returns a dictionary containing keys startdate (start date as datetime), enddate (end @@ -1595,7 +1592,7 @@ def set_expected_update_frequency(self, update_frequency: str | int) -> None: int(update_frequency) except ValueError: update_frequency = Dataset.transform_update_frequency(update_frequency) - if update_frequency not in Dataset.update_frequencies.keys(): + if update_frequency not in Dataset.update_frequencies: raise HDXError("Invalid update frequency supplied!") self.data["data_update_frequency"] = update_frequency @@ -1748,7 +1745,7 @@ def add_country_location( Returns: True if country added or False if country already present """ - iso3, match = Country.get_iso3_country_code_fuzzy(country, use_live=use_live) + iso3, _match = Country.get_iso3_country_code_fuzzy(country, use_live=use_live) if iso3 is None: raise HDXError(f"Country: {country} - cannot find iso3 code!") return self.add_other_location( @@ -1883,7 +1880,7 @@ def set_maintainer(self, maintainer: Union["User", dict, str]) -> None: Returns: None """ - if isinstance(maintainer, user.User) or isinstance(maintainer, dict): + if isinstance(maintainer, (user.User, dict)): if "id" not in maintainer: maintainer = user.User.read_from_hdx( maintainer["name"], configuration=self.configuration @@ -1918,9 +1915,7 @@ def set_organization( Returns: None """ - if isinstance(organization, org_module.Organization) or isinstance( - organization, dict - ): + if isinstance(organization, (org_module.Organization, dict)): if "id" not in organization: organization = org_module.Organization.read_from_hdx( organization["name"], configuration=self.configuration @@ -1966,7 +1961,7 @@ def _get_dataset_showcase_dict( Returns: Dataset showcase dict """ - if isinstance(showcase, sc_module.Showcase) or isinstance(showcase, dict): + if isinstance(showcase, (sc_module.Showcase, dict)): if "id" not in showcase: showcase = sc_module.Showcase.read_from_hdx(showcase["name"]) showcase = showcase["id"] @@ -1981,7 +1976,7 @@ def _get_dataset_showcase_dict( def add_showcase( self, showcase: Union["Showcase", dict, str], - showcases_to_check: Sequence["Showcase"] = None, + showcases_to_check: Sequence["Showcase"] | None = None, ) -> bool: """Add dataset to showcase @@ -1995,8 +1990,8 @@ def add_showcase( dataset_showcase = self._get_dataset_showcase_dict(showcase) if showcases_to_check is None: showcases_to_check = self.get_showcases() - for showcase in showcases_to_check: - if dataset_showcase["showcase_id"] == showcase["id"]: + for showcase_to_check in showcases_to_check: + if dataset_showcase["showcase_id"] == showcase_to_check["id"]: return False showcase = sc_module.Showcase( {"id": dataset_showcase["showcase_id"]}, @@ -2008,7 +2003,7 @@ def add_showcase( def add_showcases( self, showcases: Sequence[Union["Showcase", dict, str]], - showcases_to_check: Sequence["Showcase"] = None, + showcases_to_check: Sequence["Showcase"] | None = None, ) -> bool: """Add dataset to multiple showcases @@ -2229,7 +2224,7 @@ def set_preview_resource( """ if isinstance(resource, int) and not isinstance(resource, bool): resource = self._resources[resource] - if isinstance(resource, res_module.Resource) or isinstance(resource, dict): + if isinstance(resource, (res_module.Resource, dict)): res = resource.get("id") if res is None: resource = resource["name"] @@ -2576,12 +2571,10 @@ def process_row(row: Sequence | Mapping) -> Sequence | Mapping | None: return None startdate = result.get("startdate") if startdate is not None: - if startdate < dates[0]: - dates[0] = startdate + dates[0] = min(dates[0], startdate) enddate = result.get("enddate") if enddate is not None: - if enddate > dates[1]: - dates[1] = enddate + dates[1] = max(dates[1], enddate) return row filepath = Path(folder) / filename diff --git a/src/hdx/data/hdxobject.py b/src/hdx/data/hdxobject.py old mode 100755 new mode 100644 index 42051543..984a14a3 --- a/src/hdx/data/hdxobject.py +++ b/src/hdx/data/hdxobject.py @@ -425,7 +425,7 @@ def _write_to_hdx( try: if files_to_upload: for key, value in files_to_upload.items(): - open_files_to_upload[key] = open(value, "rb") + open_files_to_upload[key] = open(value, "rb") # noqa: SIM115 return self.configuration.call_remoteckan( self.actions()[action], data, files=open_files_to_upload ) @@ -603,7 +603,7 @@ def _remove_hdxobject( return False if isinstance(obj, str): obj_id = obj - elif isinstance(obj, dict) or isinstance(obj, HDXObject): + elif isinstance(obj, (dict, HDXObject)): obj_id = obj.get(matchon) else: raise HDXError("Type of object not a string, dict or T<=HDXObject") @@ -613,7 +613,7 @@ def _remove_hdxobject( objid = objdata.get(matchon) if objid and objid == obj_id: if delete: - objlist[i].delete_from_hdx() + objdata.delete_from_hdx() del objlist[i] return True return False diff --git a/src/hdx/data/location.py b/src/hdx/data/location.py index 27457e1a..46d790e1 100644 --- a/src/hdx/data/location.py +++ b/src/hdx/data/location.py @@ -2,11 +2,12 @@ import logging from collections.abc import Sequence +from contextlib import suppress from typing import Any, Optional from hdx.api.configuration import Configuration from hdx.data.activity import Activity -from hdx.data.hdxobject import HDXObject +from hdx.data.hdxobject import HDXError, HDXObject logger = logging.getLogger(__name__) @@ -187,12 +188,10 @@ def get_all_location_names( if name in compiled_locations: final_locations.append(compiled_locations[name]) else: - try: + with suppress(HDXError): missing_location = location._write_to_hdx("show", {"id": name}) if missing_location: final_locations.append(missing_location) - except Exception: - pass return final_locations diff --git a/src/hdx/data/organization.py b/src/hdx/data/organization.py old mode 100755 new mode 100644 index e8e01adb..3e33bd32 --- a/src/hdx/data/organization.py +++ b/src/hdx/data/organization.py @@ -2,6 +2,7 @@ import logging from collections.abc import Sequence +from contextlib import suppress from pathlib import Path from typing import TYPE_CHECKING, Any, Optional, Union @@ -303,12 +304,10 @@ def get_all_organization_names( if name in compiled_orgs: final_orgs.append(compiled_orgs[name]) else: - try: + with suppress(HDXError): missing_org = organization._write_to_hdx("show", {"id": name}) if missing_org: final_orgs.append(missing_org) - except Exception: - pass return final_orgs diff --git a/src/hdx/data/resource.py b/src/hdx/data/resource.py old mode 100755 new mode 100644 index ece0a4e6..9280a470 --- a/src/hdx/data/resource.py +++ b/src/hdx/data/resource.py @@ -8,7 +8,7 @@ from typing import Any, Optional from urllib.parse import urlsplit -from hdx.utilities.dateparse import now_utc, now_utc_notz, parse_date +from hdx.utilities.dateparse import now_utc_notz, parse_date from hdx.utilities.downloader import Download from hdx.utilities.file_hashing import get_size_and_hash from hdx.utilities.retriever import Retrieve @@ -132,7 +132,7 @@ def read_from_hdx( def get_date_of_resource( self, date_format: str | None = None, - today: datetime = now_utc(), + today: datetime | None = None, ) -> dict: """Get resource date as datetimes and strings in specified format. If no format is supplied, the ISO 8601 format is used. Returns a dictionary containing keys @@ -446,7 +446,7 @@ def check_neither_url_filetoupload(self) -> None: if self._file_to_upload is None and "url" not in self.data: raise HDXError("Either a url or a file to upload must be supplied!") - def correct_format(self, data: dict = None) -> None: + def correct_format(self, data: dict | None = None) -> None: """Correct the format of the file Args: @@ -744,7 +744,7 @@ def search_in_hdx( resources = [] resource = Resource(configuration=configuration) - success, result = resource._read_from_hdx( + _success, result = resource._read_from_hdx( "resource", query, "query", Resource.actions()["search"] ) if result: @@ -1257,7 +1257,7 @@ def reorder_resource_views( if is_valid_uuid(resource_view_id) is False: raise HDXError(f"{resource_view} is not a valid resource view id!") ids.append(resource_view_id) - _, result = self._read_from_hdx( + self._read_from_hdx( "resource view", self.data["id"], "id", diff --git a/src/hdx/data/resource_matcher.py b/src/hdx/data/resource_matcher.py old mode 100755 new mode 100644 index c170bbd3..03024df2 --- a/src/hdx/data/resource_matcher.py +++ b/src/hdx/data/resource_matcher.py @@ -46,9 +46,8 @@ def match_resource_list( for i, name1 in enumerate(names1): if name1 != name2: continue - if name1 in dupnames: - if formats1[i] != format2: - continue + if name1 in dupnames and formats1[i] != format2: + continue index1_match = i return index1_match @@ -83,10 +82,9 @@ def match_resource_lists( for j, id2 in enumerate(ids2): if id2 is None: continue - if id1 == id2: - if groups1[i] == groups2[j]: - index1_matches.append(i) - index2_matches.append(j) + if id1 == id2 and groups1[i] == groups2[j]: + index1_matches.append(i) + index2_matches.append(j) dupnames1 = { item for item, count in collections.Counter(names1).items() if count > 1 } @@ -102,9 +100,8 @@ def match_resource_lists( continue if name1 != name2: continue - if name1 in dupnames: - if formats1[i] != formats2[j]: - continue + if name1 in dupnames and formats1[i] != formats2[j]: + continue if groups1[i] == groups2[j]: index1_matches.append(i) index2_matches.append(j) diff --git a/src/hdx/data/resource_view.py b/src/hdx/data/resource_view.py old mode 100755 new mode 100644 diff --git a/src/hdx/data/showcase.py b/src/hdx/data/showcase.py old mode 100755 new mode 100644 index e0f7c939..36bf3a83 --- a/src/hdx/data/showcase.py +++ b/src/hdx/data/showcase.py @@ -246,7 +246,7 @@ def _get_showcase_dataset_dict( Returns: showcase dataset dict """ - if isinstance(dataset, hdx.data.dataset.Dataset) or isinstance(dataset, dict): + if isinstance(dataset, (hdx.data.dataset.Dataset, dict)): if "id" not in dataset: dataset = hdx.data.dataset.Dataset.read_from_hdx(dataset["name"]) dataset = dataset["id"] @@ -261,7 +261,7 @@ def _get_showcase_dataset_dict( def add_dataset( self, dataset: Union["Dataset", dict, str], # noqa: F821 - datasets_to_check: Sequence["Dataset"] = None, # noqa: F821 + datasets_to_check: Sequence["Dataset"] | None = None, # noqa: F821 ) -> bool: """Add a dataset @@ -275,8 +275,8 @@ def add_dataset( showcase_dataset = self._get_showcase_dataset_dict(dataset) if datasets_to_check is None: datasets_to_check = self.get_datasets() - for dataset in datasets_to_check: - if showcase_dataset["package_id"] == dataset["id"]: + for dataset_to_check in datasets_to_check: + if showcase_dataset["package_id"] == dataset_to_check["id"]: return False self._write_to_hdx("associate", showcase_dataset, "package_id") return True @@ -284,7 +284,7 @@ def add_dataset( def add_datasets( self, datasets: Sequence[Union["Dataset", dict, str]], # noqa: F821 - datasets_to_check: Sequence["Dataset"] = None, # noqa: F821 + datasets_to_check: Sequence["Dataset"] | None = None, # noqa: F821 ) -> bool: """Add multiple datasets diff --git a/src/hdx/data/user.py b/src/hdx/data/user.py old mode 100755 new mode 100644 index 2229edc0..769efafe --- a/src/hdx/data/user.py +++ b/src/hdx/data/user.py @@ -263,7 +263,7 @@ def email_users( **kwargs, ) - def get_organization_dicts(self, permission: str = "read") -> list[dict]: # noqa: F821 + def get_organization_dicts(self, permission: str = "read") -> list[dict]: """Get organization dictionaries (not organization objects) in HDX that this user is a member of. Args: @@ -340,7 +340,7 @@ def get_current_user_organization_dicts( return user.configuration.call_remoteckan( cls.actions()["listorgs"], {"permission": permission} ) - except Exception: + except Exception: # noqa: BLE001 return [] @classmethod diff --git a/src/hdx/data/vocabulary.py b/src/hdx/data/vocabulary.py old mode 100755 new mode 100644 index d42bedf1..695f20f6 --- a/src/hdx/data/vocabulary.py +++ b/src/hdx/data/vocabulary.py @@ -34,7 +34,7 @@ class Vocabulary(HDXObject): def __init__( self, initial_data: dict | None = None, - name: str = None, + name: str | None = None, tags: list | None = None, configuration: Configuration | None = None, ) -> None: @@ -361,9 +361,7 @@ def is_approved(cls, tag: str, configuration: Configuration | None = None) -> bo Returns: True if tag is approved, False if not """ - if tag.lower() in cls.approved_tags(configuration): - return True - return False + return tag.lower() in cls.approved_tags(configuration) @classmethod def read_tags_mappings( diff --git a/src/hdx/facades/__init__.py b/src/hdx/facades/__init__.py old mode 100755 new mode 100644 diff --git a/src/hdx/facades/infer_arguments.py b/src/hdx/facades/infer_arguments.py old mode 100755 new mode 100644 index 24b714a1..0ffa9eae --- a/src/hdx/facades/infer_arguments.py +++ b/src/hdx/facades/infer_arguments.py @@ -85,11 +85,10 @@ def facade(projectmainfn: Callable[[Any], None], **kwargs: Any): main_sig = f"{projectmainname}({','.join(param_names)})" argv = sys.argv[1:] - for key in kwargs: + for key, value in kwargs.items(): name = f"--{key.replace('_', '-')}" if name not in argv: argv.append(name) - value = kwargs[key] if isinstance(value, Path): value = str(value) argv.append(value) diff --git a/src/hdx/facades/keyword_arguments.py b/src/hdx/facades/keyword_arguments.py old mode 100755 new mode 100644 diff --git a/src/hdx/facades/simple.py b/src/hdx/facades/simple.py old mode 100755 new mode 100644 diff --git a/tests/hdx/__init__.py b/tests/hdx/__init__.py old mode 100755 new mode 100644 diff --git a/tests/hdx/api/test_ckan.py b/tests/hdx/api/test_ckan.py index 257a7f7b..6d182d1e 100644 --- a/tests/hdx/api/test_ckan.py +++ b/tests/hdx/api/test_ckan.py @@ -154,7 +154,7 @@ def create_resource(): resource.set_format("csv") resource.set_file_to_upload(test_data) else: - wks, url = create_gsheet( + _wks, url = create_gsheet( "resource1", [[random.random() for i in range(10)] for j in range(10)], ) diff --git a/tests/hdx/api/test_configuration.py b/tests/hdx/api/test_configuration.py old mode 100755 new mode 100644 diff --git a/tests/hdx/api/test_locations.py b/tests/hdx/api/test_locations.py old mode 100755 new mode 100644 diff --git a/tests/hdx/api/utilities/test_hdx_error_handler.py b/tests/hdx/api/utilities/test_hdx_error_handler.py index 19df5a93..84ef89d1 100644 --- a/tests/hdx/api/utilities/test_hdx_error_handler.py +++ b/tests/hdx/api/utilities/test_hdx_error_handler.py @@ -36,44 +36,41 @@ def test_hdx_error_handler(self, caplog): assert error_handler._write_to_hdx is False error_handler = HDXErrorHandler(write_to_hdx=0) assert error_handler._write_to_hdx is False - with pytest.raises(SystemExit): - with caplog.at_level(logging.ERROR): - with HDXErrorHandler( - should_exit_on_error=True, write_to_hdx=False - ) as errors: - errors.add_message( - "pipeline1", - "dataset1", - "error message", - "resource1", - err_to_hdx=True, + with pytest.raises(SystemExit), caplog.at_level(logging.ERROR): + with HDXErrorHandler( + should_exit_on_error=True, write_to_hdx=False + ) as errors: + errors.add_message( + "pipeline1", + "dataset1", + "error message", + "resource1", + err_to_hdx=True, + ) + errors.add_missing_value_message( + "pipeline1", + "dataset1", + "field1", + 123, + "resource1", + err_to_hdx=True, + ) + errors.add_multi_valued_message( + "pipeline1", + "dataset1", + "following values changed", + [1, 2, 3, 4], + "resource1", + err_to_hdx=True, + ) + assert len(errors.shared_errors["error"]["pipeline1 - dataset1"]) == 3 + assert ( + len( + errors.shared_errors["hdx_error"][ + ("pipeline1", "dataset1", "resource1") + ] ) - errors.add_missing_value_message( - "pipeline1", - "dataset1", - "field1", - 123, - "resource1", - err_to_hdx=True, - ) - errors.add_multi_valued_message( - "pipeline1", - "dataset1", - "following values changed", - [1, 2, 3, 4], - "resource1", - err_to_hdx=True, - ) - assert ( - len(errors.shared_errors["error"]["pipeline1 - dataset1"]) == 3 - ) - assert ( - len( - errors.shared_errors["hdx_error"][ - ("pipeline1", "dataset1", "resource1") - ] - ) - == 3 - ) - errors.output_errors() - assert "following values changed" in caplog.text + == 3 + ) + errors.output_errors() + assert "following values changed" in caplog.text diff --git a/tests/hdx/conftest.py b/tests/hdx/conftest.py old mode 100755 new mode 100644 diff --git a/tests/hdx/data/test_activity.py b/tests/hdx/data/test_activity.py index 1578bb6c..54ef7203 100644 --- a/tests/hdx/data/test_activity.py +++ b/tests/hdx/data/test_activity.py @@ -1,6 +1,7 @@ """Activity Tests""" import json +from typing import ClassVar import pytest @@ -47,7 +48,7 @@ def mocknotfound(action): class TestActivity: - calls = [] + calls: ClassVar[list] = [] @pytest.fixture(scope="function") def post_activity(self): diff --git a/tests/hdx/data/test_dataset_core.py b/tests/hdx/data/test_dataset_core.py old mode 100755 new mode 100644 index c8148656..b0e33df0 --- a/tests/hdx/data/test_dataset_core.py +++ b/tests/hdx/data/test_dataset_core.py @@ -304,9 +304,8 @@ def post(url, data, **kwargs): return resource_view_mockshow(url, datadict) if "list" in url: return resource_view_mocklist(url, datadict) - if "create" in url: - if datadict["title"] == "Preview": - return resource_view_mockcreate(url, datadict) + if "create" in url and datadict["title"] == "Preview": + return resource_view_mockcreate(url, datadict) return MockResponse( 404, '{"success": false, "error": {"message": "TEST ERROR: Not create", "__type": "TEST ERROR: Not Create Error"}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=resource_view_create"}', @@ -591,7 +590,7 @@ def test_create_in_hdx(self, configuration, post_create): dataset = Dataset(datasetdata) resource = Resource(resourcesdata[0]) try: - file = tempfile.NamedTemporaryFile(delete=False) + file = tempfile.NamedTemporaryFile(delete=False) # noqa: SIM115 resource.set_file_to_upload(file.name) dataset.add_update_resource(resource) statuses = dataset.create_in_hdx() diff --git a/tests/hdx/data/test_dataset_noncore.py b/tests/hdx/data/test_dataset_noncore.py old mode 100755 new mode 100644 index cde1a519..4a1f6bd3 --- a/tests/hdx/data/test_dataset_noncore.py +++ b/tests/hdx/data/test_dataset_noncore.py @@ -142,9 +142,8 @@ def post(url, data, **kwargs): return resource_view_mockshow(url, datadict) if "list" in url: return resource_view_mocklist(url, datadict) - if "create" in url: - if datadict["title"] == "Preview": - return resource_view_mockcreate(url, datadict) + if "create" in url and datadict["title"] == "Preview": + return resource_view_mockcreate(url, datadict) return MockResponse( 404, '{"success": false, "error": {"message": "TEST ERROR: Not create", "__type": "TEST ERROR: Not Create Error"}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=resource_view_create"}', diff --git a/tests/hdx/data/test_organization.py b/tests/hdx/data/test_organization.py old mode 100755 new mode 100644 diff --git a/tests/hdx/data/test_resource.py b/tests/hdx/data/test_resource.py old mode 100755 new mode 100644 index b68842ff..fa936133 --- a/tests/hdx/data/test_resource.py +++ b/tests/hdx/data/test_resource.py @@ -383,15 +383,15 @@ def post(url, data, **kwargs): datadict = json.loads(data.decode("utf-8")) if "show" in url: return mockshow(url, datadict) - if "resource_id" in datadict: - if ( - datadict["resource_id"] - == "de6549d8-268b-4dfe-adaf-a4ae5c8510d5" - ): - return MockResponse( - 200, - '{"success": true, "result": {"fields": [{"type": "text", "id": "code"}, {"type": "text", "id": "title"}, {"type": "float", "id": "value"}, {"type": "timestamp", "id": "latest_date"}, {"type": "text", "id": "source"}, {"type": "text", "id": "source_link"}, {"type": "text", "id": "notes"}, {"type": "text", "id": "explore"}, {"type": "text", "id": "units"}], "method": "insert", "primary_key": "code", "resource_id": "bfa6b55f-10b6-4ba2-8470-33bb9a5194a5"}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=datastore_create"}', - ) + if ( + "resource_id" in datadict + and datadict["resource_id"] + == "de6549d8-268b-4dfe-adaf-a4ae5c8510d5" + ): + return MockResponse( + 200, + '{"success": true, "result": {"fields": [{"type": "text", "id": "code"}, {"type": "text", "id": "title"}, {"type": "float", "id": "value"}, {"type": "timestamp", "id": "latest_date"}, {"type": "text", "id": "source"}, {"type": "text", "id": "source_link"}, {"type": "text", "id": "notes"}, {"type": "text", "id": "explore"}, {"type": "text", "id": "units"}], "method": "insert", "primary_key": "code", "resource_id": "bfa6b55f-10b6-4ba2-8470-33bb9a5194a5"}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=datastore_create"}', + ) if "create" not in url: return MockResponse( 404, @@ -446,15 +446,15 @@ def post(url, data, **kwargs): datadict = json.loads(data.decode("utf-8")) if "show" in url: return mockshow(url, datadict) - if "resource_id" in datadict: - if ( - datadict["resource_id"] - == "74b74ae1-df0c-4716-829f-4f939a046811" - ): - return MockResponse( - 200, - '{"success": true, "result": {"fields": [{"type": "text", "id": "code"}, {"type": "text", "id": "title"}, {"type": "float", "id": "value"}, {"type": "timestamp", "id": "latest_date"}, {"type": "text", "id": "source"}, {"type": "text", "id": "source_link"}, {"type": "text", "id": "notes"}, {"type": "text", "id": "explore"}, {"type": "text", "id": "units"}], "method": "insert", "primary_key": "code", "resource_id": "bfa6b55f-10b6-4ba2-8470-33bb9a5194a5"}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=datastore_create"}', - ) + if ( + "resource_id" in datadict + and datadict["resource_id"] + == "74b74ae1-df0c-4716-829f-4f939a046811" + ): + return MockResponse( + 200, + '{"success": true, "result": {"fields": [{"type": "text", "id": "code"}, {"type": "text", "id": "title"}, {"type": "float", "id": "value"}, {"type": "timestamp", "id": "latest_date"}, {"type": "text", "id": "source"}, {"type": "text", "id": "source_link"}, {"type": "text", "id": "notes"}, {"type": "text", "id": "explore"}, {"type": "text", "id": "units"}], "method": "insert", "primary_key": "code", "resource_id": "bfa6b55f-10b6-4ba2-8470-33bb9a5194a5"}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=datastore_create"}', + ) if "update" not in url: return MockResponse( 404, diff --git a/tests/hdx/data/test_resource_view.py b/tests/hdx/data/test_resource_view.py old mode 100755 new mode 100644 index a473c959..70b50118 --- a/tests/hdx/data/test_resource_view.py +++ b/tests/hdx/data/test_resource_view.py @@ -2,6 +2,7 @@ import copy import json +from typing import Any, ClassVar import pytest from hdx.utilities.dictandlist import merge_two_dictionaries @@ -118,7 +119,7 @@ def resource_view_mockcreate(url, datadict): class TestResourceView: - resource_view_data = { + resource_view_data: ClassVar[dict[str, Any]] = { "resource_id": "25982d1c-f45a-45e1-b14e-87d367413045", "title": "Data Explorer", "view_type": "recline_view", diff --git a/tests/hdx/data/test_showcase.py b/tests/hdx/data/test_showcase.py old mode 100755 new mode 100644 index b675bc8e..4164979c --- a/tests/hdx/data/test_showcase.py +++ b/tests/hdx/data/test_showcase.py @@ -3,6 +3,7 @@ import copy import json from pathlib import Path +from typing import Any, ClassVar import pytest from hdx.utilities.dictandlist import merge_two_dictionaries @@ -161,7 +162,7 @@ def mockallsearch(url, datadict): class TestShowcase: - showcase_data = { + showcase_data: ClassVar[dict[str, Any]] = { "title": "MyShowcase1", "notes": "My Showcase", "package_id": "6f36a41c-f126-4b18-aaaf-6c2ddfbc5d4d", diff --git a/tests/hdx/data/test_update_dataset_resources.py b/tests/hdx/data/test_update_dataset_resources.py index df4e1236..c8dcb3d7 100644 --- a/tests/hdx/data/test_update_dataset_resources.py +++ b/tests/hdx/data/test_update_dataset_resources.py @@ -1,5 +1,6 @@ from os.path import join from pathlib import Path +from typing import ClassVar import pytest from hdx.location.country import Country @@ -13,7 +14,7 @@ class TestUpdateDatasetResourcesLogic: - file_mapping = { + file_mapping: ClassVar[dict[str, str]] = { "SDG 4 Global and Thematic data": "sdg_data_zwe.csv", "SDG 4 Global and Thematic indicator list": "sdg_indicatorlist_zwe.csv", "SDG 4 Global and Thematic metadata": "sdg_metadata_zwe.csv", @@ -143,7 +144,7 @@ def test_dataset_update_resources( ) results = dataset._revise_dataset( False, - tuple(), + (), resources_to_update, resources_to_delete, filestore_resources, diff --git a/tests/hdx/data/test_user.py b/tests/hdx/data/test_user.py old mode 100755 new mode 100644 index 61a0b009..ddb2aadc --- a/tests/hdx/data/test_user.py +++ b/tests/hdx/data/test_user.py @@ -3,6 +3,7 @@ import copy import json from pathlib import Path +from typing import Any, ClassVar import pytest from hdx.utilities.dictandlist import merge_two_dictionaries @@ -106,7 +107,7 @@ def mocklist(url): class TestUser: token_valid = True - smtp_initargs = { + smtp_initargs: ClassVar[dict[str, Any]] = { "host": "localhost", "port": 123, "local_hostname": "mycomputer.fqdn.com", @@ -115,7 +116,7 @@ class TestUser: } username = "user@user.com" password = "pass" - email_config_dict = { + email_config_dict: ClassVar[dict[str, Any]] = { "connection_type": "ssl", "username": username, "password": password, @@ -132,8 +133,8 @@ class TestUser: """ sender = "me@gmail.com" - mail_options = ["a", "b"] - rcpt_options = [1, 2] + mail_options: ClassVar[list[str]] = ["a", "b"] + rcpt_options: ClassVar[list[int]] = [1, 2] email_config_dict.update(smtp_initargs) @pytest.fixture(scope="class") @@ -304,17 +305,16 @@ def post(url, data, **kwargs): 200, f'{{"success": true, "result": {json.dumps(orglist)}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=organization_list"}}', ) - elif "organization" in url: - if "show" in url: - result = json.dumps(orgdict) - if ( - datadict["id"] == "b67e6c74-c185-4f43-b561-0e114a736f19" - or datadict["id"] == "TEST1" - ): - return MockResponse( - 200, - f'{{"success": true, "result": {result}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=user_show"}}', - ) + elif "organization" in url and "show" in url: + result = json.dumps(orgdict) + if ( + datadict["id"] == "b67e6c74-c185-4f43-b561-0e114a736f19" + or datadict["id"] == "TEST1" + ): + return MockResponse( + 200, + f'{{"success": true, "result": {result}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=user_show"}}', + ) Configuration.read().remoteckan().session = MockSession() @@ -344,17 +344,16 @@ def post(url, data, **kwargs): 200, f'{{"success": true, "result": {json.dumps(orglist)}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=organization_list"}}', ) - elif "organization" in url: - if "show" in url: - result = json.dumps(orgdict) - if ( - datadict["id"] == "b67e6c74-c185-4f43-b561-0e114a736f19" - or datadict["id"] == "TEST1" - ): - return MockResponse( - 200, - f'{{"success": true, "result": {result}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=user_show"}}', - ) + elif "organization" in url and "show" in url: + result = json.dumps(orgdict) + if ( + datadict["id"] == "b67e6c74-c185-4f43-b561-0e114a736f19" + or datadict["id"] == "TEST1" + ): + return MockResponse( + 200, + f'{{"success": true, "result": {result}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=user_show"}}', + ) Configuration.read().remoteckan().session = MockSession() @@ -385,13 +384,12 @@ def post(url, data, **kwargs): datadict = json.loads(decodedata) if "user" in url: return user_mockshow(url, datadict) - elif "token" in url: - if datadict["user_id"] == "MyUser1": - result = json.dumps(user_tokenlist) - return MockResponse( - 200, - f'{{"success": true, "result": {result}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=user_tokenlist"}}', - ) + elif "token" in url and datadict["user_id"] == "MyUser1": + result = json.dumps(user_tokenlist) + return MockResponse( + 200, + f'{{"success": true, "result": {result}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=user_tokenlist"}}', + ) return MockResponse( 404, '{"success": false, "error": {"message": "Not found", "__type": "Not Found Error"}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=user_tokenlist"}', diff --git a/tests/hdx/data/test_vocabulary.py b/tests/hdx/data/test_vocabulary.py old mode 100755 new mode 100644 index 48ac0e41..5a3b1c4e --- a/tests/hdx/data/test_vocabulary.py +++ b/tests/hdx/data/test_vocabulary.py @@ -2,6 +2,7 @@ import copy import json +from typing import Any, ClassVar import pytest from hdx.utilities.dictandlist import merge_two_dictionaries @@ -1120,7 +1121,7 @@ def vocabulary_mocklist(url, datadict): class TestVocabulary: - vocabulary_data = { + vocabulary_data: ClassVar[dict[str, Any]] = { "tags": [{"name": "economy"}, {"name": "health"}], "name": "Things", } diff --git a/tests/hdx/facades/__init__.py b/tests/hdx/facades/__init__.py old mode 100755 new mode 100644 diff --git a/tests/hdx/facades/test_infer_arguments.py b/tests/hdx/facades/test_infer_arguments.py old mode 100755 new mode 100644 diff --git a/tests/hdx/facades/test_keyword_arguments.py b/tests/hdx/facades/test_keyword_arguments.py old mode 100755 new mode 100644 diff --git a/tests/hdx/facades/test_simple.py b/tests/hdx/facades/test_simple.py old mode 100755 new mode 100644 diff --git a/uv.lock b/uv.lock index 4a9c32a3..8f13db66 100644 --- a/uv.lock +++ b/uv.lock @@ -799,7 +799,7 @@ requires-dist = [ { name = "defopt", specifier = ">=7.0.0" }, { name = "email-validator" }, { name = "hdx-python-country", specifier = ">=4.2.6" }, - { name = "hdx-python-utilities", specifier = ">=4.2.5" }, + { name = "hdx-python-utilities", specifier = ">=4.2.6" }, { name = "makefun" }, { name = "mkapi", marker = "extra == 'docs'" }, { name = "requests" }, @@ -814,7 +814,7 @@ dev = [ { name = "pytest-check" }, { name = "pytest-cov" }, { name = "pytest-mock" }, - { name = "ruff", specifier = "==0.14.14" }, + { name = "ruff", specifier = "==0.17.3" }, ] [[package]] @@ -832,7 +832,7 @@ wheels = [ [[package]] name = "hdx-python-utilities" -version = "4.2.5" +version = "4.2.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "frictionless" }, @@ -850,9 +850,9 @@ dependencies = [ { name = "xlsx2csv" }, { name = "xlwt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/12/555c5ddae628be195a6fb423f9fda0d7cf9e7a359e361b931573811cc894/hdx_python_utilities-4.2.5.tar.gz", hash = "sha256:d1ab4af1bb87342dd68931718f8ac74d1c34d38891fb72c266630819aa4040a3", size = 2155419, upload-time = "2026-10-27T03:53:36.231Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/16/c83fded8fb583b996a0289324d1b56cd6af3969144f32fdbf3cb6c744373/hdx_python_utilities-4.2.6.tar.gz", hash = "sha256:a4273917d6705a02732862a273a5322c65f64ee8b314f53a4aa6d7495572d6a9", size = 2155845, upload-time = "2026-10-28T02:34:35.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/2f/a09faddb45865f7f2a0dd94fa72f4b4e5fd693105b7713968a29106cb515/hdx_python_utilities-4.2.5-py3-none-any.whl", hash = "sha256:689bf297553db7797b47b6dc1b2de533c538db54547558dd5413fcabdad03a08", size = 67547, upload-time = "2026-10-27T03:53:34.876Z" }, + { url = "https://files.pythonhosted.org/packages/dd/7c/be12c40bfc44537cc83c7f5ab5aab9ee63963574f8ac80a655f51a665608/hdx_python_utilities-4.2.6-py3-none-any.whl", hash = "sha256:fcfb66e1f61870203e8923ad32df041617e241ae9089327dbc9425ac6a1e72f1", size = 67752, upload-time = "2026-10-28T02:34:34.067Z" }, ] [[package]] @@ -1645,7 +1645,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.1.1" +version = "9.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1654,9 +1654,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/42/bb4641652e58dedeac2ec51abc1f8cae4fee4ec63899a0999016229a0f2a/pytest-9.2.0.tar.gz", hash = "sha256:97c4f3b1f3c7414aa36465e09b8220f1d03627993bd51b1282e193753c80557b", size = 1797555, upload-time = "2026-10-27T21:30:20.728Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, + { url = "https://files.pythonhosted.org/packages/51/39/1f5d8c8877a0fa327db959c19341d4be61660de1ab6b7391f9eb805bcd67/pytest-9.2.0-py3-none-any.whl", hash = "sha256:07acdcf88ec174f907dc01af70bf52d49b014bbab03faeeade31c96b18c2e588", size = 411776, upload-time = "2026-10-27T21:30:19.131Z" }, ] [[package]] @@ -2140,28 +2140,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, - { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, - { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, - { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, - { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, - { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, - { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, - { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, - { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, - { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, - { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, +version = "0.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/73/81f140c4edd9cc323de5478e6bbd3bbcfae9a82692df1a258de8cec6a6d2/ruff-0.17.3.tar.gz", hash = "sha256:a57049c8addb3662073859bb55a7ad35aa54f8a7c9ed268911d1333c5aadaaa9", size = 5339444, upload-time = "2026-10-27T00:34:05.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/8c/083365310c4e79c842f25bf5a842facbd9b0c0ec336f96dffa5074fe723d/ruff-0.17.3-py3-none-linux_armv6l.whl", hash = "sha256:ab86b84cd9d05e56d329742c0cabdcbe2d1d56c98f4a04aab9b5b66196febfb4", size = 10216522, upload-time = "2026-10-27T00:33:29.822Z" }, + { url = "https://files.pythonhosted.org/packages/72/26/4b2edf31cfebe9605d8f6ed8f1d6f376d3a53d4c9a123e62661c64c440f0/ruff-0.17.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e407b4a6852853a9a2cdce96963e94ad9775578bb44e7561a0f2829f3cc04b11", size = 10217786, upload-time = "2026-10-27T00:33:33.949Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7e/537aba4b7f54397a374d2f6a6fc93e9c8015e18553f31a4a7b7dbced48b5/ruff-0.17.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b75cc113b3aca388933d8296b667b1ac401a80317ea3074c9b0a66541199da29", size = 9440362, upload-time = "2026-10-27T00:33:35.784Z" }, + { url = "https://files.pythonhosted.org/packages/3d/66/283e39734f66c8a5ebaa93c22eb7b7515779a0b1d5a5c0ad20e05c0edb41/ruff-0.17.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab5fa7ed5ce18554ea69d850ad162cbaa0c85fc47c08d83a569f806681c9a6ad", size = 10094281, upload-time = "2026-10-27T00:33:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/21/4c/1ffc768497bd025a32c72152771e40c0b99bddc5cc6e4f50c7949a395346/ruff-0.17.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c673c170e27834d1262e3a9c1e97bdc9b9c3b180c63d89173ce69747232474c1", size = 10022543, upload-time = "2026-10-27T00:33:40.181Z" }, + { url = "https://files.pythonhosted.org/packages/73/c3/f90db547301080fcb82c92abf914c8bd57b89911d9b65a6c4cc6b52d0635/ruff-0.17.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a754c0341bc9ca151b7b335988b3263921c08859f84ca219b70cee825571b6f", size = 10683921, upload-time = "2026-10-27T00:33:42.182Z" }, + { url = "https://files.pythonhosted.org/packages/e5/98/92d4a8a26c6c96cf1a5b34dd59036d8a71e52d650aab065c58867e964e63/ruff-0.17.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4590b9d86c0b1543ebb09094254e191cd43fe8edaf330baa035aab03da251532", size = 11373408, upload-time = "2026-10-27T00:33:44.19Z" }, + { url = "https://files.pythonhosted.org/packages/43/02/1f24d2e3064c9753926deeb901afa57777b37ee46799b09ddd7aee6e5145/ruff-0.17.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbde18beccd98c29a227c63c99022e3443b99258e08326df6425dfe0443705ef", size = 10977469, upload-time = "2026-10-27T00:33:46.179Z" }, + { url = "https://files.pythonhosted.org/packages/f0/20/fecedea2209f574ab92033b344e01a7f0c2dc76c6d6d19a14c9f8141173f/ruff-0.17.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fa4b0f931cdbcb8a52044f370a3eb429fb42eb88807b0e800fcaa98dd42b24e", size = 10577491, upload-time = "2026-10-27T00:33:48.233Z" }, + { url = "https://files.pythonhosted.org/packages/33/36/b93fdf0bc6f84f0cd487aadfb0354deb40013669ea35ac3d26855de1e959/ruff-0.17.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:39405f341deec934b4289634324ad695f72df2970f9121582872eaf5b722f0f5", size = 10608748, upload-time = "2026-10-27T00:33:50.284Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b7/7d3f5f14145daead1efb0b9a8289ed1daa100f419d60774375951723ce00/ruff-0.17.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68074094f55ca3d29ab19491f640282f7a84061c4c7977aa5094b968ca457e9a", size = 10075057, upload-time = "2026-10-27T00:33:52.074Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ed/93ff7a5909ee56e2bd4b982deae9e0efb2724417874a90c9e41ad66c6dd7/ruff-0.17.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:27bb571ac78fef178fe5118d2827df95c2aae74a48fff2b15f6acd03af5ff5bb", size = 10022431, upload-time = "2026-10-27T00:33:54.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/07/a83b9eeec8c1c3c0e4eda15f06c3a91001bfd2687ea2f07b750719bf9712/ruff-0.17.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:029c37cbd3de48ca37aa12117bead2d065f15da17d66bf84e0d4274013705db2", size = 10394903, upload-time = "2026-10-27T00:33:56.158Z" }, + { url = "https://files.pythonhosted.org/packages/af/b2/05c39857c2526fcbebe46ad93a8dc97424656dfef14d3ef933abe1ffd331/ruff-0.17.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:692fdb46d0fd9c71e380947dc5fc72f2c3f881073bc1f739e4579376231affbe", size = 10799780, upload-time = "2026-10-27T00:33:58.204Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d9/47171a16739eb7a353936b31c20cf54340531e68c9af7d72fd7a784e235d/ruff-0.17.3-py3-none-win32.whl", hash = "sha256:46539cb031b01fd29ae0a02142483c46640b233ee12bb1c1b690bffa1555d5ca", size = 9908481, upload-time = "2026-10-27T00:34:00.087Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a0/f08c1631bfd81faa6a90eaf70c37c786c11bed2046302b56bb506c25e1a0/ruff-0.17.3-py3-none-win_amd64.whl", hash = "sha256:56b8ffb3f3b3cbccb8b76b71b38261b7ed982ef5d20f5b447b1b61a7f4949b24", size = 10341333, upload-time = "2026-10-27T00:34:02.099Z" }, + { url = "https://files.pythonhosted.org/packages/99/d5/4ad4ed31413c301430918e70d3d38ae6f097af0766cb9ab8c9d502b2a4d6/ruff-0.17.3-py3-none-win_arm64.whl", hash = "sha256:81fce74bcd6be0e9da7c67c7553fd0f1f12964a2052cd728ef917b937a5ca1d9", size = 9854332, upload-time = "2026-10-27T00:34:03.847Z" }, ] [[package]] @@ -2422,7 +2421,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.15.0" +version = "21.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -2431,9 +2430,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ee/00/c27937345311d2aaf6197203231a59010073d7abffae3971503ac0e65477/virtualenv-21.15.0.tar.gz", hash = "sha256:41a0cfec413c886a991bf7657d02f8996e69e192485aa127b410f29f1e35cfd3", size = 5515331, upload-time = "2026-10-27T06:02:22.096Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/24/20282bc8e69ff8dee680dc2b95e9dc81fa7e7894b14c1a8d06330e97fef7/virtualenv-21.15.1.tar.gz", hash = "sha256:e79c90b8f601701599c25905e02839924fede2c798d52325670d24fbdaab74ee", size = 5517048, upload-time = "2026-10-28T01:12:50.919Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/25/fe570da03bdfda1bd489f3286ff9ca820e15ad8a1480d0d2c12871d58c84/virtualenv-21.15.0-py3-none-any.whl", hash = "sha256:013eb9c340466186ac8923ce920ae05becfa1390fdf3565b7eae0d191a2f4a2e", size = 5558480, upload-time = "2026-10-27T06:02:19.717Z" }, + { url = "https://files.pythonhosted.org/packages/ce/23/cdab19d3a9da42ca0938081e163e25f81e610141800ead743ba1ee881f0e/virtualenv-21.15.1-py3-none-any.whl", hash = "sha256:0d14a991689c67600316880eadc0a37b7690cb632e3b4ac67e2bc05b2b2e24a4", size = 5558848, upload-time = "2026-10-28T01:12:48.605Z" }, ] [[package]] diff --git a/workingexample/my_code.py b/workingexample/my_code.py old mode 100755 new mode 100644 diff --git a/workingexample/run.py b/workingexample/run.py old mode 100755 new mode 100644