paperforge-diagrams 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- paperforge_diagrams-0.1.0/PKG-INFO +408 -0
- paperforge_diagrams-0.1.0/README.md +390 -0
- paperforge_diagrams-0.1.0/paperforge_diagrams.egg-info/PKG-INFO +408 -0
- paperforge_diagrams-0.1.0/paperforge_diagrams.egg-info/SOURCES.txt +21 -0
- paperforge_diagrams-0.1.0/paperforge_diagrams.egg-info/dependency_links.txt +1 -0
- paperforge_diagrams-0.1.0/paperforge_diagrams.egg-info/requires.txt +8 -0
- paperforge_diagrams-0.1.0/paperforge_diagrams.egg-info/top_level.txt +1 -0
- paperforge_diagrams-0.1.0/pyproject.toml +37 -0
- paperforge_diagrams-0.1.0/setup.cfg +4 -0
- paperforge_diagrams-0.1.0/tests/test_base.py +141 -0
- paperforge_diagrams-0.1.0/tests/test_class_diagram.py +139 -0
- paperforge_diagrams-0.1.0/tests/test_er_diagram.py +134 -0
- paperforge_diagrams-0.1.0/tests/test_flowchart.py +128 -0
- paperforge_diagrams-0.1.0/tests/test_integration.py +172 -0
- paperforge_diagrams-0.1.0/tests/test_layout.py +135 -0
- paperforge_diagrams-0.1.0/tests/test_network.py +186 -0
- paperforge_diagrams-0.1.0/tests/test_notes_upgrades.py +124 -0
- paperforge_diagrams-0.1.0/tests/test_sequence.py +127 -0
- paperforge_diagrams-0.1.0/tests/test_shapes.py +184 -0
- paperforge_diagrams-0.1.0/tests/test_stack.py +96 -0
- paperforge_diagrams-0.1.0/tests/test_state_machine.py +127 -0
- paperforge_diagrams-0.1.0/tests/test_theme.py +46 -0
- paperforge_diagrams-0.1.0/tests/test_timing.py +83 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: paperforge-diagrams
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Vector-native PDF diagram toolkit for ReportLab Platypus
|
|
5
|
+
Author: Bharat Dangi
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: reportlab>=4.5.1
|
|
12
|
+
Requires-Dist: pydantic>=2.13.4
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=9.0.3; extra == "dev"
|
|
15
|
+
Requires-Dist: pytest-cov>=6.0; extra == "dev"
|
|
16
|
+
Requires-Dist: ruff>=0.9.0; extra == "dev"
|
|
17
|
+
Requires-Dist: mypy>=1.11.0; extra == "dev"
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
# 📊 paperforge_diagrams
|
|
21
|
+
|
|
22
|
+
[](https://www.python.org/)
|
|
23
|
+
[](LICENSE)
|
|
24
|
+
[](#)
|
|
25
|
+
|
|
26
|
+
Vector-native PDF diagram toolkit for ReportLab Platypus. Draw complex diagrams natively inside your ReportLab documents with a clean, pythonic API.
|
|
27
|
+
|
|
28
|
+
No raster images, no external command-line tools, and no internet connection required. All diagram types compile directly to `reportlab.graphics.shapes.Drawing` objects that wrap seamlessly as Platypus Flowables.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 🚀 Features
|
|
33
|
+
|
|
34
|
+
* **Flowchart**: ANSI/ISO flowchart symbols (terminals, processes, decisions, io, connectors).
|
|
35
|
+
* **SequenceDiagram**: UML sequence diagrams (actors, lifelines, activation bars, message arrows, dividers).
|
|
36
|
+
* **ClassDiagram**: UML class diagrams (class boxes, headers, attributes, methods, relations).
|
|
37
|
+
* **ERDiagram**: Entity-Relationship diagrams (entities, relations, primary key and multi-value attributes).
|
|
38
|
+
* **StateMachine**: DFA/NFA and process state diagrams (states, transitions, initial/accepting indicators).
|
|
39
|
+
* **NetworkDiagram**: Network topology (hosts, servers, databases, links).
|
|
40
|
+
* **ArchitectureDiagram**: System architecture topologies with clients, services, databases, queues, and auto-routed connections.
|
|
41
|
+
* **AWSDiagram**: Cloud infrastructure architecture with AWS icons (EC2, S3, RDS, Lambda, SQS).
|
|
42
|
+
* **GitDiagram**: Git branch timeline diagrams with commits, merges, and distinct branch lanes.
|
|
43
|
+
* **SchemaDiagram**: Database table schema definition diagrams with field types, primary keys, and foreign-key links.
|
|
44
|
+
* **C4ContainerDiagram**: C4 Model container diagrams with System Context, Container, Component layers, and descriptive relationships.
|
|
45
|
+
* **TimingDiagram**: Digital waveform timing diagrams.
|
|
46
|
+
* **LayeredStack**: OSI model, TCP/IP stack, memory hierarchy.
|
|
47
|
+
* **Standalone Export**: Save drawings directly to PDF, SVG, and PNG/JPG formats.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 📦 Installation
|
|
52
|
+
|
|
53
|
+
Install the package locally:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install ./paperforge_diagrams
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 🎨 Supported Diagram Types & Examples
|
|
62
|
+
|
|
63
|
+
### 1. Flowchart
|
|
64
|
+
Draw processes, decisions, and connectors with auto-layout or manual coordinates.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import paperforge_diagrams as pd
|
|
68
|
+
|
|
69
|
+
# Create a horizontal flowchart with a custom scale factor
|
|
70
|
+
fc = pd.Flowchart(
|
|
71
|
+
width=400,
|
|
72
|
+
height=150,
|
|
73
|
+
direction="LR", # 'TB' (top-to-bottom, default) or 'LR' (left-to-right)
|
|
74
|
+
scale_factor=1.0, # Optional manual override for text/box sizing (default: auto-computed)
|
|
75
|
+
)
|
|
76
|
+
fc.terminal("start", "START")
|
|
77
|
+
fc.process("calc", "Compute Sum")
|
|
78
|
+
fc.decision("check", "Sum > 100?")
|
|
79
|
+
fc.terminal("end", "END")
|
|
80
|
+
|
|
81
|
+
fc.edge("start", "calc")
|
|
82
|
+
fc.edge("calc", "check")
|
|
83
|
+
fc.edge("check", "end", branch="yes")
|
|
84
|
+
fc.edge("check", "calc", branch="no", orthogonal=True)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. Sequence Diagram
|
|
88
|
+
Draw actor interactions over lifelines.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import paperforge_diagrams as pd
|
|
92
|
+
|
|
93
|
+
seq = pd.SequenceDiagram(width=400, height=220)
|
|
94
|
+
seq.actor("c", "Client")
|
|
95
|
+
seq.actor("s", "Server")
|
|
96
|
+
|
|
97
|
+
seq.activate("c")
|
|
98
|
+
seq.message("c", "s", "HTTP GET /index.html")
|
|
99
|
+
seq.activate("s")
|
|
100
|
+
seq.divider("Internal Processing")
|
|
101
|
+
# Arrow styles: 'solid' (default), 'dashed', 'solid_open', 'dashed_open'
|
|
102
|
+
seq.message("s", "c", "200 OK (HTML Document)", arrow="dashed")
|
|
103
|
+
seq.deactivate("s")
|
|
104
|
+
seq.deactivate("c")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
### 3. Layered Stack
|
|
109
|
+
Draw OSI stacks, memory hierarchies, or layered architectures.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import paperforge_diagrams as pd
|
|
113
|
+
|
|
114
|
+
stack = pd.LayeredStack(width=300, height=180)
|
|
115
|
+
stack.layer("Application", sublabel="HTTP, DNS")
|
|
116
|
+
stack.layer("Transport", sublabel="TCP, UDP")
|
|
117
|
+
stack.divider() # Draw a thicker divider line after Transport layer
|
|
118
|
+
stack.layer("Network", sublabel="IP, ICMP")
|
|
119
|
+
stack.layer("Link", sublabel="Ethernet")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
### 4. Network Diagram
|
|
124
|
+
Draw network topologies with hosts, clouds, switches, routers, and firewalls.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
import paperforge_diagrams as pd
|
|
128
|
+
|
|
129
|
+
net = pd.NetworkDiagram(width=500, height=220)
|
|
130
|
+
net.node("inet", "Internet", x=50, y=110, kind="cloud")
|
|
131
|
+
net.node("fw", "Firewall", x=160, y=110, kind="firewall")
|
|
132
|
+
net.node("sw", "Core Switch", x=270, y=110, kind="switch")
|
|
133
|
+
net.node("srv", "Web Server", x=380, y=150, kind="server")
|
|
134
|
+
net.node("db", "Database", x=380, y=70, kind="database")
|
|
135
|
+
|
|
136
|
+
net.link("inet", "fw")
|
|
137
|
+
net.link("fw", "sw")
|
|
138
|
+
net.link("sw", "srv")
|
|
139
|
+
net.link("sw", "db")
|
|
140
|
+
|
|
141
|
+
# Also supports building standard topologies programmatically:
|
|
142
|
+
# net.star_topology(center_id="sw", center_label="Switch", spoke_ids=["h1", "h2", "h3"])
|
|
143
|
+
# net.bus_topology(node_ids=["n1", "n2", "n3"])
|
|
144
|
+
# net.ring_topology(node_ids=["r1", "r2", "r3"])
|
|
145
|
+
# net.mesh_topology(node_ids=["m1", "m2", "m3"])
|
|
146
|
+
# net.tree_topology(parent_child_map={"root": ["child1", "child2"]})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
### 5. UML Class Diagram
|
|
151
|
+
Draw UML class diagrams with visibility, attributes, and methods.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
import paperforge_diagrams as pd
|
|
155
|
+
|
|
156
|
+
cd = pd.ClassDiagram(width=400, height=250, class_w=120)
|
|
157
|
+
cd.uml_class("Shape", "Shape", stereotype="abstract", methods=["+ area(): double"])
|
|
158
|
+
cd.uml_class("Circle", "Circle", attributes=["- radius: double"], methods=["+ area(): double"])
|
|
159
|
+
# Relationship kinds: 'inheritance', 'realization', 'composition', 'aggregation', 'association', 'dependency'
|
|
160
|
+
cd.relate("Circle", "Shape", kind="inheritance")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
### 6. Entity-Relationship Diagram (ER)
|
|
165
|
+
Draw entity-relationship diagrams (Chen notation) with cardinalities.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
import paperforge_diagrams as pd
|
|
169
|
+
|
|
170
|
+
er = pd.ERDiagram(width=450, height=200)
|
|
171
|
+
er.entity("Customer")
|
|
172
|
+
er.relationship("Buys")
|
|
173
|
+
er.entity("Product")
|
|
174
|
+
er.entity_attributes("Customer", [("ID", {"pk": True}), "Name"])
|
|
175
|
+
er.entity_attributes("Product", [("SKU", {"pk": True}), "Price"])
|
|
176
|
+
er.connect("Customer", "Buys", card_from="1", card_to="N")
|
|
177
|
+
er.connect("Product", "Buys", card_from="1", card_to="N")
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 7. State Machine (DFA / Process Transitions)
|
|
181
|
+
Draw finite state automata or lifecycle models.
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
import paperforge_diagrams as pd
|
|
185
|
+
|
|
186
|
+
sm = pd.StateMachine(width=400, height=180)
|
|
187
|
+
sm.state("s0", "Init", x=70, y=90, initial=True)
|
|
188
|
+
sm.state("s1", "Active", x=200, y=90)
|
|
189
|
+
sm.state("s2", "Success", x=330, y=90, accepting=True)
|
|
190
|
+
|
|
191
|
+
sm.transition("s0", "s1", label="start")
|
|
192
|
+
sm.transition("s1", "s1", label="process")
|
|
193
|
+
sm.transition("s1", "s2", label="complete")
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 8. Timing Diagram
|
|
197
|
+
Draw digital timing signal waveforms.
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
import paperforge_diagrams as pd
|
|
201
|
+
|
|
202
|
+
td = pd.TimingDiagram(width=400, height=150)
|
|
203
|
+
td.clock("CLK", period=20.0, cycles=6)
|
|
204
|
+
td.signal("RESET", transitions=[(0, 1), (15, 0)])
|
|
205
|
+
td.signal("DATA", transitions=[(0, 0), (35, 1), (75, 0)])
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 9. Database Schema Diagram
|
|
209
|
+
Draw database table structures with primary/foreign keys and relationships.
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
import paperforge_diagrams as pd
|
|
213
|
+
|
|
214
|
+
schema = pd.SchemaDiagram(width=450, height=200)
|
|
215
|
+
schema.table("users", [
|
|
216
|
+
("id", "INTEGER", {"pk": True}),
|
|
217
|
+
("email", "VARCHAR", {}),
|
|
218
|
+
("created_at", "TIMESTAMP", {})
|
|
219
|
+
])
|
|
220
|
+
schema.table("orders", [
|
|
221
|
+
("id", "INTEGER", {"pk": True}),
|
|
222
|
+
("user_id", "INTEGER", {"fk": True}),
|
|
223
|
+
("total", "DECIMAL", {})
|
|
224
|
+
])
|
|
225
|
+
schema.relation("orders", "user_id", "users", "id")
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 10. Service Architecture Diagram
|
|
229
|
+
Draw multi-tier system topologies (clients, microservices, databases, queues) with automatic or manual layouts.
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
import paperforge_diagrams as pd
|
|
233
|
+
|
|
234
|
+
# Supports orientation: 'horizontal' (default) or 'vertical'
|
|
235
|
+
arch = pd.ArchitectureDiagram(width=450, height=220, orientation="horizontal")
|
|
236
|
+
arch.client("web", "Web Client")
|
|
237
|
+
arch.service("api", "Gateway API")
|
|
238
|
+
arch.database("db", "Main Database")
|
|
239
|
+
arch.queue("q", "Message Broker")
|
|
240
|
+
|
|
241
|
+
arch.connect("web", "api", "HTTPS")
|
|
242
|
+
arch.connect("api", "db", "TCP/3306")
|
|
243
|
+
arch.connect("api", "q", "AMQP")
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
### 11. C4 Container Diagram
|
|
248
|
+
Draw C4 Model Container views to document software systems, containers, technologies, and relationships.
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
import paperforge_diagrams as pd
|
|
252
|
+
|
|
253
|
+
c4 = pd.C4ContainerDiagram(width=400, height=200)
|
|
254
|
+
c4.system("user", "Customer")
|
|
255
|
+
c4.container("web", "SPA (React)", "Provides shopping UI")
|
|
256
|
+
c4.container("api", "API Application (Go)", "Handles business logic")
|
|
257
|
+
c4.container("db", "Database (Postgres)", "Stores order data")
|
|
258
|
+
|
|
259
|
+
c4.relate("user", "web", "Uses")
|
|
260
|
+
c4.relate("web", "api", "Makes API calls to")
|
|
261
|
+
c4.relate("api", "db", "Reads/Writes to")
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 12. Git Branch Flow Diagram
|
|
265
|
+
Draw commits, branch lanes, merges, and timelines horizontally.
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
import paperforge_diagrams as pd
|
|
269
|
+
|
|
270
|
+
git = pd.GitDiagram(width=400, height=150)
|
|
271
|
+
git.commit("main", "C1: Initial Commit")
|
|
272
|
+
git.branch("main", "feature")
|
|
273
|
+
git.commit("feature", "C2: Implement login")
|
|
274
|
+
git.commit("main", "C3: Hotfix bug")
|
|
275
|
+
git.merge("feature", "main", "C4: Merge feature/login")
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### 13. AWS Diagram
|
|
279
|
+
Draw AWS cloud infrastructure diagrams using vector-native AWS icons.
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
import paperforge_diagrams as pd
|
|
283
|
+
|
|
284
|
+
# Supports orientation: 'horizontal' (default) or 'vertical'
|
|
285
|
+
aws = pd.AWSDiagram(width=450, height=220, orientation="horizontal")
|
|
286
|
+
# Supported node methods: ec2(), rds(), s3(), lambda_fn(), sqs()
|
|
287
|
+
aws.ec2("web", "Web Server")
|
|
288
|
+
aws.rds("db", "RDS PostgreSQL")
|
|
289
|
+
aws.s3("bucket", "S3 Storage")
|
|
290
|
+
aws.sqs("queue", "Job Queue")
|
|
291
|
+
|
|
292
|
+
aws.connect("web", "db", "SQL Connection")
|
|
293
|
+
aws.connect("web", "bucket", "Uploads")
|
|
294
|
+
aws.connect("web", "queue", "Events")
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## 🎨 Diagram Theming & Integration
|
|
301
|
+
|
|
302
|
+
`paperforge_diagrams` comes with built-in dark and light themes, and can dynamically inherit themes from `paperforge_notes` for consistent visual layouts.
|
|
303
|
+
|
|
304
|
+
### 1. Using Preset Diagram Themes
|
|
305
|
+
Apply a theme directly to your builder instance:
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
import paperforge_diagrams as pd
|
|
309
|
+
|
|
310
|
+
# Use preset theme (pd.DARK or pd.LIGHT)
|
|
311
|
+
diagram = pd.Flowchart(width=300, height=200, theme=pd.LIGHT)
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### 2. Matching Notes Theme (Integration)
|
|
315
|
+
Derive matching diagram settings from the active document notes theme:
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
import paperforge_notes as pn
|
|
319
|
+
import paperforge_diagrams as pd
|
|
320
|
+
|
|
321
|
+
# 1. Set the notes theme
|
|
322
|
+
pn.set_theme(pn.OCEAN_DARK)
|
|
323
|
+
|
|
324
|
+
# 2. Derive the matching diagram theme
|
|
325
|
+
diag_theme = pd.DiagramTheme.from_notes_theme(pn.get_theme())
|
|
326
|
+
|
|
327
|
+
# 3. Apply it to your diagrams
|
|
328
|
+
fc = pd.Flowchart(width=400, height=200, theme=diag_theme)
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 3. Customizing & Overriding Themes
|
|
332
|
+
Modify a theme on the fly using `model_copy(update={...})`:
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
# Create a print-optimized black-and-white diagram theme
|
|
336
|
+
bw_theme = pd.DiagramTheme.from_notes_theme(pn.get_theme()).model_copy(
|
|
337
|
+
update={
|
|
338
|
+
"stack_colors": ("#ffffff", "#ffffff", "#ffffff"),
|
|
339
|
+
"stack_stroke": "#000000",
|
|
340
|
+
"stack_text": "#000000",
|
|
341
|
+
"stack_sublabel_text": "#000000",
|
|
342
|
+
"bg": "#ffffff",
|
|
343
|
+
"text": "#000000",
|
|
344
|
+
"node_fill": "#ffffff",
|
|
345
|
+
"node_stroke": "#000000",
|
|
346
|
+
"node_text": "#000000",
|
|
347
|
+
"font_name": "Times-Roman",
|
|
348
|
+
"font_name_bold": "Times-Bold",
|
|
349
|
+
"font_name_italic": "Times-Italic",
|
|
350
|
+
}
|
|
351
|
+
)
|
|
352
|
+
# Apply to stack diagram
|
|
353
|
+
stack = pd.LayeredStack(width=300, height=180, theme=bw_theme)
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## 💾 Standalone Export
|
|
359
|
+
|
|
360
|
+
Export your diagrams directly to vector or raster formats without a ReportLab story:
|
|
361
|
+
|
|
362
|
+
```python
|
|
363
|
+
diagram = pd.Flowchart(width=300, height=200)
|
|
364
|
+
diagram.terminal("s", "START").terminal("e", "END").edge("s", "e")
|
|
365
|
+
|
|
366
|
+
# Save to PDF (vector)
|
|
367
|
+
diagram.save("output.pdf")
|
|
368
|
+
|
|
369
|
+
# Save to SVG (vector)
|
|
370
|
+
diagram.save("output.svg")
|
|
371
|
+
|
|
372
|
+
# Save to PNG (raster)
|
|
373
|
+
diagram.save("output.png")
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## 📝 Integration with `paperforge_notes`
|
|
379
|
+
|
|
380
|
+
To embed a vector diagram into a PaperForge notes document, call `.as_flowable()` and pass it to `pn.add(...)`:
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
import paperforge_notes as pn
|
|
384
|
+
import paperforge_diagrams as pd
|
|
385
|
+
|
|
386
|
+
fc = pd.Flowchart(width=pn.CW, height=200)
|
|
387
|
+
fc.terminal("s", "START").terminal("e", "END").edge("s", "e")
|
|
388
|
+
|
|
389
|
+
# Add the diagram flowables directly
|
|
390
|
+
pn.add(fc.as_flowable())
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Static Type-Checking (Pyright / Pylance)
|
|
394
|
+
If you see static type warnings in your IDE:
|
|
395
|
+
> `Argument of type "list[Unknown]" cannot be assigned to parameter "x" of type "Flowable" in function "add"`
|
|
396
|
+
|
|
397
|
+
This happens because `.as_flowable()` returns a `list[Flowable]` (which packages the drawing flowable along with its optional caption `Paragraph` flowable) rather than a single raw `Flowable`.
|
|
398
|
+
|
|
399
|
+
`paperforge_notes` handles this union type cleanly. Calling `pn.add(diagram.as_flowable())` is type-safe and fully compliant.
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## 📋 Requirements & License
|
|
404
|
+
|
|
405
|
+
* **Python** >= 3.11
|
|
406
|
+
* **reportlab** >= 4.5.1
|
|
407
|
+
* **pydantic** >= 2.13.4
|
|
408
|
+
* Licensed under the **MIT License**.
|