diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..b38c8d84 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,29 @@ +--- +include: + - project: nci-gdc/gitlab-templates + ref: 0.6.2 + file: + - templates/global/full.yaml + - templates/python/full.yaml + - templates/common/python.yaml + +tox: + parallel: + matrix: + - BUILD_PY_VERSION: + - python3.6 + - python3.7 + script: + - tox -r -e py + + +release: + before_script: + # unshallow the git repo to resolve version with setuptools_scm. + - git fetch --unshallow || true + - | + if [ ${CI_COMMIT_TAG+x} ]; then + export TWINE_REPOSITORY_URL=https://nexus.osdc.io/repository/pypi-releases/ + fi + variables: + RELEASE_PY_VERSION: python3.6 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 330d0325..fd02d30c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,15 +1,17 @@ -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.5.0 - hooks: - - id: end-of-file-fixer - - id: no-commit-to-branch - args: [--branch, develop, --branch, master, --pattern, release/.*] -- repo: https://github.com/psf/black - rev: 19.10b0 - hooks: - - id: black -- repo: git@github.com:Yelp/detect-secrets - rev: v0.13.0 +--- +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.5.0 hooks: - - id: detect-secrets + - id: end-of-file-fixer + - id: no-commit-to-branch + args: [--branch, develop, --branch, master, --pattern, release/.*] + - repo: https://github.com/psf/black + rev: 24.1.1 + hooks: + - id: black + - repo: git@github.com:Yelp/detect-secrets + rev: v1.4.0 + hooks: + - id: detect-secrets args: ['--baseline', '.secrets.baseline'] diff --git a/.secrets.baseline b/.secrets.baseline index 6001a567..87226102 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -1,33 +1,29 @@ { - "exclude": { - "files": null, - "lines": null - }, - "generated_at": "2020-05-07T19:50:44Z", + "version": "1.4.0", "plugins_used": [ { - "name": "AWSKeyDetector" + "name": "ArtifactoryDetector" }, { - "name": "ArtifactoryDetector" + "name": "AWSKeyDetector" }, { - "base64_limit": 4.5, - "name": "Base64HighEntropyString" + "name": "Base64HighEntropyString", + "limit": 4.5 }, { "name": "BasicAuthDetector" }, { - "hex_limit": 3, - "name": "HexHighEntropyString" + "name": "HexHighEntropyString", + "limit": 3 }, { "name": "JwtTokenDetector" }, { - "keyword_exclude": null, - "name": "KeywordDetector" + "name": "KeywordDetector", + "keyword_exclude": "" }, { "name": "MailchimpDetector" @@ -45,10 +41,46 @@ "name": "StripeDetector" } ], + "filters_used": [ + { + "path": "detect_secrets.filters.allowlist.is_line_allowlisted" + }, + { + "path": "detect_secrets.filters.common.is_baseline_file", + "filename": ".secrets.baseline" + }, + { + "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", + "min_level": 2 + }, + { + "path": "detect_secrets.filters.heuristic.is_indirect_reference" + }, + { + "path": "detect_secrets.filters.heuristic.is_likely_id_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_lock_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_potential_uuid" + }, + { + "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" + }, + { + "path": "detect_secrets.filters.heuristic.is_sequential_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_swagger_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_templated_secret" + } + ], "results": {}, - "version": "0.13.0", - "word_list": { - "file": null, - "hash": null - } + "generated_at": "2024-01-30T20:56:48Z" } diff --git a/bin/gdc-client b/bin/gdc-client index 72f03c01..df83b31a 100755 --- a/bin/gdc-client +++ b/bin/gdc-client @@ -46,10 +46,14 @@ def log_version_header(log): if __name__ == "__main__": - parser = GDCClientArgumentParser(description=DESCRIPTION,) + parser = GDCClientArgumentParser( + description=DESCRIPTION, + ) parser.add_argument( - "--version", action="version", version=version.__version__, + "--version", + action="version", + version=version.__version__, ) conf_parser = argparse.ArgumentParser(add_help=False) @@ -62,7 +66,9 @@ if __name__ == "__main__": # Generate template parser for use by all sub-commands. This will # contain any shared, top-level arguments and flags that will be # needed by most, if not all, subcommands. - template = GDCClientArgumentParser(add_help=False,) + template = GDCClientArgumentParser( + add_help=False, + ) logger.parser.config(template) auth.parser.config(template) @@ -76,17 +82,22 @@ if __name__ == "__main__": subparsers.required = True download_subparser = subparsers.add_parser( - "download", parents=[template], help="download data from the GDC", + "download", + parents=[template], + help="download data from the GDC", ) download.parser.config(download_subparser, config_loader.to_dict("download")) upload_subparser = subparsers.add_parser( - "upload", parents=[template], help="upload data to the GDC", + "upload", + parents=[template], + help="upload data to the GDC", ) upload.parser.config(upload_subparser, config_loader.to_dict("upload")) settings_subparser = subparsers.add_parser( - "settings", help="display default settings", + "settings", + help="display default settings", ) settings.parser.config(settings_subparser, conf_parser_args.config) diff --git a/gdc_client/auth/auth.py b/gdc_client/auth/auth.py index 83762420..ad9259bc 100644 --- a/gdc_client/auth/auth.py +++ b/gdc_client/auth/auth.py @@ -2,8 +2,7 @@ class GDCTokenAuth(requests.auth.AuthBase): - """ GDC Token Authentication - """ + """GDC Token Authentication""" def __init__(self, token): self.token = token diff --git a/gdc_client/auth/parser.py b/gdc_client/auth/parser.py index 0d80483a..48334558 100644 --- a/gdc_client/auth/parser.py +++ b/gdc_client/auth/parser.py @@ -27,8 +27,7 @@ def read_token_file(path): - """ Safely open, read and close a token file. - """ + """Safely open, read and close a token file.""" # there's a circular dependency on setting up logging to process this arg # but also needing the logs to be set up before you can process args @@ -54,7 +53,9 @@ def read_token_file(path): ) if invalid_permissions: - permissions_msg = PERMISSIONS_MSG.format(token_file=abspath,) + permissions_msg = PERMISSIONS_MSG.format( + token_file=abspath, + ) log.warning(permissions_msg) # FIXME convert to error after investigation on windows # raise argparse.ArgumentTypeError(permissions_msg) @@ -69,9 +70,11 @@ def read_token_file(path): def config(parser): - """ Configure argparse parser for GDC auth token parsing. - """ + """Configure argparse parser for GDC auth token parsing.""" parser.add_argument( - "-t", "--token-file", type=read_token_file, help="GDC API auth token file", + "-t", + "--token-file", + type=read_token_file, + help="GDC API auth token file", ) diff --git a/gdc_client/client/client.py b/gdc_client/client/client.py index 11f9b165..590742ae 100644 --- a/gdc_client/client/client.py +++ b/gdc_client/client/client.py @@ -12,8 +12,7 @@ class GDCClient(object): - """ GDC API Requests Client - """ + """GDC API Requests Client""" def __init__(self, host=GDC_API_HOST, port=GDC_API_PORT, token=None): self.host = host @@ -35,12 +34,13 @@ def __init__(self, host=GDC_API_HOST, port=GDC_API_PORT, token=None): @contextmanager def request(self, verb, path, **kwargs): - """ Make a request to the GDC API. - """ + """Make a request to the GDC API.""" res = self.session.request( verb, "https://{host}:{port}{path}".format( - host=self.host, port=self.port, path=path, + host=self.host, + port=self.port, + path=path, ), auth=auth.GDCTokenAuth(self.token), **kwargs diff --git a/gdc_client/client/parser.py b/gdc_client/client/parser.py index 2d10a0ce..2ecc3916 100644 --- a/gdc_client/client/parser.py +++ b/gdc_client/client/parser.py @@ -5,8 +5,7 @@ def config(parser): - """ Configure an argparse parser for use with the GDC Client. - """ + """Configure an argparse parser for use with the GDC Client.""" parser.add_argument( "-H", "--host", diff --git a/gdc_client/download/client.py b/gdc_client/download/client.py index 30b7a027..4ed53e57 100644 --- a/gdc_client/download/client.py +++ b/gdc_client/download/client.py @@ -20,10 +20,10 @@ def fix_url(url): # type: (str) -> str - """ Fix a url to be used in the rest of the program + """Fix a url to be used in the rest of the program - example: - api.gdc.cancer.gov -> https://api.gdc.cancer.gov/ + example: + api.gdc.cancer.gov -> https://api.gdc.cancer.gov/ """ if not url.endswith("/"): url = "{0}/".format(url) @@ -47,7 +47,7 @@ def __init__( *args, **kwargs ): - """ GDC parcel client that overrides parallel download + """GDC parcel client that overrides parallel download Args: uri (str): download_related_files (bool): @@ -135,7 +135,7 @@ def download_annotations(self, file_id): def _untar_file(self, tarfile_name): # type: (str) -> list[str] - """ untar the file and return all the file names inside the tarfile """ + """untar the file and return all the file names inside the tarfile""" t = tarfile.open(tarfile_name) members = [m for m in t.getmembers() if m.name != "MANIFEST.txt"] @@ -149,7 +149,7 @@ def _untar_file(self, tarfile_name): def _md5_members(self, members): # type: (list[str]) -> list[str] - """ Calculate md5 hash and compare them with values given by the API """ + """Calculate md5 hash and compare them with values given by the API""" errors = [] for m in members: @@ -175,7 +175,7 @@ def _md5_members(self, members): def _post(self, path, headers=None, json=None, stream=True): # type: (str, dict[str,str], dict[str,object], bool) -> requests.models.Response - """ custom post request that will query both active and legacy api + """custom post request that will query both active and legacy api return a python requests object to be handled by the method calling self._post """ @@ -210,7 +210,7 @@ def _post(self, path, headers=None, json=None, stream=True): def _download_tarfile(self, small_files): # type: (list[str]) -> tuple[str, object] - """ Make the request to the API for the tarfile downloads """ + """Make the request to the API for the tarfile downloads""" errors = [] headers = { @@ -249,7 +249,8 @@ def _download_tarfile(self, small_files): if content_filename: tarfile_name = os.path.join( - self.base_directory, content_filename.split("=")[1], + self.base_directory, + content_filename.split("=")[1], ) else: tarfile_name = time.strftime("gdc-client-%Y%m%d-%H%M%S.tar") @@ -264,7 +265,7 @@ def _download_tarfile(self, small_files): def download_small_groups(self, smalls): # type: (list[str]) -> tuple[list[str], int] - """ Download small groups + """Download small groups Smalls are predetermined groupings of smaller file size files. They are grouped to reduce the number of open connections per download. diff --git a/gdc_client/download/parser.py b/gdc_client/download/parser.py index 50c4a42d..e66ac156 100644 --- a/gdc_client/download/parser.py +++ b/gdc_client/download/parser.py @@ -14,8 +14,7 @@ def validate_args(parser, args): - """ Validate argparse namespace. - """ + """Validate argparse namespace.""" if not args.file_ids and not args.manifest: msg = "must specify either --manifest or file_id" parser.error(msg) @@ -42,12 +41,12 @@ def get_client(args, index_client): def download(parser, args): - """ Downloads data from the GDC. + """Downloads data from the GDC. - Combine the smaller files (~KB range) into a grouped download. - The API now supports combining UUID's into one uncompressed tarfile - using the ?tarfile url parameter. Combining many smaller files into one - download decreases the number of open connections we have to make + Combine the smaller files (~KB range) into a grouped download. + The API now supports combining UUID's into one uncompressed tarfile + using the ?tarfile url parameter. Combining many smaller files into one + download decreases the number of open connections we have to make """ successful_count = 0 unsuccessful_count = 0 @@ -194,8 +193,7 @@ def retry_download(client, url, retry_amount, no_auto_retry, wait_time): def config(parser, download_defaults): - """ Configure a parser for download. - """ + """Configure a parser for download.""" func = partial(download, parser) download_defaults["func"] = func diff --git a/gdc_client/log/parser.py b/gdc_client/log/parser.py index f1088fc9..aad9ab29 100644 --- a/gdc_client/log/parser.py +++ b/gdc_client/log/parser.py @@ -8,8 +8,7 @@ def setup_logging(args): - """ Set up logging given parsed logging arguments. - """ + """Set up logging given parsed logging arguments.""" log_level = min(args.log_levels) if hasattr(args, "log_levels") else logging.INFO color_off = args.color_off if hasattr(args, "color_off") else False log_file = args.log_file if hasattr(args, "log_file") else None @@ -34,8 +33,7 @@ def setup_logging(args): def config(parser): - """ Configure an argparse parser for logging. - """ + """Configure an argparse parser for logging.""" parser.set_defaults(log_levels=[logging.INFO]) diff --git a/gdc_client/parcel/client.py b/gdc_client/parcel/client.py index 4af656ff..37a2d54b 100644 --- a/gdc_client/parcel/client.py +++ b/gdc_client/parcel/client.py @@ -143,9 +143,11 @@ def download_files(self, urls, *args, **kwargs): self.parallel_download(stream) utils.validate_file_md5sum( stream, - stream.temp_path - if os.path.isfile(stream.temp_path) - else stream.path, + ( + stream.temp_path + if os.path.isfile(stream.temp_path) + else stream.path + ), ) if os.path.isfile(stream.temp_path): utils.remove_partial_extension(stream.temp_path) diff --git a/gdc_client/parcel/download_stream.py b/gdc_client/parcel/download_stream.py index b868d0e9..5157c97c 100644 --- a/gdc_client/parcel/download_stream.py +++ b/gdc_client/parcel/download_stream.py @@ -222,7 +222,6 @@ def get_information(self): return self.name, self.size def write_segment(self, segment, q_complete, retries=5): - """Read data from the data server and write it to a file. :param str file_id: The id of the file diff --git a/gdc_client/parcel/log.py b/gdc_client/parcel/log.py index 18d52171..9671d768 100644 --- a/gdc_client/parcel/log.py +++ b/gdc_client/parcel/log.py @@ -17,8 +17,7 @@ # Logging def get_logger(name="parcel"): - """Create or return an existing logger with given name - """ + """Create or return an existing logger with given name""" if name in loggers: return loggers[name] diff --git a/gdc_client/parcel/utils.py b/gdc_client/parcel/utils.py index f423b9b2..c60ae03f 100644 --- a/gdc_client/parcel/utils.py +++ b/gdc_client/parcel/utils.py @@ -49,7 +49,10 @@ def check_transfer_size(actual, expected): def get_file_transfer_pbar( - file_id: str, maxval: int, start_val: int = 0, desc: str = "Downloading", + file_id: str, + maxval: int, + start_val: int = 0, + desc: str = "Downloading", ) -> ProgressBar: """Create and initialize a custom progressbar @@ -86,7 +89,12 @@ def get_file_transfer_pbar( def get_percentage_pbar(maxval: int): """Create and initialize a simple percentage progressbar""" pbar = ProgressBar( - widgets=[Percentage(), " ", Bar(marker="#", left="[", right="]"), " ",], + widgets=[ + Percentage(), + " ", + Bar(marker="#", left="[", right="]"), + " ", + ], max_value=maxval, ) pbar.start() diff --git a/gdc_client/query/index.py b/gdc_client/query/index.py index a090017f..2045e3e3 100644 --- a/gdc_client/query/index.py +++ b/gdc_client/query/index.py @@ -151,7 +151,7 @@ def _get_metadata(self, uuids): return self.metadata def separate_small_files(self, ids, chunk_size): - """ Separate big and small files + """Separate big and small files Separate the small files from the larger files in order to combine them into single grouped downloads. This will reduce diff --git a/gdc_client/query/versions.py b/gdc_client/query/versions.py index 34d37c35..85f2df60 100644 --- a/gdc_client/query/versions.py +++ b/gdc_client/query/versions.py @@ -2,6 +2,7 @@ Functionality related to versioning. """ + import logging import requests @@ -36,7 +37,12 @@ def get_latest_versions(url, uuids, verify=True): ( "The following request {0} for ids {1} returned with " "status code: {2} and response content: {3}" - ).format(versions_url, chunk, resp.status_code, resp.content,), + ).format( + versions_url, + chunk, + resp.status_code, + resp.content, + ), response=resp, ) diff --git a/gdc_client/upload/client.py b/gdc_client/upload/client.py index d822932a..8e39ba28 100644 --- a/gdc_client/upload/client.py +++ b/gdc_client/upload/client.py @@ -127,7 +127,7 @@ def get_sleep_time(tries): def create_resume_path(file_path): - """ in case the user enters a path, you want to create + """in case the user enters a path, you want to create a resume_filename.yml inside the same directory as the manifest.yml """ @@ -185,7 +185,10 @@ def _get_node_metadata_via_graphql( } response = requests.post( - self.graphql_url, json=query, headers=self.headers, verify=self.verify, + self.graphql_url, + json=query, + headers=self.headers, + verify=self.verify, ) return response @@ -226,7 +229,9 @@ def get_metadata(self, node_id, field): # get metadata about file_type r = self._get_node_metadata_via_graphql( - node_id, node_type=file_type, fields=fields, + node_id, + node_type=file_type, + fields=fields, ) if r.status_code != 200: @@ -345,8 +350,7 @@ def load_file(self, file_entity): self.__dict__.update(file_entity.__dict__) def upload(self): - """ Upload files to the GDC. - """ + """Upload files to the GDC.""" if os.path.isfile(self.resume_path): use_resume = input( "Found a {}. Press Y to resume last upload and n to start a new upload [Y/n]: ".format( @@ -378,7 +382,7 @@ def upload(self): self.incompleted.popleft() def abort(self): - """ Abort multipart upload""" + """Abort multipart upload""" self.get_files() for f in self.file_entities: self.load_file(f) diff --git a/gdc_client/upload/exceptions.py b/gdc_client/upload/exceptions.py index 37c24e14..ed93f653 100644 --- a/gdc_client/upload/exceptions.py +++ b/gdc_client/upload/exceptions.py @@ -2,5 +2,4 @@ class ValidationError(ClientError): - """ Base validation error. - """ + """Base validation error.""" diff --git a/gdc_client/upload/manifest.py b/gdc_client/upload/manifest.py index 295d585a..daa3e44b 100644 --- a/gdc_client/upload/manifest.py +++ b/gdc_client/upload/manifest.py @@ -6,8 +6,7 @@ def validate(manifest, schema=UPLOAD_MANIFEST_SCHEMA): - """ Validate a manifest against the current schema. - """ + """Validate a manifest against the current schema.""" try: jsonschema.validate(manifest, schema) except jsonschema.ValidationError as err: @@ -15,12 +14,12 @@ def validate(manifest, schema=UPLOAD_MANIFEST_SCHEMA): def load(m, schema=UPLOAD_MANIFEST_SCHEMA): - """ Load and validate a manifest. - """ + """Load and validate a manifest.""" manifest = yaml.load(m) validate( - manifest, schema=schema, + manifest, + schema=schema, ) return manifest diff --git a/gdc_client/upload/parser.py b/gdc_client/upload/parser.py index 69b975f4..c23d143e 100644 --- a/gdc_client/upload/parser.py +++ b/gdc_client/upload/parser.py @@ -11,8 +11,7 @@ def validate_args(parser, args): - """ Validate argparse namespace. - """ + """Validate argparse namespace.""" if args.part_size: log.warning("[--part-size] is DEPRECATED in favor of [--upload-part-size]") args.upload_part_size = args.part_size @@ -27,8 +26,7 @@ def validate_args(parser, args): def upload(parser, args): - """ Upload data to the GDC. - """ + """Upload data to the GDC.""" validate_args(parser, args) @@ -71,8 +69,7 @@ def upload(parser, args): def config(parser, upload_defaults): - """ Configure a parser for upload. - """ + """Configure a parser for upload.""" func = partial(upload, parser) upload_defaults["func"] = func diff --git a/setup.py b/setup.py index f66014af..1454119f 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,9 @@ setup( name="gdc_client", - use_scm_version={"local_scheme": "dirty-tag",}, + use_scm_version={ + "local_scheme": "dirty-tag", + }, setup_requires=["setuptools_scm<6"], packages=find_packages(), package_data={}, diff --git a/tests/conftest.py b/tests/conftest.py index 3aec0376..2c01e9d8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -64,23 +64,53 @@ def get_big_content(n: int) -> str: uuids = { "invalid": generate_metadata_dict(None, None, [], []), "small": generate_metadata_dict( - "controlled", "small content 1", ["annotation 1"], ["related 1"], + "controlled", + "small content 1", + ["annotation 1"], + ["related 1"], ), "small_ann": generate_metadata_dict( - "open", "small content 2", ["annotations.txt"], [], + "open", + "small content 2", + ["annotations.txt"], + [], + ), + "small_rel": generate_metadata_dict( + "open", + "small content 3", + [], + ["related 3"], ), - "small_rel": generate_metadata_dict("open", "small content 3", [], ["related 3"],), "small_no_friends": generate_metadata_dict( - "controlled", "small content 4", [], [], + "controlled", + "small content 4", + [], + [], ), "big": generate_metadata_dict( - "controlled", get_big_content(1), ["annotation 1"], ["related 1"], + "controlled", + get_big_content(1), + ["annotation 1"], + ["related 1"], ), "big_ann": generate_metadata_dict( - "controlled", get_big_content(2), ["annotation 2"], [], + "controlled", + get_big_content(2), + ["annotation 2"], + [], + ), + "big_rel": generate_metadata_dict( + "open", + get_big_content(3), + [], + ["related 3"], + ), + "big_no_friends": generate_metadata_dict( + "open", + get_big_content(4), + [], + [], ), - "big_rel": generate_metadata_dict("open", get_big_content(3), [], ["related 3"],), - "big_no_friends": generate_metadata_dict("open", get_big_content(4), [], [],), "annotations.txt": {"contents": "id\tsubmitter_id\t\n123\t456\n"}, } diff --git a/tests/test_import.py b/tests/test_import.py index e2c10d1b..47ea3cb0 100644 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -1,4 +1,3 @@ def test_import(): - """ Test that gdc_client imports correctly. - """ + """Test that gdc_client imports correctly.""" import gdc_client diff --git a/tests/test_query.py b/tests/test_query.py index 042ac737..9bb67cd2 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -94,7 +94,13 @@ def test_small_invalid_separate_small_files(self) -> None: @pytest.mark.parametrize( - "case", [range(1), range(499), range(500), range(1000),], + "case", + [ + range(1), + range(499), + range(500), + range(1000), + ], ) def test_chunk_list(case: Iterable[int]) -> None: assert all(len(chunk) <= 500 for chunk in _chunk_list(case)) @@ -123,7 +129,10 @@ def test_get_latest_versions( @pytest.mark.parametrize("ids", [(["foo", "bar"])]) -def test_get_latest_versions_error(versions_response_error, ids: List[str],) -> None: +def test_get_latest_versions_error( + versions_response_error, + ids: List[str], +) -> None: url = "https://example.com" versions_response_error(url + "/files/versions") diff --git a/tests/test_upload_client.py b/tests/test_upload_client.py index 30ae46bb..1c734ad7 100644 --- a/tests/test_upload_client.py +++ b/tests/test_upload_client.py @@ -35,7 +35,9 @@ def parse_graphql_query(query: str) -> Optional[QueryParts]: return None return QueryParts( - parts.group("node_type"), parts.group("node_id"), parts.group("fields"), + parts.group("node_type"), + parts.group("node_id"), + parts.group("fields"), ) @@ -272,7 +274,11 @@ def handle_list_multipart(url, _): upload_id = parsed_qs["uploadId"][0] - result = client.list_parts(Bucket="test-bucket", Key=key, UploadId=upload_id,) + result = client.list_parts( + Bucket="test-bucket", + Key=key, + UploadId=upload_id, + ) if "Parts" in result: result["Part"] = result.pop("Parts") @@ -350,7 +356,9 @@ def handle_delete_multipart(url, _): upload_id = parsed_qs["uploadId"][0] result = client.abort_multipart_upload( - Bucket="test-bucket", Key=key, UploadId=upload_id, + Bucket="test-bucket", + Key=key, + UploadId=upload_id, ) return httmock.response( diff --git a/tox.ini b/tox.ini index 93d5c800..24a722eb 100644 --- a/tox.ini +++ b/tox.ini @@ -18,3 +18,23 @@ deps= requests codacy-coverage commands=python-codacy-coverage -r coverage.xml + +[testenv:publish] +changedir = +passenv = + TWINE_* + CI_COMMIT_* +skip_install=true +deps = + setuptools_scm + setuptools_git_versioning + build + twine +install_command = + python -m pip install {opts} {packages} +commands = + python -m setuptools_git_versioning + python -m build + python -m twine check dist/* + python -m twine upload dist/* +commands_post=