qodev-apollo-api 0.2.2__tar.gz → 0.3.1__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.
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/CHANGELOG.md +14 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/PKG-INFO +1 -1
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/pyproject.toml +1 -1
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/src/qodev_apollo_api/__init__.py +4 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/src/qodev_apollo_api/client.py +45 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/src/qodev_apollo_api/models.py +28 -1
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/tests/test_client.py +48 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/uv.lock +1 -1
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/.github/workflows/ci.yml +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/.github/workflows/publish.yml +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/.gitignore +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/.pre-commit-config.yaml +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/CLAUDE.md +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/LICENSE +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/Makefile +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/README.md +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/src/qodev_apollo_api/exceptions.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/src/qodev_apollo_api/py.typed +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/src/qodev_apollo_api/utils.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/tests/__init__.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/tests/integration/__init__.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/tests/integration/validate_all_models.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/tests/integration/validate_email_task_flow.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/tests/test_exceptions.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/tests/test_models.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/tests/test_utils.py +0 -0
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.1] - 2026-07-08
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- `update_opportunity_roles(...)` now types its `roles` parameter as `list[RoleAssignment]` (a `TypedDict` with a required `contact_id` and optional `opportunity_contact_role_type_id` / `is_primary`) instead of the loose `list[dict]`, giving callers type checking and autocomplete. `RoleAssignment` is exported from the package root. Non-breaking — plain dicts still satisfy it structurally.
|
|
15
|
+
|
|
16
|
+
## [0.3.0] - 2026-07-08
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `ApolloClient.update_opportunity_roles(opportunity_id, roles)` — `POST /opportunities/update_roles`. Sets the contact roles on a deal (replaces the full set; read the current roles from `get_deal(...).opportunity_contact_roles` and modify). Returns the updated `Deal`. Surfaces the previously curl-only role-management endpoint.
|
|
21
|
+
- `ApolloClient.list_custom_fields()` — `GET /typed_custom_fields`. Returns the account/contact/opportunity custom field definitions as a new `CustomField` model (`id`, `modality`, `name`, `type`, `picklist_options`, `mapped_crm_field`).
|
|
22
|
+
- `CustomField` model, exported from the package root.
|
|
23
|
+
|
|
10
24
|
## [0.2.1] - 2026-07-01
|
|
11
25
|
|
|
12
26
|
### Fixed
|
|
@@ -35,6 +35,7 @@ from .models import (
|
|
|
35
35
|
CrmJob,
|
|
36
36
|
CrmNote,
|
|
37
37
|
Currency,
|
|
38
|
+
CustomField,
|
|
38
39
|
Deal,
|
|
39
40
|
Email,
|
|
40
41
|
EmailerMessage,
|
|
@@ -62,6 +63,7 @@ from .models import (
|
|
|
62
63
|
PaginatedResponse,
|
|
63
64
|
PhoneEntry,
|
|
64
65
|
Pipeline,
|
|
66
|
+
RoleAssignment,
|
|
65
67
|
SortOrder,
|
|
66
68
|
Stage,
|
|
67
69
|
Task,
|
|
@@ -111,6 +113,7 @@ __all__ = [
|
|
|
111
113
|
"CrmJob",
|
|
112
114
|
"CrmNote",
|
|
113
115
|
"Currency",
|
|
116
|
+
"CustomField",
|
|
114
117
|
"Deal",
|
|
115
118
|
"Email",
|
|
116
119
|
"EmailParticipant",
|
|
@@ -139,6 +142,7 @@ __all__ = [
|
|
|
139
142
|
"PhoneEntry",
|
|
140
143
|
"Pipeline",
|
|
141
144
|
"RateLimitError",
|
|
145
|
+
"RoleAssignment",
|
|
142
146
|
"SortOrder",
|
|
143
147
|
"Stage",
|
|
144
148
|
"Task",
|
|
@@ -22,6 +22,7 @@ from .models import (
|
|
|
22
22
|
ContactDetail,
|
|
23
23
|
Conversation,
|
|
24
24
|
ConversationDetail,
|
|
25
|
+
CustomField,
|
|
25
26
|
Deal,
|
|
26
27
|
Email,
|
|
27
28
|
EmailerMessage,
|
|
@@ -32,6 +33,7 @@ from .models import (
|
|
|
32
33
|
OpportunityContactRoleType,
|
|
33
34
|
PaginatedResponse,
|
|
34
35
|
Pipeline,
|
|
36
|
+
RoleAssignment,
|
|
35
37
|
SortOrder,
|
|
36
38
|
Stage,
|
|
37
39
|
Task,
|
|
@@ -520,6 +522,49 @@ class ApolloClient:
|
|
|
520
522
|
page=1,
|
|
521
523
|
)
|
|
522
524
|
|
|
525
|
+
async def update_opportunity_roles(
|
|
526
|
+
self, opportunity_id: str, roles: list[RoleAssignment]
|
|
527
|
+
) -> Deal:
|
|
528
|
+
"""Set the contact roles on a deal/opportunity (undocumented endpoint).
|
|
529
|
+
|
|
530
|
+
This **replaces** the full set of contact roles on the opportunity, so
|
|
531
|
+
callers should pass the complete desired set (read the current roles from
|
|
532
|
+
``get_deal(...).opportunity_contact_roles`` and modify them). Each entry is
|
|
533
|
+
a dict with ``contact_id`` and, optionally, ``opportunity_contact_role_type_id``
|
|
534
|
+
and ``is_primary``::
|
|
535
|
+
|
|
536
|
+
roles = [
|
|
537
|
+
{"contact_id": "abc", "opportunity_contact_role_type_id": "t1", "is_primary": True},
|
|
538
|
+
{"contact_id": "def", "is_primary": False},
|
|
539
|
+
]
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
opportunity_id: The opportunity/deal ID.
|
|
543
|
+
roles: The complete list of contact-role entries to set.
|
|
544
|
+
|
|
545
|
+
Returns:
|
|
546
|
+
The updated Deal.
|
|
547
|
+
"""
|
|
548
|
+
data = {"opportunity_id": opportunity_id, "roles": roles}
|
|
549
|
+
result = await self._post("/opportunities/update_roles", data)
|
|
550
|
+
return Deal.model_validate(result.get("opportunity", result))
|
|
551
|
+
|
|
552
|
+
# ========================================================================
|
|
553
|
+
# CUSTOM FIELDS
|
|
554
|
+
# ========================================================================
|
|
555
|
+
|
|
556
|
+
async def list_custom_fields(self) -> list[CustomField]:
|
|
557
|
+
"""List all typed custom field definitions (undocumented endpoint).
|
|
558
|
+
|
|
559
|
+
Returns definitions for custom fields across every modality (contact,
|
|
560
|
+
account, opportunity). Use ``modality`` to filter client-side.
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
List of CustomField definitions.
|
|
564
|
+
"""
|
|
565
|
+
result = await self._get("/typed_custom_fields")
|
|
566
|
+
return [CustomField.model_validate(f) for f in result.get("typed_custom_fields", [])]
|
|
567
|
+
|
|
523
568
|
# ========================================================================
|
|
524
569
|
# ENRICHMENT
|
|
525
570
|
# ========================================================================
|
|
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
from datetime import date, datetime
|
|
10
10
|
from decimal import Decimal
|
|
11
11
|
from enum import StrEnum
|
|
12
|
-
from typing import Annotated, Any, Generic, Literal, TypeAlias, TypeVar
|
|
12
|
+
from typing import Annotated, Any, Generic, Literal, NotRequired, TypeAlias, TypedDict, TypeVar
|
|
13
13
|
|
|
14
14
|
from pydantic import BaseModel, ConfigDict, Discriminator, Field, TypeAdapter, ValidationError
|
|
15
15
|
|
|
@@ -183,6 +183,18 @@ class OpportunityContactRole(ApolloModel):
|
|
|
183
183
|
updated_at: datetime | None = None
|
|
184
184
|
|
|
185
185
|
|
|
186
|
+
class RoleAssignment(TypedDict):
|
|
187
|
+
"""A single contact-role entry accepted by ``update_opportunity_roles``.
|
|
188
|
+
|
|
189
|
+
``contact_id`` is required; ``opportunity_contact_role_type_id`` and
|
|
190
|
+
``is_primary`` are optional.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
contact_id: str
|
|
194
|
+
opportunity_contact_role_type_id: NotRequired[str | None]
|
|
195
|
+
is_primary: NotRequired[bool]
|
|
196
|
+
|
|
197
|
+
|
|
186
198
|
class OpportunityContactRoleType(ApolloModel):
|
|
187
199
|
"""Role type definition for opportunity contact roles (e.g., Decision Maker, Buyer)."""
|
|
188
200
|
|
|
@@ -640,6 +652,21 @@ class Stage(ApolloModel):
|
|
|
640
652
|
team_id: str | None = None
|
|
641
653
|
|
|
642
654
|
|
|
655
|
+
class CustomField(ApolloModel):
|
|
656
|
+
"""A typed custom field definition (from GET /typed_custom_fields).
|
|
657
|
+
|
|
658
|
+
``modality`` is the entity the field belongs to (e.g. "contact", "account",
|
|
659
|
+
"opportunity"); ``type`` is the value type (e.g. "text", "date", "picklist").
|
|
660
|
+
"""
|
|
661
|
+
|
|
662
|
+
id: str
|
|
663
|
+
modality: str | None = None
|
|
664
|
+
name: str | None = None
|
|
665
|
+
type: str | None = None
|
|
666
|
+
picklist_options: list[str] = Field(default_factory=list)
|
|
667
|
+
mapped_crm_field: str | None = None
|
|
668
|
+
|
|
669
|
+
|
|
643
670
|
# ============================================================================
|
|
644
671
|
# ACTIVITY MODELS
|
|
645
672
|
# ============================================================================
|
|
@@ -21,6 +21,7 @@ from qodev_apollo_api.models import (
|
|
|
21
21
|
ContactDetail,
|
|
22
22
|
Conversation,
|
|
23
23
|
ConversationDetail,
|
|
24
|
+
CustomField,
|
|
24
25
|
Deal,
|
|
25
26
|
Email,
|
|
26
27
|
EmailerMessage,
|
|
@@ -618,6 +619,53 @@ async def test_list_opportunity_contact_role_types(client: ApolloClient):
|
|
|
618
619
|
assert call_args[1]["json"] == {}
|
|
619
620
|
|
|
620
621
|
|
|
622
|
+
async def test_update_opportunity_roles(client: ApolloClient):
|
|
623
|
+
"""Test POST /opportunities/update_roles returns the updated Deal with the roles body."""
|
|
624
|
+
client._client.request.return_value = _make_response(
|
|
625
|
+
{"opportunity": {"id": "d1", "name": "Big Deal"}}
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
roles = [
|
|
629
|
+
{"contact_id": "c1", "opportunity_contact_role_type_id": "rt1", "is_primary": True},
|
|
630
|
+
{"contact_id": "c2", "is_primary": False},
|
|
631
|
+
]
|
|
632
|
+
result = await client.update_opportunity_roles("d1", roles)
|
|
633
|
+
|
|
634
|
+
assert isinstance(result, Deal)
|
|
635
|
+
assert result.id == "d1"
|
|
636
|
+
|
|
637
|
+
call_args = client._client.request.call_args
|
|
638
|
+
assert call_args[0] == ("POST", "/opportunities/update_roles")
|
|
639
|
+
assert call_args[1]["json"] == {"opportunity_id": "d1", "roles": roles}
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
async def test_list_custom_fields(client: ApolloClient):
|
|
643
|
+
"""Test GET /typed_custom_fields returns list[CustomField]."""
|
|
644
|
+
client._client.request.return_value = _make_response(
|
|
645
|
+
{
|
|
646
|
+
"typed_custom_fields": [
|
|
647
|
+
{"id": "f1", "modality": "contact", "name": "First Message", "type": "date"},
|
|
648
|
+
{
|
|
649
|
+
"id": "f2",
|
|
650
|
+
"modality": "opportunity",
|
|
651
|
+
"name": "Region",
|
|
652
|
+
"type": "picklist",
|
|
653
|
+
"picklist_options": ["EU", "US"],
|
|
654
|
+
},
|
|
655
|
+
]
|
|
656
|
+
}
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
result = await client.list_custom_fields()
|
|
660
|
+
|
|
661
|
+
assert len(result) == 2
|
|
662
|
+
assert all(isinstance(f, CustomField) for f in result)
|
|
663
|
+
assert result[0].name == "First Message"
|
|
664
|
+
assert result[1].picklist_options == ["EU", "US"]
|
|
665
|
+
|
|
666
|
+
assert client._client.request.call_args[0] == ("GET", "/typed_custom_fields")
|
|
667
|
+
|
|
668
|
+
|
|
621
669
|
async def test_get_contact_stages(client: ApolloClient):
|
|
622
670
|
"""Test GET /contact_stages returns list[dict]."""
|
|
623
671
|
client._client.request.return_value = _make_response(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.1}/tests/integration/validate_email_task_flow.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|