Skip to content

Commit 2c14a4f

Browse files
authored
Merge pull request #36 from csett86/copilot/conditional-import-cairoffi
Make cairocffi optional: lazy import only when render pipeline is used
2 parents 1a96a07 + 91c229d commit 2c14a4f

File tree

7 files changed

+299
-221
lines changed

7 files changed

+299
-221
lines changed

.github/workflows/tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ jobs:
3333
toxenv: py314
3434
- python-version: "3.x"
3535
toxenv: no-flask-caching
36+
- python-version: "3.12"
37+
toxenv: cairo
3638

3739
steps:
3840
- uses: actions/checkout@v6

docs/installation.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ Installing Graphite-Render requires:
1212

1313
* Python 3 (3.9 and above).
1414

15-
* Cairo. On debian/ubuntu, install the ``libcairo2`` package.
16-
1715
* Pip, the Python package manager. On debian/ubuntu, install ``python-pip``.
1816

17+
* For image rendering (PNG/SVG/PDF graphs): Cairo. On debian/ubuntu, install the ``libcairo2`` package.
18+
1919
Global installation
2020
-------------------
2121

graphite_render/render/glyph.py

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,27 @@
2121
from urllib.parse import unquote_plus
2222
from zoneinfo import ZoneInfo
2323

24-
import cairocffi as cairo
25-
2624
from .datalib import TimeSeries
2725
from ..utils import to_seconds
2826

27+
# Lazy import for cairocffi - only loaded when actually used in the render pipeline
28+
cairo = None
29+
30+
31+
def _get_cairo():
32+
"""Lazily import cairocffi when needed for rendering."""
33+
global cairo
34+
if cairo is None:
35+
try:
36+
import cairocffi as cairo_module
37+
cairo = cairo_module
38+
except ImportError as e:
39+
raise ImportError(
40+
"cairocffi is required for image rendering but is not installed. "
41+
"Install it with: pip install cairocffi"
42+
) from e
43+
return cairo
44+
2945

3046
INFINITY = float('inf')
3147

@@ -737,7 +753,7 @@ def __init__(self, **params):
737753
self.loadTemplate(params.get('template', 'default'))
738754

739755
opts = self.ctx.get_font_options()
740-
opts.set_antialias(cairo.ANTIALIAS_NONE)
756+
opts.set_antialias(self.cairo.ANTIALIAS_NONE)
741757
self.ctx.set_font_options(opts)
742758

743759
self.foregroundColor = params.get('fgcolor', self.defaultForeground)
@@ -755,22 +771,23 @@ def __init__(self, **params):
755771

756772
def setupCairo(self, outputFormat='png'):
757773
self.outputFormat = outputFormat
774+
self.cairo = _get_cairo()
758775
if outputFormat == 'png':
759-
self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
760-
self.width, self.height)
776+
self.surface = self.cairo.ImageSurface(self.cairo.FORMAT_ARGB32,
777+
self.width, self.height)
761778
elif outputFormat == 'svg':
762779
self.surfaceData = BytesIO()
763-
self.surface = cairo.SVGSurface(self.surfaceData,
764-
self.width, self.height)
780+
self.surface = self.cairo.SVGSurface(self.surfaceData,
781+
self.width, self.height)
765782
elif outputFormat == 'pdf':
766783
self.surfaceData = BytesIO()
767-
self.surface = cairo.PDFSurface(self.surfaceData,
768-
self.width, self.height)
784+
self.surface = self.cairo.PDFSurface(self.surfaceData,
785+
self.width, self.height)
769786
res_x, res_y = self.surface.get_fallback_resolution()
770787
self.width = float(self.width / res_x) * 72
771788
self.height = float(self.height / res_y) * 72
772789
self.surface.set_size(self.width, self.height)
773-
self.ctx = cairo.Context(self.surface)
790+
self.ctx = self.cairo.Context(self.surface)
774791

775792
def setColor(self, value, alpha=1.0, forceAlpha=False):
776793
if isinstance(value, tuple) and len(value) == 3:
@@ -1395,14 +1412,14 @@ def drawLines(self, width=None, dash=None, linecap='butt',
13951412
else:
13961413
self.ctx.set_dash([], 0)
13971414
self.ctx.set_line_cap({
1398-
'butt': cairo.LINE_CAP_BUTT,
1399-
'round': cairo.LINE_CAP_ROUND,
1400-
'square': cairo.LINE_CAP_SQUARE,
1415+
'butt': self.cairo.LINE_CAP_BUTT,
1416+
'round': self.cairo.LINE_CAP_ROUND,
1417+
'square': self.cairo.LINE_CAP_SQUARE,
14011418
}[linecap])
14021419
self.ctx.set_line_join({
1403-
'miter': cairo.LINE_JOIN_MITER,
1404-
'round': cairo.LINE_JOIN_ROUND,
1405-
'bevel': cairo.LINE_JOIN_BEVEL,
1420+
'miter': self.cairo.LINE_JOIN_MITER,
1421+
'round': self.cairo.LINE_JOIN_ROUND,
1422+
'bevel': self.cairo.LINE_JOIN_BEVEL,
14061423
}[linejoin])
14071424

14081425
# check whether there is an stacked metric

tests/test_http.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ def test_cors(self):
2626
'Access-Control-Allow-Origin' in response.headers)
2727

2828
def test_trailing_slash(self):
29-
response = self.app.get('/render?target=foo')
29+
response = self.app.get('/render?target=foo&format=json')
3030
self.assertEqual(response.status_code, 200)
3131

32-
response = self.app.get('/render/?target=foo')
32+
response = self.app.get('/render/?target=foo&format=json')
3333
self.assertEqual(response.status_code, 200)

0 commit comments

Comments
 (0)