forktex-documents 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.
Files changed (30) hide show
  1. forktex_documents-0.1.0/LICENSE +45 -0
  2. forktex_documents-0.1.0/NOTICE +41 -0
  3. forktex_documents-0.1.0/PKG-INFO +179 -0
  4. forktex_documents-0.1.0/README.md +143 -0
  5. forktex_documents-0.1.0/pyproject.toml +155 -0
  6. forktex_documents-0.1.0/src/forktex_documents/__init__.py +291 -0
  7. forktex_documents-0.1.0/src/forktex_documents/assets/styles/paged.css +314 -0
  8. forktex_documents-0.1.0/src/forktex_documents/domain/__init__.py +36 -0
  9. forktex_documents-0.1.0/src/forktex_documents/domain/base.py +81 -0
  10. forktex_documents-0.1.0/src/forktex_documents/domain/blocks.py +1253 -0
  11. forktex_documents-0.1.0/src/forktex_documents/domain/canonical.py +139 -0
  12. forktex_documents-0.1.0/src/forktex_documents/domain/catalog.py +280 -0
  13. forktex_documents-0.1.0/src/forktex_documents/domain/document.py +118 -0
  14. forktex_documents-0.1.0/src/forktex_documents/domain/entities.py +563 -0
  15. forktex_documents-0.1.0/src/forktex_documents/domain/errors.py +163 -0
  16. forktex_documents-0.1.0/src/forktex_documents/domain/fiscal.py +193 -0
  17. forktex_documents-0.1.0/src/forktex_documents/domain/formatting.py +167 -0
  18. forktex_documents-0.1.0/src/forktex_documents/domain/instance.py +159 -0
  19. forktex_documents-0.1.0/src/forktex_documents/domain/localization.py +250 -0
  20. forktex_documents-0.1.0/src/forktex_documents/domain/mandates.py +137 -0
  21. forktex_documents-0.1.0/src/forktex_documents/domain/numbering.py +108 -0
  22. forktex_documents-0.1.0/src/forktex_documents/domain/registry.py +268 -0
  23. forktex_documents-0.1.0/src/forktex_documents/domain/series.py +119 -0
  24. forktex_documents-0.1.0/src/forktex_documents/domain/theme.py +317 -0
  25. forktex_documents-0.1.0/src/forktex_documents/ports.py +290 -0
  26. forktex_documents-0.1.0/src/forktex_documents/py.typed +0 -0
  27. forktex_documents-0.1.0/src/forktex_documents/render/__init__.py +36 -0
  28. forktex_documents-0.1.0/src/forktex_documents/render/assets.py +113 -0
  29. forktex_documents-0.1.0/src/forktex_documents/render/charts.py +318 -0
  30. forktex_documents-0.1.0/src/forktex_documents/render/projector.py +271 -0
@@ -0,0 +1,45 @@
1
+ ForkTex Documents -- Dual License
2
+
3
+ Copyright (C) 2026 FORKTEX S.R.L.
4
+
5
+ This software is licensed under a dual-license model:
6
+
7
+ ============================================================================
8
+
9
+ 1. OPEN-SOURCE LICENSE -- GNU Affero General Public License v3.0 (AGPL-3.0)
10
+
11
+ You may use, modify, and redistribute this software under the terms of the
12
+ GNU Affero General Public License v3.0 or later, as published by the Free
13
+ Software Foundation. The full license text is available at:
14
+ https://www.gnu.org/licenses/agpl-3.0.html
15
+
16
+ Key obligations under AGPL-3.0:
17
+ - Any modified version MUST be released under the same AGPL-3.0 license.
18
+ - If you run a modified version on a server and let users interact with it
19
+ over a network, you MUST make the complete source code available to those
20
+ users under AGPL-3.0.
21
+ - You MUST preserve all copyright notices and attribution to FORKTEX S.R.L.
22
+
23
+ ============================================================================
24
+
25
+ 2. COMMERCIAL LICENSE
26
+
27
+ If you wish to use this software without the obligations of AGPL-3.0 --
28
+ for example, to keep your modifications proprietary, to embed this software
29
+ in a closed-source product, or to operate it as part of a commercial service
30
+ without releasing your source code -- you MUST obtain a commercial license
31
+ from FORKTEX S.R.L.
32
+
33
+ Commercial licensing includes, but is not limited to:
34
+ - SaaS / hosted deployments generating revenue
35
+ - Embedding in proprietary products or platforms
36
+ - Redistribution in closed-source form
37
+ - Use by organizations exceeding the AGPL-3.0 compliance scope
38
+
39
+ For commercial licensing inquiries, contact:
40
+ info@forktex.com
41
+
42
+ ============================================================================
43
+
44
+ Unless you have obtained a separate commercial license from FORKTEX S.R.L.,
45
+ your use of this software is governed exclusively by the AGPL-3.0 license.
@@ -0,0 +1,41 @@
1
+ ForkTex Documents
2
+ Copyright (C) 2026 FORKTEX S.R.L.
3
+
4
+ This product includes software developed by FORKTEX S.R.L. (info@forktex.com).
5
+
6
+ `forktex-documents` generates the documents a company issues -- invoices,
7
+ contracts, stock-reception notes, declarations, reports -- from structured
8
+ data. A record is validated by a Pydantic canonical, composed into a typed
9
+ block tree, and projected to HTML, PDF or UBL XML. The rendered page is a view
10
+ over the record, never a source of truth. It ships no company's data: a
11
+ consumer supplies the parties, the lines and the numbering.
12
+
13
+ LICENSING
14
+
15
+ This software is dual-licensed:
16
+
17
+ 1. GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later).
18
+ Use it under the AGPL and its obligations apply -- notably that running a
19
+ modified version over a network requires offering the corresponding source
20
+ to its users.
21
+
22
+ 2. A proprietary commercial license from FORKTEX S.R.L.
23
+
24
+ If you cannot meet the AGPL's obligations -- use in a proprietary product, a
25
+ SaaS deployment, or any context where offering source is not possible -- you
26
+ MUST obtain a commercial license. Contact info@forktex.com.
27
+
28
+ See the LICENSE file for the full terms of both.
29
+
30
+ THIRD-PARTY NOTICES
31
+
32
+ This package depends on, but does not vendor:
33
+
34
+ * pydantic -- MIT
35
+ * jinja2 -- BSD-3-Clause (sandboxed text-fragment rendering only)
36
+ * forktex -- AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
37
+ * weasyprint -- BSD-3-Clause (optional, for the `pdf` extra)
38
+
39
+ The document text carried in this repository's Romanian locale bundle is
40
+ transcribed from statutory and administrative forms; the legal wording is not
41
+ the copyrightable work of FORKTEX S.R.L.
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.4
2
+ Name: forktex-documents
3
+ Version: 0.1.0
4
+ Summary: A framework for generating documents from structured data: typed blocks that render themselves, canonicals that compute their own totals, and cited mandates. Document types belong to consumers.
5
+ License-Expression: AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
6
+ License-File: LICENSE
7
+ License-File: NOTICE
8
+ Author: FORKTEX
9
+ Author-email: info@forktex.com
10
+ Requires-Python: >=3.14,<4.0
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Financial and Insurance Industry
14
+ Classifier: Intended Audience :: Legal Industry
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Office/Business
20
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
21
+ Classifier: Topic :: Printing
22
+ Classifier: Topic :: Text Processing :: Markup :: HTML
23
+ Classifier: Typing :: Typed
24
+ Provides-Extra: pdf
25
+ Requires-Dist: forktex (>=0.10,<0.11)
26
+ Requires-Dist: jinja2 (>=3.1)
27
+ Requires-Dist: pydantic (>=2.12)
28
+ Requires-Dist: weasyprint (>=62) ; extra == "pdf"
29
+ Project-URL: Bug Tracker, https://github.com/forktex/forktex-documents/issues
30
+ Project-URL: Changelog, https://github.com/forktex/forktex-documents/blob/master/CHANGELOG.md
31
+ Project-URL: Documentation, https://github.com/forktex/forktex-documents/tree/master/docs
32
+ Project-URL: Homepage, https://forktex.com
33
+ Project-URL: Repository, https://github.com/forktex/forktex-documents
34
+ Description-Content-Type: text/markdown
35
+
36
+ # forktex-documents
37
+
38
+ A framework for generating documents from structured data. It ships the
39
+ vocabulary and the machinery — **not** document types: an invoice, a contract, a
40
+ delivery note are things a consumer defines, because what they must contain is
41
+ a fact about a business and a jurisdiction, not about a renderer.
42
+
43
+ ```
44
+ data (JSON) → canonical (Pydantic) → block tree → HTML → PDF
45
+ ↘ read directly for a machine projection (XML, JSON…)
46
+ ```
47
+
48
+ A PDF here is a **view over an object**, never a source of truth. The canonical
49
+ record is the document; the page is one projection of it. A machine projection
50
+ reads the canonical directly rather than the block tree — you cannot recover
51
+ "19% VAT on this line" from a table cell that says `19%`, which is why the two
52
+ representations both exist.
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pip install forktex-documents # canonical, blocks, HTML
58
+ pip install "forktex-documents[pdf]" # + PDF, via WeasyPrint
59
+ ```
60
+
61
+ PDF is an extra because WeasyPrint pulls native libraries (cairo, pango) that a
62
+ consumer producing only HTML or a machine projection should not have to install.
63
+
64
+ ## What it does
65
+
66
+ A consumer declares its own canonical, and gets validation, composition and
67
+ rendering from the framework:
68
+
69
+ ```python
70
+ from forktex_documents import FamilyCanonical, Mandate, ParagraphBlock, known
71
+
72
+ @known
73
+ class DeliveryNote(FamilyCanonical):
74
+ family = "logistics"
75
+ mandates = (
76
+ Mandate(
77
+ path="carrier",
78
+ authority="acme-logistics",
79
+ reference="ops:carrier-is-named",
80
+ reason="A note with no carrier cannot be chased when the goods do not arrive.",
81
+ ),
82
+ )
83
+ reference: str
84
+ carrier: str
85
+
86
+ def blocks(self, profile):
87
+ return (ParagraphBlock(text=f"Carried by {self.carrier}."),)
88
+
89
+ note = DeliveryNote(reference="DN-1", carrier="Speedy")
90
+ note.check() # raises if a mandate is unsatisfied
91
+ note.blocks(profile) # a typed block tree, ready to project
92
+ ```
93
+
94
+ See [`examples/showcase.py`](examples/showcase.py) for a document exercising
95
+ every block kind.
96
+
97
+ ## The ideas worth knowing
98
+
99
+ **Derived values are never stored.** A consumer's totals, VAT subtotals and
100
+ expiry dates are `computed_field`s on its own canonical — and this package is
101
+ what makes that hold. Every model sets `extra="forbid"`, so a record carrying a
102
+ hand-written `total` is a validation error rather than a silent disagreement,
103
+ and a stored total that was tampered with is dropped and recomputed on load.
104
+ A structural guarantee rather than a review rule.
105
+
106
+ **Mandates are cited and conditional.** What a jurisdiction requires is data:
107
+ a path, the authority, the legal reference, and *why*. Conditions read computed
108
+ flags, so a retail stock-reception note demands a selling price while a
109
+ cost-method one does not, and a delivery showing quantity differences demands a
110
+ three-member committee and a third copy — because those are the rules the form
111
+ actually carries.
112
+
113
+ **Fiscal facts are time-versioned.** VAT rates resolve against the document's
114
+ own date, so re-rendering a July 2025 Romanian invoice reproduces 19% rather
115
+ than silently restating it at the current 21%. A date no schedule covers raises
116
+ instead of guessing.
117
+
118
+ **Blocks render themselves.** Twenty-five typed block kinds form a closed
119
+ discriminated union, each emitting its own HTML. There is no template language
120
+ deciding layout — the only templating is sandboxed *text* substitution for
121
+ locale fragments, where a clause has placeholders to resolve.
122
+
123
+ **Identifiers are validated, not merely shaped.** CUI and CNP check digits,
124
+ IBAN mod-97 across every country's length rule, per-member-state VAT formats.
125
+ A transposed digit in a national id files a real document against the wrong
126
+ person; the checksum is what catches it.
127
+
128
+ ## What is *not* here
129
+
130
+ No invoice, no contract, no delivery note, and no jurisdiction. There is no
131
+ `RO_VAT_RATES`, no CUI or CNP check digit, and no default country, language or
132
+ currency anywhere — a default jurisdiction is a wrong jurisdiction for everyone
133
+ else, quietly. A consumer supplies its own:
134
+
135
+ - a `LegislationProfile` with its date format, separators, currency and mandates
136
+ - a `VatSchedule` with its rates and their validity windows
137
+ - national identifier validators, registered into `NATIONAL_TAX_ID_VALIDATORS`
138
+ - the numbers already spent, passed to a register as `BURNED`
139
+
140
+ ## Layout
141
+
142
+ ```
143
+ domain/ blocks, canonical, mandates, entities, fiscal, localization, theme,
144
+ registry, series ↑ no I/O — `grep Path domain/` is empty, by test
145
+ render/ the HTML projector, inline SVG charts, the asset port
146
+ ```
147
+
148
+ Imports run one way: `domain → render → facade`.
149
+
150
+ ## Development
151
+
152
+ ```bash
153
+ make install # poetry install --with dev
154
+ make ci # format, lint, licences, docs, types, audit, tests, build, smoke
155
+ make reference # rewrite committed reference renders — then read the diff
156
+ make docs # render the design records in sources/ into docs/
157
+ ```
158
+
159
+ `make ci` re-renders every reference document and diffs it byte-for-byte
160
+ against the committed HTML. A generated artefact that was hand-edited, or a
161
+ change that altered output nobody intended, fails the build.
162
+
163
+ The design notes in [`docs/`](docs/) are generated the same way, from JSON
164
+ records in [`sources/`](sources/) via
165
+ [`forktex-knowledge`](https://pypi.org/project/forktex-knowledge/) — the
166
+ library eats its own principle, so a hand-edited page fails the gate rather
167
+ than becoming a second author. Start with
168
+ [everything-is-a-document](docs/everything-is-a-document.md) for why a page is
169
+ never a source of truth, then
170
+ [mandates-are-cited-data](docs/mandates-are-cited-data.md) for why a legal
171
+ requirement travels with its citation.
172
+
173
+ ## Licence
174
+
175
+ Dual-licensed: **AGPL-3.0-or-later**, or a commercial licence from FORKTEX
176
+ S.R.L. for use in proprietary products, SaaS deployments, or any context where
177
+ the AGPL's obligations cannot be met. See `LICENSE` and `NOTICE`, or contact
178
+ info@forktex.com.
179
+
@@ -0,0 +1,143 @@
1
+ # forktex-documents
2
+
3
+ A framework for generating documents from structured data. It ships the
4
+ vocabulary and the machinery — **not** document types: an invoice, a contract, a
5
+ delivery note are things a consumer defines, because what they must contain is
6
+ a fact about a business and a jurisdiction, not about a renderer.
7
+
8
+ ```
9
+ data (JSON) → canonical (Pydantic) → block tree → HTML → PDF
10
+ ↘ read directly for a machine projection (XML, JSON…)
11
+ ```
12
+
13
+ A PDF here is a **view over an object**, never a source of truth. The canonical
14
+ record is the document; the page is one projection of it. A machine projection
15
+ reads the canonical directly rather than the block tree — you cannot recover
16
+ "19% VAT on this line" from a table cell that says `19%`, which is why the two
17
+ representations both exist.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install forktex-documents # canonical, blocks, HTML
23
+ pip install "forktex-documents[pdf]" # + PDF, via WeasyPrint
24
+ ```
25
+
26
+ PDF is an extra because WeasyPrint pulls native libraries (cairo, pango) that a
27
+ consumer producing only HTML or a machine projection should not have to install.
28
+
29
+ ## What it does
30
+
31
+ A consumer declares its own canonical, and gets validation, composition and
32
+ rendering from the framework:
33
+
34
+ ```python
35
+ from forktex_documents import FamilyCanonical, Mandate, ParagraphBlock, known
36
+
37
+ @known
38
+ class DeliveryNote(FamilyCanonical):
39
+ family = "logistics"
40
+ mandates = (
41
+ Mandate(
42
+ path="carrier",
43
+ authority="acme-logistics",
44
+ reference="ops:carrier-is-named",
45
+ reason="A note with no carrier cannot be chased when the goods do not arrive.",
46
+ ),
47
+ )
48
+ reference: str
49
+ carrier: str
50
+
51
+ def blocks(self, profile):
52
+ return (ParagraphBlock(text=f"Carried by {self.carrier}."),)
53
+
54
+ note = DeliveryNote(reference="DN-1", carrier="Speedy")
55
+ note.check() # raises if a mandate is unsatisfied
56
+ note.blocks(profile) # a typed block tree, ready to project
57
+ ```
58
+
59
+ See [`examples/showcase.py`](examples/showcase.py) for a document exercising
60
+ every block kind.
61
+
62
+ ## The ideas worth knowing
63
+
64
+ **Derived values are never stored.** A consumer's totals, VAT subtotals and
65
+ expiry dates are `computed_field`s on its own canonical — and this package is
66
+ what makes that hold. Every model sets `extra="forbid"`, so a record carrying a
67
+ hand-written `total` is a validation error rather than a silent disagreement,
68
+ and a stored total that was tampered with is dropped and recomputed on load.
69
+ A structural guarantee rather than a review rule.
70
+
71
+ **Mandates are cited and conditional.** What a jurisdiction requires is data:
72
+ a path, the authority, the legal reference, and *why*. Conditions read computed
73
+ flags, so a retail stock-reception note demands a selling price while a
74
+ cost-method one does not, and a delivery showing quantity differences demands a
75
+ three-member committee and a third copy — because those are the rules the form
76
+ actually carries.
77
+
78
+ **Fiscal facts are time-versioned.** VAT rates resolve against the document's
79
+ own date, so re-rendering a July 2025 Romanian invoice reproduces 19% rather
80
+ than silently restating it at the current 21%. A date no schedule covers raises
81
+ instead of guessing.
82
+
83
+ **Blocks render themselves.** Twenty-five typed block kinds form a closed
84
+ discriminated union, each emitting its own HTML. There is no template language
85
+ deciding layout — the only templating is sandboxed *text* substitution for
86
+ locale fragments, where a clause has placeholders to resolve.
87
+
88
+ **Identifiers are validated, not merely shaped.** CUI and CNP check digits,
89
+ IBAN mod-97 across every country's length rule, per-member-state VAT formats.
90
+ A transposed digit in a national id files a real document against the wrong
91
+ person; the checksum is what catches it.
92
+
93
+ ## What is *not* here
94
+
95
+ No invoice, no contract, no delivery note, and no jurisdiction. There is no
96
+ `RO_VAT_RATES`, no CUI or CNP check digit, and no default country, language or
97
+ currency anywhere — a default jurisdiction is a wrong jurisdiction for everyone
98
+ else, quietly. A consumer supplies its own:
99
+
100
+ - a `LegislationProfile` with its date format, separators, currency and mandates
101
+ - a `VatSchedule` with its rates and their validity windows
102
+ - national identifier validators, registered into `NATIONAL_TAX_ID_VALIDATORS`
103
+ - the numbers already spent, passed to a register as `BURNED`
104
+
105
+ ## Layout
106
+
107
+ ```
108
+ domain/ blocks, canonical, mandates, entities, fiscal, localization, theme,
109
+ registry, series ↑ no I/O — `grep Path domain/` is empty, by test
110
+ render/ the HTML projector, inline SVG charts, the asset port
111
+ ```
112
+
113
+ Imports run one way: `domain → render → facade`.
114
+
115
+ ## Development
116
+
117
+ ```bash
118
+ make install # poetry install --with dev
119
+ make ci # format, lint, licences, docs, types, audit, tests, build, smoke
120
+ make reference # rewrite committed reference renders — then read the diff
121
+ make docs # render the design records in sources/ into docs/
122
+ ```
123
+
124
+ `make ci` re-renders every reference document and diffs it byte-for-byte
125
+ against the committed HTML. A generated artefact that was hand-edited, or a
126
+ change that altered output nobody intended, fails the build.
127
+
128
+ The design notes in [`docs/`](docs/) are generated the same way, from JSON
129
+ records in [`sources/`](sources/) via
130
+ [`forktex-knowledge`](https://pypi.org/project/forktex-knowledge/) — the
131
+ library eats its own principle, so a hand-edited page fails the gate rather
132
+ than becoming a second author. Start with
133
+ [everything-is-a-document](docs/everything-is-a-document.md) for why a page is
134
+ never a source of truth, then
135
+ [mandates-are-cited-data](docs/mandates-are-cited-data.md) for why a legal
136
+ requirement travels with its citation.
137
+
138
+ ## Licence
139
+
140
+ Dual-licensed: **AGPL-3.0-or-later**, or a commercial licence from FORKTEX
141
+ S.R.L. for use in proprietary products, SaaS deployments, or any context where
142
+ the AGPL's obligations cannot be met. See `LICENSE` and `NOTICE`, or contact
143
+ info@forktex.com.
@@ -0,0 +1,155 @@
1
+ # Copyright (C) 2026 FORKTEX S.R.L.
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
4
+ #
5
+ # This file is part of ForkTex Documents.
6
+ #
7
+ # For commercial licensing -- including use in proprietary products, SaaS
8
+ # deployments, or any context where AGPL obligations cannot be met -- you
9
+ # MUST obtain a commercial license from FORKTEX S.R.L. (info@forktex.com).
10
+ #
11
+ # This program is free software: you can redistribute it and/or modify
12
+ # it under the terms of the GNU Affero General Public License as published by
13
+ # the Free Software Foundation, either version 3 of the License, or
14
+ # (at your option) any later version.
15
+ #
16
+ # This program is distributed in the hope that it will be useful,
17
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ # GNU Affero General Public License for more details.
20
+ #
21
+ # You should have received a copy of the GNU Affero General Public License
22
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
23
+
24
+ [project]
25
+ name = "forktex-documents"
26
+ version = "0.1.0"
27
+ description = "A framework for generating documents from structured data: typed blocks that render themselves, canonicals that compute their own totals, and cited mandates. Document types belong to consumers."
28
+ authors = [
29
+ {name = "FORKTEX",email = "info@forktex.com"}
30
+ ]
31
+ readme = "README.md"
32
+ # Dual-licensed: AGPL-3.0-or-later for OSS use; a proprietary commercial
33
+ # license is also available (see LICENSE + NOTICE; info@forktex.com).
34
+ license = "AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial"
35
+ license-files = ["LICENSE", "NOTICE"]
36
+ requires-python = ">=3.14,<4.0"
37
+ # 4 - Beta: the vocabulary is proven — it was extracted from a working consumer
38
+ # that renders thirteen real documents across four families. What has not been
39
+ # proven is a *second* consumer pushing on the seams, which is what would justify
40
+ # promoting this to 5.
41
+ classifiers = [
42
+ "Development Status :: 4 - Beta",
43
+ "Intended Audience :: Developers",
44
+ "Intended Audience :: Financial and Insurance Industry",
45
+ "Intended Audience :: Legal Industry",
46
+ "Operating System :: OS Independent",
47
+ "Programming Language :: Python :: 3",
48
+ "Programming Language :: Python :: 3 :: Only",
49
+ "Programming Language :: Python :: 3.14",
50
+ "Topic :: Office/Business",
51
+ "Topic :: Office/Business :: Financial :: Accounting",
52
+ "Topic :: Printing",
53
+ "Topic :: Text Processing :: Markup :: HTML",
54
+ "Typing :: Typed",
55
+ ]
56
+ dependencies = [
57
+ # The shared substrate. Consumed, not re-implemented: `forktex.error` supplies
58
+ # the one exception hierarchy this package's errors are leaves of, and
59
+ # `forktex.types` supplies the frozen value-object base every model derives
60
+ # from. Pinned to a minor range because both are surfaces we track closely.
61
+ "forktex>=0.10,<0.11",
62
+ # Every model, every validator, every computed total. The design rule that
63
+ # derived values are never stored is enforced by `computed_field` plus
64
+ # `extra="forbid"`, so this is load-bearing rather than convenience.
65
+ "pydantic>=2.12",
66
+ # Sandboxed rendering of *text fragments* only — a locale bundle's clause and
67
+ # declaration text, where a placeholder must resolve against document data.
68
+ # No template decides layout: blocks render themselves in Python. That
69
+ # distinction is why the dependency is small and stays that way.
70
+ "jinja2>=3.1",
71
+ ]
72
+
73
+ [project.optional-dependencies]
74
+ # PDF is optional because the canonical, the block tree and the HTML projection
75
+ # are useful without it, and WeasyPrint pulls native libraries (cairo, pango)
76
+ # that a consumer producing only HTML or UBL should not be made to install.
77
+ pdf = ["weasyprint>=62"]
78
+
79
+ # No [project.scripts]: this is a library. A consumer wires its own entry point,
80
+ # because the asset roots, the theme and the number register are its own.
81
+
82
+ [project.urls]
83
+ "Homepage" = "https://forktex.com"
84
+ "Repository" = "https://github.com/forktex/forktex-documents"
85
+ "Documentation" = "https://github.com/forktex/forktex-documents/tree/master/docs"
86
+ "Changelog" = "https://github.com/forktex/forktex-documents/blob/master/CHANGELOG.md"
87
+ "Bug Tracker" = "https://github.com/forktex/forktex-documents/issues"
88
+
89
+ [build-system]
90
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
91
+ build-backend = "poetry.core.masonry.api"
92
+
93
+ [tool.poetry]
94
+ # The distribution is `forktex-documents` and the import is `forktex_documents`
95
+ # — PEP 503 wants the hyphen on an index, Python wants the underscore in code.
96
+ packages = [{ include = "forktex_documents", from = "src" }]
97
+ # The stylesheet is not a sample: `HtmlProjector` reads `styles/paged.css`
98
+ # through the asset port on every render, so a wheel without it produces
99
+ # unstyled output. Shipped explicitly because it is not a `.py` file and would
100
+ # otherwise be dropped from both the sdist and the wheel.
101
+ include = [
102
+ { path = "src/forktex_documents/py.typed", format = ["sdist", "wheel"] },
103
+ { path = "src/forktex_documents/assets/**/*", format = ["sdist", "wheel"] },
104
+ ]
105
+
106
+ [dependency-groups]
107
+ dev = [
108
+ "pytest>=8.0",
109
+ "pytest-cov>=6.0",
110
+ "ruff>=0.8.0",
111
+ "pyright>=1.1.350",
112
+ "pip-audit>=2.7.0",
113
+ "build>=1.2.0",
114
+ "twine>=5.0.0",
115
+ # The drift gate renders the reference document, and `make render` takes it
116
+ # to PDF. Without it the example cannot run.
117
+ "weasyprint>=62",
118
+ # Renders the design records in `sources/` into `docs/`.
119
+ "forktex-knowledge (>=0.1.0,<0.2.0)",
120
+ ]
121
+
122
+ [tool.ruff]
123
+ target-version = "py314"
124
+ line-length = 110
125
+
126
+ [tool.ruff.lint]
127
+ select = ["E", "F", "I", "UP", "B", "SIM", "RUF", "ANN"]
128
+ # RUF001/2/3 flag characters that could be homoglyph attacks in *code*. This library's
129
+ # strings are document text in Romanian, where they are simply correct typography: an
130
+ # en dash in a range ("3–4 săptămâni"), a multiplication sign in a specification
131
+ # ("10 × 450 W"), and — load-bearing — a NO-BREAK SPACE as the thousands separator, which
132
+ # is what stops "201 020,27" being broken across a line in a printed invoice. Replacing
133
+ # them with ASCII would change what the documents say, so the rules are off rather than
134
+ # suppressed line by line.
135
+ ignore = ["RUF001", "RUF002", "RUF003"]
136
+
137
+ [tool.ruff.lint.per-file-ignores]
138
+ # `src/` carries NO exemptions — every module passes the full strict set. A new
139
+ # `Any` in a signature is a prompt to name the type, not to add a line here.
140
+ # Tests and the demo builders are held to the baseline only.
141
+ "tests/**" = ["E1", "E2", "E5", "I", "UP", "B", "SIM", "RUF", "ANN"]
142
+ "examples/**" = ["E1", "E2", "E5", "I", "UP", "B", "SIM", "RUF", "ANN"]
143
+
144
+ [tool.pyright]
145
+ # src/ only: `python:ci-is-the-gate` type-checks the library, not the demos.
146
+ include = ["src"]
147
+ exclude = ["**/__pycache__"]
148
+ pythonVersion = "3.14"
149
+ typeCheckingMode = "standard"
150
+
151
+ [tool.pytest.ini_options]
152
+ pythonpath = ["src"]
153
+ testpaths = ["tests"]
154
+ log_cli = false
155
+ log_level = "WARNING"