Skip to content

Commit 235cc44

Browse files
committed
Update CHANGELOG for version 0.1.2; fix Account model validation, correct CustomTagMapping documentation, and enhance model generation script to patch OpenAPI spec issues.
1 parent 7b5b711 commit 235cc44

5 files changed

Lines changed: 101 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ All notable changes to this project are documented here. Format follows
77

88
## [Unreleased]
99

10+
## [0.1.2] - 2026-07-23
11+
12+
### Fixed
13+
14+
- `Account.model_validate()` no longer raises on `client.accounts.list(include_tags=True)`.
15+
The generated `Account` model was missing the `tags` field the API embeds per account
16+
when `include_tags=true` is set, so every call crashed against real data
17+
(`extra="forbid"` rejected the extra `tags` array).
18+
- Corrected the reversed `resource_type` mapping in the generated `CustomTagMapping` /
19+
`ResourceType` docs. It previously read "1 for campaigns or 2 for accounts", the
20+
opposite of what the API and `custom_tags.py` actually use (`1` = Account, `2` =
21+
Campaign) -- purely a doc/docstring fix, the underlying values were never wrong.
22+
- `scripts/generate_models.py` now patches both gaps in the upstream OpenAPI spec
23+
before codegen runs, so future regenerations don't silently reintroduce them.
24+
1025
## [0.1.0] - 2026-07-22
1126

1227
### Added

scripts/generate_models.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from __future__ import annotations
1515

1616
import ast
17+
import json
1718
import subprocess
1819
import sys
1920
import urllib.request
@@ -45,12 +46,63 @@
4546

4647

4748
def fetch_spec(source: str) -> Path:
49+
dest = MODELS_DIR / ".openapi-spec.json"
4850
if source.startswith("http://") or source.startswith("https://"):
49-
dest = MODELS_DIR / ".openapi-spec.json"
5051
with urllib.request.urlopen(source) as response:
5152
dest.write_bytes(response.read())
52-
return dest
53-
return Path(source)
53+
else:
54+
dest.write_bytes(Path(source).read_bytes())
55+
return dest
56+
57+
58+
def patch_spec(spec_path: Path) -> None:
59+
"""Correct known gaps/bugs in the upstream spec before codegen sees it.
60+
61+
Both patches below are tracked as issues against Instantly's published
62+
OpenAPI document; they live here (rather than as post-codegen edits to
63+
``_generated.py``) so they survive the next regeneration.
64+
"""
65+
spec = json.loads(spec_path.read_text())
66+
schemas = spec["components"]["schemas"]
67+
68+
# `GET /accounts?include_tags=true` embeds a `tags` array per account, but
69+
# the spec only declares that on an anonymous inline extension of the
70+
# `Account` schema used by that one response, not on `Account` itself.
71+
# datamodel-code-generator drops the inline extension, so the generated
72+
# `Account` model (which has `extra="forbid"`) has no `tags` field and
73+
# raises a `ValidationError` on every `include_tags=True` call. Move the
74+
# property onto `Account` directly, matching what the API actually sends.
75+
schemas["Account"]["properties"]["tags"] = {
76+
"type": ["array", "null"],
77+
"description": "Tags associated with the account, set to `include_tags` to populate",
78+
"items": {
79+
"type": "object",
80+
"properties": {
81+
"id": {
82+
"type": "string",
83+
"description": "Unique identifier for the custom tag",
84+
},
85+
"label": {
86+
"type": "string",
87+
"description": "Display label for the custom tag",
88+
},
89+
"description": {
90+
"type": ["string", "null"],
91+
"description": "Detailed description of the custom tag purpose",
92+
},
93+
},
94+
},
95+
}
96+
97+
# `CustomTagMapping.resource_type`'s free-text description has the
98+
# account/campaign mapping backwards relative to its own
99+
# `x-enumDescriptions` (1: Account, 2: Campaign) and relative to the
100+
# `toggle-resource` endpoint, which agrees with `x-enumDescriptions`.
101+
schemas["CustomTagMapping"]["properties"]["resource_type"]["description"] = (
102+
"Resource type of custom tag, can be 1 for accounts or 2 for campaigns"
103+
)
104+
105+
spec_path.write_text(json.dumps(spec))
54106

55107

56108
def run_codegen(spec_path: Path) -> None:
@@ -99,6 +151,7 @@ def write_init(names: list[str]) -> None:
99151
def main() -> None:
100152
source = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_SPEC_URL
101153
spec_path = fetch_spec(source)
154+
patch_spec(spec_path)
102155
run_codegen(spec_path)
103156
names = top_level_names(GENERATED_FILE.read_text())
104157
write_init(names)

src/instantlyai/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
build backend (``pyproject.toml`` -> ``[tool.hatch.version]``).
55
"""
66

7-
__version__ = "0.1.1"
7+
__version__ = "0.1.2"

src/instantlyai/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
Step1,
119119
SubsequenceSchedule,
120120
SuperSearchEnrichment,
121+
TagsItem,
121122
TemplateType,
122123
Then,
123124
Timezone,
@@ -262,6 +263,7 @@
262263
"Step1",
263264
"SubsequenceSchedule",
264265
"SuperSearchEnrichment",
266+
"TagsItem",
265267
"TemplateType",
266268
"Then",
267269
"Timezone",

src/instantlyai/models/_generated.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,27 @@ class ProviderCode(Enum):
135135
number_8 = 8
136136

137137

138+
class TagsItem(BaseModel):
139+
model_config = ConfigDict(
140+
extra="forbid",
141+
)
142+
id: str | None = Field(
143+
None,
144+
description="Unique identifier for the custom tag",
145+
examples=["019f8c44-f5cf-74e9-b3e4-2b4151efd79b"],
146+
)
147+
label: str | None = Field(
148+
None,
149+
description="Display label for the custom tag",
150+
examples=["Important tag"],
151+
)
152+
description: str | None = Field(
153+
None,
154+
description="Detailed description of the custom tag purpose",
155+
examples=[None],
156+
)
157+
158+
138159
class Account(BaseModel):
139160
"""
140161
An email account that can be used to send campaigns
@@ -257,6 +278,10 @@ class Account(BaseModel):
257278
description="Whether automatic reconnection attempts have failed. null = in progress, true = failed, false = succeeded.",
258279
examples=[False],
259280
)
281+
tags: list[TagsItem] | None = Field(
282+
None,
283+
description="Tags associated with the account, set to `include_tags` to populate",
284+
)
260285

261286

262287
class Status1(Enum):
@@ -2453,7 +2478,7 @@ class CustomTag(BaseModel):
24532478

24542479
class ResourceType(Enum):
24552480
"""
2456-
Resource type of custom tag, can be 1 for campaigns or 2 for accounts
2481+
Resource type of custom tag, can be 1 for accounts or 2 for campaigns
24572482
"""
24582483

24592484
number_1 = 1
@@ -2485,7 +2510,7 @@ class CustomTagMapping(BaseModel):
24852510
)
24862511
resource_type: ResourceType = Field(
24872512
...,
2488-
description="Resource type of custom tag, can be 1 for campaigns or 2 for accounts",
2513+
description="Resource type of custom tag, can be 1 for accounts or 2 for campaigns",
24892514
examples=[1],
24902515
)
24912516
timestamp_created: AwareDatetime = Field(

0 commit comments

Comments
 (0)