Summary
- Context: The
setup.py file is the entry point for installing the django-webpack-loader package and generating its metadata.
- Bug:
setup.py attempts to read the README.md file without specifying an encoding and without using the project's own rel() function to ensure the file is found relative to the script's directory.
- Actual vs. expected: It attempts to read
README.md using the default system encoding and expects the file to be in the current working directory, whereas it should always use UTF-8 and find the file relative to setup.py.
- Impact: The installation will crash with a
UnicodeDecodeError on systems where the default encoding is not UTF-8 (such as many Windows installations) because README.md contains emojis and special characters. It also fails with a FileNotFoundError if setup.py is executed from outside the project root directory.
Code with bug
12: with open("README.md", "r") as handler: # <-- BUG 🔴 [Missing rel() and encoding="utf-8"]
13: README = handler.read()
Evidence
1. UnicodeDecodeError in non-UTF-8 environments
The README.md file contains UTF-8 characters such as emojis (💥, ⚠️). On systems with a different default encoding (e.g., CP1252 on Windows), the open("README.md", "r") call will fail.
# Simulating CP1252 environment (typical of Windows)
python3 -c 'with open("README.md", "r", encoding="cp1252") as f: f.read()'
# Traceback (most recent call last):
# ...
# File "/root/.pyenv/versions/3.12.10/lib/python3.12/encodings/cp1252.py", line 23, in decode
# return codecs.charmap_decode(input,self.errors,decoding_table)[0]
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# UnicodeDecodeError: 'charmap' codec can't decode byte 0x8f in position 4654: character maps to <undefined>
2. FileNotFoundError when run from outside project root
Although a rel() helper function is defined to find files relative to the project directory, it is not used for the README.md file.
# Executing setup.py from a different directory
cd /tmp && python3 /home/user/django-webpack-loader/setup.py --version
# Traceback (most recent call last):
# File "/home/user/django-webpack-loader/setup.py", line 12, in <module>
# with open("README.md", "r") as handler:
# ^^^^^^^^^^^^^^^^^^^^^^
# FileNotFoundError: [Errno 2] No such file or directory: 'README.md'
Note that rel() is correctly used for __init__.py just a few lines later:
15: with open(rel("webpack_loader", "__init__.py")) as handler:
Why has this bug gone undetected?
Most developers and automated CI systems run python setup.py or pip install . from the root of the project, where the current working directory contains README.md. Additionally, many of these environments default to UTF-8 encoding (e.g., Linux/macOS), masking the encoding issue. The bug only surfaces when installing from source on platforms like Windows or when using build tools that execute setup.py from a different working directory.
Recommended fix
Use the rel() function and explicitly set the encoding to utf-8:
with open(rel("README.md"), "r", encoding="utf-8") as handler: # <-- FIX 🟢
README = handler.read()
Related bugs
The packages list uses non-standard slash notation which leads to incorrect metadata in top_level.txt, although it happens to work for installation in most cases:
packages=[
"webpack_loader",
"webpack_loader/templatetags", # Should be "webpack_loader.templatetags"
"webpack_loader/contrib", # Should be "webpack_loader.contrib"
],
Additionally, setup.cfg references a non-existent README.rst file:
[metadata]
description_file = README.rst # Should be README.md
History
This bug was introduced in commit c81d425 (@owais, 2016-11-06). While attempting to add support for README.rst for PyPI, the rel() helper function (which ensures path resolution works from any directory) was omitted for the README file, though it was preserved for reading the package's __init__.py.
Summary
setup.pyfile is the entry point for installing thedjango-webpack-loaderpackage and generating its metadata.setup.pyattempts to read theREADME.mdfile without specifying an encoding and without using the project's ownrel()function to ensure the file is found relative to the script's directory.README.mdusing the default system encoding and expects the file to be in the current working directory, whereas it should always useUTF-8and find the file relative tosetup.py.UnicodeDecodeErroron systems where the default encoding is notUTF-8(such as many Windows installations) becauseREADME.mdcontains emojis and special characters. It also fails with aFileNotFoundErrorifsetup.pyis executed from outside the project root directory.Code with bug
Evidence
1. UnicodeDecodeError in non-UTF-8 environments
The⚠️ ). On systems with a different default encoding (e.g., CP1252 on Windows), the
README.mdfile contains UTF-8 characters such as emojis (💥,open("README.md", "r")call will fail.2. FileNotFoundError when run from outside project root
Although a
rel()helper function is defined to find files relative to the project directory, it is not used for theREADME.mdfile.Note that
rel()is correctly used for__init__.pyjust a few lines later:Why has this bug gone undetected?
Most developers and automated CI systems run
python setup.pyorpip install .from the root of the project, where the current working directory containsREADME.md. Additionally, many of these environments default toUTF-8encoding (e.g., Linux/macOS), masking the encoding issue. The bug only surfaces when installing from source on platforms like Windows or when using build tools that executesetup.pyfrom a different working directory.Recommended fix
Use the
rel()function and explicitly set the encoding toutf-8:Related bugs
The
packageslist uses non-standard slash notation which leads to incorrect metadata intop_level.txt, although it happens to work for installation in most cases:Additionally,
setup.cfgreferences a non-existentREADME.rstfile:History
This bug was introduced in commit c81d425 (@owais, 2016-11-06). While attempting to add support for
README.rstfor PyPI, therel()helper function (which ensures path resolution works from any directory) was omitted for the README file, though it was preserved for reading the package's__init__.py.