Repository: crashvb/docker-registry-client-async (language: python)

Issue:
HTTP accept parameters not default for HEAD methods
The default values for the HTTP `accept` header are populated for the GET methods, but not the HEAD methods, leading to a divergence between the two method types.


Discussion / hints:

Fix patch:
diff --git a/docker_registry_client_async/dockerregistryclientasync.py b/docker_registry_client_async/dockerregistryclientasync.py
index 2074a5a..e2aeff4 100644
--- a/docker_registry_client_async/dockerregistryclientasync.py
+++ b/docker_registry_client_async/dockerregistryclientasync.py
@@ -900,12 +900,15 @@ async def head_blob(
             "result": client_response.status == HTTPStatus.OK,
         }
 
-    async def _head_manifest(self, image_name: ImageName, **kwargs) -> ClientResponse:
+    async def _head_manifest(
+        self, image_name: ImageName, *, accept: str = None, **kwargs
+    ) -> ClientResponse:
         """
         Check an image manifest for existence.
 
         Args:
             image_name: The image name.
+            accept: The "Accept" HTTP request header.
         Keyword Args:
             protocol: Protocol to use when connecting to the endpoint.
 
@@ -916,10 +919,16 @@ async def _head_manifest(self, image_name: ImageName, **kwargs) -> ClientRespons
             identifier = image_name.resolve_digest()
         else:
             identifier = image_name.resolve_tag()
+        if accept is None:
+            accept = (
+                f"{DockerMediaTypes.DISTRIBUTION_MANIFEST_V2};q=1.0,{OCIMediaTypes.IMAGE_MANIFEST_V1};q=0.5,"
+                f"{DockerMediaTypes.DISTRIBUTION_MANIFEST_V1};q=0.1"
+            )
         protocol = kwargs.pop("protocol", DockerRegistryClientAsync.DEFAULT_PROTOCOL)
 
         headers = await self._get_request_headers(
             image_name,
+            {"Accept": accept},
             scope=DockerAuthentication.SCOPE_REPOSITORY_PULL_PATTERN.format(
                 image_name.resolve_image()
             ),
@@ -929,13 +938,14 @@ async def _head_manifest(self, image_name: ImageName, **kwargs) -> ClientRespons
         return await client_session.head(headers=headers, url=url, **kwargs)
 
     async def head_manifest(
-        self, image_name: ImageName, **kwargs
+        self, image_name: ImageName, *, accept: str = None, **kwargs
     ) -> DockerRegistryClientAsyncHeadManifest:
         """
         Check an image manifest for existence.
 
         Args:
             image_name: The image name.
+            accept: The "Accept" HTTP request header.
         Keyword Args:
             protocol: Protocol to use when connecting to the endpoint.
 
@@ -945,7 +955,7 @@ async def head_manifest(
                 digest: The manifest digest returned by the server, or None.
                 result: True if the image manifest exists, False otherwise.
         """
-        client_response = await self._head_manifest(image_name, **kwargs)
+        client_response = await self._head_manifest(image_name, accept=accept, **kwargs)
         digest = None
         if "Docker-Content-Digest" in client_response.headers:
             digest = FormattedSHA256.parse(
@@ -1046,7 +1056,10 @@ async def patch_blob_upload_from_disk(
         """
         hashing_generator = HashingGenerator(file, file_is_async=file_is_async)
         client_response = await self._patch_blob_upload(
-            location, hashing_generator, raise_for_status=True, **kwargs,
+            location,
+            hashing_generator,
+            raise_for_status=True,
+            **kwargs,
         )
         return {
             "client_response": client_response,
@@ -1257,7 +1270,11 @@ async def put_blob_upload_from_disk(
         """
         hashing_generator = HashingGenerator(file, file_is_async=file_is_async)
         client_response = await self._put_blob_upload(
-            location, digest, data=hashing_generator, raise_for_status=True, **kwargs,
+            location,
+            digest,
+            data=hashing_generator,
+            raise_for_status=True,
+            **kwargs,
         )
         digest = FormattedSHA256.parse(client_response.headers["Docker-Content-Digest"])
         if check_digest:
diff --git a/makefile b/makefile
index 22bfd1a..cff5330 100644
--- a/makefile
+++ b/makefile
@@ -2,7 +2,7 @@
 
 -include makefile.config
 
-.PHONY: black build clean default deploy purge sign test test_code test_package venv verify
+.PHONY: black build clean default deploy deploy-test purge release sign test test-all test-all-verbose test-code test-package test-verbose venv .venv verify
 
 tmpdir:=$(shell mktemp --directory)
 
@@ -13,27 +13,52 @@ black:
 
 build:
 	python setup.py bdist_wheel sdist
+	tar --file dist/*.tar.gz --list --verbose
+	unzip -l dist/*.whl
+
+deploy: clean build sign
+
+	python -m twine upload dist/*
+
+deploy-test: clean build sign
+	python -m twine upload --repository testpypi dist/*
+
+release:
+	@[ "X$(shell git status --porcelain 2>&1)" = "X" ] || (echo "GIT work tree is dirty!" && /bin/false)
+	$(eval package_name := $(shell sed --expression='s/_/-/g' --expression='s/^.*name="\(.*\)",/\1/p' --quiet setup.py))
+	@echo "Detected package: $(package_name)"
+	$(eval version_current := $(shell sed --expression='s/^__version__ = "\(.*\)"/\1/p' --quiet */__init__.py))
+	@echo "Detected current version as: $(version_current)"
+	$(eval version_release := $(shell echo "$(version_current)" | sed --expression='s/.dev0//g'))
+	@echo "Setting release version to: $(version_release)"
+	@sed --expression='s/"$(version_current)"$$/"$(version_release)"/' --in-place */__init__.py
+	@git commit --all --message "prepare release $(package_name)-$(version_release)"
+	@git tag --message="release $(package_name)-$(version_release)" --sign "$(package_name)-$(version_release)"
+
+	$(eval version_next := $(shell echo "$(version_release)" | awk --field-separator=. --assign OFS=. 'NF==1{print ++$$NF}; NF>1{$$NF=sprintf("%0*d", length($$NF), ($$NF+1)); print}'))
+	$(eval version_next := $(version_next).dev0)
+	@echo "Setting next version to: $(version_next)"
+	@sed --expression='s/"$(version_release)"$$/"$(version_next)"/' --in-place */__init__.py
+	@git commit --all --message "prepare for next development iteration"
+
 
 sign:
 	find dist -type f \( -iname "*.tar.gz" -o -iname "*.whl" \) -exec gpg --armor --detach-sig --sign {} \;
 
-verify:
-	find dist -type f -iname "*.asc" -exec gpg --verify {} \;
-
 test:
 	python -m pytest --log-cli-level info $(args)
 
-test_all:
+test-all:
 	python -m pytest --log-cli-level info --allow-online-deletion $(args)
 
-test_all_verbose:
-	python -m pytest -r sx --log-cli-level debug --allow-online-deletion $(args)
+test-all-verbose:
+	python -m pytest --log-cli-level debug --allow-online-deletion $(args)
 
-test_code:
+test-code:
 	# Note: https://github.com/PyCQA/pylint/issues/289
 	python -m pylint --disable C0330,R0801 --max-line-length=120 docker_registry_client_async tests
 
-test_package: build
+test-package: build
 	python -m venv $(tmpdir)
 
 	cd /tmp
@@ -41,11 +66,8 @@ test_package: build
 	$(tmpdir)/bin/python -m pytest
 	rm --force --recursive $(tmpdir)
 
-deploy: clean build sign
-	python -m twine upload dist/*
-
-deploy_test: clean build sign
-	python -m twine upload --repository testpypi dist/*
+test-verbose:
+	python -m pytest -r sx --log-cli-level debug $(args)
 
 .venv:
 	python -m venv .venv
@@ -54,6 +76,9 @@ deploy_test: clean build sign
 
 venv: .venv
 
+verify:
+	find dist -type f -iname "*.asc" -exec gpg --verify {} \;
+
 clean:
 	rm --force --recursive .eggs build dist *.egg-info
 	find . -type f -name "*.pyc" -delete
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index b7e4789..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[aliases]
-test=pytest
diff --git a/setup.py b/setup.py
index beffb30..c4dc738 100755
--- a/setup.py
+++ b/setup.py
@@ -54,7 +54,6 @@ def find_version(*segments):
     long_description_content_type="text/markdown",
     name="docker_registry_client_async",
     packages=find_packages(),
-    setup_requires=["pytest-runner"],
     tests_require=["pytest", "pytest-asyncio", "pytest-docker-registry-fixtures"],
     test_suite="tests",
     url="https://pypi.org/project/docker-registry-client-async/",


Test patch:
diff --git a/tests/test_hashinggenerator.py b/tests/test_hashinggenerator.py
index b401906..c556339 100644
--- a/tests/test_hashinggenerator.py
+++ b/tests/test_hashinggenerator.py
@@ -65,7 +65,9 @@ def hashing_generator_sync(
         }
 
 
-async def test_get_digest_async(hashing_generator_async: TypingHashingGenerator,):
+async def test_get_digest_async(
+    hashing_generator_async: TypingHashingGenerator,
+):
     """Test digest value calculation (async)."""
     async for _ in hashing_generator_async["hashing_generator"]:
         pass
@@ -73,7 +75,9 @@ async def test_get_digest_async(hashing_generator_async: TypingHashingGenerator,
     assert hashing_generator_async["hashing_generator"].get_digest() == digest
 
 
-async def test_get_digest_sync(hashing_generator_sync: TypingHashingGenerator,):
+async def test_get_digest_sync(
+    hashing_generator_sync: TypingHashingGenerator,
+):
     """Test digest value calculation (sync)."""
     async for _ in hashing_generator_sync["hashing_generator"]:
         pass