affinity-sdk 0.9.5__py3-none-any.whl
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.
- affinity/__init__.py +139 -0
- affinity/cli/__init__.py +7 -0
- affinity/cli/click_compat.py +27 -0
- affinity/cli/commands/__init__.py +1 -0
- affinity/cli/commands/_entity_files_dump.py +219 -0
- affinity/cli/commands/_list_entry_fields.py +41 -0
- affinity/cli/commands/_v1_parsing.py +77 -0
- affinity/cli/commands/company_cmds.py +2139 -0
- affinity/cli/commands/completion_cmd.py +33 -0
- affinity/cli/commands/config_cmds.py +540 -0
- affinity/cli/commands/entry_cmds.py +33 -0
- affinity/cli/commands/field_cmds.py +413 -0
- affinity/cli/commands/interaction_cmds.py +875 -0
- affinity/cli/commands/list_cmds.py +3152 -0
- affinity/cli/commands/note_cmds.py +433 -0
- affinity/cli/commands/opportunity_cmds.py +1174 -0
- affinity/cli/commands/person_cmds.py +1980 -0
- affinity/cli/commands/query_cmd.py +444 -0
- affinity/cli/commands/relationship_strength_cmds.py +62 -0
- affinity/cli/commands/reminder_cmds.py +595 -0
- affinity/cli/commands/resolve_url_cmd.py +127 -0
- affinity/cli/commands/session_cmds.py +84 -0
- affinity/cli/commands/task_cmds.py +110 -0
- affinity/cli/commands/version_cmd.py +29 -0
- affinity/cli/commands/whoami_cmd.py +36 -0
- affinity/cli/config.py +108 -0
- affinity/cli/context.py +749 -0
- affinity/cli/csv_utils.py +195 -0
- affinity/cli/date_utils.py +42 -0
- affinity/cli/decorators.py +77 -0
- affinity/cli/errors.py +28 -0
- affinity/cli/field_utils.py +355 -0
- affinity/cli/formatters.py +551 -0
- affinity/cli/help_json.py +283 -0
- affinity/cli/logging.py +100 -0
- affinity/cli/main.py +261 -0
- affinity/cli/options.py +53 -0
- affinity/cli/paths.py +32 -0
- affinity/cli/progress.py +183 -0
- affinity/cli/query/__init__.py +163 -0
- affinity/cli/query/aggregates.py +357 -0
- affinity/cli/query/dates.py +194 -0
- affinity/cli/query/exceptions.py +147 -0
- affinity/cli/query/executor.py +1236 -0
- affinity/cli/query/filters.py +248 -0
- affinity/cli/query/models.py +333 -0
- affinity/cli/query/output.py +331 -0
- affinity/cli/query/parser.py +619 -0
- affinity/cli/query/planner.py +430 -0
- affinity/cli/query/progress.py +270 -0
- affinity/cli/query/schema.py +439 -0
- affinity/cli/render.py +1589 -0
- affinity/cli/resolve.py +222 -0
- affinity/cli/resolvers.py +249 -0
- affinity/cli/results.py +308 -0
- affinity/cli/runner.py +218 -0
- affinity/cli/serialization.py +65 -0
- affinity/cli/session_cache.py +276 -0
- affinity/cli/types.py +70 -0
- affinity/client.py +771 -0
- affinity/clients/__init__.py +19 -0
- affinity/clients/http.py +3664 -0
- affinity/clients/pipeline.py +165 -0
- affinity/compare.py +501 -0
- affinity/downloads.py +114 -0
- affinity/exceptions.py +615 -0
- affinity/filters.py +1128 -0
- affinity/hooks.py +198 -0
- affinity/inbound_webhooks.py +302 -0
- affinity/models/__init__.py +163 -0
- affinity/models/entities.py +798 -0
- affinity/models/pagination.py +513 -0
- affinity/models/rate_limit_snapshot.py +48 -0
- affinity/models/secondary.py +413 -0
- affinity/models/types.py +663 -0
- affinity/policies.py +40 -0
- affinity/progress.py +22 -0
- affinity/py.typed +0 -0
- affinity/services/__init__.py +42 -0
- affinity/services/companies.py +1286 -0
- affinity/services/lists.py +1892 -0
- affinity/services/opportunities.py +1330 -0
- affinity/services/persons.py +1348 -0
- affinity/services/rate_limits.py +173 -0
- affinity/services/tasks.py +193 -0
- affinity/services/v1_only.py +2445 -0
- affinity/types.py +83 -0
- affinity_sdk-0.9.5.dist-info/METADATA +622 -0
- affinity_sdk-0.9.5.dist-info/RECORD +92 -0
- affinity_sdk-0.9.5.dist-info/WHEEL +4 -0
- affinity_sdk-0.9.5.dist-info/entry_points.txt +2 -0
- affinity_sdk-0.9.5.dist-info/licenses/LICENSE +21 -0
affinity/downloads.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator, Iterator
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
from urllib.parse import unquote_to_bytes
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class DownloadedFile:
|
|
12
|
+
"""
|
|
13
|
+
A streamed download response with useful metadata extracted from headers.
|
|
14
|
+
|
|
15
|
+
Notes:
|
|
16
|
+
- `filename` is derived from `Content-Disposition` when present.
|
|
17
|
+
- `size` is derived from `Content-Length` when present.
|
|
18
|
+
- `headers` is a lossy dict view (last value wins); use `raw_headers` when needed.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
headers: dict[str, str]
|
|
22
|
+
raw_headers: list[tuple[str, str]]
|
|
23
|
+
content_type: str | None
|
|
24
|
+
filename: str | None
|
|
25
|
+
size: int | None
|
|
26
|
+
iter_bytes: Iterator[bytes]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class AsyncDownloadedFile:
|
|
31
|
+
"""
|
|
32
|
+
Async variant of `DownloadedFile`.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
headers: dict[str, str]
|
|
36
|
+
raw_headers: list[tuple[str, str]]
|
|
37
|
+
content_type: str | None
|
|
38
|
+
filename: str | None
|
|
39
|
+
size: int | None
|
|
40
|
+
iter_bytes: AsyncIterator[bytes]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _filename_from_content_disposition(value: str | None) -> str | None:
|
|
44
|
+
if not value:
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
# Common shapes:
|
|
48
|
+
# - attachment; filename="file.txt"
|
|
49
|
+
# - attachment; filename=file.txt
|
|
50
|
+
# - attachment; filename*=UTF-8''file%20name.txt
|
|
51
|
+
parts = [p.strip() for p in value.split(";") if p.strip()]
|
|
52
|
+
if len(parts) <= 1:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
params: dict[str, str] = {}
|
|
56
|
+
for part in parts[1:]:
|
|
57
|
+
if "=" not in part:
|
|
58
|
+
continue
|
|
59
|
+
k, v = part.split("=", 1)
|
|
60
|
+
key = k.strip().lower()
|
|
61
|
+
raw = v.strip()
|
|
62
|
+
if len(raw) >= 2 and raw[0] == raw[-1] and raw[0] in {'"', "'"}:
|
|
63
|
+
raw = raw[1:-1]
|
|
64
|
+
params[key] = raw
|
|
65
|
+
|
|
66
|
+
filename: str | None = None
|
|
67
|
+
if "filename*" in params:
|
|
68
|
+
raw = params["filename*"]
|
|
69
|
+
try:
|
|
70
|
+
charset = "utf-8"
|
|
71
|
+
encoded = raw
|
|
72
|
+
if "''" in raw:
|
|
73
|
+
charset, encoded = raw.split("''", 1)
|
|
74
|
+
elif "'" in raw:
|
|
75
|
+
# charset'lang'value
|
|
76
|
+
charset, rest = raw.split("'", 1)
|
|
77
|
+
_, encoded = rest.split("'", 1)
|
|
78
|
+
decoded_bytes = unquote_to_bytes(encoded)
|
|
79
|
+
filename = decoded_bytes.decode(charset or "utf-8", errors="replace")
|
|
80
|
+
except Exception:
|
|
81
|
+
filename = None
|
|
82
|
+
|
|
83
|
+
if filename is None and "filename" in params:
|
|
84
|
+
filename = params["filename"]
|
|
85
|
+
|
|
86
|
+
if not filename:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
# Avoid returning path-like values.
|
|
90
|
+
safe = Path(filename).name
|
|
91
|
+
return safe or None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _download_info_from_headers(
|
|
95
|
+
raw_headers: list[tuple[str, str]],
|
|
96
|
+
) -> dict[str, Any]:
|
|
97
|
+
headers = dict(raw_headers)
|
|
98
|
+
content_type = headers.get("Content-Type") or headers.get("content-type")
|
|
99
|
+
content_disposition = headers.get("Content-Disposition") or headers.get("content-disposition")
|
|
100
|
+
length = headers.get("Content-Length") or headers.get("content-length")
|
|
101
|
+
|
|
102
|
+
size: int | None = None
|
|
103
|
+
if length is not None:
|
|
104
|
+
try:
|
|
105
|
+
size = int(length)
|
|
106
|
+
except Exception:
|
|
107
|
+
size = None
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"headers": headers,
|
|
111
|
+
"content_type": content_type,
|
|
112
|
+
"filename": _filename_from_content_disposition(content_disposition),
|
|
113
|
+
"size": size,
|
|
114
|
+
}
|