optout 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 (55) hide show
  1. optout-0.1.0/PKG-INFO +227 -0
  2. optout-0.1.0/README.md +188 -0
  3. optout-0.1.0/pyproject.toml +93 -0
  4. optout-0.1.0/setup.cfg +4 -0
  5. optout-0.1.0/src/optout/__init__.py +3 -0
  6. optout-0.1.0/src/optout/brokers/__init__.py +0 -0
  7. optout-0.1.0/src/optout/brokers/loader.py +36 -0
  8. optout-0.1.0/src/optout/brokers/registry.py +56 -0
  9. optout-0.1.0/src/optout/brokers/schema.py +196 -0
  10. optout-0.1.0/src/optout/cli.py +1185 -0
  11. optout-0.1.0/src/optout/config.py +137 -0
  12. optout-0.1.0/src/optout/data/__init__.py +0 -0
  13. optout-0.1.0/src/optout/data/brokers/__init__.py +0 -0
  14. optout-0.1.0/src/optout/data/brokers/beenverified.yml +197 -0
  15. optout-0.1.0/src/optout/data/brokers/mylife.yml +122 -0
  16. optout-0.1.0/src/optout/data/brokers/radaris.yml +127 -0
  17. optout-0.1.0/src/optout/data/brokers/spokeo.yml +99 -0
  18. optout-0.1.0/src/optout/data/brokers/whitepages.yml +149 -0
  19. optout-0.1.0/src/optout/db.py +265 -0
  20. optout-0.1.0/src/optout/engine/__init__.py +24 -0
  21. optout-0.1.0/src/optout/engine/browser.py +45 -0
  22. optout-0.1.0/src/optout/engine/dispatcher.py +73 -0
  23. optout-0.1.0/src/optout/engine/escalation.py +1 -0
  24. optout-0.1.0/src/optout/engine/methods/__init__.py +0 -0
  25. optout-0.1.0/src/optout/engine/methods/email.py +158 -0
  26. optout-0.1.0/src/optout/engine/methods/manual.py +16 -0
  27. optout-0.1.0/src/optout/engine/methods/postal.py +18 -0
  28. optout-0.1.0/src/optout/engine/methods/web_form.py +728 -0
  29. optout-0.1.0/src/optout/logging.py +85 -0
  30. optout-0.1.0/src/optout/monitoring.py +172 -0
  31. optout-0.1.0/src/optout/scan/__init__.py +0 -0
  32. optout-0.1.0/src/optout/scan/scanner.py +111 -0
  33. optout-0.1.0/src/optout/utils/__init__.py +0 -0
  34. optout-0.1.0/src/optout/utils/playwright_helpers.py +42 -0
  35. optout-0.1.0/src/optout/utils/statutes.py +124 -0
  36. optout-0.1.0/src/optout/verify.py +146 -0
  37. optout-0.1.0/src/optout/wizard.py +365 -0
  38. optout-0.1.0/src/optout.egg-info/PKG-INFO +227 -0
  39. optout-0.1.0/src/optout.egg-info/SOURCES.txt +53 -0
  40. optout-0.1.0/src/optout.egg-info/dependency_links.txt +1 -0
  41. optout-0.1.0/src/optout.egg-info/entry_points.txt +2 -0
  42. optout-0.1.0/src/optout.egg-info/requires.txt +18 -0
  43. optout-0.1.0/src/optout.egg-info/top_level.txt +1 -0
  44. optout-0.1.0/tests/test_broker_loader.py +105 -0
  45. optout-0.1.0/tests/test_brokers_commands.py +137 -0
  46. optout-0.1.0/tests/test_db.py +213 -0
  47. optout-0.1.0/tests/test_dispatcher.py +405 -0
  48. optout-0.1.0/tests/test_doctor.py +188 -0
  49. optout-0.1.0/tests/test_engine_email.py +335 -0
  50. optout-0.1.0/tests/test_engine_web_form.py +534 -0
  51. optout-0.1.0/tests/test_production_brokers.py +146 -0
  52. optout-0.1.0/tests/test_scan_monitor.py +458 -0
  53. optout-0.1.0/tests/test_status_command.py +347 -0
  54. optout-0.1.0/tests/test_verify.py +217 -0
  55. optout-0.1.0/tests/test_wizard.py +137 -0
optout-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: optout
3
+ Version: 0.1.0
4
+ Summary: Self-hosted CLI that automates CCPA/CPRA data-broker opt-out requests via Playwright
5
+ Author: Blake Matas
6
+ License-Expression: AGPL-3.0-only
7
+ Project-URL: Homepage, https://github.com/Blake104/OptOut
8
+ Project-URL: Repository, https://github.com/Blake104/OptOut
9
+ Project-URL: Bug Tracker, https://github.com/Blake104/OptOut/issues
10
+ Keywords: privacy,ccpa,cpra,data-broker,opt-out,playwright,automation
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
19
+ Classifier: Topic :: Utilities
20
+ Requires-Python: <3.14,>=3.12
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: typer[all]>=0.12
23
+ Requires-Dist: rich>=13
24
+ Requires-Dist: pydantic>=2
25
+ Requires-Dist: pydantic-settings>=2
26
+ Requires-Dist: sqlmodel>=0.0.19
27
+ Requires-Dist: pyyaml>=6
28
+ Requires-Dist: jinja2>=3
29
+ Requires-Dist: httpx>=0.27
30
+ Requires-Dist: playwright>=1.44
31
+ Requires-Dist: structlog>=23.0.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=8; extra == "dev"
34
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
35
+ Requires-Dist: ruff>=0.4; extra == "dev"
36
+ Requires-Dist: mypy>=1.10; extra == "dev"
37
+ Requires-Dist: types-PyYAML; extra == "dev"
38
+ Requires-Dist: aiosmtpd>=1.4; extra == "dev"
39
+
40
+ # OptOut
41
+
42
+ Self-hosted CLI tool that automates submitting opt-out and data-deletion requests to data brokers.
43
+
44
+ You run it on your own machine with your own information. There is no central server, no accounts, no SaaS. Because *you* are the data subject submitting on your own behalf, the entire "authorized agent" legal apparatus that commercial services like DeleteMe and Optery have to build does not apply here.
45
+
46
+ ---
47
+
48
+ ## What it does and does not do
49
+
50
+ **Does:**
51
+ - Submit opt-out / deletion requests to people-search and marketing data brokers.
52
+ - Track deadlines (CCPA: 45 days, GDPR: 30 days, etc.) and surface overdue requests.
53
+ - Re-scan brokers periodically and re-submit if your data reappears.
54
+ - Open a real browser window (Chromium) so *you* solve CAPTCHAs — no bot-detection arms race.
55
+ - Send opt-out emails from your own inbox so brokers can't block "the service."
56
+
57
+ **Does not:**
58
+ - Submit requests for anyone other than the person running the tool.
59
+ - Touch credit bureaus (Experian, Equifax, TransUnion) — those have separate, regulated dispute processes.
60
+ - Remove government records, court filings, or news articles.
61
+ - Guarantee removal from sites that ignore opt-out requests.
62
+ - Store your personal information anywhere except your own machine (`~/.config/optout/`).
63
+
64
+ ---
65
+
66
+ ## Install
67
+
68
+ **Requirements:** Python 3.11+ and `pip` (or `uv`).
69
+
70
+ ```bash
71
+ git clone https://github.com/your-username/optout.git
72
+ cd optout
73
+ pip install -e ".[dev]" # or: uv pip install -e ".[dev]"
74
+ playwright install chromium # one-time browser download
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Quickstart
80
+
81
+ ### 1. Initialize your profile
82
+
83
+ ```
84
+ optout init
85
+ ```
86
+
87
+ The interactive wizard asks for your name, address, email, and SMTP credentials. It writes `~/.config/optout/config.yml` (mode `600`) and creates the local SQLite database.
88
+
89
+ ### 2. Queue brokers for opt-out
90
+
91
+ ```
92
+ optout brokers list # see all supported brokers
93
+ optout queue # queue all brokers
94
+ optout queue --broker whitepages # queue one specific broker
95
+ ```
96
+
97
+ ### 3. Submit opt-out requests
98
+
99
+ ```
100
+ optout submit
101
+ ```
102
+
103
+ A browser window opens for each web-form broker. Follow the on-screen prompts — select your listing, solve CAPTCHAs, confirm emails. Email-method brokers are submitted automatically using your configured SMTP credentials.
104
+
105
+ ### 4. Check status
106
+
107
+ ```
108
+ optout status
109
+ ```
110
+
111
+ Shows a table of every submission: method used, current status, statutory deadline, and days remaining.
112
+
113
+ ### 5. Re-scan periodically
114
+
115
+ ```
116
+ optout monitor
117
+ ```
118
+
119
+ Checks which brokers still list you and re-queues any that have re-added your data. Cron-friendly — run it monthly.
120
+
121
+ ---
122
+
123
+ ## Commands
124
+
125
+ ```
126
+ optout init # interactive setup wizard
127
+ optout brokers list [--category] # list all known brokers
128
+ optout brokers info <slug> # details + your submission history for one broker
129
+ optout scan [--broker SLUG] # check which brokers currently list you
130
+ optout queue [--broker SLUG] # add brokers to the submission queue
131
+ optout submit [--broker SLUG] # process the queue and submit opt-outs
132
+ optout status [--broker] [--status] # table of submissions, deadlines, callouts
133
+ optout monitor # one-shot re-scan (schedule with cron or launchd)
134
+ optout escalate # send follow-ups for submissions past their deadline
135
+ optout export # dump all submission history as JSON
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Configuration
141
+
142
+ `~/.config/optout/config.yml` is generated by `optout init`. Key sections:
143
+
144
+ ```yaml
145
+ profile:
146
+ legal_name: "Jane Q Public"
147
+ current_address:
148
+ street: "123 Main St"
149
+ city: "Austin"
150
+ state: "TX"
151
+ zip: "78701"
152
+ emails:
153
+ current: ["jane@example.com"]
154
+ phones:
155
+ current: ["+15125551234"]
156
+
157
+ email:
158
+ method: smtp
159
+ smtp:
160
+ host: smtp.gmail.com
161
+ port: 587
162
+ username: jane@example.com
163
+ password_env: OPTOUT_SMTP_PASSWORD # never stored in the file itself
164
+
165
+ playwright:
166
+ headless: false # keep false so you can solve CAPTCHAs
167
+ slow_mo_ms: 0
168
+
169
+ monitoring:
170
+ rescan_interval_days: 30
171
+ ```
172
+
173
+ Sensitive values (SMTP passwords) are read from environment variables, not stored in the config file.
174
+
175
+ ---
176
+
177
+ ## Supported brokers
178
+
179
+ | Slug | Name | Method | Verification |
180
+ |---|---|---|---|
181
+ | `whitepages` | Whitepages | Web form | Phone (SMS) |
182
+ | `spokeo` | Spokeo | Web form | Email |
183
+ | `beenverified` | BeenVerified | Web form | Email |
184
+ | `radaris` | Radaris | Email | None |
185
+ | `mylife` | MyLife | Email | None |
186
+
187
+ Community contributions add brokers as YAML files — see [CONTRIBUTING.md](CONTRIBUTING.md).
188
+
189
+ ---
190
+
191
+ ## Running tests
192
+
193
+ ```bash
194
+ pytest # full suite
195
+ pytest tests/test_production_brokers.py # validate all broker YAMLs only
196
+ ```
197
+
198
+ ---
199
+
200
+ ## Legal basis
201
+
202
+ OptOut cites the applicable statute in every opt-out email and web-form submission:
203
+
204
+ - **CCPA §1798.105** (California Consumer Privacy Act) — right to deletion, 45-day response window
205
+ - **CPRA** (California Privacy Rights Act) — extends CCPA, same 45-day window
206
+ - **GDPR Art. 17** (General Data Protection Regulation) — right to erasure, 30-day response window
207
+ - **VCDPA, CPA, CTDPA, UCPA** — state-level equivalents
208
+
209
+ Each broker's YAML lists which statutes apply. The tool automatically includes the correct citation in every submission.
210
+
211
+ ---
212
+
213
+ ## Honest limitations
214
+
215
+ - Removal is not guaranteed. Some brokers comply reliably; others are slow or re-add data.
216
+ - Web-form flows break when brokers redesign their opt-out pages. If a broker's steps stop working, open an issue or submit a YAML fix.
217
+ - CAPTCHAs require your attention — the tool will pause and prompt you.
218
+ - Email submissions depend on your SMTP server being reachable and not blocked by the broker.
219
+ - Data frequently reappears (60–90 days is typical). Use `optout monitor` to catch this.
220
+
221
+ ---
222
+
223
+ ## License
224
+
225
+ AGPL-3.0-only. See [LICENSE](LICENSE).
226
+
227
+ This license was chosen intentionally: anyone who forks this and runs it as a hosted service must publish their changes. The project's legal model depends on each user running their own copy — a hosted fork changes the legal character of the tool.
optout-0.1.0/README.md ADDED
@@ -0,0 +1,188 @@
1
+ # OptOut
2
+
3
+ Self-hosted CLI tool that automates submitting opt-out and data-deletion requests to data brokers.
4
+
5
+ You run it on your own machine with your own information. There is no central server, no accounts, no SaaS. Because *you* are the data subject submitting on your own behalf, the entire "authorized agent" legal apparatus that commercial services like DeleteMe and Optery have to build does not apply here.
6
+
7
+ ---
8
+
9
+ ## What it does and does not do
10
+
11
+ **Does:**
12
+ - Submit opt-out / deletion requests to people-search and marketing data brokers.
13
+ - Track deadlines (CCPA: 45 days, GDPR: 30 days, etc.) and surface overdue requests.
14
+ - Re-scan brokers periodically and re-submit if your data reappears.
15
+ - Open a real browser window (Chromium) so *you* solve CAPTCHAs — no bot-detection arms race.
16
+ - Send opt-out emails from your own inbox so brokers can't block "the service."
17
+
18
+ **Does not:**
19
+ - Submit requests for anyone other than the person running the tool.
20
+ - Touch credit bureaus (Experian, Equifax, TransUnion) — those have separate, regulated dispute processes.
21
+ - Remove government records, court filings, or news articles.
22
+ - Guarantee removal from sites that ignore opt-out requests.
23
+ - Store your personal information anywhere except your own machine (`~/.config/optout/`).
24
+
25
+ ---
26
+
27
+ ## Install
28
+
29
+ **Requirements:** Python 3.11+ and `pip` (or `uv`).
30
+
31
+ ```bash
32
+ git clone https://github.com/your-username/optout.git
33
+ cd optout
34
+ pip install -e ".[dev]" # or: uv pip install -e ".[dev]"
35
+ playwright install chromium # one-time browser download
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Quickstart
41
+
42
+ ### 1. Initialize your profile
43
+
44
+ ```
45
+ optout init
46
+ ```
47
+
48
+ The interactive wizard asks for your name, address, email, and SMTP credentials. It writes `~/.config/optout/config.yml` (mode `600`) and creates the local SQLite database.
49
+
50
+ ### 2. Queue brokers for opt-out
51
+
52
+ ```
53
+ optout brokers list # see all supported brokers
54
+ optout queue # queue all brokers
55
+ optout queue --broker whitepages # queue one specific broker
56
+ ```
57
+
58
+ ### 3. Submit opt-out requests
59
+
60
+ ```
61
+ optout submit
62
+ ```
63
+
64
+ A browser window opens for each web-form broker. Follow the on-screen prompts — select your listing, solve CAPTCHAs, confirm emails. Email-method brokers are submitted automatically using your configured SMTP credentials.
65
+
66
+ ### 4. Check status
67
+
68
+ ```
69
+ optout status
70
+ ```
71
+
72
+ Shows a table of every submission: method used, current status, statutory deadline, and days remaining.
73
+
74
+ ### 5. Re-scan periodically
75
+
76
+ ```
77
+ optout monitor
78
+ ```
79
+
80
+ Checks which brokers still list you and re-queues any that have re-added your data. Cron-friendly — run it monthly.
81
+
82
+ ---
83
+
84
+ ## Commands
85
+
86
+ ```
87
+ optout init # interactive setup wizard
88
+ optout brokers list [--category] # list all known brokers
89
+ optout brokers info <slug> # details + your submission history for one broker
90
+ optout scan [--broker SLUG] # check which brokers currently list you
91
+ optout queue [--broker SLUG] # add brokers to the submission queue
92
+ optout submit [--broker SLUG] # process the queue and submit opt-outs
93
+ optout status [--broker] [--status] # table of submissions, deadlines, callouts
94
+ optout monitor # one-shot re-scan (schedule with cron or launchd)
95
+ optout escalate # send follow-ups for submissions past their deadline
96
+ optout export # dump all submission history as JSON
97
+ ```
98
+
99
+ ---
100
+
101
+ ## Configuration
102
+
103
+ `~/.config/optout/config.yml` is generated by `optout init`. Key sections:
104
+
105
+ ```yaml
106
+ profile:
107
+ legal_name: "Jane Q Public"
108
+ current_address:
109
+ street: "123 Main St"
110
+ city: "Austin"
111
+ state: "TX"
112
+ zip: "78701"
113
+ emails:
114
+ current: ["jane@example.com"]
115
+ phones:
116
+ current: ["+15125551234"]
117
+
118
+ email:
119
+ method: smtp
120
+ smtp:
121
+ host: smtp.gmail.com
122
+ port: 587
123
+ username: jane@example.com
124
+ password_env: OPTOUT_SMTP_PASSWORD # never stored in the file itself
125
+
126
+ playwright:
127
+ headless: false # keep false so you can solve CAPTCHAs
128
+ slow_mo_ms: 0
129
+
130
+ monitoring:
131
+ rescan_interval_days: 30
132
+ ```
133
+
134
+ Sensitive values (SMTP passwords) are read from environment variables, not stored in the config file.
135
+
136
+ ---
137
+
138
+ ## Supported brokers
139
+
140
+ | Slug | Name | Method | Verification |
141
+ |---|---|---|---|
142
+ | `whitepages` | Whitepages | Web form | Phone (SMS) |
143
+ | `spokeo` | Spokeo | Web form | Email |
144
+ | `beenverified` | BeenVerified | Web form | Email |
145
+ | `radaris` | Radaris | Email | None |
146
+ | `mylife` | MyLife | Email | None |
147
+
148
+ Community contributions add brokers as YAML files — see [CONTRIBUTING.md](CONTRIBUTING.md).
149
+
150
+ ---
151
+
152
+ ## Running tests
153
+
154
+ ```bash
155
+ pytest # full suite
156
+ pytest tests/test_production_brokers.py # validate all broker YAMLs only
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Legal basis
162
+
163
+ OptOut cites the applicable statute in every opt-out email and web-form submission:
164
+
165
+ - **CCPA §1798.105** (California Consumer Privacy Act) — right to deletion, 45-day response window
166
+ - **CPRA** (California Privacy Rights Act) — extends CCPA, same 45-day window
167
+ - **GDPR Art. 17** (General Data Protection Regulation) — right to erasure, 30-day response window
168
+ - **VCDPA, CPA, CTDPA, UCPA** — state-level equivalents
169
+
170
+ Each broker's YAML lists which statutes apply. The tool automatically includes the correct citation in every submission.
171
+
172
+ ---
173
+
174
+ ## Honest limitations
175
+
176
+ - Removal is not guaranteed. Some brokers comply reliably; others are slow or re-add data.
177
+ - Web-form flows break when brokers redesign their opt-out pages. If a broker's steps stop working, open an issue or submit a YAML fix.
178
+ - CAPTCHAs require your attention — the tool will pause and prompt you.
179
+ - Email submissions depend on your SMTP server being reachable and not blocked by the broker.
180
+ - Data frequently reappears (60–90 days is typical). Use `optout monitor` to catch this.
181
+
182
+ ---
183
+
184
+ ## License
185
+
186
+ AGPL-3.0-only. See [LICENSE](LICENSE).
187
+
188
+ This license was chosen intentionally: anyone who forks this and runs it as a hosted service must publish their changes. The project's legal model depends on each user running their own copy — a hosted fork changes the legal character of the tool.
@@ -0,0 +1,93 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "optout"
7
+ version = "0.1.0"
8
+ description = "Self-hosted CLI that automates CCPA/CPRA data-broker opt-out requests via Playwright"
9
+ readme = "README.md"
10
+ license = "AGPL-3.0-only"
11
+ requires-python = ">=3.12,<3.14"
12
+ authors = [{ name = "Blake Matas" }]
13
+ keywords = ["privacy", "ccpa", "cpra", "data-broker", "opt-out", "playwright", "automation"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: Console",
17
+ "Intended Audience :: End Users/Desktop",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: Internet :: WWW/HTTP :: Browsers",
23
+ "Topic :: Utilities",
24
+ ]
25
+ dependencies = [
26
+ "typer[all]>=0.12",
27
+ "rich>=13",
28
+ "pydantic>=2",
29
+ "pydantic-settings>=2",
30
+ "sqlmodel>=0.0.19",
31
+ "pyyaml>=6",
32
+ "jinja2>=3",
33
+ "httpx>=0.27",
34
+ "playwright>=1.44",
35
+ "structlog>=23.0.0",
36
+ ]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/Blake104/OptOut"
40
+ Repository = "https://github.com/Blake104/OptOut"
41
+ "Bug Tracker" = "https://github.com/Blake104/OptOut/issues"
42
+
43
+ [project.scripts]
44
+ optout = "optout.cli:app"
45
+
46
+ [project.optional-dependencies]
47
+ dev = [
48
+ "pytest>=8",
49
+ "pytest-asyncio>=0.23",
50
+ "ruff>=0.4",
51
+ "mypy>=1.10",
52
+ "types-PyYAML",
53
+ "aiosmtpd>=1.4",
54
+ ]
55
+
56
+ [tool.setuptools.packages.find]
57
+ where = ["src"]
58
+
59
+ [tool.setuptools.package-data]
60
+ optout = ["data/brokers/*.yml"]
61
+
62
+ [tool.ruff]
63
+ line-length = 100
64
+ target-version = "py312"
65
+
66
+ [tool.ruff.lint]
67
+ select = ["E", "F", "I", "UP"]
68
+
69
+ [tool.mypy]
70
+ python_version = "3.12"
71
+ ignore_missing_imports = true
72
+ warn_unused_ignores = true
73
+ warn_return_any = false
74
+ disallow_untyped_defs = false
75
+ check_untyped_defs = true
76
+
77
+ # Playwright, SQLModel, and the engine use heavy Any-based dynamic dispatch.
78
+ # Type-check the schema, config, and CLI where types are well-defined.
79
+ [[tool.mypy.overrides]]
80
+ module = [
81
+ "optout.engine.*",
82
+ "optout.scan.*",
83
+ "optout.monitoring",
84
+ "optout.db",
85
+ "optout.cli",
86
+ "optout.wizard",
87
+ ]
88
+ ignore_errors = true
89
+
90
+ [tool.pytest.ini_options]
91
+ testpaths = ["tests"]
92
+ asyncio_mode = "auto"
93
+ asyncio_default_fixture_loop_scope = "function"
optout-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """OptOut — self-hosted data broker opt-out tool."""
2
+
3
+ __version__ = "0.1.0"
File without changes
@@ -0,0 +1,36 @@
1
+ """Loads and validates broker YAML files against BrokerDef."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import yaml
8
+ from pydantic import ValidationError
9
+
10
+ from .schema import BrokerDef
11
+
12
+
13
+ def load_broker(path: Path) -> BrokerDef:
14
+ """Parse and validate a single broker YAML file."""
15
+ data = yaml.safe_load(path.read_text())
16
+ try:
17
+ return BrokerDef.model_validate(data)
18
+ except ValidationError as exc:
19
+ raise ValueError(f"Invalid broker definition at {path}:\n{exc}") from exc
20
+
21
+
22
+ def load_all_brokers(brokers_dir: Path) -> list[BrokerDef]:
23
+ """Load every *.yml file in brokers_dir, sorted by slug."""
24
+ results: list[BrokerDef] = []
25
+ errors: list[str] = []
26
+
27
+ for yml in sorted(brokers_dir.glob("*.yml")):
28
+ try:
29
+ results.append(load_broker(yml))
30
+ except (ValueError, yaml.YAMLError) as exc:
31
+ errors.append(str(exc))
32
+
33
+ if errors:
34
+ raise ValueError(f"{len(errors)} broker(s) failed validation:\n" + "\n\n".join(errors))
35
+
36
+ return results
@@ -0,0 +1,56 @@
1
+ """In-memory broker catalog, populated from the brokers/ YAML directory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from importlib.resources import files
7
+ from pathlib import Path
8
+
9
+ from .loader import load_all_brokers
10
+ from .schema import BrokerDef
11
+
12
+ _registry: dict[str, BrokerDef] = {}
13
+ _loaded = False
14
+
15
+
16
+ def _brokers_dir() -> Path:
17
+ """Return the directory containing broker YAML files.
18
+
19
+ Priority:
20
+ 1. OPTOUT_BROKERS_DIR env var — lets users/tests override with a custom set.
21
+ 2. importlib.resources — finds the YAMLs bundled inside the installed package,
22
+ works for both editable installs (real filesystem path) and wheel installs.
23
+ """
24
+ env = os.environ.get("OPTOUT_BROKERS_DIR")
25
+ if env:
26
+ return Path(env)
27
+ return Path(str(files("optout").joinpath("data/brokers")))
28
+
29
+
30
+ def load_registry(brokers_dir: Path | None = None) -> None:
31
+ global _registry, _loaded
32
+ directory = brokers_dir or _brokers_dir()
33
+ if not directory.exists():
34
+ _registry = {}
35
+ _loaded = True
36
+ return
37
+ _registry = {b.slug: b for b in load_all_brokers(directory)}
38
+ _loaded = True
39
+
40
+
41
+ def _ensure_loaded() -> None:
42
+ if not _loaded:
43
+ load_registry()
44
+
45
+
46
+ def get_broker(slug: str) -> BrokerDef:
47
+ _ensure_loaded()
48
+ try:
49
+ return _registry[slug]
50
+ except KeyError:
51
+ raise KeyError(f"Unknown broker slug: '{slug}'. Run `optout brokers list` to see options.")
52
+
53
+
54
+ def all_brokers() -> list[BrokerDef]:
55
+ _ensure_loaded()
56
+ return sorted(_registry.values(), key=lambda b: b.name)