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.
Files changed (92) hide show
  1. affinity/__init__.py +139 -0
  2. affinity/cli/__init__.py +7 -0
  3. affinity/cli/click_compat.py +27 -0
  4. affinity/cli/commands/__init__.py +1 -0
  5. affinity/cli/commands/_entity_files_dump.py +219 -0
  6. affinity/cli/commands/_list_entry_fields.py +41 -0
  7. affinity/cli/commands/_v1_parsing.py +77 -0
  8. affinity/cli/commands/company_cmds.py +2139 -0
  9. affinity/cli/commands/completion_cmd.py +33 -0
  10. affinity/cli/commands/config_cmds.py +540 -0
  11. affinity/cli/commands/entry_cmds.py +33 -0
  12. affinity/cli/commands/field_cmds.py +413 -0
  13. affinity/cli/commands/interaction_cmds.py +875 -0
  14. affinity/cli/commands/list_cmds.py +3152 -0
  15. affinity/cli/commands/note_cmds.py +433 -0
  16. affinity/cli/commands/opportunity_cmds.py +1174 -0
  17. affinity/cli/commands/person_cmds.py +1980 -0
  18. affinity/cli/commands/query_cmd.py +444 -0
  19. affinity/cli/commands/relationship_strength_cmds.py +62 -0
  20. affinity/cli/commands/reminder_cmds.py +595 -0
  21. affinity/cli/commands/resolve_url_cmd.py +127 -0
  22. affinity/cli/commands/session_cmds.py +84 -0
  23. affinity/cli/commands/task_cmds.py +110 -0
  24. affinity/cli/commands/version_cmd.py +29 -0
  25. affinity/cli/commands/whoami_cmd.py +36 -0
  26. affinity/cli/config.py +108 -0
  27. affinity/cli/context.py +749 -0
  28. affinity/cli/csv_utils.py +195 -0
  29. affinity/cli/date_utils.py +42 -0
  30. affinity/cli/decorators.py +77 -0
  31. affinity/cli/errors.py +28 -0
  32. affinity/cli/field_utils.py +355 -0
  33. affinity/cli/formatters.py +551 -0
  34. affinity/cli/help_json.py +283 -0
  35. affinity/cli/logging.py +100 -0
  36. affinity/cli/main.py +261 -0
  37. affinity/cli/options.py +53 -0
  38. affinity/cli/paths.py +32 -0
  39. affinity/cli/progress.py +183 -0
  40. affinity/cli/query/__init__.py +163 -0
  41. affinity/cli/query/aggregates.py +357 -0
  42. affinity/cli/query/dates.py +194 -0
  43. affinity/cli/query/exceptions.py +147 -0
  44. affinity/cli/query/executor.py +1236 -0
  45. affinity/cli/query/filters.py +248 -0
  46. affinity/cli/query/models.py +333 -0
  47. affinity/cli/query/output.py +331 -0
  48. affinity/cli/query/parser.py +619 -0
  49. affinity/cli/query/planner.py +430 -0
  50. affinity/cli/query/progress.py +270 -0
  51. affinity/cli/query/schema.py +439 -0
  52. affinity/cli/render.py +1589 -0
  53. affinity/cli/resolve.py +222 -0
  54. affinity/cli/resolvers.py +249 -0
  55. affinity/cli/results.py +308 -0
  56. affinity/cli/runner.py +218 -0
  57. affinity/cli/serialization.py +65 -0
  58. affinity/cli/session_cache.py +276 -0
  59. affinity/cli/types.py +70 -0
  60. affinity/client.py +771 -0
  61. affinity/clients/__init__.py +19 -0
  62. affinity/clients/http.py +3664 -0
  63. affinity/clients/pipeline.py +165 -0
  64. affinity/compare.py +501 -0
  65. affinity/downloads.py +114 -0
  66. affinity/exceptions.py +615 -0
  67. affinity/filters.py +1128 -0
  68. affinity/hooks.py +198 -0
  69. affinity/inbound_webhooks.py +302 -0
  70. affinity/models/__init__.py +163 -0
  71. affinity/models/entities.py +798 -0
  72. affinity/models/pagination.py +513 -0
  73. affinity/models/rate_limit_snapshot.py +48 -0
  74. affinity/models/secondary.py +413 -0
  75. affinity/models/types.py +663 -0
  76. affinity/policies.py +40 -0
  77. affinity/progress.py +22 -0
  78. affinity/py.typed +0 -0
  79. affinity/services/__init__.py +42 -0
  80. affinity/services/companies.py +1286 -0
  81. affinity/services/lists.py +1892 -0
  82. affinity/services/opportunities.py +1330 -0
  83. affinity/services/persons.py +1348 -0
  84. affinity/services/rate_limits.py +173 -0
  85. affinity/services/tasks.py +193 -0
  86. affinity/services/v1_only.py +2445 -0
  87. affinity/types.py +83 -0
  88. affinity_sdk-0.9.5.dist-info/METADATA +622 -0
  89. affinity_sdk-0.9.5.dist-info/RECORD +92 -0
  90. affinity_sdk-0.9.5.dist-info/WHEEL +4 -0
  91. affinity_sdk-0.9.5.dist-info/entry_points.txt +2 -0
  92. affinity_sdk-0.9.5.dist-info/licenses/LICENSE +21 -0
affinity/hooks.py ADDED
@@ -0,0 +1,198 @@
1
+ """
2
+ Public hook types for request/response instrumentation (DX-008).
3
+
4
+ Long-term, the preferred API is `on_event(HookEvent)` which can represent retries,
5
+ redirects, and streaming lifecycles without ambiguity. The older
6
+ `on_request/on_response/on_error` hooks remain available as adapters.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from collections.abc import Awaitable, Callable
12
+ from dataclasses import dataclass
13
+ from typing import Literal, TypeAlias
14
+
15
+ # =============================================================================
16
+ # Legacy 3-hook API (adapters)
17
+ # =============================================================================
18
+
19
+
20
+ @dataclass(frozen=True, slots=True)
21
+ class RequestInfo:
22
+ """
23
+ Sanitized request metadata for hooks.
24
+
25
+ Note: authentication is intentionally excluded.
26
+ """
27
+
28
+ method: str
29
+ url: str
30
+ headers: dict[str, str]
31
+
32
+
33
+ @dataclass(frozen=True, slots=True)
34
+ class ResponseInfo:
35
+ """Sanitized response metadata for hooks."""
36
+
37
+ status_code: int
38
+ headers: dict[str, str]
39
+ elapsed_ms: float
40
+ request: RequestInfo
41
+ cache_hit: bool = False
42
+
43
+
44
+ @dataclass(frozen=True, slots=True)
45
+ class ErrorInfo:
46
+ """Sanitized error metadata for hooks."""
47
+
48
+ error: BaseException
49
+ elapsed_ms: float
50
+ request: RequestInfo
51
+
52
+
53
+ RequestHook: TypeAlias = Callable[[RequestInfo], None]
54
+ ResponseHook: TypeAlias = Callable[[ResponseInfo], None]
55
+ ErrorHook: TypeAlias = Callable[[ErrorInfo], None]
56
+
57
+
58
+ # =============================================================================
59
+ # Event-based hook API
60
+ # =============================================================================
61
+
62
+
63
+ @dataclass(frozen=True, slots=True)
64
+ class RequestStarted:
65
+ client_request_id: str
66
+ request: RequestInfo
67
+ api_version: Literal["v1", "v2", "external"]
68
+ type: Literal["request_started"] = "request_started"
69
+
70
+
71
+ @dataclass(frozen=True, slots=True)
72
+ class RequestRetrying:
73
+ client_request_id: str
74
+ request: RequestInfo
75
+ attempt: int
76
+ wait_seconds: float
77
+ reason: str
78
+ type: Literal["request_retrying"] = "request_retrying"
79
+
80
+
81
+ @dataclass(frozen=True, slots=True)
82
+ class RedirectFollowed:
83
+ client_request_id: str
84
+ from_url: str
85
+ to_url: str
86
+ hop: int
87
+ external: bool
88
+ type: Literal["redirect_followed"] = "redirect_followed"
89
+
90
+
91
+ @dataclass(frozen=True, slots=True)
92
+ class ResponseHeadersReceived:
93
+ client_request_id: str
94
+ request: RequestInfo
95
+ status_code: int
96
+ headers: list[tuple[str, str]]
97
+ elapsed_ms: float
98
+ external: bool
99
+ cache_hit: bool
100
+ request_id: str | None = None
101
+ type: Literal["response_headers_received"] = "response_headers_received"
102
+
103
+
104
+ @dataclass(frozen=True, slots=True)
105
+ class RequestFailed:
106
+ client_request_id: str
107
+ request: RequestInfo
108
+ error: BaseException
109
+ elapsed_ms: float
110
+ external: bool
111
+ type: Literal["request_failed"] = "request_failed"
112
+
113
+
114
+ @dataclass(frozen=True, slots=True)
115
+ class RequestSucceeded:
116
+ client_request_id: str
117
+ request: RequestInfo
118
+ status_code: int
119
+ elapsed_ms: float
120
+ external: bool
121
+ type: Literal["request_succeeded"] = "request_succeeded"
122
+
123
+
124
+ @dataclass(frozen=True, slots=True)
125
+ class StreamCompleted:
126
+ client_request_id: str
127
+ request: RequestInfo
128
+ bytes_read: int
129
+ bytes_total: int | None
130
+ elapsed_ms: float
131
+ external: bool
132
+ type: Literal["stream_completed"] = "stream_completed"
133
+
134
+
135
+ @dataclass(frozen=True, slots=True)
136
+ class StreamAborted:
137
+ client_request_id: str
138
+ request: RequestInfo
139
+ reason: str
140
+ bytes_read: int
141
+ bytes_total: int | None
142
+ elapsed_ms: float
143
+ external: bool
144
+ type: Literal["stream_aborted"] = "stream_aborted"
145
+
146
+
147
+ @dataclass(frozen=True, slots=True)
148
+ class StreamFailed:
149
+ client_request_id: str
150
+ request: RequestInfo
151
+ error: BaseException
152
+ bytes_read: int
153
+ bytes_total: int | None
154
+ elapsed_ms: float
155
+ external: bool
156
+ type: Literal["stream_failed"] = "stream_failed"
157
+
158
+
159
+ HookEvent: TypeAlias = (
160
+ RequestStarted
161
+ | RequestRetrying
162
+ | RedirectFollowed
163
+ | ResponseHeadersReceived
164
+ | RequestFailed
165
+ | RequestSucceeded
166
+ | StreamCompleted
167
+ | StreamAborted
168
+ | StreamFailed
169
+ )
170
+
171
+ EventHook: TypeAlias = Callable[[HookEvent], None]
172
+ AsyncEventHook: TypeAlias = Callable[[HookEvent], Awaitable[None]]
173
+ AnyEventHook: TypeAlias = Callable[[HookEvent], None | Awaitable[None]]
174
+
175
+
176
+ __all__ = [
177
+ # Legacy 3-hook API
178
+ "RequestInfo",
179
+ "ResponseInfo",
180
+ "ErrorInfo",
181
+ "RequestHook",
182
+ "ResponseHook",
183
+ "ErrorHook",
184
+ # Event-based API
185
+ "HookEvent",
186
+ "EventHook",
187
+ "AsyncEventHook",
188
+ "AnyEventHook",
189
+ "RequestStarted",
190
+ "RequestRetrying",
191
+ "RedirectFollowed",
192
+ "ResponseHeadersReceived",
193
+ "RequestFailed",
194
+ "RequestSucceeded",
195
+ "StreamCompleted",
196
+ "StreamAborted",
197
+ "StreamFailed",
198
+ ]
@@ -0,0 +1,302 @@
1
+ """
2
+ Inbound webhook parsing helpers.
3
+
4
+ The SDK manages webhook subscriptions via the V1 API (`client.webhooks`). This module
5
+ provides optional, framework-agnostic helpers for parsing inbound webhook payloads
6
+ sent by Affinity to your webhook URL.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from collections.abc import Callable, Mapping
13
+ from dataclasses import dataclass
14
+ from datetime import datetime, timedelta, timezone
15
+ from typing import Any, Generic, TypeVar
16
+
17
+ from pydantic import AliasChoices, BaseModel, ConfigDict, Field
18
+
19
+ from .exceptions import (
20
+ WebhookInvalidJsonError,
21
+ WebhookInvalidPayloadError,
22
+ WebhookInvalidSentAtError,
23
+ WebhookMissingKeyError,
24
+ )
25
+ from .types import WebhookEvent
26
+
27
+
28
+ class _WebhookModel(BaseModel):
29
+ model_config = ConfigDict(
30
+ extra="ignore",
31
+ populate_by_name=True,
32
+ validate_assignment=True,
33
+ )
34
+
35
+
36
+ class WebhookEnvelope(_WebhookModel):
37
+ """
38
+ Parsed webhook envelope.
39
+
40
+ Affinity webhook requests use the envelope shape:
41
+ - `type`: event string (e.g., "list_entry.created")
42
+ - `body`: event-specific payload object
43
+ - `sent_at`: unix epoch seconds (UTC)
44
+ """
45
+
46
+ type: WebhookEvent
47
+ body: dict[str, Any] = Field(default_factory=dict)
48
+ sent_at: datetime
49
+ sent_at_epoch: int
50
+
51
+
52
+ class WebhookPerson(_WebhookModel):
53
+ id: int
54
+ type: int | None = None
55
+ first_name: str | None = None
56
+ last_name: str | None = None
57
+ primary_email: str | None = None
58
+ emails: list[str] = Field(default_factory=list)
59
+ company_ids: list[int] | None = Field(
60
+ default=None,
61
+ validation_alias=AliasChoices("organization_ids", "organizationIds"),
62
+ )
63
+
64
+
65
+ class WebhookOrganization(_WebhookModel):
66
+ id: int
67
+ name: str | None = None
68
+ domain: str | None = None
69
+ domains: list[str] = Field(default_factory=list)
70
+ crunchbase_uuid: str | None = None
71
+ global_: bool | None = Field(None, alias="global")
72
+
73
+
74
+ class OrganizationMergedBody(_WebhookModel):
75
+ changer: WebhookPerson | None = None
76
+ removed_company: WebhookOrganization | None = None
77
+ company: WebhookOrganization | None = None
78
+ merged_at: str | None = None
79
+
80
+
81
+ class ListEntryCreatedBody(_WebhookModel):
82
+ id: int
83
+ list_id: int | None = None
84
+ creator_id: int | None = None
85
+ entity_id: int | None = None
86
+ entity_type: int | None = None
87
+ created_at: str | None = None
88
+ entity: dict[str, Any] | None = None
89
+
90
+
91
+ class FieldValueUpdatedBody(_WebhookModel):
92
+ id: int
93
+ field_id: int | None = None
94
+ list_entry_id: int | None = None
95
+ entity_type: int | None = None
96
+ value_type: int | None = None
97
+ entity_id: int | None = None
98
+ value: Any | None = None
99
+ field: dict[str, Any] | None = None
100
+
101
+
102
+ TBody = TypeVar("TBody")
103
+ BodyParser = Callable[[dict[str, Any]], Any]
104
+
105
+
106
+ @dataclass(frozen=True, slots=True)
107
+ class ParsedWebhook(Generic[TBody]):
108
+ type: WebhookEvent
109
+ body: TBody
110
+ sent_at: datetime
111
+ sent_at_epoch: int
112
+
113
+
114
+ def _parse_json_payload(payload: bytes | str) -> Any:
115
+ if isinstance(payload, bytes):
116
+ try:
117
+ payload = payload.decode("utf-8")
118
+ except UnicodeDecodeError as e:
119
+ raise WebhookInvalidJsonError("Webhook payload bytes are not valid UTF-8") from e
120
+ try:
121
+ return json.loads(payload)
122
+ except json.JSONDecodeError as e:
123
+ raise WebhookInvalidJsonError("Webhook payload is not valid JSON") from e
124
+
125
+
126
+ def _require_key(data: Mapping[str, Any], key: str) -> Any:
127
+ if key not in data:
128
+ raise WebhookMissingKeyError(f"Webhook payload is missing required key: {key}", key=key)
129
+ return data[key]
130
+
131
+
132
+ def _parse_sent_at_epoch(value: Any) -> int:
133
+ if isinstance(value, bool):
134
+ raise WebhookInvalidSentAtError("Webhook 'sent_at' must be an epoch seconds integer")
135
+ if isinstance(value, int):
136
+ return value
137
+ if isinstance(value, float):
138
+ if not value.is_integer():
139
+ raise WebhookInvalidSentAtError("Webhook 'sent_at' must be an integer epoch seconds")
140
+ return int(value)
141
+ if isinstance(value, str):
142
+ text = value.strip()
143
+ if text.isdigit():
144
+ return int(text)
145
+ raise WebhookInvalidSentAtError("Webhook 'sent_at' must be an epoch seconds integer")
146
+
147
+
148
+ _DEFAULT_WEBHOOK_MAX_AGE_SECONDS = 300
149
+ _DEFAULT_WEBHOOK_FUTURE_SKEW_SECONDS = 120
150
+
151
+
152
+ def _normalize_now(now: datetime | None) -> datetime:
153
+ """
154
+ Return current UTC time, or normalize provided datetime to UTC.
155
+
156
+ Handles naive datetimes defensively - callers may pass datetime objects
157
+ from sources outside ISODatetime-validated models (e.g., tests, manual
158
+ construction). This is intentional defense-in-depth.
159
+ """
160
+ current = now or datetime.now(timezone.utc)
161
+ if current.tzinfo is None:
162
+ return current.replace(tzinfo=timezone.utc)
163
+ return current.astimezone(timezone.utc)
164
+
165
+
166
+ def _validate_sent_at(
167
+ sent_at: datetime,
168
+ *,
169
+ now: datetime | None,
170
+ max_age_seconds: int | None,
171
+ max_future_skew_seconds: int | None,
172
+ ) -> None:
173
+ current = _normalize_now(now)
174
+ if max_age_seconds is not None:
175
+ if max_age_seconds < 0:
176
+ raise ValueError("max_age_seconds must be >= 0")
177
+ oldest = current - timedelta(seconds=max_age_seconds)
178
+ if sent_at < oldest:
179
+ raise WebhookInvalidSentAtError(
180
+ "Webhook 'sent_at' is too old for the allowed replay window."
181
+ )
182
+ if max_future_skew_seconds is not None:
183
+ if max_future_skew_seconds < 0:
184
+ raise ValueError("max_future_skew_seconds must be >= 0")
185
+ latest = current + timedelta(seconds=max_future_skew_seconds)
186
+ if sent_at > latest:
187
+ raise WebhookInvalidSentAtError(
188
+ "Webhook 'sent_at' is too far in the future for the allowed clock skew."
189
+ )
190
+
191
+
192
+ def parse_webhook(
193
+ payload: bytes | str | Mapping[str, Any],
194
+ *,
195
+ max_age_seconds: int | None = _DEFAULT_WEBHOOK_MAX_AGE_SECONDS,
196
+ max_future_skew_seconds: int | None = _DEFAULT_WEBHOOK_FUTURE_SKEW_SECONDS,
197
+ now: datetime | None = None,
198
+ ) -> WebhookEnvelope:
199
+ """
200
+ Parse an inbound webhook payload into a `WebhookEnvelope`.
201
+
202
+ Args:
203
+ payload: Raw request body as bytes/str, or an already-decoded dict.
204
+ max_age_seconds: Reject payloads older than this many seconds (None disables).
205
+ max_future_skew_seconds: Reject payloads too far in the future (None disables).
206
+ now: Override the current time (UTC) for testing.
207
+
208
+ Raises:
209
+ WebhookInvalidJsonError: If payload is not valid JSON (bytes/str inputs).
210
+ WebhookMissingKeyError: If required keys are missing.
211
+ WebhookInvalidPayloadError: If the decoded payload isn't a JSON object.
212
+ WebhookInvalidSentAtError: If `sent_at` is invalid or outside the allowed window.
213
+ """
214
+
215
+ data: Any = _parse_json_payload(payload) if isinstance(payload, (bytes, str)) else payload
216
+
217
+ if not isinstance(data, Mapping):
218
+ raise WebhookInvalidPayloadError("Webhook payload must be a JSON object at the top level")
219
+
220
+ event_type = _require_key(data, "type")
221
+ body = _require_key(data, "body")
222
+ sent_at_raw = _require_key(data, "sent_at")
223
+
224
+ if not isinstance(body, Mapping):
225
+ raise WebhookInvalidPayloadError("Webhook 'body' must be a JSON object")
226
+
227
+ sent_at_epoch = _parse_sent_at_epoch(sent_at_raw)
228
+ sent_at = datetime.fromtimestamp(sent_at_epoch, tz=timezone.utc)
229
+ _validate_sent_at(
230
+ sent_at,
231
+ now=now,
232
+ max_age_seconds=max_age_seconds,
233
+ max_future_skew_seconds=max_future_skew_seconds,
234
+ )
235
+
236
+ return WebhookEnvelope.model_validate(
237
+ {
238
+ "type": event_type,
239
+ "body": dict(body),
240
+ "sent_at": sent_at,
241
+ "sent_at_epoch": sent_at_epoch,
242
+ }
243
+ )
244
+
245
+
246
+ def _coerce_handler(handler: type[BaseModel] | BodyParser) -> BodyParser:
247
+ if isinstance(handler, type) and issubclass(handler, BaseModel):
248
+ return lambda body: handler.model_validate(body)
249
+ return handler
250
+
251
+
252
+ class BodyRegistry:
253
+ """
254
+ Registry mapping webhook event types to body parsers.
255
+
256
+ Parsers should accept a JSON object (dict) and return either a Pydantic model
257
+ or any other parsed representation.
258
+ """
259
+
260
+ def __init__(self, handlers: Mapping[WebhookEvent, type[BaseModel] | BodyParser] | None = None):
261
+ self._handlers: dict[WebhookEvent, BodyParser] = {}
262
+ if handlers:
263
+ for event, handler in handlers.items():
264
+ self.register(event, handler)
265
+
266
+ def register(self, event: WebhookEvent | str, handler: type[BaseModel] | BodyParser) -> None:
267
+ self._handlers[WebhookEvent(event)] = _coerce_handler(handler)
268
+
269
+ def parse_body(self, event: WebhookEvent, body: dict[str, Any]) -> Any:
270
+ parser = self._handlers.get(event)
271
+ return parser(body) if parser else body
272
+
273
+
274
+ DEFAULT_BODY_REGISTRY = BodyRegistry(
275
+ {
276
+ WebhookEvent.ORGANIZATION_MERGED: OrganizationMergedBody,
277
+ WebhookEvent.LIST_ENTRY_CREATED: ListEntryCreatedBody,
278
+ WebhookEvent.FIELD_VALUE_UPDATED: FieldValueUpdatedBody,
279
+ WebhookEvent.PERSON_CREATED: WebhookPerson,
280
+ WebhookEvent.ORGANIZATION_CREATED: WebhookOrganization,
281
+ }
282
+ )
283
+
284
+
285
+ def dispatch_webhook(
286
+ envelope: WebhookEnvelope,
287
+ *,
288
+ registry: BodyRegistry = DEFAULT_BODY_REGISTRY,
289
+ ) -> ParsedWebhook[BaseModel | dict[str, Any]]:
290
+ """
291
+ Parse the envelope body using a registry and return a `ParsedWebhook`.
292
+
293
+ If the event type has no registered parser, the body remains a dict.
294
+ """
295
+
296
+ parsed_body = registry.parse_body(envelope.type, envelope.body)
297
+ return ParsedWebhook(
298
+ type=envelope.type,
299
+ body=parsed_body,
300
+ sent_at=envelope.sent_at,
301
+ sent_at_epoch=envelope.sent_at_epoch,
302
+ )
@@ -0,0 +1,163 @@
1
+ """
2
+ Affinity data models.
3
+
4
+ All Pydantic models are available from this module.
5
+
6
+ Tip:
7
+ ID types and enums live in `affinity.types`.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ # Core entities
13
+ from .entities import (
14
+ # List
15
+ AffinityList,
16
+ # Base
17
+ AffinityModel,
18
+ # Company
19
+ Company,
20
+ CompanyCreate,
21
+ CompanyUpdate,
22
+ DropdownOption,
23
+ FieldCreate,
24
+ # Field
25
+ FieldMetadata,
26
+ FieldValue,
27
+ FieldValueChange,
28
+ FieldValueCreate,
29
+ ListCreate,
30
+ # List Entry
31
+ ListEntry,
32
+ ListEntryCreate,
33
+ ListEntryWithEntity,
34
+ ListPermission,
35
+ ListSummary,
36
+ # Opportunity
37
+ Opportunity,
38
+ OpportunityCreate,
39
+ OpportunitySummary,
40
+ OpportunityUpdate,
41
+ # Person
42
+ Person,
43
+ PersonCreate,
44
+ PersonUpdate,
45
+ # Saved View
46
+ SavedView,
47
+ )
48
+
49
+ # Pagination
50
+ from .pagination import (
51
+ AsyncPageIterator,
52
+ BatchOperationResponse,
53
+ BatchOperationResult,
54
+ PageIterator,
55
+ PaginatedResponse,
56
+ PaginationInfo,
57
+ PaginationProgress,
58
+ )
59
+
60
+ # Rate limit snapshot (unified)
61
+ from .rate_limit_snapshot import RateLimitBucket, RateLimitSnapshot
62
+
63
+ # Secondary models
64
+ from .secondary import (
65
+ # File
66
+ EntityFile,
67
+ Grant,
68
+ # Interaction
69
+ Interaction,
70
+ InteractionCreate,
71
+ InteractionUpdate,
72
+ # Note
73
+ Note,
74
+ NoteCreate,
75
+ NoteUpdate,
76
+ RateLimitInfo,
77
+ RateLimits,
78
+ # Relationship
79
+ RelationshipStrength,
80
+ # Reminder
81
+ Reminder,
82
+ ReminderCreate,
83
+ ReminderUpdate,
84
+ Tenant,
85
+ WebhookCreate,
86
+ # Webhook
87
+ WebhookSubscription,
88
+ WebhookUpdate,
89
+ # Auth
90
+ WhoAmI,
91
+ )
92
+
93
+ __all__ = [
94
+ # Base
95
+ "AffinityModel",
96
+ # Person
97
+ "Person",
98
+ "PersonCreate",
99
+ "PersonUpdate",
100
+ # Company
101
+ "Company",
102
+ "CompanyCreate",
103
+ "CompanyUpdate",
104
+ # Opportunity
105
+ "Opportunity",
106
+ "OpportunityCreate",
107
+ "OpportunitySummary",
108
+ "OpportunityUpdate",
109
+ # List
110
+ "AffinityList",
111
+ "ListSummary",
112
+ "ListCreate",
113
+ "ListPermission",
114
+ # List Entry
115
+ "ListEntry",
116
+ "ListEntryCreate",
117
+ "ListEntryWithEntity",
118
+ # Field
119
+ "FieldMetadata",
120
+ "FieldCreate",
121
+ "FieldValue",
122
+ "FieldValueChange",
123
+ "FieldValueCreate",
124
+ "DropdownOption",
125
+ # Saved View
126
+ "SavedView",
127
+ # Note
128
+ "Note",
129
+ "NoteCreate",
130
+ "NoteUpdate",
131
+ # Reminder
132
+ "Reminder",
133
+ "ReminderCreate",
134
+ "ReminderUpdate",
135
+ # Webhook
136
+ "WebhookSubscription",
137
+ "WebhookCreate",
138
+ "WebhookUpdate",
139
+ # Interaction
140
+ "Interaction",
141
+ "InteractionCreate",
142
+ "InteractionUpdate",
143
+ # File
144
+ "EntityFile",
145
+ # Relationship
146
+ "RelationshipStrength",
147
+ # Auth
148
+ "WhoAmI",
149
+ "RateLimits",
150
+ "RateLimitInfo",
151
+ "RateLimitBucket",
152
+ "RateLimitSnapshot",
153
+ "Tenant",
154
+ "Grant",
155
+ # Pagination
156
+ "PaginationInfo",
157
+ "PaginationProgress",
158
+ "PaginatedResponse",
159
+ "PageIterator",
160
+ "AsyncPageIterator",
161
+ "BatchOperationResponse",
162
+ "BatchOperationResult",
163
+ ]