From 234be2ee469f6081c94d34a603637e67ce538479 Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Tue, 24 Feb 2026 23:43:34 -0400 Subject: [PATCH 01/22] Add pure Python wrapper around C++ module --- aggdraw.cxx | 4 +- aggdraw/__init__.py | 6 + aggdraw/core.py | 473 ++++++++++++++++++++++++++++++++++++++++++++ aggdraw/dib.py | 27 +++ setup.py | 5 +- 5 files changed, 511 insertions(+), 4 deletions(-) create mode 100644 aggdraw/__init__.py create mode 100644 aggdraw/core.py create mode 100644 aggdraw/dib.py diff --git a/aggdraw.cxx b/aggdraw.cxx index 743787a..ac1a20c 100644 --- a/aggdraw.cxx +++ b/aggdraw.cxx @@ -2634,7 +2634,7 @@ const char *mod_doc = "Python interface to the Anti-Grain Graphics Drawing libra #ifdef IS_PY3K static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, - "aggdraw", + "aggdraw_cpp", mod_doc, -1, aggdraw_functions, @@ -2704,7 +2704,7 @@ aggdraw_init(void) #ifdef IS_PY3K PyMODINIT_FUNC -PyInit_aggdraw(void) +PyInit_aggdraw_cpp(void) { return aggdraw_init(); } diff --git a/aggdraw/__init__.py b/aggdraw/__init__.py new file mode 100644 index 0000000..c934977 --- /dev/null +++ b/aggdraw/__init__.py @@ -0,0 +1,6 @@ +import aggdraw.aggdraw_cpp +from .core import Draw, Pen, Brush, Path, Symbol +from .dib import Dib + +VERSION = aggdraw.aggdraw_cpp.VERSION +__version__ = VERSION diff --git a/aggdraw/core.py b/aggdraw/core.py new file mode 100644 index 0000000..903b140 --- /dev/null +++ b/aggdraw/core.py @@ -0,0 +1,473 @@ +import aggdraw.aggdraw_cpp as _aggdraw + + +class Brush(object): + """Creates a brush object. + + The brush color can be an RGB tuple (e.g. `(255, 255, 255)`), a CSS-stype color + name, or a color integer (0xAARRGGBB). + + Args: + color: The brush color. + opacity (int, optional): The opacity of the brush (from 0 to 255). Defaults to + a solid brush. + + """ + def __init__(self, color, opacity=255): + self._brush = _aggdraw.Brush(color, opacity) + + +class Pen(object): + """Creates a pen object. + + The pen color can be a color tuple (e.g. `(255, 255, 255)`), a CSS-stype color + name, or a color integer (0xAARRGGBB). + + Args: + color: The pen color. + width (int, optional): The width of the pen. + opacity (int, optional): The opacity of the pen (from 0 to 255). Defaults to + a solid pen. + + """ + def __init__(self, color, width=1, opacity=255): + self._pen = _aggdraw.Pen(color, width, opacity) + + +class Font(object): + """Creates a font object. + + This creates a font object for use with :meth:`aggdraw.Draw.text` and + :meth:`aggdraw.Draw.textsize` from a TrueType font file. + + The font color can be a color tuple (e.g. `(255, 255, 255)`), a CSS-stype color + name, or a color integer (0xAARRGGBB). + + Args: + color: The font color. + file: Path to a valid TrueTyle font file. + size (optional): The font size (in pixels). Defaults to 12. + opacity (int, optional): The opacity of the font (from 0 to 255). Defaults + to solid. + + """ + def __init__(self, color, file, size=12, opacity=255): + # NOTE: Only available if compiled with FreeType support + self._font = _aggdraw.Font(color, file, size, opacity) + + +class Symbol(object): + """Symbol factory. + + This creates a symbol object from an SVG-style path descriptor for use with + :meth:`aggdraw.Draw.symbol`. + + The following operators are supported: + * M (move) + * L (line) + * H (horizontal line) + * V (vertical line) + * C (cubic bezier) + * S (smooth cubic bezier) + * Q (quadratic bezier) + * T (smooth quadratic bezier) + * Z (close path) + + Use lower-case operators for relative coordinates, upper-case for absolute + coordinates. + + Args: + path (str): An SVG-style path descriptor. + + """ + def __init__(self, path, scale=1.0): + # NOTE: 'scale' param is undocumented + self._path = _aggdraw.Symbol(path, scale) + + +class Path(object): + """Path factory. + + This creates a path object for use with :meth:`aggdraw.Draw.path`. + + """ + def __init__(self, path=None): + # NOTE: 'path' param is undocumented but defines a initial set + # of points to connect with lines + if path: + self._path = _aggdraw.Path(path) + else: + self._path = _aggdraw.Path() + + def close(self): + """Closes the current path.""" + self._path.close() + + def coords(self): + """Returns the coordinates for the path. + + Curves are flattened before being returned. + + Returns: + list: A sequence in (x, y, x, y, ...) format. + + """ + return self._path.coords() + + def curveto(self, x1, y1, x2, y2, x, y): + """Adds a bezier curve segment to the path.""" + self._path.curveto(x1, y1, x2, y2, x, y) + + def lineto(self, x, y): + """Adds a line segment to the path.""" + self._path.lineto(x, y) + + def moveto(self, x, y): + """Moves the path pointer to the given location.""" + self._path.moveto(x, y) + + def rcurveto(self, x1, y1, x2, y2, x, y): + """Adds a bezier curve segment to the path using relative coordinates. + + Same as :meth:`~curveto`, but the coordinates are relative to the current + position. + + """ + self._path.rcurveto(x1, y1, x2, y2, x, y) + + def rlineto(self, x, y): + """Adds a line segment to the path using relative coordinates. + + Same as :meth:`~lineto`, but the coordinates are relative to the current + position. + + """ + self._path.rlineto(x, y) + + def rmoveto(self, x, y): + """Moves the path pointer relative to the current position.""" + self._path.rmoveto(x, y) + + +class Draw(object): + """Creates a drawing interface object. + + The constructor can either take a PIL Image object, or mode and size specifiers. + + Examples:: + d = aggdraw.Draw(im) + d = aggdraw.Draw("RGB", (800, 600), "white") + + Args: + image_or_mode: A PIL image or a mode string. The following modes are + supported: “L”, “RGB”, “RGBA”, “BGR”, “BGRA”. + size (tuple, optional): The size of the image (width, height). + color (optional): An optional background color. If omitted, defaults + to white with full alpha. + + """ + def __init__(self, image_or_mode, size=None, color="white"): + if size: + self._draw = _aggdraw.Draw(image_or_mode, size, color) + else: + self._draw = _aggdraw.Draw(image_or_mode) + + @property + def size(self): + """tuple: The size in pixels of the draw surface (width, height).""" + return self._draw.size + + @property + def mode(self): + """str: The mode of the draw surface (e.g. "RGBA").""" + return self._draw.mode + + def _parse_args(self, brush=None, pen=None): + # Allow order of brush and pen to be reversed, matching C++ API + # NOTE: This segfaults for some reason if pen and brush are swapped here + # instead of leaving it to the C++ extension to handle + if brush: + brush = brush._pen if isinstance(brush, Pen) else brush._brush + if pen: + pen = pen._brush if isinstance(pen, Brush) else pen._pen + return (brush, pen) + + def arc(self, xy, start, end, pen=None): + """Draws an arc. + + Args: + xy: A 4-element Python sequence (x, y, x, y) with the upper-left corner + given first. + start (float): The start angle of the arc. + end (float): The end angle of the arc. + pen (:obj:`aggdraw.Pen`, optional): A pen to use for drawing the arc. + + """ + # NOTE: Why is pen optional? + if pen: + pen = pen._pen + self._draw.arc(xy, start, end, pen) + + def chord(self, xy, start, end, pen=None, brush=None): + """Draws a chord. + + If a brush is given, it is used to fill the chord. If a pen is given, + it is used to draw an outline around the chord. Either one (or both) + can be left out. + + Args: + xy: A 4-element Python sequence (x, y, x, y) with the upper-left corner + given first. + start (float): The start angle of the chord. + end (float): The end angle of the chord. + pen (:obj:`aggdraw.Pen`, optional): A pen to use for drawing an outline + around the chord. + brush (:obj:`aggdraw.Brush`, optional): A brush to use for filling + the chord. + + """ + brush, pen = self._parse_args(brush, pen) + self._draw.chord(xy, start, end, pen, brush) + + def ellipse(self, xy, pen=None, brush=None): + """Draws an ellipse. + + If a brush is given, it is used to fill the ellipse. If a pen is given, + it is used to draw an outline around the ellipse. Either one (or both) + can be left out. + + To draw a circle, make sure the coordinates form a square. + + Args: + xy: A bounding rectangle as a 4-element Python sequence (x, y, x, y), + with the upper-left corner given first. + pen (:obj:`aggdraw.Pen`, optional): A pen to use for drawing an outline + around the ellipse. + brush (:obj:`aggdraw.Brush`, optional): A brush to use for filling + the ellipse. + + """ + brush, pen = self._parse_args(brush, pen) + self._draw.ellipse(xy, brush, pen) + + def flush(self): + """Updates the associated image. + + If the drawing area is attached to a PIL Image object, this method must + be called to make sure that the image updated. + + """ + return self._draw.flush() + + def frombytes(self, data): + """Copies data from a bytes buffer to the drawing area. + + Args: + data (bytes): Packed image data compatible with PIL's tobytes method. + + """ + self._draw.frombytes(data) + + def line(self, xy, pen=None): + """Draws a line. + + If the sequence contains multiple x/y pairs, multiple connected lines + will be drawn. + + Args: + xy: A Python sequence in the format (x, y, x, y, ...) + pen (:obj:`aggdraw.Pen`, optional): A pen to use for drawing the line. + + """ + if isinstance(xy, Path): + xy = xy._path + if pen: + pen = pen._pen + self._draw.line(xy, pen) + + def path(self, xy, path, pen=None, brush=None): + """Draws a path at the given positions. + + If a brush is given, it is used to fill the path. If a pen is given, + it is used to draw an outline around the path. Either one (or both) + can be left out. + + Args: + xy: A Python sequence in the format (x, y, x, y, ...) + path (:obj:`aggdraw.Path`): The Path object to draw. + pen (:obj:`aggdraw.Pen`, optional): A pen to use for drawing an outline + around the path. + brush (:obj:`aggdraw.Brush`, optional): A brush to use for filling + the path. + + """ + brush, pen = self._parse_args(brush, pen) + self._draw.path(xy, path._path, brush, pen) + + def pieslice(self, xy, start, end, pen=None, brush=None): + """Draws a pie slice. + + If a brush is given, it is used to fill the pie slice. If a pen is given, + it is used to draw an outline around the pie slice. Either one (or both) + can be left out. + + Args: + xy: A 4-element Python sequence (x, y, x, y) with the upper-left corner + given first. + start (float): The start angle of the pie slice. + end (float): The end angle of the pie slice. + pen (:obj:`aggdraw.Pen`, optional): A pen to use for drawing an outline + around the pie slice. + brush (:obj:`aggdraw.Brush`, optional): A brush to use for filling + the pie slice. + + """ + brush, pen = self._parse_args(brush, pen) + self._draw.pieslice(xy, start, end, pen, brush) + + def polygon(self, xy, pen=None, brush=None): + """Draws a polygon. + + If a brush is given, it is used to fill the polygon. If a pen is given, + it is used to draw an outline around the polygon. Either one (or both) + can be left out. + + Args: + xy: A Python sequence (x, y, x, y, ...). + pen (:obj:`aggdraw.Pen`, optional): A pen to use for drawing an outline + around the polygon. + brush (:obj:`aggdraw.Brush`, optional): A brush to use for filling + the polygon. + + """ + if isinstance(xy, Path): + xy = xy._path + brush, pen = self._parse_args(brush, pen) + self._draw.polygon(xy, brush, pen) + + def rectangle(self, xy, pen=None, brush=None): + """Draws a rectangle. + + If a brush is given, it is used to fill the rectangle. If a pen is given, + it is used to draw an outline around the rectangle. Either one (or both) + can be left out. + + Args: + xy: A 4-element Python sequence (x, y, x, y), with the upper left corner + given first. + pen (:obj:`aggdraw.Pen`, optional): A pen to use for drawing an outline + around the rectangle. + brush (:obj:`aggdraw.Brush`, optional): A brush to use for filling + the rectangle. + + """ + brush, pen = self._parse_args(brush, pen) + self._draw.rectangle(xy, brush, pen) + + def rounded_rectangle(self, xy, radius, pen=None, brush=None): + """Draws a rounded rectangle. + + If a brush is given, it is used to fill the rectangle. If a pen is given, + it is used to draw an outline around the rectangle. Either one (or both) + can be left out. + + Args: + xy: A 4-element Python sequence (x, y, x, y), with the upper left corner + given first. + radius (float): The corner radius. + pen (:obj:`aggdraw.Pen`, optional): A pen to use for drawing an outline + around the rectangle. + brush (:obj:`aggdraw.Brush`, optional): A brush to use for filling + the rectangle. + + """ + brush, pen = self._parse_args(brush, pen) + self._draw.rounded_rectangle(xy, radius, brush, pen) + + def setantialias(self, flag): + """Controls anti-aliasing. + + Args: + flag (bool): True to enable anti-aliasing, False to disable it. + + """ + self._draw.setantialias(flag) + + def settransform(self, transform=None): + """Replaces the current drawing transform. + + The transform must either be a (dx, dy) translation tuple, or a PIL-style + (a, b, c, d, e, f) affine transform tuple. If the transform is omitted, + it is reset. + + Example:: + draw.settransform((dx, dy)) + + Args: + transform (tuple, optional): The new transform, or None to reset. + + """ + if transform: + self._draw.settransform(transform) + else: + self._draw.settransform() + + def symbol(self, xy, symbol, pen=None, brush=None): + """Draws a symbol at the given positions. + + If a brush is given, it is used to fill the symbol. If a pen is given, + it is used to draw an outline around the symbol. Either one (or both) + can be left out. + + Args: + xy: A Python sequence in the format (x, y, x, y, ...) + symbol (:obj:`aggdraw.Symbol`): The Symbol object to draw. + pen (:obj:`aggdraw.Pen`, optional): A pen to use for drawing an outline + around the symbol. + brush (:obj:`aggdraw.Brush`, optional): A brush to use for filling + the symbol. + + """ + brush, pen = self._parse_args(brush, pen) + self._draw.symbol(xy, symbol._path, brush, pen) + + def text(self, xy, text, font): + """Determines the size of a text string. + + Example:: + font = aggdraw.Font(black, times) + draw.text((100, 100), "hello, world", font) + + Args: + xy: A 2-element Python sequence (x, y). + text (str): A string of text to render. + font (:obj:`aggdraw.Font`): The font object to render with. + + Returns: + tuple: A (width, height) tuple. + + """ + self._draw.text(xy, text, font._font) + + def textsize(self, text, font): + """Determines the size of a text string. + + Args: + text (str): A string of text to measure. + font (:obj:`aggdraw.Font`): The font object to render with. + + Returns: + tuple: A (width, height) tuple. + + """ + return self._draw.textsize(text, font._font) + + def tobytes(self): + """Copies data from the drawing area to a bytes buffer. + + Returns: + bytes: Packed image data compatible with PIL's frombytes method. + + """ + # NOTE: Check how pillow docs refer to returned data + return self._draw.tobytes() \ No newline at end of file diff --git a/aggdraw/dib.py b/aggdraw/dib.py new file mode 100644 index 0000000..c9c4c71 --- /dev/null +++ b/aggdraw/dib.py @@ -0,0 +1,27 @@ +import aggdraw.aggdraw_cpp as _aggdraw +from .core import Draw + + +class Dib(Draw): + """Creates a drawing interface object that can be copied to a window. + + Windows-only. + + This object has the same methods as Draw, plus an expose method that copies + the contents to a given window or device context. + + """ + def __init__(self, mode, size, color): + self._draw = _aggdraw.Dib(mode, size, color) + + def expose(self, hwnd=0, hwc=0): + """Copies the contents of the drawing object to the given window or context. + + You must provide either a hwnd or a hdc keyword argument. + + Args: + hwnd (int): An HWND handle cast to an integer. + hdc (int): An HDC handle cast to an integer. + + """ + self._draw.expose(hwnd, hwc) diff --git a/setup.py b/setup.py index 5863557..2967953 100644 --- a/setup.py +++ b/setup.py @@ -156,8 +156,9 @@ def _get_freetype_with_pkgconfig(): download_url="http://www.effbot.org/downloads#aggdraw", license="Python (MIT style)", url="https://github.com/pytroll/aggdraw", + packages=["aggdraw"], ext_modules=[ - Extension("aggdraw", ["aggdraw.cxx"] + sources, + Extension("aggdraw.aggdraw_cpp", ["aggdraw.cxx"] + sources, define_macros=defines, include_dirs=include_dirs, library_dirs=library_dirs, libraries=libraries, @@ -170,5 +171,5 @@ def _get_freetype_with_pkgconfig(): "sphinx_rtd_theme", ], }, - python_requires='>=3.11', + python_requires='>=3.10', ) From 8090ce211e9a69cca3668795cb39445c37a0b677 Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Tue, 24 Feb 2026 23:53:59 -0400 Subject: [PATCH 02/22] Update unit test organization to support new module setup --- .github/workflows/ci.yml | 4 ++-- README.rst | 2 +- pyproject.toml | 5 +++++ selftest.py => tests/test_aggdraw.py | 9 --------- 4 files changed, 8 insertions(+), 12 deletions(-) rename selftest.py => tests/test_aggdraw.py (96%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0092180..da93f04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: shell: bash -l {0} run: | pip install -e . - python selftest.py + pytest -v build: name: "Build wheels on ${{ matrix.os }} ${{ matrix.cibw_archs }}" @@ -63,7 +63,7 @@ jobs: - name: Build wheels uses: pypa/cibuildwheel@v3.3.1 env: - CIBW_TEST_COMMAND: python {project}/selftest.py + CIBW_TEST_COMMAND: pytest {project}/tests CIBW_BEFORE_BUILD_LINUX: yum install -y freetype-devel CIBW_SKIP: "cp39-* cp310-* *-win32 *i686 *-musllinux*" CIBW_TEST_REQUIRES: numpy pillow pytest diff --git a/README.rst b/README.rst index d60f3ff..a9e2488 100644 --- a/README.rst +++ b/README.rst @@ -57,7 +57,7 @@ Build instructions (all platforms) 4. Once aggdraw is installed run the tests:: - $ python selftest.py + $ pytest -v 5. Enjoy! diff --git a/pyproject.toml b/pyproject.toml index 4b51ebc..cbed9dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,8 @@ [build-system] requires = ["packaging", "setuptools"] build-backend = "setuptools.build_meta" + +[tool.pytest.ini_options] +addopts = [ + "--import-mode=importlib", +] diff --git a/selftest.py b/tests/test_aggdraw.py similarity index 96% rename from selftest.py rename to tests/test_aggdraw.py index 47643e1..a580c3c 100644 --- a/selftest.py +++ b/tests/test_aggdraw.py @@ -1,7 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# sanity check - import pytest @@ -158,8 +154,3 @@ def test_transform(): draw.settransform((1, 0, 250, 0, 1, 250)) draw.settransform((2.0, 0.5, 250, 0.5, 2.0, 250)) draw.settransform() - - -if __name__ == "__main__": - import sys - sys.exit(pytest.main(sys.argv)) From 2e719f9b63f236e8911b5efa0ddcf7a259fc6b86 Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Wed, 25 Feb 2026 00:06:24 -0400 Subject: [PATCH 03/22] Revert minimum version change --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2967953..89eafb1 100644 --- a/setup.py +++ b/setup.py @@ -171,5 +171,5 @@ def _get_freetype_with_pkgconfig(): "sphinx_rtd_theme", ], }, - python_requires='>=3.10', + python_requires='>=3.11', ) From 56a1cda4e0781b0b5f970ca9caa928d4423f4c71 Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Wed, 25 Feb 2026 00:32:08 -0400 Subject: [PATCH 04/22] Fix API autodocs --- aggdraw/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aggdraw/__init__.py b/aggdraw/__init__.py index c934977..414f287 100644 --- a/aggdraw/__init__.py +++ b/aggdraw/__init__.py @@ -2,5 +2,7 @@ from .core import Draw, Pen, Brush, Path, Symbol from .dib import Dib +__all__ = ["Pen", "Brush", "Path", "Symbol", "Draw", "Dib"] + VERSION = aggdraw.aggdraw_cpp.VERSION __version__ = VERSION From b3e757c214cb088ae57e308fc21ed18c8695f1a8 Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Wed, 25 Feb 2026 22:17:39 -0400 Subject: [PATCH 05/22] Update C++ module name --- aggdraw.cxx | 4 ++-- aggdraw/__init__.py | 4 ++-- aggdraw/core.py | 2 +- aggdraw/dib.py | 2 +- setup.py | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aggdraw.cxx b/aggdraw.cxx index ac1a20c..c04c678 100644 --- a/aggdraw.cxx +++ b/aggdraw.cxx @@ -2634,7 +2634,7 @@ const char *mod_doc = "Python interface to the Anti-Grain Graphics Drawing libra #ifdef IS_PY3K static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, - "aggdraw_cpp", + "_aggdraw", mod_doc, -1, aggdraw_functions, @@ -2704,7 +2704,7 @@ aggdraw_init(void) #ifdef IS_PY3K PyMODINIT_FUNC -PyInit_aggdraw_cpp(void) +PyInit__aggdraw(void) { return aggdraw_init(); } diff --git a/aggdraw/__init__.py b/aggdraw/__init__.py index 414f287..3197676 100644 --- a/aggdraw/__init__.py +++ b/aggdraw/__init__.py @@ -1,8 +1,8 @@ -import aggdraw.aggdraw_cpp +import aggdraw._aggdraw from .core import Draw, Pen, Brush, Path, Symbol from .dib import Dib __all__ = ["Pen", "Brush", "Path", "Symbol", "Draw", "Dib"] -VERSION = aggdraw.aggdraw_cpp.VERSION +VERSION = aggdraw._aggdraw.VERSION __version__ = VERSION diff --git a/aggdraw/core.py b/aggdraw/core.py index 903b140..252f015 100644 --- a/aggdraw/core.py +++ b/aggdraw/core.py @@ -1,4 +1,4 @@ -import aggdraw.aggdraw_cpp as _aggdraw +import aggdraw._aggdraw as _aggdraw class Brush(object): diff --git a/aggdraw/dib.py b/aggdraw/dib.py index c9c4c71..0c5db0d 100644 --- a/aggdraw/dib.py +++ b/aggdraw/dib.py @@ -1,4 +1,4 @@ -import aggdraw.aggdraw_cpp as _aggdraw +import aggdraw._aggdraw as _aggdraw from .core import Draw diff --git a/setup.py b/setup.py index 89eafb1..d2e80ec 100644 --- a/setup.py +++ b/setup.py @@ -158,7 +158,7 @@ def _get_freetype_with_pkgconfig(): url="https://github.com/pytroll/aggdraw", packages=["aggdraw"], ext_modules=[ - Extension("aggdraw.aggdraw_cpp", ["aggdraw.cxx"] + sources, + Extension("aggdraw._aggdraw", ["aggdraw.cxx"] + sources, define_macros=defines, include_dirs=include_dirs, library_dirs=library_dirs, libraries=libraries, From c7215e56cca1d9c51dffa10f746981f22ff2a9de Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Wed, 25 Feb 2026 22:20:12 -0400 Subject: [PATCH 06/22] Modernize class definitions --- aggdraw/core.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/aggdraw/core.py b/aggdraw/core.py index 252f015..371833a 100644 --- a/aggdraw/core.py +++ b/aggdraw/core.py @@ -1,7 +1,7 @@ import aggdraw._aggdraw as _aggdraw -class Brush(object): +class Brush(): """Creates a brush object. The brush color can be an RGB tuple (e.g. `(255, 255, 255)`), a CSS-stype color @@ -17,7 +17,7 @@ def __init__(self, color, opacity=255): self._brush = _aggdraw.Brush(color, opacity) -class Pen(object): +class Pen(): """Creates a pen object. The pen color can be a color tuple (e.g. `(255, 255, 255)`), a CSS-stype color @@ -34,7 +34,7 @@ def __init__(self, color, width=1, opacity=255): self._pen = _aggdraw.Pen(color, width, opacity) -class Font(object): +class Font(): """Creates a font object. This creates a font object for use with :meth:`aggdraw.Draw.text` and @@ -56,7 +56,7 @@ def __init__(self, color, file, size=12, opacity=255): self._font = _aggdraw.Font(color, file, size, opacity) -class Symbol(object): +class Symbol(): """Symbol factory. This creates a symbol object from an SVG-style path descriptor for use with @@ -85,7 +85,7 @@ def __init__(self, path, scale=1.0): self._path = _aggdraw.Symbol(path, scale) -class Path(object): +class Path(): """Path factory. This creates a path object for use with :meth:`aggdraw.Draw.path`. @@ -149,7 +149,7 @@ def rmoveto(self, x, y): self._path.rmoveto(x, y) -class Draw(object): +class Draw(): """Creates a drawing interface object. The constructor can either take a PIL Image object, or mode and size specifiers. @@ -470,4 +470,4 @@ def tobytes(self): """ # NOTE: Check how pillow docs refer to returned data - return self._draw.tobytes() \ No newline at end of file + return self._draw.tobytes() From 202611cc8bf9c4cf2a2fc0141208cb13260a59b1 Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Thu, 26 Feb 2026 13:15:20 -0400 Subject: [PATCH 07/22] Set VERSION using importlib --- aggdraw/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aggdraw/__init__.py b/aggdraw/__init__.py index 3197676..7ce95f8 100644 --- a/aggdraw/__init__.py +++ b/aggdraw/__init__.py @@ -1,8 +1,8 @@ -import aggdraw._aggdraw +import importlib.metadata from .core import Draw, Pen, Brush, Path, Symbol from .dib import Dib __all__ = ["Pen", "Brush", "Path", "Symbol", "Draw", "Dib"] -VERSION = aggdraw._aggdraw.VERSION +VERSION = importlib.metadata.version(__name__) __version__ = VERSION From 9993f2fd80e30f40e73c2aec92f7fba67fc0746f Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Thu, 26 Feb 2026 13:48:14 -0400 Subject: [PATCH 08/22] Move aggdraw.cxx into package dir --- aggdraw.cxx => aggdraw/_aggdraw.cxx | 0 setup.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename aggdraw.cxx => aggdraw/_aggdraw.cxx (100%) diff --git a/aggdraw.cxx b/aggdraw/_aggdraw.cxx similarity index 100% rename from aggdraw.cxx rename to aggdraw/_aggdraw.cxx diff --git a/setup.py b/setup.py index d2e80ec..dd8dcdb 100644 --- a/setup.py +++ b/setup.py @@ -158,7 +158,7 @@ def _get_freetype_with_pkgconfig(): url="https://github.com/pytroll/aggdraw", packages=["aggdraw"], ext_modules=[ - Extension("aggdraw._aggdraw", ["aggdraw.cxx"] + sources, + Extension("aggdraw._aggdraw", ["aggdraw/_aggdraw.cxx"] + sources, define_macros=defines, include_dirs=include_dirs, library_dirs=library_dirs, libraries=libraries, From 56fa49bf34509d1be14f1e06863a0b32b4219ac0 Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Thu, 26 Feb 2026 14:20:15 -0400 Subject: [PATCH 09/22] Use regex to get setup.py package version from __init__ --- aggdraw/__init__.py | 3 +-- setup.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/aggdraw/__init__.py b/aggdraw/__init__.py index 7ce95f8..dae92c9 100644 --- a/aggdraw/__init__.py +++ b/aggdraw/__init__.py @@ -1,8 +1,7 @@ -import importlib.metadata from .core import Draw, Pen, Brush, Path, Symbol from .dib import Dib __all__ = ["Pen", "Brush", "Path", "Symbol", "Draw", "Dib"] -VERSION = importlib.metadata.version(__name__) +VERSION = "1.4.1" __version__ = VERSION diff --git a/setup.py b/setup.py index dd8dcdb..46144b7 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,7 @@ # from __future__ import print_function import os +import re import sys import subprocess import platform @@ -21,11 +22,16 @@ from packaging.version import Version from setuptools import setup, Extension -VERSION = "1.4.1" - SUMMARY = "High quality drawing interface for PIL." README = open("README.rst", "r").read() +def get_version(path): + version_regex = re.compile('\nVERSION = "([\w\.]+)"') + with open(path, "r") as f: + return version_regex.findall(f.read())[0] + +VERSION = get_version(os.path.join("aggdraw", "__init__.py")) + def is_platform_mac(): return sys.platform == 'darwin' From 73c3039e276fa411639f4cddd58c7fae71964ec2 Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Thu, 26 Feb 2026 14:24:32 -0400 Subject: [PATCH 10/22] Move tests into package --- {tests => aggdraw/tests}/test_aggdraw.py | 0 pyproject.toml | 5 ----- 2 files changed, 5 deletions(-) rename {tests => aggdraw/tests}/test_aggdraw.py (100%) diff --git a/tests/test_aggdraw.py b/aggdraw/tests/test_aggdraw.py similarity index 100% rename from tests/test_aggdraw.py rename to aggdraw/tests/test_aggdraw.py diff --git a/pyproject.toml b/pyproject.toml index cbed9dc..4b51ebc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,3 @@ [build-system] requires = ["packaging", "setuptools"] build-backend = "setuptools.build_meta" - -[tool.pytest.ini_options] -addopts = [ - "--import-mode=importlib", -] From 53f7d8116a89c90727cf8820e5b8caa60358316b Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Thu, 26 Feb 2026 14:37:05 -0400 Subject: [PATCH 11/22] Fix tests path for cibuildwheel --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da93f04..1014973 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: - name: Build wheels uses: pypa/cibuildwheel@v3.3.1 env: - CIBW_TEST_COMMAND: pytest {project}/tests + CIBW_TEST_COMMAND: pytest {project}/aggdraw/tests CIBW_BEFORE_BUILD_LINUX: yum install -y freetype-devel CIBW_SKIP: "cp39-* cp310-* *-win32 *i686 *-musllinux*" CIBW_TEST_REQUIRES: numpy pillow pytest From 3ba385167a6d89b702ac16410bf1f24333d36641 Mon Sep 17 00:00:00 2001 From: David Hoese Date: Thu, 26 Feb 2026 13:26:09 -0600 Subject: [PATCH 12/22] Add init module to tests to make it a subpkg --- aggdraw/tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 aggdraw/tests/__init__.py diff --git a/aggdraw/tests/__init__.py b/aggdraw/tests/__init__.py new file mode 100644 index 0000000..e69de29 From a0d9942e435f407fef8276caa9fc92c789ccbfe2 Mon Sep 17 00:00:00 2001 From: David Hoese Date: Thu, 26 Feb 2026 13:38:33 -0600 Subject: [PATCH 13/22] Use find_packages to define packages to include in build --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 46144b7..ffd52b1 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ from sysconfig import get_config_var from packaging.version import Version -from setuptools import setup, Extension +from setuptools import setup, Extension, find_packages SUMMARY = "High quality drawing interface for PIL." README = open("README.rst", "r").read() @@ -162,7 +162,7 @@ def _get_freetype_with_pkgconfig(): download_url="http://www.effbot.org/downloads#aggdraw", license="Python (MIT style)", url="https://github.com/pytroll/aggdraw", - packages=["aggdraw"], + packages=find_packages(), ext_modules=[ Extension("aggdraw._aggdraw", ["aggdraw/_aggdraw.cxx"] + sources, define_macros=defines, From b602ed4bc8b27bf45b06fae211499d580c1e4a7a Mon Sep 17 00:00:00 2001 From: David Hoese Date: Thu, 26 Feb 2026 13:38:57 -0600 Subject: [PATCH 14/22] Move cibuildwheel options to pyproject.toml for easier testing Also change the test command to use the installed wheel/package. --- .github/workflows/ci.yml | 11 +---------- pyproject.toml | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1014973..98f83c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,17 +63,8 @@ jobs: - name: Build wheels uses: pypa/cibuildwheel@v3.3.1 env: - CIBW_TEST_COMMAND: pytest {project}/aggdraw/tests - CIBW_BEFORE_BUILD_LINUX: yum install -y freetype-devel - CIBW_SKIP: "cp39-* cp310-* *-win32 *i686 *-musllinux*" - CIBW_TEST_REQUIRES: numpy pillow pytest - CIBW_TEST_SKIP: "*-win_arm64" + # see pyproject.toml for other options CIBW_ARCHS: "${{ matrix.cibw_archs }}" - # disable finding unintended freetype installations - CIBW_ENVIRONMENT_WINDOWS: "AGGDRAW_FREETYPE_ROOT=''" - # we use libpng/libfreetype from homebrew which has a current limit of - # macos 14 - CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET=14 - name: upload uses: actions/upload-artifact@v6 with: diff --git a/pyproject.toml b/pyproject.toml index 4b51ebc..13914f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,20 @@ [build-system] requires = ["packaging", "setuptools"] build-backend = "setuptools.build_meta" + +[tool.cibuildwheel] +test-command = "pytest --pyargs aggdraw.tests" +skip = "cp39-* cp310-* *-win32 *i686 *-musllinux*" +test-requires = ["numpy", "pillow", "pytest"] +test-skip = "*-win_arm64" + +[tool.cibuildwheel.linux] +before-build = "yum install -y freetype-devel" + +[tool.cibuildwheel.windows] +# disable finding unintended freetype installations +environment = "AGGDRAW_FREETYPE_ROOT=''" + +[tool.cibuildwheel.macos] +# we use libpng/libfreetype from homebrew which has a current limit of macos 14 +environment = "MACOSX_DEPLOYMENT_TARGET=14" From ba883043963dc8a605b1a05e1d54639d4b90ee02 Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Thu, 26 Feb 2026 22:11:37 -0400 Subject: [PATCH 15/22] Raise exception if creating Dib on non-Windows platform --- aggdraw/dib.py | 13 ++++++++++++- aggdraw/tests/test_aggdraw.py | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/aggdraw/dib.py b/aggdraw/dib.py index 0c5db0d..44d5785 100644 --- a/aggdraw/dib.py +++ b/aggdraw/dib.py @@ -1,3 +1,5 @@ +import platform + import aggdraw._aggdraw as _aggdraw from .core import Draw @@ -10,8 +12,17 @@ class Dib(Draw): This object has the same methods as Draw, plus an expose method that copies the contents to a given window or device context. + Args: + mode (str): The pixel mode of the draw surface. Currently only supports + "RGB". + size (tuple): The size (width, height) of the drawing surface in pixels. + color (tuple, optional): A default background fill color for the surface. + """ - def __init__(self, mode, size, color): + def __init__(self, mode, size, color=None): + if platform.system() != "Windows": + e = "Dib class only available on Windows." + raise RuntimeError(e) self._draw = _aggdraw.Dib(mode, size, color) def expose(self, hwnd=0, hwc=0): diff --git a/aggdraw/tests/test_aggdraw.py b/aggdraw/tests/test_aggdraw.py index a580c3c..91d06df 100644 --- a/aggdraw/tests/test_aggdraw.py +++ b/aggdraw/tests/test_aggdraw.py @@ -154,3 +154,16 @@ def test_transform(): draw.settransform((1, 0, 250, 0, 1, 250)) draw.settransform((2.0, 0.5, 250, 0.5, 2.0, 250)) draw.settransform() + + +def test_dib(): + from aggdraw import Dib + import platform + + if platform.system() == "Windows": + dib = Dib("RGB", (400, 300)) + assert dib.mode == 'RGB' + assert dib.size == (400, 300) + else: + with pytest.raises(RuntimeError): + Dib("RGB", (400, 300)) From 89621217e1d690995f64ff3c781c6643004b6472 Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Thu, 26 Feb 2026 22:12:01 -0400 Subject: [PATCH 16/22] Packaging cleanup --- MANIFEST.in | 1 - PKG-INFO | 16 ---------------- 2 files changed, 17 deletions(-) delete mode 100644 PKG-INFO diff --git a/MANIFEST.in b/MANIFEST.in index 17b2c5c..3a91bd7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ -include aggdraw.cxx include LICENSE.txt recursive-include agg2 *.cpp recursive-include agg2 *.h diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index bfe5db3..0000000 --- a/PKG-INFO +++ /dev/null @@ -1,16 +0,0 @@ -Metadata-Version: 1.0 -Name: aggdraw -Version: 1.2a3-20060212 -Summary: High quality drawing interface for PIL. -Home-page: http://www.effbot.org/zone/aggdraw.htm -Author: Fredrik Lundh -Author-email: fredrik@pythonware.com -License: Python (MIT style) -Download-URL: http://www.effbot.org/downloads#aggdraw -Description: The aggdraw module implements the basic WCK 2D Drawing Interface on - top of the AGG library. This library provides high-quality drawing, - with anti-aliasing and alpha compositing, while being fully compatible - with the WCK renderer. -Platform: Python 2.1 and later. -Classifier: Development Status :: 4 - Beta -Classifier: Topic :: Multimedia :: Graphics From 3e03846b06bf6d451f039c6dc01fee215405d56f Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Thu, 26 Feb 2026 22:20:37 -0400 Subject: [PATCH 17/22] Don't test mode for Dib since apparently it can be reversed --- aggdraw/tests/test_aggdraw.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aggdraw/tests/test_aggdraw.py b/aggdraw/tests/test_aggdraw.py index 91d06df..370d42e 100644 --- a/aggdraw/tests/test_aggdraw.py +++ b/aggdraw/tests/test_aggdraw.py @@ -162,7 +162,6 @@ def test_dib(): if platform.system() == "Windows": dib = Dib("RGB", (400, 300)) - assert dib.mode == 'RGB' assert dib.size == (400, 300) else: with pytest.raises(RuntimeError): From c9298475bff583a4ff91920583dc44c7ce09b77e Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Thu, 26 Feb 2026 23:29:54 -0400 Subject: [PATCH 18/22] Remove tests for broken Dib class --- aggdraw/dib.py | 3 ++- aggdraw/tests/test_aggdraw.py | 12 ------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/aggdraw/dib.py b/aggdraw/dib.py index 44d5785..a420896 100644 --- a/aggdraw/dib.py +++ b/aggdraw/dib.py @@ -35,4 +35,5 @@ def expose(self, hwnd=0, hwc=0): hdc (int): An HDC handle cast to an integer. """ - self._draw.expose(hwnd, hwc) + self._draw.expose(hwnd=hwnd, hdc=hdc) + diff --git a/aggdraw/tests/test_aggdraw.py b/aggdraw/tests/test_aggdraw.py index 370d42e..a580c3c 100644 --- a/aggdraw/tests/test_aggdraw.py +++ b/aggdraw/tests/test_aggdraw.py @@ -154,15 +154,3 @@ def test_transform(): draw.settransform((1, 0, 250, 0, 1, 250)) draw.settransform((2.0, 0.5, 250, 0.5, 2.0, 250)) draw.settransform() - - -def test_dib(): - from aggdraw import Dib - import platform - - if platform.system() == "Windows": - dib = Dib("RGB", (400, 300)) - assert dib.size == (400, 300) - else: - with pytest.raises(RuntimeError): - Dib("RGB", (400, 300)) From 9a3c4ceb6ccc458df103746ff6989474f3e104dd Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Thu, 26 Feb 2026 23:58:44 -0400 Subject: [PATCH 19/22] Docstring fixes --- aggdraw/core.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/aggdraw/core.py b/aggdraw/core.py index 371833a..ef6ef67 100644 --- a/aggdraw/core.py +++ b/aggdraw/core.py @@ -4,7 +4,7 @@ class Brush(): """Creates a brush object. - The brush color can be an RGB tuple (e.g. `(255, 255, 255)`), a CSS-stype color + The brush color can be an RGB tuple (e.g. `(255, 255, 255)`), a CSS-style color name, or a color integer (0xAARRGGBB). Args: @@ -20,7 +20,7 @@ def __init__(self, color, opacity=255): class Pen(): """Creates a pen object. - The pen color can be a color tuple (e.g. `(255, 255, 255)`), a CSS-stype color + The pen color can be a color tuple (e.g. `(255, 255, 255)`), a CSS-style color name, or a color integer (0xAARRGGBB). Args: @@ -40,12 +40,12 @@ class Font(): This creates a font object for use with :meth:`aggdraw.Draw.text` and :meth:`aggdraw.Draw.textsize` from a TrueType font file. - The font color can be a color tuple (e.g. `(255, 255, 255)`), a CSS-stype color + The font color can be a color tuple (e.g. `(255, 255, 255)`), a CSS-style color name, or a color integer (0xAARRGGBB). Args: color: The font color. - file: Path to a valid TrueTyle font file. + file: Path to a valid TrueType font file. size (optional): The font size (in pixels). Defaults to 12. opacity (int, optional): The opacity of the font (from 0 to 255). Defaults to solid. @@ -260,7 +260,7 @@ def flush(self): return self._draw.flush() def frombytes(self, data): - """Copies data from a bytes buffer to the drawing area. + """Copies data from a bytes object to the drawing area. Args: data (bytes): Packed image data compatible with PIL's tobytes method. @@ -463,11 +463,10 @@ def textsize(self, text, font): return self._draw.textsize(text, font._font) def tobytes(self): - """Copies data from the drawing area to a bytes buffer. + """Copies data from the drawing area to a bytes object. Returns: bytes: Packed image data compatible with PIL's frombytes method. """ - # NOTE: Check how pillow docs refer to returned data return self._draw.tobytes() From 4c431c21eab0b4077ce664018b184238bea25666 Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Fri, 27 Feb 2026 00:14:18 -0400 Subject: [PATCH 20/22] Fix dead links --- README.rst | 3 ++- doc/source/index.rst | 18 +++++++++--------- setup.py | 1 - 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index a9e2488..8f3497f 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,8 @@ with the WCK renderer. The necessary AGG sources are included in the aggdraw source kit. -For posterity, reference `the old documentation `_. +For posterity, reference +`the old documentation `_. Build instructions (all platforms) ---------------------------------- diff --git a/doc/source/index.rst b/doc/source/index.rst index e28ef57..0cc699f 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,23 +1,23 @@ AggDraw ======= -The AggDraw library provides a python interface on top of -`the AGG library `_. The library was originally -developed by Fredrik Lundh (effbot), but has since been picked up by various -developers. It is currently maintained by the PyTroll developmer group. The -official repository can be found on GitHub: +The AggDraw library provides a Python interface on top of +`the AGG library `_. +The library was originally developed by Fredrik Lundh (effbot), but has since +been picked up by various developers. It is currently maintained by the PyTroll +developer group. The official repository can be found on GitHub: https://github.com/pytroll/aggdraw The original documentation by effbot is -`still available `_ but may be out -of date with the current version of the library. Original examples will be -migrated as time is available (pull requests welcome). +`still available `_ +but is out of date with the current version of the library. Original examples +will be migrated as time is available (pull requests welcome). Installation ------------ -Aggdraw is available on Linux, OSX, and Windows. It can be installed from PyPI +Aggdraw is available on Linux, macOS, and Windows. It can be installed from PyPI with pip: .. code-block:: bash diff --git a/setup.py b/setup.py index ffd52b1..c648d19 100644 --- a/setup.py +++ b/setup.py @@ -159,7 +159,6 @@ def _get_freetype_with_pkgconfig(): description=SUMMARY, long_description=README, long_description_content_type="text/x-rst", - download_url="http://www.effbot.org/downloads#aggdraw", license="Python (MIT style)", url="https://github.com/pytroll/aggdraw", packages=find_packages(), From 5753feb2da67b05877cd8fcdf8928236b91aa54a Mon Sep 17 00:00:00 2001 From: Austin Hurst Date: Fri, 27 Feb 2026 01:09:59 -0400 Subject: [PATCH 21/22] Fix Font export and Draw.text docs --- aggdraw/__init__.py | 4 ++-- aggdraw/core.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aggdraw/__init__.py b/aggdraw/__init__.py index dae92c9..897e01b 100644 --- a/aggdraw/__init__.py +++ b/aggdraw/__init__.py @@ -1,7 +1,7 @@ -from .core import Draw, Pen, Brush, Path, Symbol +from .core import Draw, Pen, Brush, Path, Symbol, Font from .dib import Dib -__all__ = ["Pen", "Brush", "Path", "Symbol", "Draw", "Dib"] +__all__ = ["Pen", "Brush", "Font", "Path", "Symbol", "Draw", "Dib"] VERSION = "1.4.1" __version__ = VERSION diff --git a/aggdraw/core.py b/aggdraw/core.py index ef6ef67..07e1d44 100644 --- a/aggdraw/core.py +++ b/aggdraw/core.py @@ -432,7 +432,7 @@ def symbol(self, xy, symbol, pen=None, brush=None): self._draw.symbol(xy, symbol._path, brush, pen) def text(self, xy, text, font): - """Determines the size of a text string. + """Draws a text string at a given position using a given font. Example:: font = aggdraw.Font(black, times) From cfa6edd99a2a026bf9ee410e4b3bbd9854939f54 Mon Sep 17 00:00:00 2001 From: David Hoese Date: Fri, 27 Feb 2026 16:02:09 -0600 Subject: [PATCH 22/22] Fix regex definition in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c648d19..6da6b4e 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ README = open("README.rst", "r").read() def get_version(path): - version_regex = re.compile('\nVERSION = "([\w\.]+)"') + version_regex = re.compile(r'\nVERSION = "([\w\.]+)"') with open(path, "r") as f: return version_regex.findall(f.read())[0]