documentors 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.
@@ -0,0 +1,205 @@
1
+ # Secrets and credentials (NEVER COMMIT)
2
+ .env
3
+ .env.*
4
+ .env.secure.server
5
+ .env.production
6
+ *.key
7
+ *.pem
8
+ *_keys.txt
9
+ vault-backup-CRITICAL.txt
10
+
11
+ # Linode API tokens
12
+ personal_access_token.txt
13
+ *_access_token.txt
14
+ *.token
15
+ .linode-token
16
+
17
+ # Terraform
18
+ *.tfvars
19
+ !*.tfvars.example
20
+ *.tfstate
21
+ *.tfstate.backup
22
+ .terraform/
23
+ .terraform.lock.hcl
24
+ linode-verified-*.json
25
+
26
+ # Python virtual environments
27
+ .venv/
28
+ venv/
29
+ ENV/
30
+ env/
31
+ *.venv
32
+
33
+ # Generated indexes and temp deploy artifacts
34
+ .code_index.json
35
+ .code_index_metadata.json
36
+ tmp_deploy/
37
+
38
+ # Python cache and compiled files
39
+ __pycache__/
40
+ *.py[cod]
41
+ *$py.class
42
+ *.so
43
+ .Python
44
+
45
+ # Python distribution / packaging
46
+ build/
47
+ develop-eggs/
48
+ dist/
49
+ downloads/
50
+ eggs/
51
+ .eggs/
52
+ lib64/
53
+ parts/
54
+ sdist/
55
+ var/
56
+ wheels/
57
+ *.egg-info/
58
+ .installed.cfg
59
+ *.egg
60
+ MANIFEST
61
+
62
+ # PyInstaller
63
+ *.manifest
64
+ *.spec
65
+
66
+ # Testing
67
+ .pytest_cache/
68
+ .coverage
69
+ .coverage.*
70
+ coverage.xml
71
+ coverage.json
72
+ .tox/
73
+ htmlcov/
74
+ .hypothesis/
75
+
76
+ # Production database files
77
+ *.db
78
+ *.sqlite
79
+ *.sqlite3
80
+
81
+ # Logs
82
+ *.log
83
+ logs/
84
+ ci-logs/
85
+
86
+ # Docker volumes
87
+ docker/data/
88
+ **/data/
89
+ *_data/
90
+ *_backups/
91
+
92
+ # Node.js (if present)
93
+ node_modules/
94
+ npm-debug.log*
95
+ yarn-debug.log*
96
+ yarn-error.log*
97
+
98
+ # Temporary files
99
+ *.tmp
100
+ *.temp
101
+ tmp_*
102
+ .DS_Store
103
+ Thumbs.db
104
+
105
+ # Build/test output artifacts
106
+ build_output.txt
107
+ test_output.txt
108
+ test_result*.txt
109
+ *_out.txt
110
+ ssh_test.txt
111
+ fairness_test_results.txt
112
+ pip_outdated.json
113
+ openapi_test.json
114
+ test_register.json
115
+ *_infrastructure_*.json
116
+ *_infrastructure_*.md
117
+
118
+ # Backup files
119
+ *.backup
120
+ *.old
121
+ *.OLD
122
+ *.bak
123
+
124
+ # Binary archives (deployment artifacts)
125
+ *.tar.gz
126
+ *.tgz
127
+ *.tar
128
+ *.zip
129
+
130
+ # Sensitive directories
131
+ vault/
132
+
133
+ # Runtime/temp folders
134
+ watch_folders/
135
+ errors/
136
+ processed/
137
+
138
+ # Deployment sensitive files
139
+ deployment.config
140
+ authorized_keys*
141
+ *_key.txt
142
+ *_keys.txt
143
+ ssh_config_*
144
+
145
+ # Binary executables
146
+ *.exe
147
+ *.dll
148
+
149
+ # Auto-generated TypeScript
150
+ next-env.d.ts
151
+
152
+ # IDE files
153
+ .vscode/
154
+ .idea/
155
+ *.swp
156
+ *.swo
157
+
158
+ # Project status (machine-specific)
159
+ project/status.yaml
160
+
161
+ # Machine identity file (each machine has its own)
162
+ .computer
163
+
164
+ # Task folders (each machine only tracks its own)
165
+ # On LEV: git update-index --skip-worktree project/tasks/asus project/tasks/gmk project/tasks/hp
166
+ # On GMK: git update-index --skip-worktree project/tasks/asus project/tasks/lev project/tasks/hp
167
+ project/tasks/*/
168
+
169
+ # Large database migrations (>1MB)
170
+ # These are stored on S:\ drive to avoid repo bloat
171
+ # See migrations/sql/README.md for guidelines
172
+ migrations/sql/*_bulk_*.sql
173
+ migrations/sql/*_import_*.sql
174
+ migrations/sql/*_performance_*.sql
175
+ migrations/sql/V*__large_*.sql
176
+
177
+ frontend/.next/
178
+ frontend/tsconfig.tsbuildinfo
179
+ hub/frontend/.next/
180
+ hub/frontend/tsconfig.tsbuildinfo
181
+
182
+ # Archival results — moved to S:\testing_results\
183
+ DDL-Core/test-results/
184
+ frontend/lighthouse-reports/
185
+
186
+ # API Change Detection (machine-specific baselines)
187
+ # Each developer may work with different backend versions
188
+ frontend/.api-baseline.json
189
+ frontend/.api-changes.log
190
+ frontend/src/lib/api/generated-types.ts
191
+ cookies.txt
192
+ cookies2.txt
193
+ proxy_cookies.txt
194
+
195
+ # PDS publishing helper scripts (local utilities)
196
+ migrations/_pds_demo_init.sql
197
+ scripts/create_pds_page.ps1
198
+ scripts/create_phishing_page.ps1
199
+ scripts/create_phishing_page.py
200
+ scripts/test_simple_page.ps1
201
+ project/handoff/LEV_ONBOARDING.md
202
+
203
+ # Next.js build artifacts
204
+ hub/frontend/.next/
205
+ frontend/.next/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Docu-Mentors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: documentors
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the DocuMentors document lifecycle management platform
5
+ Project-URL: Homepage, https://github.com/Docu-Mentors/developer-sdk
6
+ Project-URL: Documentation, https://docs.docu-mentors.com/sdk/python
7
+ Project-URL: Repository, https://github.com/Docu-Mentors/developer-sdk
8
+ Project-URL: Issues, https://github.com/Docu-Mentors/developer-sdk/issues
9
+ Author-email: Docu-Mentors <sdk@docu-mentors.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: httpx<1,>=0.25.0
22
+ Requires-Dist: pydantic<3,>=2.0.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest<10,>=7.0; extra == 'dev'
25
+ Requires-Dist: respx<1,>=0.21; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # DocuMentors Python SDK
29
+
30
+ Official Python SDK for the DocuMentors document lifecycle management API.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install documentors
36
+ ```
37
+
38
+ Or install from source:
39
+
40
+ ```bash
41
+ pip install -e sdk/python/
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ ```python
47
+ from documentors import DocumentorsClient
48
+
49
+ client = DocumentorsClient(
50
+ base_url="https://your-instance.docu-mentors.com",
51
+ api_key="dk_prod_your_key_here",
52
+ )
53
+
54
+ # Upload a document
55
+ doc = client.documents.upload("contract.pdf", title="Q2 Contract", classification="CONFIDENTIAL")
56
+
57
+ # Scan for PII
58
+ scan = client.security.detect_pii(doc.id)
59
+ for finding in scan.findings:
60
+ print(f"{finding.type}: {finding.masked_value}")
61
+
62
+ client.close()
63
+ ```
64
+
65
+ ## Authentication
66
+
67
+ ### API Key (recommended for integrations)
68
+
69
+ ```python
70
+ client = DocumentorsClient(base_url="...", api_key="dk_prod_xxx")
71
+ ```
72
+
73
+ ### JWT (interactive / service-to-service)
74
+
75
+ ```python
76
+ client = DocumentorsClient(base_url="...")
77
+ client.login(email="user@example.com", password="secret")
78
+ ```
79
+
80
+ ### Context Manager
81
+
82
+ ```python
83
+ with DocumentorsClient(base_url="...", api_key="dk_prod_xxx") as client:
84
+ docs = client.documents.list()
85
+ ```
86
+
87
+ ## Resource Namespaces
88
+
89
+ | Namespace | Description |
90
+ |---|---|
91
+ | `client.documents` | Upload, list, get, delete, versions, download |
92
+ | `client.security` | PII detection/redaction, phishing analysis |
93
+ | `client.compliance` | Legal holds, eDiscovery, retention policies |
94
+ | `client.admin` | Users, API keys, audit logs, devices |
95
+
96
+ ## Error Handling
97
+
98
+ ```python
99
+ from documentors import DocumentorsClient, NotFoundError, RateLimitError
100
+
101
+ try:
102
+ doc = client.documents.get("bad-id")
103
+ except NotFoundError:
104
+ print("Document not found")
105
+ except RateLimitError as e:
106
+ print(f"Rate limited — retry after {e.retry_after}s")
107
+ ```
108
+
109
+ ### Exception Hierarchy
110
+
111
+ - `DocumentorsError` — base
112
+ - `AuthenticationError` — 401
113
+ - `PermissionDeniedError` — 403
114
+ - `NotFoundError` — 404
115
+ - `ValidationError` — 422
116
+ - `ConflictError` — 409
117
+ - `RateLimitError` — 429 (includes `retry_after`)
118
+ - `ServerError` — 500+
119
+
120
+ ## Transport Features
121
+
122
+ - Automatic retry with exponential backoff on 429/5xx
123
+ - `Retry-After` header support
124
+ - Configurable timeout and max retries
125
+ - Clean error mapping from HTTP status codes
126
+
127
+ ## Examples
128
+
129
+ See [`examples/`](examples/) for full working scripts:
130
+
131
+ - **quickstart.py** — Upload + PII scan + redaction
132
+ - **bulk_upload.py** — Batch upload with auto-scan
133
+ - **pii_scan_pipeline.py** — Full PII audit → CSV report
134
+ - **compliance_report.py** — Legal holds + retention summary
135
+
136
+ ## Development
137
+
138
+ ```bash
139
+ pip install -e ".[dev]"
140
+ pytest
141
+ ```
142
+
143
+ ## License
144
+
145
+ MIT
@@ -0,0 +1,118 @@
1
+ # DocuMentors Python SDK
2
+
3
+ Official Python SDK for the DocuMentors document lifecycle management API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install documentors
9
+ ```
10
+
11
+ Or install from source:
12
+
13
+ ```bash
14
+ pip install -e sdk/python/
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```python
20
+ from documentors import DocumentorsClient
21
+
22
+ client = DocumentorsClient(
23
+ base_url="https://your-instance.docu-mentors.com",
24
+ api_key="dk_prod_your_key_here",
25
+ )
26
+
27
+ # Upload a document
28
+ doc = client.documents.upload("contract.pdf", title="Q2 Contract", classification="CONFIDENTIAL")
29
+
30
+ # Scan for PII
31
+ scan = client.security.detect_pii(doc.id)
32
+ for finding in scan.findings:
33
+ print(f"{finding.type}: {finding.masked_value}")
34
+
35
+ client.close()
36
+ ```
37
+
38
+ ## Authentication
39
+
40
+ ### API Key (recommended for integrations)
41
+
42
+ ```python
43
+ client = DocumentorsClient(base_url="...", api_key="dk_prod_xxx")
44
+ ```
45
+
46
+ ### JWT (interactive / service-to-service)
47
+
48
+ ```python
49
+ client = DocumentorsClient(base_url="...")
50
+ client.login(email="user@example.com", password="secret")
51
+ ```
52
+
53
+ ### Context Manager
54
+
55
+ ```python
56
+ with DocumentorsClient(base_url="...", api_key="dk_prod_xxx") as client:
57
+ docs = client.documents.list()
58
+ ```
59
+
60
+ ## Resource Namespaces
61
+
62
+ | Namespace | Description |
63
+ |---|---|
64
+ | `client.documents` | Upload, list, get, delete, versions, download |
65
+ | `client.security` | PII detection/redaction, phishing analysis |
66
+ | `client.compliance` | Legal holds, eDiscovery, retention policies |
67
+ | `client.admin` | Users, API keys, audit logs, devices |
68
+
69
+ ## Error Handling
70
+
71
+ ```python
72
+ from documentors import DocumentorsClient, NotFoundError, RateLimitError
73
+
74
+ try:
75
+ doc = client.documents.get("bad-id")
76
+ except NotFoundError:
77
+ print("Document not found")
78
+ except RateLimitError as e:
79
+ print(f"Rate limited — retry after {e.retry_after}s")
80
+ ```
81
+
82
+ ### Exception Hierarchy
83
+
84
+ - `DocumentorsError` — base
85
+ - `AuthenticationError` — 401
86
+ - `PermissionDeniedError` — 403
87
+ - `NotFoundError` — 404
88
+ - `ValidationError` — 422
89
+ - `ConflictError` — 409
90
+ - `RateLimitError` — 429 (includes `retry_after`)
91
+ - `ServerError` — 500+
92
+
93
+ ## Transport Features
94
+
95
+ - Automatic retry with exponential backoff on 429/5xx
96
+ - `Retry-After` header support
97
+ - Configurable timeout and max retries
98
+ - Clean error mapping from HTTP status codes
99
+
100
+ ## Examples
101
+
102
+ See [`examples/`](examples/) for full working scripts:
103
+
104
+ - **quickstart.py** — Upload + PII scan + redaction
105
+ - **bulk_upload.py** — Batch upload with auto-scan
106
+ - **pii_scan_pipeline.py** — Full PII audit → CSV report
107
+ - **compliance_report.py** — Legal holds + retention summary
108
+
109
+ ## Development
110
+
111
+ ```bash
112
+ pip install -e ".[dev]"
113
+ pytest
114
+ ```
115
+
116
+ ## License
117
+
118
+ MIT
@@ -0,0 +1,59 @@
1
+ """DocuMentors Python SDK — secure document lifecycle management."""
2
+
3
+ from .client import DocumentorsClient
4
+ from .exceptions import (
5
+ AuthenticationError,
6
+ ConflictError,
7
+ DocumentorsError,
8
+ NotFoundError,
9
+ PermissionDeniedError,
10
+ RateLimitError,
11
+ ServerError,
12
+ ValidationError,
13
+ )
14
+ from .models import (
15
+ APIKey,
16
+ APIKeyCreated,
17
+ Document,
18
+ DocumentVersion,
19
+ LegalHold,
20
+ PaginatedResponse,
21
+ PhishingVerdict,
22
+ PIIFinding,
23
+ RetentionPolicy,
24
+ ScanHistoryEntry,
25
+ ScanResult,
26
+ TokenResponse,
27
+ URLThreat,
28
+ User,
29
+ )
30
+
31
+ __all__ = [
32
+ "DocumentorsClient",
33
+ # Exceptions
34
+ "DocumentorsError",
35
+ "AuthenticationError",
36
+ "PermissionDeniedError",
37
+ "NotFoundError",
38
+ "ValidationError",
39
+ "RateLimitError",
40
+ "ServerError",
41
+ "ConflictError",
42
+ # Models
43
+ "Document",
44
+ "DocumentVersion",
45
+ "PaginatedResponse",
46
+ "ScanResult",
47
+ "PIIFinding",
48
+ "ScanHistoryEntry",
49
+ "PhishingVerdict",
50
+ "URLThreat",
51
+ "LegalHold",
52
+ "RetentionPolicy",
53
+ "User",
54
+ "APIKey",
55
+ "APIKeyCreated",
56
+ "TokenResponse",
57
+ ]
58
+
59
+ __version__ = "0.1.0"
@@ -0,0 +1,173 @@
1
+ """HTTP transport with retry, error mapping, and auth injection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import time
7
+ from typing import Any, Optional
8
+
9
+ import httpx
10
+
11
+ from .exceptions import (
12
+ AuthenticationError,
13
+ ConflictError,
14
+ DocumentorsError,
15
+ NotFoundError,
16
+ PermissionDeniedError,
17
+ RateLimitError,
18
+ ServerError,
19
+ ValidationError,
20
+ )
21
+
22
+ logger = logging.getLogger("documentors")
23
+
24
+ _STATUS_MAP: dict[int, type[DocumentorsError]] = {
25
+ 401: AuthenticationError,
26
+ 403: PermissionDeniedError,
27
+ 404: NotFoundError,
28
+ 409: ConflictError,
29
+ 422: ValidationError,
30
+ }
31
+
32
+ _DEFAULT_TIMEOUT = 30.0
33
+ _DEFAULT_MAX_RETRIES = 3
34
+ _DEFAULT_BACKOFF_FACTOR = 0.5
35
+ _RETRYABLE_STATUS = {429, 500, 502, 503, 504}
36
+
37
+
38
+ class Transport:
39
+ """Low-level HTTP helper used by resource modules."""
40
+
41
+ def __init__(
42
+ self,
43
+ *,
44
+ base_url: str,
45
+ headers: dict[str, str],
46
+ timeout: float = _DEFAULT_TIMEOUT,
47
+ max_retries: int = _DEFAULT_MAX_RETRIES,
48
+ backoff_factor: float = _DEFAULT_BACKOFF_FACTOR,
49
+ ) -> None:
50
+ self._base_url = base_url.rstrip("/")
51
+ self._headers = headers
52
+ self._timeout = timeout
53
+ self._max_retries = max_retries
54
+ self._backoff_factor = backoff_factor
55
+ self._client = httpx.Client(
56
+ base_url=self._base_url,
57
+ headers=self._headers,
58
+ timeout=self._timeout,
59
+ follow_redirects=True,
60
+ )
61
+
62
+ # -- public helpers -----------------------------------------------------
63
+
64
+ def request(
65
+ self,
66
+ method: str,
67
+ path: str,
68
+ *,
69
+ json: Optional[dict[str, Any]] = None,
70
+ params: Optional[dict[str, Any]] = None,
71
+ data: Optional[dict[str, Any]] = None,
72
+ files: Optional[Any] = None,
73
+ extra_headers: Optional[dict[str, str]] = None,
74
+ ) -> httpx.Response:
75
+ """Send an HTTP request with retry and error mapping."""
76
+ url = path if path.startswith("http") else path
77
+ headers = {**self._headers, **(extra_headers or {})}
78
+
79
+ last_exc: Optional[Exception] = None
80
+ for attempt in range(1, self._max_retries + 1):
81
+ try:
82
+ resp = self._client.request(
83
+ method,
84
+ url,
85
+ json=json,
86
+ params=_strip_none(params),
87
+ data=data,
88
+ files=files,
89
+ headers=headers,
90
+ )
91
+ except httpx.TransportError as exc:
92
+ last_exc = exc
93
+ if attempt < self._max_retries:
94
+ self._sleep(attempt)
95
+ continue
96
+ raise DocumentorsError(f"Connection error: {exc}") from exc
97
+
98
+ if resp.status_code < 400:
99
+ return resp
100
+
101
+ if resp.status_code in _RETRYABLE_STATUS and attempt < self._max_retries:
102
+ retry_after = _parse_retry_after(resp)
103
+ self._sleep(attempt, retry_after)
104
+ continue
105
+
106
+ self._raise_for_status(resp)
107
+
108
+ raise DocumentorsError(f"Request failed after {self._max_retries} retries") from last_exc
109
+
110
+ def get(self, path: str, **kwargs: Any) -> httpx.Response:
111
+ return self.request("GET", path, **kwargs)
112
+
113
+ def post(self, path: str, **kwargs: Any) -> httpx.Response:
114
+ return self.request("POST", path, **kwargs)
115
+
116
+ def put(self, path: str, **kwargs: Any) -> httpx.Response:
117
+ return self.request("PUT", path, **kwargs)
118
+
119
+ def patch(self, path: str, **kwargs: Any) -> httpx.Response:
120
+ return self.request("PATCH", path, **kwargs)
121
+
122
+ def delete(self, path: str, **kwargs: Any) -> httpx.Response:
123
+ return self.request("DELETE", path, **kwargs)
124
+
125
+ def close(self) -> None:
126
+ self._client.close()
127
+
128
+ # -- internals ----------------------------------------------------------
129
+
130
+ def _sleep(self, attempt: int, override: Optional[float] = None) -> None:
131
+ delay = override if override is not None else self._backoff_factor * (2 ** (attempt - 1))
132
+ logger.debug("Retry attempt %d — sleeping %.1fs", attempt, delay)
133
+ time.sleep(delay)
134
+
135
+ @staticmethod
136
+ def _raise_for_status(resp: httpx.Response) -> None:
137
+ detail = ""
138
+ details: dict[str, Any] = {}
139
+ try:
140
+ body = resp.json()
141
+ detail = body.get("detail", body.get("message", ""))
142
+ details = body if isinstance(body, dict) else {}
143
+ except Exception:
144
+ detail = resp.text[:500]
145
+
146
+ if resp.status_code == 429:
147
+ raise RateLimitError(
148
+ detail or "Rate limit exceeded",
149
+ retry_after=_parse_retry_after(resp),
150
+ details=details,
151
+ )
152
+
153
+ exc_cls = _STATUS_MAP.get(resp.status_code, ServerError if resp.status_code >= 500 else DocumentorsError)
154
+ raise exc_cls(
155
+ detail or f"HTTP {resp.status_code}",
156
+ details=details,
157
+ )
158
+
159
+
160
+ def _strip_none(params: Optional[dict[str, Any]]) -> Optional[dict[str, Any]]:
161
+ if params is None:
162
+ return None
163
+ return {k: v for k, v in params.items() if v is not None}
164
+
165
+
166
+ def _parse_retry_after(resp: httpx.Response) -> Optional[int]:
167
+ val = resp.headers.get("Retry-After")
168
+ if val is None:
169
+ return None
170
+ try:
171
+ return int(val)
172
+ except ValueError:
173
+ return None