0

I got a strange error in my pytest after some Dependabot updates.

In my case it's triggered by an non-existent MinIO bucket exception.

None of the updates made by Dependabot were in MinIO or Python (but "pytest-asyncio" was updated, to 1.2).

Some parts of the error log:

>       raise response_error
E       minio.error.S3Error: S3 operation failed; code: NoSuchBucket, message: Volume not found, resource: None, request_id: None, host_id: None

/Users/ar/Library/Caches/pypoetry/virtualenvs/classifier-api-cL9y8buz-py3.13/lib/python3.13/site-packages/minio/api.py:427: S3Error

During handling of the above exception, another exception occurred:

self = <contextlib._GeneratorContextManager object at 0x1178816a0>, typ = <class 'minio.error.S3Error'>
value = S3Error(response=<urllib3.response.HTTPResponse object at 0x116243820>, code='NoSuchBucket', message='Volume not found', resource=None, request_id=None, host_id=None, bucket_name=None, object_name=None)
traceback = <traceback object at 0x11600b480>

  def __exit__(self, typ, value, traceback):

I think the relevant part of the error is this last bit:

            except BaseException as exc:
                # only re-raise if it's *not* the exception that was
                # passed to throw(), because __exit__() must not raise
                # an exception unless __exit__() itself failed.  But throw()
                # has to raise the exception to signal propagation, so this
                # fixes the impedance mismatch between the throw() protocol
                # and the __exit__() protocol.
                if exc is not value:
                    raise
>               exc.__traceback__ = traceback
                ^^^^^^^^^^^^^^^^^
E               dataclasses.FrozenInstanceError: cannot assign to field '__traceback__'

I assume the exc (should be the one from MinIO) here is now frozen and can't be modified any more. But where was it frozen? By whom? There was no change in the MinIO libs.

The quick fix was to fix the missing bucket in MinIO, but that's not the cause of that exception error.

EDIT:

Full error from a pytest run:

__________________________________________ ERROR at teardown of TestPostSubmitEndpoint.test_par_document_is_created_correctly_post __________________________________________

self = <contextlib._GeneratorContextManager object at 0x1178816a0>, typ = <class 'minio.error.S3Error'>
value = S3Error(response=<urllib3.response.HTTPResponse object at 0x116243820>, code='NoSuchBucket', message='Volume not found', resource=None, request_id=None, host_id=None, bucket_name=None, object_name=None)
traceback = <traceback object at 0x11600b480>

    def __exit__(self, typ, value, traceback):
        if typ is None:
            try:
                next(self.gen)
            except StopIteration:
                return False
            else:
                try:
                    raise RuntimeError("generator didn't stop")
                finally:
                    self.gen.close()
        else:
            if value is None:
                # Need to force instantiation so we can reliably
                # tell if we get the same exception back
                value = typ()
            try:
>               self.gen.throw(value)

/opt/homebrew/Cellar/[email protected]/3.13.9_1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py:162:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/Users/ar/Library/Caches/pypoetry/virtualenvs/classifier-api-cL9y8buz-py3.13/lib/python3.13/site-packages/pytest_asyncio/plugin.py:335: in finalizer
    runner.run(async_finalizer(), context=context)
/opt/homebrew/Cellar/[email protected]/3.13.9_1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py:118: in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/homebrew/Cellar/[email protected]/3.13.9_1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/base_events.py:725: in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
/Users/ar/Library/Caches/pypoetry/virtualenvs/classifier-api-cL9y8buz-py3.13/lib/python3.13/site-packages/pytest_asyncio/plugin.py:327: in async_finalizer
    await gen_obj.__anext__()
tests/routers/conftest.py:121: in minio_client
    for obj in minio_client.list_objects(settings.minio_bucket, recursive=True):
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/Users/ar/Library/Caches/pypoetry/virtualenvs/classifier-api-cL9y8buz-py3.13/lib/python3.13/site-packages/minio/api.py:3323: in _list_objects
    response = self._execute(
/Users/ar/Library/Caches/pypoetry/virtualenvs/classifier-api-cL9y8buz-py3.13/lib/python3.13/site-packages/minio/api.py:441: in _execute
    region = self._get_region(bucket_name)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/Users/ar/Library/Caches/pypoetry/virtualenvs/classifier-api-cL9y8buz-py3.13/lib/python3.13/site-packages/minio/api.py:498: in _get_region
    response = self._url_open(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <minio.api.Minio object at 0x116248440>, method = 'GET', region = 'us-east-1', bucket_name = 'nuke', object_name = None, body = None
headers = {'Authorization': 'AWS4-HMAC-SHA256 Credential=minio/20251105/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-cont...m64) minio-py/7.2.18', 'x-amz-content-sha256': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', ...}
query_params = {'location': ''}, preload_content = True, no_body_trace = False

    def _url_open(
            self,
            method: str,
            region: str,
            bucket_name: Optional[str] = None,
            object_name: Optional[str] = None,
            body: Optional[bytes] = None,
            headers: Optional[DictType] = None,
            query_params: Optional[DictType] = None,
            preload_content: bool = True,
            no_body_trace: bool = False,
    ) -> BaseHTTPResponse:
        """Execute HTTP request."""
        creds = self._provider.retrieve() if self._provider else None
        url = self._base_url.build(
            method=method,
            region=region,
            bucket_name=bucket_name,
            object_name=object_name,
            query_params=query_params,
        )
        headers, date = self._build_headers(url.netloc, headers, body, creds)
        if creds:
            headers = sign_v4_s3(
                method=method,
                url=url,
                region=region,
                headers=headers,
                credentials=creds,
                content_sha256=cast(str, headers.get("x-amz-content-sha256")),
                date=date,
            )

        if self._trace_stream:
            self._trace_stream.write("---------START-HTTP---------\n")
            query = ("?" + url.query) if url.query else ""
            self._trace_stream.write(f"{method} {url.path}{query} HTTP/1.1\n")
            self._trace_stream.write(
                headers_to_strings(headers, titled_key=True),
            )
            self._trace_stream.write("\n")
            if not no_body_trace and body is not None:
                self._trace_stream.write("\n")
                self._trace_stream.write(
                    body.decode() if isinstance(body, bytes) else str(body),
                )
                self._trace_stream.write("\n")
            self._trace_stream.write("\n")

        http_headers = HTTPHeaderDict()
        for key, value in (headers or {}).items():
            if isinstance(value, (list, tuple)):
                for val in value:
                    http_headers.add(key, val)
            else:
                http_headers.add(key, value)

        response = self._http.urlopen(
            method,
            urlunsplit(url),
            body=body,
            headers=http_headers,
            preload_content=preload_content,
        )

        if self._trace_stream:
            self._trace_stream.write(f"HTTP/1.1 {response.status}\n")
            self._trace_stream.write(
                headers_to_strings(response.headers),
            )
            self._trace_stream.write("\n")

        if response.status in [200, 204, 206]:
            if self._trace_stream:
                if preload_content:
                    self._trace_stream.write("\n")
                    self._trace_stream.write(response.data.decode())
                    self._trace_stream.write("\n")
                self._trace_stream.write("----------END-HTTP----------\n")
            return response

        response.read(cache_content=True)
        if not preload_content:
            response.release_conn()

        if self._trace_stream and method != "HEAD" and response.data:
            self._trace_stream.write(response.data.decode())
            self._trace_stream.write("\n")

        if (
                method != "HEAD" and
                "application/xml" not in response.headers.get(
                    "content-type", "",
                ).split(";")
        ):
            if self._trace_stream:
                self._trace_stream.write("----------END-HTTP----------\n")
            if response.status == 304 and not response.data:
                raise ServerError(
                    f"server failed with HTTP status code {response.status}",
                    response.status,
                )
            raise InvalidResponseError(
                response.status,
                cast(str, response.headers.get("content-type")),
                response.data.decode() if response.data else None,
            )

        if not response.data and method != "HEAD":
            if self._trace_stream:
                self._trace_stream.write("----------END-HTTP----------\n")
            raise InvalidResponseError(
                response.status,
                response.headers.get("content-type"),
                None,
            )

        response_error = S3Error.fromxml(response) if response.data else None

        if self._trace_stream:
            self._trace_stream.write("----------END-HTTP----------\n")

        error_map = {
            301: lambda: self._handle_redirect_response(
                method, response, bucket_name, True,
            ),
            307: lambda: self._handle_redirect_response(
                method, response, bucket_name, True,
            ),
            400: lambda: self._handle_redirect_response(
                method, response, bucket_name, True,
            ),
            403: lambda: ("AccessDenied", "Access denied"),
            404: lambda: (
                ("NoSuchKey", "Object does not exist")
                if object_name
                else ("NoSuchBucket", "Bucket does not exist")
                if bucket_name
                else ("ResourceNotFound", "Request resource not found")
            ),
            405: lambda: (
                "MethodNotAllowed",
                "The specified method is not allowed against this resource",
            ),
            409: lambda: (
                ("NoSuchBucket", "Bucket does not exist")
                if bucket_name
                else ("ResourceConflict", "Request resource conflicts"),
            ),
            501: lambda: (
                "MethodNotAllowed",
                "The specified method is not allowed against this resource",
            ),
        }

        if not response_error:
            func = error_map.get(response.status)
            code, message = func() if func else (None, None)
            if not code:
                raise ServerError(
                    f"server failed with HTTP status code {response.status}",
                    response.status,
                )
            response_error = S3Error(
                response=response,
                code=cast(str, code),
                message=cast(Union[str, None], message),
                resource=url.path,
                request_id=response.headers.get("x-amz-request-id"),
                host_id=response.headers.get("x-amz-id-2"),
                bucket_name=bucket_name,
                object_name=object_name,
            )

        if response_error.code in ["NoSuchBucket", "RetryHead"]:
            if bucket_name is not None:
                self._region_map.pop(bucket_name, None)

>       raise response_error
E       minio.error.S3Error: S3 operation failed; code: NoSuchBucket, message: Volume not found, resource: None, request_id: None, host_id: None

/Users/ar/Library/Caches/pypoetry/virtualenvs/classifier-api-cL9y8buz-py3.13/lib/python3.13/site-packages/minio/api.py:427: S3Error

During handling of the above exception, another exception occurred:

self = <contextlib._GeneratorContextManager object at 0x1178816a0>, typ = <class 'minio.error.S3Error'>
value = S3Error(response=<urllib3.response.HTTPResponse object at 0x116243820>, code='NoSuchBucket', message='Volume not found', resource=None, request_id=None, host_id=None, bucket_name=None, object_name=None)
traceback = <traceback object at 0x11600b480>

    def __exit__(self, typ, value, traceback):
        if typ is None:
            try:
                next(self.gen)
            except StopIteration:
                return False
            else:
                try:
                    raise RuntimeError("generator didn't stop")
                finally:
                    self.gen.close()
        else:
            if value is None:
                # Need to force instantiation so we can reliably
                # tell if we get the same exception back
                value = typ()
            try:
                self.gen.throw(value)
            except StopIteration as exc:
                # Suppress StopIteration *unless* it's the same exception that
                # was passed to throw().  This prevents a StopIteration
                # raised inside the "with" statement from being suppressed.
                return exc is not value
            except RuntimeError as exc:
                # Don't re-raise the passed in exception. (issue27122)
                if exc is value:
                    exc.__traceback__ = traceback
                    return False
                # Avoid suppressing if a StopIteration exception
                # was passed to throw() and later wrapped into a RuntimeError
                # (see PEP 479 for sync generators; async generators also
                # have this behavior). But do this only if the exception wrapped
                # by the RuntimeError is actually Stop(Async)Iteration (see
                # issue29692).
                if (
                    isinstance(value, StopIteration)
                    and exc.__cause__ is value
                ):
                    value.__traceback__ = traceback
                    return False
                raise
            except BaseException as exc:
                # only re-raise if it's *not* the exception that was
                # passed to throw(), because __exit__() must not raise
                # an exception unless __exit__() itself failed.  But throw()
                # has to raise the exception to signal propagation, so this
                # fixes the impedance mismatch between the throw() protocol
                # and the __exit__() protocol.
                if exc is not value:
                    raise
>               exc.__traceback__ = traceback
                ^^^^^^^^^^^^^^^^^
E               dataclasses.FrozenInstanceError: cannot assign to field '__traceback__'

/opt/homebrew/Cellar/[email protected]/3.13.9_1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py:195: FrozenInstanceError
1
  • "I think the relevant part of the error ..." Thank you for trying to boil down the details and directing our attention to the relevant part. But the details matter, and you're still left with questions like "where?" and "by whom?". Show us the details. What was on the call stack at the time that the .__traceback__ assignment failed? You have a stacktrace, but you've not yet shared it with us. Commented Nov 5 at 16:06

1 Answer 1

0

In Python 3.13, the contextlib.__exit__ method tries to assign a traceback to the exception:

pythonexc.__traceback__ = traceback.

But the minio.error.S3Error class is defined as a frozen dataclass, which makes all its attributes immutable, including the special __traceback__ attribute.

Update minio package and check current version (using bash):

pip install --upgrade minio
pip show minio
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.