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
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from affinity.models.entities import FieldCreate, FieldMetadata, FieldValueChange
|
|
4
|
+
from affinity.models.types import EntityType, FieldValueType
|
|
5
|
+
from affinity.types import (
|
|
6
|
+
CompanyId,
|
|
7
|
+
FieldId,
|
|
8
|
+
FieldValueChangeAction,
|
|
9
|
+
ListEntryId,
|
|
10
|
+
ListId,
|
|
11
|
+
OpportunityId,
|
|
12
|
+
PersonId,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from ..click_compat import RichCommand, RichGroup, click
|
|
16
|
+
from ..context import CLIContext
|
|
17
|
+
from ..decorators import category, destructive
|
|
18
|
+
from ..errors import CLIError
|
|
19
|
+
from ..options import output_options
|
|
20
|
+
from ..resolve import resolve_list_selector
|
|
21
|
+
from ..results import CommandContext
|
|
22
|
+
from ..runner import CommandOutput, run_command
|
|
23
|
+
from ..serialization import serialize_model_for_cli
|
|
24
|
+
from ._v1_parsing import parse_choice
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.group(name="field", cls=RichGroup)
|
|
28
|
+
def field_group() -> None:
|
|
29
|
+
"""Field commands."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_ENTITY_TYPE_MAP = {
|
|
33
|
+
"person": EntityType.PERSON,
|
|
34
|
+
"people": EntityType.PERSON,
|
|
35
|
+
"company": EntityType.ORGANIZATION,
|
|
36
|
+
"organization": EntityType.ORGANIZATION,
|
|
37
|
+
"opportunity": EntityType.OPPORTUNITY,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_VALUE_TYPE_MAP = {ft.value: ft for ft in FieldValueType}
|
|
41
|
+
|
|
42
|
+
_ACTION_TYPE_MAP = {
|
|
43
|
+
"create": FieldValueChangeAction.CREATE,
|
|
44
|
+
"delete": FieldValueChangeAction.DELETE,
|
|
45
|
+
"update": FieldValueChangeAction.UPDATE,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_ACTION_TYPE_NAMES = {
|
|
49
|
+
FieldValueChangeAction.CREATE: "create",
|
|
50
|
+
FieldValueChangeAction.DELETE: "delete",
|
|
51
|
+
FieldValueChangeAction.UPDATE: "update",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _field_payload(field: FieldMetadata) -> dict[str, object]:
|
|
56
|
+
return serialize_model_for_cli(field)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _field_value_change_payload(item: FieldValueChange) -> dict[str, object]:
|
|
60
|
+
"""Convert FieldValueChange to CLI output format."""
|
|
61
|
+
# Display enum name instead of integer (consistent with interaction_cmds.py)
|
|
62
|
+
action_name = _ACTION_TYPE_NAMES.get(
|
|
63
|
+
FieldValueChangeAction(item.action_type),
|
|
64
|
+
str(item.action_type),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Flatten changer name for table display
|
|
68
|
+
changer_name = None
|
|
69
|
+
if item.changer:
|
|
70
|
+
first = item.changer.first_name or ""
|
|
71
|
+
last = item.changer.last_name or ""
|
|
72
|
+
changer_name = f"{first} {last}".strip() or None
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
"id": int(item.id),
|
|
76
|
+
"fieldId": str(item.field_id),
|
|
77
|
+
"entityId": item.entity_id,
|
|
78
|
+
"listEntryId": int(item.list_entry_id) if item.list_entry_id else None,
|
|
79
|
+
"actionType": action_name,
|
|
80
|
+
"value": item.value,
|
|
81
|
+
"changedAt": item.changed_at,
|
|
82
|
+
"changerName": changer_name,
|
|
83
|
+
"changer": serialize_model_for_cli(item.changer) if item.changer else None,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _validate_exactly_one_selector(
|
|
88
|
+
person_id: int | None,
|
|
89
|
+
company_id: int | None,
|
|
90
|
+
opportunity_id: int | None,
|
|
91
|
+
list_entry_id: int | None,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Validate that exactly one entity selector is provided."""
|
|
94
|
+
selectors = {
|
|
95
|
+
"--person-id": person_id,
|
|
96
|
+
"--company-id": company_id,
|
|
97
|
+
"--opportunity-id": opportunity_id,
|
|
98
|
+
"--list-entry-id": list_entry_id,
|
|
99
|
+
}
|
|
100
|
+
provided = [name for name, value in selectors.items() if value is not None]
|
|
101
|
+
|
|
102
|
+
if len(provided) == 1:
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
if len(provided) == 0:
|
|
106
|
+
raise CLIError(
|
|
107
|
+
"Exactly one entity selector is required: "
|
|
108
|
+
"--person-id, --company-id, --opportunity-id, or --list-entry-id.\n"
|
|
109
|
+
"Example: xaffinity field history field-123 --person-id 456",
|
|
110
|
+
error_type="usage_error",
|
|
111
|
+
exit_code=2,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
raise CLIError(
|
|
115
|
+
f"Only one entity selector allowed, but got {len(provided)}: {', '.join(provided)}",
|
|
116
|
+
error_type="usage_error",
|
|
117
|
+
exit_code=2,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@category("read")
|
|
122
|
+
@field_group.command(name="ls", cls=RichCommand)
|
|
123
|
+
@click.option(
|
|
124
|
+
"--list-id",
|
|
125
|
+
type=str,
|
|
126
|
+
default=None,
|
|
127
|
+
help="Filter by list (ID or name).",
|
|
128
|
+
)
|
|
129
|
+
@click.option(
|
|
130
|
+
"--entity-type",
|
|
131
|
+
type=click.Choice(sorted(_ENTITY_TYPE_MAP.keys())),
|
|
132
|
+
default=None,
|
|
133
|
+
help="Filter by entity type (person/company/opportunity).",
|
|
134
|
+
)
|
|
135
|
+
@output_options
|
|
136
|
+
@click.pass_obj
|
|
137
|
+
def field_ls(
|
|
138
|
+
ctx: CLIContext,
|
|
139
|
+
*,
|
|
140
|
+
list_id: str | None,
|
|
141
|
+
entity_type: str | None,
|
|
142
|
+
) -> None:
|
|
143
|
+
"""List fields with dropdown options."""
|
|
144
|
+
|
|
145
|
+
def fn(ctx: CLIContext, warnings: list[str]) -> CommandOutput:
|
|
146
|
+
client = ctx.get_client(warnings=warnings)
|
|
147
|
+
cache = ctx.session_cache
|
|
148
|
+
parsed_type = parse_choice(entity_type, _ENTITY_TYPE_MAP, label="entity type")
|
|
149
|
+
|
|
150
|
+
# Resolve list selector (accepts name or ID)
|
|
151
|
+
resolved_list_id: int | None = None
|
|
152
|
+
ctx_resolved: dict[str, str] | None = None
|
|
153
|
+
if list_id is not None:
|
|
154
|
+
resolved = resolve_list_selector(client=client, selector=list_id, cache=cache)
|
|
155
|
+
resolved_list_id = int(resolved.list.id)
|
|
156
|
+
# Only include resolved name if different from input (i.e., name was provided)
|
|
157
|
+
if resolved.list.name and resolved.list.name != list_id:
|
|
158
|
+
ctx_resolved = {"listId": resolved.list.name}
|
|
159
|
+
|
|
160
|
+
# Build cache key from resolved ID (not input string) for consistency
|
|
161
|
+
cache_key = f"fields_v1_list{resolved_list_id or 'all'}_type{entity_type or 'all'}"
|
|
162
|
+
|
|
163
|
+
# Check session cache first
|
|
164
|
+
fields: list[FieldMetadata] | None = None
|
|
165
|
+
api_called = False
|
|
166
|
+
if cache.enabled:
|
|
167
|
+
fields = cache.get_list(cache_key, FieldMetadata)
|
|
168
|
+
|
|
169
|
+
if fields is None:
|
|
170
|
+
fields = client.fields.list(
|
|
171
|
+
list_id=ListId(resolved_list_id) if resolved_list_id is not None else None,
|
|
172
|
+
entity_type=parsed_type,
|
|
173
|
+
)
|
|
174
|
+
api_called = True
|
|
175
|
+
# Cache the result
|
|
176
|
+
if cache.enabled:
|
|
177
|
+
cache.set(cache_key, fields)
|
|
178
|
+
|
|
179
|
+
payload = [_field_payload(field) for field in fields]
|
|
180
|
+
|
|
181
|
+
# Build CommandContext
|
|
182
|
+
ctx_modifiers: dict[str, object] = {}
|
|
183
|
+
if resolved_list_id is not None:
|
|
184
|
+
ctx_modifiers["listId"] = resolved_list_id
|
|
185
|
+
if entity_type:
|
|
186
|
+
ctx_modifiers["entityType"] = entity_type
|
|
187
|
+
|
|
188
|
+
cmd_context = CommandContext(
|
|
189
|
+
name="field ls",
|
|
190
|
+
inputs={},
|
|
191
|
+
modifiers=ctx_modifiers,
|
|
192
|
+
resolved=ctx_resolved,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return CommandOutput(data={"fields": payload}, context=cmd_context, api_called=api_called)
|
|
196
|
+
|
|
197
|
+
run_command(ctx, command="field ls", fn=fn)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@category("write")
|
|
201
|
+
@field_group.command(name="create", cls=RichCommand)
|
|
202
|
+
@click.option("--name", required=True, help="Field name.")
|
|
203
|
+
@click.option(
|
|
204
|
+
"--entity-type",
|
|
205
|
+
type=click.Choice(sorted(_ENTITY_TYPE_MAP.keys())),
|
|
206
|
+
required=True,
|
|
207
|
+
help="Entity type (person/company/opportunity).",
|
|
208
|
+
)
|
|
209
|
+
@click.option(
|
|
210
|
+
"--value-type",
|
|
211
|
+
type=click.Choice(sorted(_VALUE_TYPE_MAP.keys())),
|
|
212
|
+
required=True,
|
|
213
|
+
help="Field value type (e.g. text, dropdown, person, number).",
|
|
214
|
+
)
|
|
215
|
+
@click.option("--list-id", type=int, default=None, help="List id for list-specific field.")
|
|
216
|
+
@click.option("--allows-multiple", is_flag=True, help="Allow multiple values.")
|
|
217
|
+
@click.option("--list-specific", is_flag=True, help="Mark as list-specific.")
|
|
218
|
+
@click.option("--required", is_flag=True, help="Mark as required.")
|
|
219
|
+
@output_options
|
|
220
|
+
@click.pass_obj
|
|
221
|
+
def field_create(
|
|
222
|
+
ctx: CLIContext,
|
|
223
|
+
*,
|
|
224
|
+
name: str,
|
|
225
|
+
entity_type: str,
|
|
226
|
+
value_type: str,
|
|
227
|
+
list_id: int | None,
|
|
228
|
+
allows_multiple: bool,
|
|
229
|
+
list_specific: bool,
|
|
230
|
+
required: bool,
|
|
231
|
+
) -> None:
|
|
232
|
+
"""Create a field."""
|
|
233
|
+
|
|
234
|
+
def fn(ctx: CLIContext, warnings: list[str]) -> CommandOutput:
|
|
235
|
+
parsed_entity_type = parse_choice(entity_type, _ENTITY_TYPE_MAP, label="entity type")
|
|
236
|
+
parsed_value_type = parse_choice(value_type, _VALUE_TYPE_MAP, label="value type")
|
|
237
|
+
if parsed_entity_type is None or parsed_value_type is None:
|
|
238
|
+
raise CLIError("Missing required field options.", error_type="usage_error", exit_code=2)
|
|
239
|
+
client = ctx.get_client(warnings=warnings)
|
|
240
|
+
created = client.fields.create(
|
|
241
|
+
FieldCreate(
|
|
242
|
+
name=name,
|
|
243
|
+
entity_type=parsed_entity_type,
|
|
244
|
+
value_type=parsed_value_type,
|
|
245
|
+
list_id=ListId(list_id) if list_id is not None else None,
|
|
246
|
+
allows_multiple=allows_multiple,
|
|
247
|
+
is_list_specific=list_specific,
|
|
248
|
+
is_required=required,
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Invalidate field-related caches
|
|
253
|
+
cache = ctx.session_cache
|
|
254
|
+
cache.invalidate_prefix("list_fields_")
|
|
255
|
+
cache.invalidate_prefix("person_fields_")
|
|
256
|
+
cache.invalidate_prefix("company_fields_")
|
|
257
|
+
cache.invalidate_prefix("fields_v1_")
|
|
258
|
+
|
|
259
|
+
payload = _field_payload(created)
|
|
260
|
+
|
|
261
|
+
# Build CommandContext
|
|
262
|
+
ctx_modifiers: dict[str, object] = {
|
|
263
|
+
"entityType": entity_type,
|
|
264
|
+
"valueType": value_type,
|
|
265
|
+
}
|
|
266
|
+
if list_id is not None:
|
|
267
|
+
ctx_modifiers["listId"] = list_id
|
|
268
|
+
if allows_multiple:
|
|
269
|
+
ctx_modifiers["allowsMultiple"] = True
|
|
270
|
+
if list_specific:
|
|
271
|
+
ctx_modifiers["listSpecific"] = True
|
|
272
|
+
if required:
|
|
273
|
+
ctx_modifiers["required"] = True
|
|
274
|
+
|
|
275
|
+
cmd_context = CommandContext(
|
|
276
|
+
name="field create",
|
|
277
|
+
inputs={"name": name},
|
|
278
|
+
modifiers=ctx_modifiers,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
return CommandOutput(data={"field": payload}, context=cmd_context, api_called=True)
|
|
282
|
+
|
|
283
|
+
run_command(ctx, command="field create", fn=fn)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@category("write")
|
|
287
|
+
@destructive
|
|
288
|
+
@field_group.command(name="delete", cls=RichCommand)
|
|
289
|
+
@click.argument("field_id", type=str)
|
|
290
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt.")
|
|
291
|
+
@output_options
|
|
292
|
+
@click.pass_obj
|
|
293
|
+
def field_delete(ctx: CLIContext, field_id: str, yes: bool) -> None:
|
|
294
|
+
"""Delete a field."""
|
|
295
|
+
if not yes:
|
|
296
|
+
click.confirm(f"Delete field {field_id}?", abort=True)
|
|
297
|
+
|
|
298
|
+
def fn(ctx: CLIContext, warnings: list[str]) -> CommandOutput:
|
|
299
|
+
client = ctx.get_client(warnings=warnings)
|
|
300
|
+
success = client.fields.delete(FieldId(field_id))
|
|
301
|
+
|
|
302
|
+
# Invalidate field-related caches
|
|
303
|
+
cache = ctx.session_cache
|
|
304
|
+
cache.invalidate_prefix("list_fields_")
|
|
305
|
+
cache.invalidate_prefix("person_fields_")
|
|
306
|
+
cache.invalidate_prefix("company_fields_")
|
|
307
|
+
cache.invalidate_prefix("fields_v1_")
|
|
308
|
+
|
|
309
|
+
cmd_context = CommandContext(
|
|
310
|
+
name="field delete",
|
|
311
|
+
inputs={"fieldId": field_id},
|
|
312
|
+
modifiers={},
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
return CommandOutput(data={"success": success}, context=cmd_context, api_called=True)
|
|
316
|
+
|
|
317
|
+
run_command(ctx, command="field delete", fn=fn)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@category("read")
|
|
321
|
+
@field_group.command(name="history", cls=RichCommand)
|
|
322
|
+
@click.argument("field_id", type=str)
|
|
323
|
+
@click.option("--person-id", type=int, default=None, help="Filter by person ID.")
|
|
324
|
+
@click.option("--company-id", type=int, default=None, help="Filter by company ID.")
|
|
325
|
+
@click.option("--opportunity-id", type=int, default=None, help="Filter by opportunity ID.")
|
|
326
|
+
@click.option("--list-entry-id", type=int, default=None, help="Filter by list entry ID.")
|
|
327
|
+
@click.option(
|
|
328
|
+
"--action-type",
|
|
329
|
+
type=click.Choice(["create", "update", "delete"]),
|
|
330
|
+
default=None,
|
|
331
|
+
help="Filter by action type.",
|
|
332
|
+
)
|
|
333
|
+
@click.option(
|
|
334
|
+
"--max-results", "--limit", "-n", type=int, default=None, help="Limit number of results."
|
|
335
|
+
)
|
|
336
|
+
@output_options
|
|
337
|
+
@click.pass_obj
|
|
338
|
+
def field_history(
|
|
339
|
+
ctx: CLIContext,
|
|
340
|
+
*,
|
|
341
|
+
field_id: str,
|
|
342
|
+
person_id: int | None,
|
|
343
|
+
company_id: int | None,
|
|
344
|
+
opportunity_id: int | None,
|
|
345
|
+
list_entry_id: int | None,
|
|
346
|
+
action_type: str | None,
|
|
347
|
+
max_results: int | None,
|
|
348
|
+
) -> None:
|
|
349
|
+
"""Show field value change history.
|
|
350
|
+
|
|
351
|
+
FIELD_ID is the field identifier (e.g., 'field-123').
|
|
352
|
+
Use 'xaffinity field ls --list-id LIST' to find field IDs.
|
|
353
|
+
|
|
354
|
+
Exactly one entity selector is required.
|
|
355
|
+
|
|
356
|
+
Examples:
|
|
357
|
+
|
|
358
|
+
- `xaffinity field history field-123 --person-id 456`
|
|
359
|
+
|
|
360
|
+
- `xaffinity field history field-260415 --list-entry-id 789 --action-type update`
|
|
361
|
+
|
|
362
|
+
- `xaffinity field history field-123 --company-id 100 --max-results 10`
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
def fn(ctx: CLIContext, warnings: list[str]) -> CommandOutput:
|
|
366
|
+
_validate_exactly_one_selector(person_id, company_id, opportunity_id, list_entry_id)
|
|
367
|
+
|
|
368
|
+
client = ctx.get_client(warnings=warnings)
|
|
369
|
+
changes = client.field_value_changes.list(
|
|
370
|
+
field_id=FieldId(field_id),
|
|
371
|
+
person_id=PersonId(person_id) if person_id is not None else None,
|
|
372
|
+
company_id=CompanyId(company_id) if company_id is not None else None,
|
|
373
|
+
opportunity_id=OpportunityId(opportunity_id) if opportunity_id is not None else None,
|
|
374
|
+
list_entry_id=ListEntryId(list_entry_id) if list_entry_id is not None else None,
|
|
375
|
+
action_type=_ACTION_TYPE_MAP[action_type] if action_type else None,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# Apply client-side max_results limit
|
|
379
|
+
if max_results is not None:
|
|
380
|
+
changes = changes[:max_results]
|
|
381
|
+
|
|
382
|
+
payload = [_field_value_change_payload(item) for item in changes]
|
|
383
|
+
|
|
384
|
+
# Build CommandContext for richer output metadata
|
|
385
|
+
# Per spec: required params are inputs, optional params are modifiers
|
|
386
|
+
# fieldId and exactly one entity selector are required → both are inputs
|
|
387
|
+
inputs: dict[str, object] = {"fieldId": field_id}
|
|
388
|
+
if person_id is not None:
|
|
389
|
+
inputs["personId"] = person_id
|
|
390
|
+
elif company_id is not None:
|
|
391
|
+
inputs["companyId"] = company_id
|
|
392
|
+
elif opportunity_id is not None:
|
|
393
|
+
inputs["opportunityId"] = opportunity_id
|
|
394
|
+
elif list_entry_id is not None:
|
|
395
|
+
inputs["listEntryId"] = list_entry_id
|
|
396
|
+
|
|
397
|
+
modifiers: dict[str, object] = {}
|
|
398
|
+
if action_type is not None:
|
|
399
|
+
modifiers["actionType"] = action_type
|
|
400
|
+
if max_results is not None:
|
|
401
|
+
modifiers["maxResults"] = max_results
|
|
402
|
+
|
|
403
|
+
cmd_context = CommandContext(
|
|
404
|
+
name="field history",
|
|
405
|
+
inputs=inputs,
|
|
406
|
+
modifiers=modifiers,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
return CommandOutput(
|
|
410
|
+
data={"fieldValueChanges": payload}, context=cmd_context, api_called=True
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
run_command(ctx, command="field history", fn=fn)
|