
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: April 5, 2025
In this tutorial, we’ll explore why Docker Compose can suddenly fail when building Python-based images and throwing an AttributeError: cython_sources message. This error commonly appears after a Cython release or a PyYAML update that unexpectedly breaks our dependency chain during the Docker image build phase.
Although it looks like a Docker Compose problem, the core issue is a dependency conflict in Python packages, often involving PyYAML and Cython.
We’ll explore how this error surfaces, why yesterday’s working image might fail today, and the concrete steps to fix it. By the end, we’ll know exactly what changed behind the scenes and how to ensure our container images build reliably again.
First, we need to understand what the AttributeError: cython_sources error means.
Cython is a tool that compiles Python-like syntax into highly-optimized C extensions. When we install specific libraries with pip install, the build system invokes Cython to generate C source files. Those C files are then compiled into platform-specific binaries for better performance.
Additionally, cython_sources refers to the collection of files (usually .pyx or .pxd) that Cython processes before producing the final compiled object.
With Cython 3.0, the maintainers restructured some internal APIs. Packages that relied on older Cython features—like PyYAML—started failing because their setup.py scripts (or equivalent) could no longer find the attributes or functions they expected.
In PyYAML’s case, the build step looked for cython_sources in a way that is no longer compatible with the newer version of Cython. This mismatch triggers the AttributeError: cython_sources error.
Whenever pip attempts to build PyYAML from source in this situation, the build process breaks. This can also cascade into other libraries (for example, drf-spectacular) that depend on PyYAML. If PyYAML fails, the entire installation chain collapses.
Docker Compose orchestrates multi-container applications by running the build steps defined in each service’s Dockerfile. When we see the AttributeError: cython_sources error in the Docker Compose logs, it means the Python environment inside the container is trying to install (or upgrade) a package that calls out to Cython 3.0.
Even though Docker Compose triggers the build, it’s not normally the cause.
Let’s see a minimal example showing how two pinned dependencies can break under Docker, beginning with the requirements.txt file:
pyyaml==5.4.1
cython==3.0.0
This pairs an older PyYAML (5.4.1) with a newer Cython release (3.0.0). Now, let’s build a Docker image using the following Dockerfile:
FROM python:3.9-alpine
WORKDIR /app
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "--version"]
If we run:
$ docker build -t pyyaml-cython-bug .
This build will create an error with logs ending with:
...
AttributeError: cython_sources
This happens because PyYAML 5.4.1 expects an older Cython API. Pip tries to compile PyYAML with Cython 3.0.0 and fails. Again, Docker Compose isn’t at fault—it simply reveals that we have a dependency mismatch.
In a real-world scenario, unpinned or partially pinned dependencies can similarly drift to incompatible releases over time. A dependency we installed successfully last week may fail if a new version of PyYAML or Cython is published in the interim.
The Docker Compose logs expose this breakage, but the problem still lies within Python’s packaging ecosystem.
Once we understand Cython’s role in Python builds, we can see how AttributeError: cython_sources often appears after a dependency tries to compile C-based modules against the latest Cython.
Below, let’s examine the PyYAML conflict in detail and explain how unpinned “floating” dependencies contribute to sudden failures in Docker builds.
PyYAML is one of the most common libraries impacted by this error because it relies on C-based parsing for speed. When installed via pip, PyYAML runs a small build script that calls out to Cython. Old PyYAML versions (below 6.0.1) are incompatible with Cython ≥ 3.0, causing an immediate build failure:
28.12 error: subprocess-exited-with-error
28.12
28.12 × Getting requirements to build wheel did not run successfully.
...
28.12 raise AttributeError(attr)
28.12 AttributeError: cython_sources
28.12 [end of output]
In many cases, we might not even know PyYAML is being installed because it’s a transitive dependency of libraries like drf-spectacular or awscli. For example, we might have:
$ cat requirements.txt
drf_spectacular>=0.26.2
awscli>=1.29.0
...
Neither line explicitly says “PyYAML,” but each library can pull in PyYAML with an incompatible build script. We can illustrate a typical failing scenario with a tiny snippet from a Dockerfile:
FROM python:3.9-alpine
WORKDIR /app
COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
When we run docker-compose build, the log might show something like:
...
Collecting drf-spectacular>=0.26.2
Downloading drf_spectacular-0.26.3-py3-none-any.whl (76 kB)
Collecting PyYAML>=5.1
Downloading PyYAML-5.4.1.tar.gz (115 kB)
Installing build dependencies: finished with status 'error'
...
AttributeError: cython_sources
Because drf-spectacular brings along PyYAML, the build pipeline fails right at the step where PyYAML tries to use a Cython feature that no longer exists or behaves differently in Cython 3.0.
Another culprit behind these sudden failures is how Python requirements are defined. We can pin exact versions to lock down the build environments, or we can allow “floating” versions to pick up updates.
However, floating versions increase the risk of surprise breakages:
...
pyyaml>=5.4.0
Cython>=0.29.0
drf-spectacular>=0.26.2
...
The above approach tells pip to grab the newest version that meets those minimum constraints. If PyYAML or Cython suddenly releases a version that breaks compatibility (like the jump to Cython 3.0), our once-stable build fails overnight.
In contrast, pinned dependencies protect our environment from unexpected updates:
...
pyyaml==6.0.1
Cython==0.29.36
drf-spectacular==0.26.2
...
With these exact pins, our Docker image build will always install the same versions. This ensures our environment is repeatable. Conversely, older pins may block important fixes or security patches.
For example, older PyYAML releases (like 5.3.1) contain CVE-2020-14343, a flaw that allows remote code execution when loading untrusted YAML data, so relying on that version to keep builds stable means accepting a known vulnerability.
We’ve seen how AttributeError: cython_sources usually emerges from PyYAML’s conflict with Cython ≥ 3.0. Let’s now go through the different strategies to resolve this issue.
The simplest way is to upgrade PyYAML to a version that can handle new Cython releases. PyYAML 6.0.1 and higher include metadata, ensuring they don’t try to build against incompatible Cython APIs.
For example, we can depend on a known good version in our requirements.txt:
...
pyyaml==6.0.1
drf-spectacular==0.26.2
awscli==1.29.4
Then, rebuild our container:
$ docker-compose build
If any library we depend on forcibly downgrades PyYAML (for instance, a strict version pin in a transitive dependency), we might need to override that behavior. We could do so by placing pyyaml==6.0.1 at the top of our requirements or using a separate constraints file.
Another option is to keep Cython at an earlier version so PyYAML’s older build process doesn’t fail. This is more of a stopgap measure, as eventually, we’ll want to upgrade these versions. Still, it can be helpful if we’re in a pinch to fix a production build:
FROM python:3.9-alpine
WORKDIR /app
RUN pip install --no-cache-dir "cython<3.0.0"
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
CMD ["python", "main.py"]
...
By explicitly stating cython<3, we keep the environment stable. However, we’ll miss out on features in newer versions. Additionally, any library that requires Cython ≥ 3.0 will fail if we lock it to an older version.
For advanced scenarios, we can instruct pip not to create isolated virtual environments when installing packages. This allows direct control over the environment and can bypass certain setup logic in PyYAML or other libraries that might assume a fresh environment:
$ pip install --no-build-isolation pyyaml==5.4.1
This essentially merges the build environment with our currently installed packages, which might help if we’ve already pinned Cython to < 3 or we’re shipping a special build of PyYAML. However, it can introduce subtle conflicts:
Only use –no-build-isolation if we thoroughly understand our environment’s dependencies. It’s a powerful escape hatch but not a universal fix.
Sometimes, PyYAML is pulled by libraries like awscli or drf-spectacular. If we see errors that mention:
...
Collecting awscli...
Collecting PyYAML>=5.1
...
AttributeError: cython_sources
We can usually fix this by upgrading these other libraries to a release that explicitly pins or updates PyYAML:
...
awscli>=1.29.4
drf-spectacular>=0.29.0
...
We can always check their GitHub pages or changelogs for issues referencing Cython or PyYAML. For instance, AWS CLI issue #8036 deals with a PyYAML/Cython conflict. The drf-spectacular maintainers also merged changes to accommodate newer PyYAML releases.
Since the community has addressed this issue, using new enough dependency versions ensures we’ll avoid the older PyYAML entirely.
We’ve seen how Docker Compose can fail with an unexpected AttributeError: cython_sources message when older PyYAML versions collide with Cython over version 3.0. Although it appears to be a Docker Compose issue, it’s a Python packaging conflict.
We can restore stable builds by upgrading PyYAML, pinning Cython, or carefully managing dependencies. Additionally, it’s crucial to keep versions current for performance and security.
These strategies ensure our Docker-based Python workflows remain stable, reliable, and predictable over time.