-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpyproject.toml
More file actions
100 lines (93 loc) · 3.39 KB
/
Copy pathpyproject.toml
File metadata and controls
100 lines (93 loc) · 3.39 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
[project]
name = "ujin"
version = "0.49.0"
description = "The ultimate scraper-poller: adaptive multi-role poller with jitter, a rich scrape HTTP service (HTTP→obscura→sitemap→RSS fallback chain), site-change detection, social/trends sources, and a bundled headless renderer."
readme = "README.md"
requires-python = ">=3.11"
license = { text = "MIT" }
authors = [{ name = "Muk Chunpongtong" }]
# Core (engine + adaptive/jitter primitives + callable/command roles) is
# dependency-free. Everything heavier is layered behind the extras below.
dependencies = []
[project.optional-dependencies]
# HTTP + parsing toolkit (fetch/extract/cache/sources).
web = [
"aiohttp>=3.10",
"selectolax>=0.3",
"trafilatura>=1.12",
"feedparser>=6.0",
]
yaml = ["pyyaml>=6.0"]
# Poller control service + the scrape HTTP surface need a web framework.
service = [
"ujin[web,yaml]",
"fastapi>=0.110",
"uvicorn[standard]>=0.29",
"websockets>=12.0",
"pydantic>=2",
]
# The rich scrape HTTP service (/scrape /feed /sitemap /discover ...).
scrape = ["ujin[web,yaml,service]"]
# Social + trends sources (X/Mastodon/Truth/Twitter, corroboration).
social = ["ujin[web,yaml]"]
# Site-change detection (selector-scoped diff + webhook sink).
diff = ["ujin[web]"]
# Cookie/session persistence + proxy rotation.
sessions = ["ujin[web]"]
# Unified job control plane (/jobs CRUD + pipeline + sinks + plugins, :8902).
# Reuses the service web stack; the job model + store themselves are stdlib-only.
jobs = ["ujin[service,scrape]"]
# Browser automation (interaction recipes, load_more pagination). Playwright is
# the default engine; selenium is the alternate. Chromium itself is NOT a pip
# artifact — install it with `playwright install chromium` (the ujin-browser
# image does this), so the wheel stays pure-python.
browser = ["ujin[scrape]", "playwright>=1.44", "selenium>=4.20"]
# MCP server: expose scrape + jobs as tools for agents (`ujin mcp-serve`).
mcp = ["ujin[scrape,jobs]", "mcp>=1.2"]
# Everything.
all = ["ujin[web,yaml,service,scrape,social,diff,sessions,jobs,browser,mcp]"]
dev = [
"ujin[all]",
"pytest>=8.0",
"pytest-asyncio>=0.23",
"httpx>=0.27",
"pytest-cov>=5.0",
"coverage[toml]>=7.4",
"pytest-benchmark>=4.0",
]
[project.scripts]
ujin = "ujin.cli:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["ujin"]
# The obscura submodule is a Rust crate vendored inside the package dir. It
# must NEVER ship in the wheel — `pip install ujin` stays pure-python and never
# triggers a cargo build. The renderer is built separately via `ujin obscura-build`.
exclude = ["ujin/obscura"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
markers = [
"browser: needs a real Playwright/Selenium browser (skipped unless available)",
"obscura: needs the real obscura binary (skipped unless built)",
]
[tool.coverage.run]
source = ["ujin"]
omit = [
"ujin/obscura/*",
]
branch = true
[tool.coverage.report]
# Ratcheted per cycle: 60 (Cycle 0) -> 80 (Gate 1) -> 85 (Gate 3) -> 90 (orchestrator
# ratchet, orchestrator/state/cov_floor.txt) — kept in sync so a human `make gate`
# run enforces the same floor the autonomous orchestrator does.
fail_under = 90
show_missing = true
exclude_also = [
"if TYPE_CHECKING:",
"raise NotImplementedError",
"@(typing\\.)?overload",
"if __name__ == .__main__.:",
]