qodev-apollo-api 0.2.2__tar.gz → 0.3.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.
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/CHANGELOG.md +8 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/PKG-INFO +1 -1
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/pyproject.toml +1 -1
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/src/qodev_apollo_api/__init__.py +2 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/src/qodev_apollo_api/client.py +42 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/src/qodev_apollo_api/models.py +15 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/tests/test_client.py +48 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/uv.lock +1 -1
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/.github/workflows/ci.yml +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/.github/workflows/publish.yml +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/.gitignore +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/.pre-commit-config.yaml +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/CLAUDE.md +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/LICENSE +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/Makefile +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/README.md +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/src/qodev_apollo_api/exceptions.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/src/qodev_apollo_api/py.typed +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/src/qodev_apollo_api/utils.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/tests/__init__.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/tests/integration/__init__.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/tests/integration/validate_all_models.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/tests/integration/validate_email_task_flow.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/tests/test_exceptions.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/tests/test_models.py +0 -0
- {qodev_apollo_api-0.2.2 → qodev_apollo_api-0.3.0}/tests/test_utils.py +0 -0
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.0] - 2026-07-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `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.
|
|
15
|
+
- `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`).
|
|
16
|
+
- `CustomField` model, exported from the package root.
|
|
17
|
+
|
|
10
18
|
## [0.2.1] - 2026-07-01
|
|
11
19
|
|
|
12
20
|
### 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,
|
|
@@ -111,6 +112,7 @@ __all__ = [
|
|
|
111
112
|
"CrmJob",
|
|
112
113
|
"CrmNote",
|
|
113
114
|
"Currency",
|
|
115
|
+
"CustomField",
|
|
114
116
|
"Deal",
|
|
115
117
|
"Email",
|
|
116
118
|
"EmailParticipant",
|
|
@@ -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,
|
|
@@ -520,6 +521,47 @@ class ApolloClient:
|
|
|
520
521
|
page=1,
|
|
521
522
|
)
|
|
522
523
|
|
|
524
|
+
async def update_opportunity_roles(self, opportunity_id: str, roles: list[dict]) -> Deal:
|
|
525
|
+
"""Set the contact roles on a deal/opportunity (undocumented endpoint).
|
|
526
|
+
|
|
527
|
+
This **replaces** the full set of contact roles on the opportunity, so
|
|
528
|
+
callers should pass the complete desired set (read the current roles from
|
|
529
|
+
``get_deal(...).opportunity_contact_roles`` and modify them). Each entry is
|
|
530
|
+
a dict with ``contact_id`` and, optionally, ``opportunity_contact_role_type_id``
|
|
531
|
+
and ``is_primary``::
|
|
532
|
+
|
|
533
|
+
roles = [
|
|
534
|
+
{"contact_id": "abc", "opportunity_contact_role_type_id": "t1", "is_primary": True},
|
|
535
|
+
{"contact_id": "def", "is_primary": False},
|
|
536
|
+
]
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
opportunity_id: The opportunity/deal ID.
|
|
540
|
+
roles: The complete list of contact-role entries to set.
|
|
541
|
+
|
|
542
|
+
Returns:
|
|
543
|
+
The updated Deal.
|
|
544
|
+
"""
|
|
545
|
+
data = {"opportunity_id": opportunity_id, "roles": roles}
|
|
546
|
+
result = await self._post("/opportunities/update_roles", data)
|
|
547
|
+
return Deal.model_validate(result.get("opportunity", result))
|
|
548
|
+
|
|
549
|
+
# ========================================================================
|
|
550
|
+
# CUSTOM FIELDS
|
|
551
|
+
# ========================================================================
|
|
552
|
+
|
|
553
|
+
async def list_custom_fields(self) -> list[CustomField]:
|
|
554
|
+
"""List all typed custom field definitions (undocumented endpoint).
|
|
555
|
+
|
|
556
|
+
Returns definitions for custom fields across every modality (contact,
|
|
557
|
+
account, opportunity). Use ``modality`` to filter client-side.
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
List of CustomField definitions.
|
|
561
|
+
"""
|
|
562
|
+
result = await self._get("/typed_custom_fields")
|
|
563
|
+
return [CustomField.model_validate(f) for f in result.get("typed_custom_fields", [])]
|
|
564
|
+
|
|
523
565
|
# ========================================================================
|
|
524
566
|
# ENRICHMENT
|
|
525
567
|
# ========================================================================
|
|
@@ -640,6 +640,21 @@ class Stage(ApolloModel):
|
|
|
640
640
|
team_id: str | None = None
|
|
641
641
|
|
|
642
642
|
|
|
643
|
+
class CustomField(ApolloModel):
|
|
644
|
+
"""A typed custom field definition (from GET /typed_custom_fields).
|
|
645
|
+
|
|
646
|
+
``modality`` is the entity the field belongs to (e.g. "contact", "account",
|
|
647
|
+
"opportunity"); ``type`` is the value type (e.g. "text", "date", "picklist").
|
|
648
|
+
"""
|
|
649
|
+
|
|
650
|
+
id: str
|
|
651
|
+
modality: str | None = None
|
|
652
|
+
name: str | None = None
|
|
653
|
+
type: str | None = None
|
|
654
|
+
picklist_options: list[str] = Field(default_factory=list)
|
|
655
|
+
mapped_crm_field: str | None = None
|
|
656
|
+
|
|
657
|
+
|
|
643
658
|
# ============================================================================
|
|
644
659
|
# ACTIVITY MODELS
|
|
645
660
|
# ============================================================================
|
|
@@ -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.0}/tests/integration/validate_email_task_flow.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|