Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
247 lines (233 loc) · 10.3 KB
/
Copy pathpyproject.toml
File metadata and controls
247 lines (233 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "convilyn"
dynamic = ["version"]
description = "Official Convilyn client SDK — file conversion, agentic workflows, community library"
readme = "docs/README.md"
license = "Apache-2.0"
license-files = ["LICENSE"]
requires-python = ">=3.10"
authors = [
{ name = "Convilyn", email = "support@convilyn.com" },
]
keywords = [
"convilyn",
"ai",
"agent",
"workflow",
"file-conversion",
"mcp",
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Internet :: WWW/HTTP",
"Typing :: Typed",
]
dependencies = [
"httpx>=0.25.0,<1.0.0",
"pydantic>=2.0.0,<3.0.0",
"click>=8.0.0,<9.0.0",
"typing-extensions>=4.7.0; python_version < '3.11'",
]
# Offline conversion (`convilyn.local`). One extra per format family, so a user
# who wants PDF → Markdown does not also install a presentation parser and, with
# it, lxml. The composites are declared in terms of the leaves rather than
# repeating their contents, so there is one list per package, not two.
#
# `csv`, `txt` and Markdown rendering need NOTHING — they work on a bare
# install, which is worth knowing before reaching for an extra.
#
# Two things these cannot cover, and both are stated in the docs rather than
# papered over: `pptx` pulls Pillow transitively (python-pptx depends on it),
# and LibreOffice / Calibre are desktop applications that no extra can install.
#
# **The floors are the org's, not invented here**, and
# `tests/unit/qa/test_cross_file_dependency_agreement.py` in backend-api holds
# them to it. The first draft of this table wrote its own ranges and was wrong
# in the direction that matters: `Pillow>=11.0` and `pypdf>=6.0` would have let
# a user of this published package install versions carrying, respectively,
# three GHSAs (one of them a decompression bomb) and twenty-two DoS advisories
# that the rest of the repo had already floored past. A published SDK is where
# a low floor does the most damage, not the least.
#
# Upper caps were dropped for the same reason: every distinct specifier string
# is one more thing to keep in step across nine manifests, and the repo's
# convention is a floor.
[project.optional-dependencies]
pdf = ["pdfplumber>=0.11.9", "pypdf>=6.16.1"]
docx = ["python-docx>=1.0.0"]
pptx = ["python-pptx>=1.0.0"]
xlsx = ["openpyxl>=3.1.0"]
xml = ["defusedxml>=0.7.0"]
images = ["Pillow>=12.2.0"]
documents = ["convilyn[pdf,docx,pptx,xlsx,xml]"]
all = ["convilyn[documents,images]"]
# The MCP transport, and deliberately NOT part of `all`. `all` means "every
# offline format"; this means "expose those formats over a protocol". They are
# orthogonal, which is what makes `convilyn[all,mcp]` read correctly instead of
# redundantly. Pinned >=2.0 because 2.x renamed FastMCP to MCPServer and the
# server module uses the new name — supporting both would be a compatibility
# branch, and nobody holds a 1.x lock on this package yet.
mcp = ["mcp>=2.0.0,<3.0.0"]
[project.urls]
Homepage = "https://convilyn.com"
Documentation = "https://docs.convilyn.com"
Issues = "https://github.com/CoreNovus/convilyn-python/issues"
Repository = "https://github.com/CoreNovus/convilyn-python"
"Source Code" = "https://github.com/CoreNovus/convilyn-python"
Changelog = "https://github.com/CoreNovus/convilyn-python/blob/main/CHANGELOG.md"
[project.scripts]
convilyn = "convilyn.cli.main:cli"
# Dev tooling lives in a PEP 735 dependency group (uv-native: `uv sync`
# installs it by default). Kept out of the wheel/sdist METADATA.
[dependency-groups]
dev = [
"mcp>=2.0.0",
"pytest>=7.0.0",
"pytest-asyncio>=0.23.0",
"pytest-cov>=4.1.0",
"respx>=0.22.0",
"ruff==0.16.6",
# The `typecheck` gate's checker (#3917). Specifier deliberately identical
# to the one already in scripts/oss/templates/pyproject.toml: this repo's
# cross-file agreement ratchet compares specifier STRINGS, and a new
# spelling here would be a new disagreement it may not accept. The exact
# version is pinned by uv.lock, which `--locked` enforces.
"pyright>=1.1.350",
# Mirrors [project.optional-dependencies]. `uv sync --locked` installs the
# project's dependencies plus this group and NOT its extras, so without the
# mirror the engine's tests cannot import their parsers — they would skip,
# and the coverage floor would then be measuring code nothing exercised.
# A green run over un-run code is the failure this repo keeps paying for.
#
# Kept in agreement with the extras by
# `scripts/ci/tests/test_sdk_local_extras_policy.py`, so the duplication is
# machine-checked rather than remembered.
"pdfplumber>=0.11.9",
"pypdf>=6.16.1",
"python-docx>=1.0.0",
"python-pptx>=1.0.0",
"openpyxl>=3.1.0",
"defusedxml>=0.7.0",
"Pillow>=12.2.0",
# Test-only: the PDF and image fixtures are BUILT, not committed, so the
# suite needs a writer for formats no extractor produces.
"reportlab>=4.1.0",
]
[tool.hatch.version]
path = "src/convilyn/_version.py"
[tool.hatch.build.targets.wheel]
packages = ["src/convilyn"]
[tool.hatch.build.targets.sdist]
# Ship CHANGELOG + examples + docs in the sdist so source-install workflows
# (e.g. `pip install --no-binary :all:`) get the user-facing repo surface. The
# wheel intentionally stays lean — README is bundled in METADATA (rendered on
# PyPI); LICENSE is picked up via `license-files = ["LICENSE"]` per PEP 639;
# CHANGELOG ships in the sdist + wheel (see `include` below) — no public GitHub
# URL exists during the beta. AGENT.md and any docs/internal paths are
# explicitly excluded — they are agent-operating instructions, not user-facing
# documentation, and must not ship to PyPI.
#
# Supply-chain invariant — SHIPPED == SCANNED: every vocabulary-bearing surface
# shipped here MUST be covered by the black-box blacklist lint's scan_roots
# (`sdk/sdks.json` → consumer-python, read by
# `scripts/ci/sdk_blackbox/lint_blacklist.py`). `examples/` ships AND is scanned
# (mirrors author-python). The `tests/` suite is neither user-facing nor
# scanned, so it is NOT shipped — dropping it keeps shipped == scanned and
# removes unnecessary attack surface from the published artifact.
include = [
"src/convilyn",
"examples",
"docs",
"CHANGELOG.md",
"LICENSE",
"pyproject.toml",
]
exclude = [
"AGENT.md",
# Whole-subtree exclusion (not the single-level `docs/internal/*`) so a
# nested internal doc can never leak into the published sdist.
"docs/internal",
# hatchling's sdist otherwise ships a `.gitignore` into the public,
# immutable PyPI artifact — leaking internal repo structure, operator
# secret-file naming conventions, and PII fixture filenames, and bypassing
# the SHIPPED==SCANNED blackbox lint (which has no `.gitignore` scan
# suffix). Exclude it explicitly.
".gitignore",
]
[tool.pyright]
# Read by the `typecheck` gate — `uv run --locked pyright src`
# (scripts/ci/sdk_local_ci.py, #3917) — and by an editor, so both agree.
#
# The mode is PINNED rather than inherited: pyright's default
# typeCheckingMode has moved before now, so leaving it unstated would make
# this gate's strictness a property of whichever pyright the lock resolves.
typeCheckingMode = "standard"
pythonVersion = "3.10"
include = ["src"]
[tool.ruff]
target-version = "py310"
line-length = 100
# `.md` is out of scope for BOTH ruff gates, stated here so a manual
# `ruff format` and the CI gate see the same tree (#3939).
#
# ruff 0.16 formats python code fences inside markdown; `ruff check` does NOT
# scan markdown at all. #3952 excluded `*.md` here rather than decide that
# silently, because these READMEs ship inside the published package.
#
# **Decided (operator, 2026-08-19, #3953): markdown fences ARE formatted.** The
# exclusion is gone. Measured on this package before flipping: 3 files change,
# all of it trailing-comment realignment — the class #3953 warned about
# (deliberately one-line multi-name imports exploded one-per-line) does not
# occur here, it was measured on the then-existing `edge-python` and on
# `author-python`. The SDKs that still carry the exclusion are NOT covered by
# this decision; each is its owner's call. (`edge-python` was one of them until
# #5450 removed the edge SDK trees — epic #5446.)
#
# What this buys is narrow, and worth stating so nobody over-reads a green run:
# consistency, not correctness. A formatter has no opinion on whether a sample
# CALLS A FUNCTION THAT EXISTS — #3953's own comment measured four real defects
# in one README that `292 files reformatted` was silent on. The check that
# catches those here is `tests/integration/test_examples_syntax.py`, which
# parses every QUICKSTART python fence and resolves its `from convilyn import`
# names against the real public surface.
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP", "B"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
# Coverage is measured AND enforced on every pytest invocation so
# regressions get caught locally before CI. Threshold is line ≥ 80%
# (`--cov-fail-under=80`) per `.claude/skills/unit-testing/SKILL.md`.
# Branch coverage is enabled (`--cov-branch`) to surface uncovered
# conditionals that line coverage hides; the threshold is line-only
# because branch behaviour varies per Python minor version.
addopts = "--cov=src/convilyn --cov-branch --cov-fail-under=80 --cov-report=term-missing --cov-report=xml"
[tool.coverage.run]
source = ["src/convilyn"]
branch = true
# Omit zero-logic modules so they don't dilute the percentage: the
# generated `_version.py` only declares __version__; the package
# `__init__.py` is re-exports only. Both are exercised by every
# import-based test indirectly.
omit = [
"src/convilyn/_version.py",
"src/convilyn/__init__.py",
]
[tool.coverage.report]
show_missing = true
skip_covered = false
# Hard floor: any drop below 80% line coverage fails pytest. Current
# measurement sits at ~93%, leaving 13 points of
# headroom — future contributors can land coverage-neutral PRs
# without triggering the gate, but a sweeping regression will fire it.
fail_under = 80