Skip to content

Commit a4a822e

Browse files
committed
fix(generator): emit header params instead of silently dropping them
extract_parameters captured "in": "header" params but generate_method_body bucketed only query/body/raw_body/path, so every header param was dropped at emission: posts.create_post(x_request_id=...) was accepted and thrown away, making the server's idempotency window unreachable from Python. BaseClient also had no headers kwarg on _post. Header dicts are emitted inline keyed on the verbatim wire name, so x-request-id stays lowercase where _build_params would have camelCased it. headers is threaded through _get/_aget/_post/_apost only, including the separate httpx client built for multipart uploads; no PUT/PATCH/DELETE operation declares a header param today. Regenerates connect, ad_campaigns, phone_numbers, posts and whatsapp_phone_numbers: 12 header params across those five were being dropped, not just x-request-id. Header params supplied via a component $ref (Idempotency-Key on 9 further operations) are still discarded by the $ref branch of extract_parameters and are left for a follow-up. https://app.crisp.chat/website/20dea5d6-a684-4c80-b097-2258b0b41421/inbox/session_0aff04b5-6834-480a-ba49-f525956329db/
1 parent 0bcc4ca commit a4a822e

6 files changed

Lines changed: 166 additions & 27 deletions

File tree

‎scripts/generate_resources.py‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ def generate_method_body(
294294
body_params = [p for p in params if p["in"] == "body"]
295295
raw_body_params = [p for p in params if p["in"] == "raw_body"]
296296
path_params = [p for p in params if p["in"] == "path"]
297+
header_params = [p for p in params if p["in"] == "header"]
297298

298299
# Handle path parameters
299300
path_expr = f'"{path}"'
@@ -314,6 +315,9 @@ def generate_method_body(
314315
use_query_params = http_method.upper() in ("GET", "DELETE") and query_params
315316
use_body_params = http_method.upper() in ("POST", "PUT", "PATCH") and body_params
316317
use_query_on_post = http_method.upper() in ("POST", "PUT", "PATCH") and query_params
318+
# Only _get and _post accept a `headers` kwarg (see BaseClient); no
319+
# operation on PUT/PATCH/DELETE currently has a header param.
320+
use_headers = http_method.upper() in ("GET", "POST") and header_params
317321

318322
# Build params dict if needed
319323
if use_query_params or use_query_on_post:
@@ -329,12 +333,27 @@ def generate_method_body(
329333
lines.append(f" {p['name']}={p['name']},")
330334
lines.append(" )")
331335

336+
# Build headers dict if needed, keyed on the wire name (original_name)
337+
if use_headers:
338+
header_entries = ", ".join(
339+
f'"{p["original_name"]}": {p["name"]}' for p in header_params
340+
)
341+
lines.append(
342+
" headers = {k: v for k, v in {"
343+
+ header_entries
344+
+ "}.items() if v is not None}"
345+
)
346+
332347
# Make the request
333348
if http_method.upper() == "GET":
349+
call_args = [path_expr]
334350
if query_params:
335-
lines.append(f" return {await_prefix}self._client.{client_method}({path_expr}, params=params)")
336-
else:
337-
lines.append(f" return {await_prefix}self._client.{client_method}({path_expr})")
351+
call_args.append("params=params")
352+
if use_headers:
353+
call_args.append("headers=headers")
354+
lines.append(
355+
f" return {await_prefix}self._client.{client_method}({', '.join(call_args)})"
356+
)
338357
elif http_method.upper() == "DELETE":
339358
if query_params:
340359
lines.append(f" return {await_prefix}self._client.{client_method}({path_expr}, params=params)")
@@ -348,6 +367,8 @@ def generate_method_body(
348367
call_args.append(f"data={raw_body_params[0]['name']}")
349368
if query_params:
350369
call_args.append("params=params")
370+
if use_headers:
371+
call_args.append("headers=headers")
351372
lines.append(
352373
f" return {await_prefix}self._client.{client_method}({', '.join(call_args)})"
353374
)

‎src/late/resources/_generated/ad_campaigns.py‎

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,12 @@ def create_ad_campaign(
214214
bid_amount=bid_amount,
215215
roas_average_floor=roas_average_floor,
216216
)
217-
return self._client._post("/v1/ads/campaigns", data=payload)
217+
headers = {
218+
k: v
219+
for k, v in {"Idempotency-Key": idempotency_key}.items()
220+
if v is not None
221+
}
222+
return self._client._post("/v1/ads/campaigns", data=payload, headers=headers)
218223

219224
def update_ad_campaign_status(
220225
self, campaign_id: str, status: str, platform: str
@@ -297,8 +302,13 @@ def duplicate_ad_campaign(
297302
rename_suffix=rename_suffix,
298303
sync_after=sync_after,
299304
)
305+
headers = {
306+
k: v
307+
for k, v in {"Idempotency-Key": idempotency_key}.items()
308+
if v is not None
309+
}
300310
return self._client._post(
301-
f"/v1/ads/campaigns/{campaign_id}/duplicate", data=payload
311+
f"/v1/ads/campaigns/{campaign_id}/duplicate", data=payload, headers=headers
302312
)
303313

304314
def duplicate_ad_set(
@@ -330,8 +340,13 @@ def duplicate_ad_set(
330340
rename_suffix=rename_suffix,
331341
sync_after=sync_after,
332342
)
343+
headers = {
344+
k: v
345+
for k, v in {"Idempotency-Key": idempotency_key}.items()
346+
if v is not None
347+
}
333348
return self._client._post(
334-
f"/v1/ads/ad-sets/{ad_set_id}/duplicate", data=payload
349+
f"/v1/ads/ad-sets/{ad_set_id}/duplicate", data=payload, headers=headers
335350
)
336351

337352
def duplicate_ad(
@@ -355,7 +370,14 @@ def duplicate_ad(
355370
rename_suffix=rename_suffix,
356371
sync_after=sync_after,
357372
)
358-
return self._client._post(f"/v1/ads/{ad_id}/duplicate", data=payload)
373+
headers = {
374+
k: v
375+
for k, v in {"Idempotency-Key": idempotency_key}.items()
376+
if v is not None
377+
}
378+
return self._client._post(
379+
f"/v1/ads/{ad_id}/duplicate", data=payload, headers=headers
380+
)
359381

360382
def get_ad_set_details(
361383
self, ad_set_id: str, account_id: str, *, fields: str | None = None
@@ -941,7 +963,14 @@ async def acreate_ad_campaign(
941963
bid_amount=bid_amount,
942964
roas_average_floor=roas_average_floor,
943965
)
944-
return await self._client._apost("/v1/ads/campaigns", data=payload)
966+
headers = {
967+
k: v
968+
for k, v in {"Idempotency-Key": idempotency_key}.items()
969+
if v is not None
970+
}
971+
return await self._client._apost(
972+
"/v1/ads/campaigns", data=payload, headers=headers
973+
)
945974

946975
async def aupdate_ad_campaign_status(
947976
self, campaign_id: str, status: str, platform: str
@@ -1026,8 +1055,13 @@ async def aduplicate_ad_campaign(
10261055
rename_suffix=rename_suffix,
10271056
sync_after=sync_after,
10281057
)
1058+
headers = {
1059+
k: v
1060+
for k, v in {"Idempotency-Key": idempotency_key}.items()
1061+
if v is not None
1062+
}
10291063
return await self._client._apost(
1030-
f"/v1/ads/campaigns/{campaign_id}/duplicate", data=payload
1064+
f"/v1/ads/campaigns/{campaign_id}/duplicate", data=payload, headers=headers
10311065
)
10321066

10331067
async def aduplicate_ad_set(
@@ -1059,8 +1093,13 @@ async def aduplicate_ad_set(
10591093
rename_suffix=rename_suffix,
10601094
sync_after=sync_after,
10611095
)
1096+
headers = {
1097+
k: v
1098+
for k, v in {"Idempotency-Key": idempotency_key}.items()
1099+
if v is not None
1100+
}
10621101
return await self._client._apost(
1063-
f"/v1/ads/ad-sets/{ad_set_id}/duplicate", data=payload
1102+
f"/v1/ads/ad-sets/{ad_set_id}/duplicate", data=payload, headers=headers
10641103
)
10651104

10661105
async def aduplicate_ad(
@@ -1084,7 +1123,14 @@ async def aduplicate_ad(
10841123
rename_suffix=rename_suffix,
10851124
sync_after=sync_after,
10861125
)
1087-
return await self._client._apost(f"/v1/ads/{ad_id}/duplicate", data=payload)
1126+
headers = {
1127+
k: v
1128+
for k, v in {"Idempotency-Key": idempotency_key}.items()
1129+
if v is not None
1130+
}
1131+
return await self._client._apost(
1132+
f"/v1/ads/{ad_id}/duplicate", data=payload, headers=headers
1133+
)
10881134

10891135
async def aget_ad_set_details(
10901136
self, ad_set_id: str, account_id: str, *, fields: str | None = None

‎src/late/resources/_generated/connect.py‎

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,14 @@ def list_pinterest_boards_for_selection(
301301
profile_id=profile_id,
302302
temp_token=temp_token,
303303
)
304-
return self._client._get("/v1/connect/pinterest/select-board", params=params)
304+
headers = {
305+
k: v
306+
for k, v in {"X-Connect-Token": x_connect_token}.items()
307+
if v is not None
308+
}
309+
return self._client._get(
310+
"/v1/connect/pinterest/select-board", params=params, headers=headers
311+
)
305312

306313
def select_pinterest_board(
307314
self,
@@ -336,7 +343,14 @@ def list_snapchat_profiles(
336343
profile_id=profile_id,
337344
temp_token=temp_token,
338345
)
339-
return self._client._get("/v1/connect/snapchat/select-profile", params=params)
346+
headers = {
347+
k: v
348+
for k, v in {"X-Connect-Token": x_connect_token}.items()
349+
if v is not None
350+
}
351+
return self._client._get(
352+
"/v1/connect/snapchat/select-profile", params=params, headers=headers
353+
)
340354

341355
def select_snapchat_profile(
342356
self,
@@ -360,7 +374,14 @@ def select_snapchat_profile(
360374
expires_in=expires_in,
361375
redirect_url=redirect_url,
362376
)
363-
return self._client._post("/v1/connect/snapchat/select-profile", data=payload)
377+
headers = {
378+
k: v
379+
for k, v in {"X-Connect-Token": x_connect_token}.items()
380+
if v is not None
381+
}
382+
return self._client._post(
383+
"/v1/connect/snapchat/select-profile", data=payload, headers=headers
384+
)
364385

365386
def connect_bluesky_credentials(
366387
self,
@@ -423,8 +444,13 @@ def list_whats_app_phone_numbers(
423444
profile_id=profile_id,
424445
temp_token=temp_token,
425446
)
447+
headers = {
448+
k: v
449+
for k, v in {"X-Connect-Token": x_connect_token}.items()
450+
if v is not None
451+
}
426452
return self._client._get(
427-
"/v1/connect/whatsapp/select-phone-number", params=params
453+
"/v1/connect/whatsapp/select-phone-number", params=params, headers=headers
428454
)
429455

430456
def complete_whats_app_phone_selection(
@@ -447,8 +473,13 @@ def complete_whats_app_phone_selection(
447473
user_profile=user_profile,
448474
redirect_url=redirect_url,
449475
)
476+
headers = {
477+
k: v
478+
for k, v in {"X-Connect-Token": x_connect_token}.items()
479+
if v is not None
480+
}
450481
return self._client._post(
451-
"/v1/connect/whatsapp/select-phone-number", data=payload
482+
"/v1/connect/whatsapp/select-phone-number", data=payload, headers=headers
452483
)
453484

454485
def connect_whats_app_embedded_signup(
@@ -1002,8 +1033,13 @@ async def alist_pinterest_boards_for_selection(
10021033
profile_id=profile_id,
10031034
temp_token=temp_token,
10041035
)
1036+
headers = {
1037+
k: v
1038+
for k, v in {"X-Connect-Token": x_connect_token}.items()
1039+
if v is not None
1040+
}
10051041
return await self._client._aget(
1006-
"/v1/connect/pinterest/select-board", params=params
1042+
"/v1/connect/pinterest/select-board", params=params, headers=headers
10071043
)
10081044

10091045
async def aselect_pinterest_board(
@@ -1041,8 +1077,13 @@ async def alist_snapchat_profiles(
10411077
profile_id=profile_id,
10421078
temp_token=temp_token,
10431079
)
1080+
headers = {
1081+
k: v
1082+
for k, v in {"X-Connect-Token": x_connect_token}.items()
1083+
if v is not None
1084+
}
10441085
return await self._client._aget(
1045-
"/v1/connect/snapchat/select-profile", params=params
1086+
"/v1/connect/snapchat/select-profile", params=params, headers=headers
10461087
)
10471088

10481089
async def aselect_snapchat_profile(
@@ -1067,8 +1108,13 @@ async def aselect_snapchat_profile(
10671108
expires_in=expires_in,
10681109
redirect_url=redirect_url,
10691110
)
1111+
headers = {
1112+
k: v
1113+
for k, v in {"X-Connect-Token": x_connect_token}.items()
1114+
if v is not None
1115+
}
10701116
return await self._client._apost(
1071-
"/v1/connect/snapchat/select-profile", data=payload
1117+
"/v1/connect/snapchat/select-profile", data=payload, headers=headers
10721118
)
10731119

10741120
async def aconnect_bluesky_credentials(
@@ -1138,8 +1184,13 @@ async def alist_whats_app_phone_numbers(
11381184
profile_id=profile_id,
11391185
temp_token=temp_token,
11401186
)
1187+
headers = {
1188+
k: v
1189+
for k, v in {"X-Connect-Token": x_connect_token}.items()
1190+
if v is not None
1191+
}
11411192
return await self._client._aget(
1142-
"/v1/connect/whatsapp/select-phone-number", params=params
1193+
"/v1/connect/whatsapp/select-phone-number", params=params, headers=headers
11431194
)
11441195

11451196
async def acomplete_whats_app_phone_selection(
@@ -1162,8 +1213,13 @@ async def acomplete_whats_app_phone_selection(
11621213
user_profile=user_profile,
11631214
redirect_url=redirect_url,
11641215
)
1216+
headers = {
1217+
k: v
1218+
for k, v in {"X-Connect-Token": x_connect_token}.items()
1219+
if v is not None
1220+
}
11651221
return await self._client._apost(
1166-
"/v1/connect/whatsapp/select-phone-number", data=payload
1222+
"/v1/connect/whatsapp/select-phone-number", data=payload, headers=headers
11671223
)
11681224

11691225
async def aconnect_whats_app_embedded_signup(

‎src/late/resources/_generated/phone_numbers.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,10 @@ def view_phone_number_kyc_document(self, document_id: str) -> dict[str, Any]:
209209

210210
def upload_phone_number_kyc_document(self, x_filename: str) -> dict[str, Any]:
211211
"""Upload a KYC document"""
212-
return self._client._post("/v1/phone-numbers/kyc/upload-document")
212+
headers = {k: v for k, v in {"X-Filename": x_filename}.items() if v is not None}
213+
return self._client._post(
214+
"/v1/phone-numbers/kyc/upload-document", headers=headers
215+
)
213216

214217
def validate_phone_number_kyc_address(
215218
self,
@@ -543,7 +546,10 @@ async def aupload_phone_number_kyc_document(
543546
self, x_filename: str
544547
) -> dict[str, Any]:
545548
"""Upload a KYC document (async)"""
546-
return await self._client._apost("/v1/phone-numbers/kyc/upload-document")
549+
headers = {k: v for k, v in {"X-Filename": x_filename}.items() if v is not None}
550+
return await self._client._apost(
551+
"/v1/phone-numbers/kyc/upload-document", headers=headers
552+
)
547553

548554
async def avalidate_phone_number_kyc_address(
549555
self,

‎src/late/resources/_generated/posts.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,10 @@ def create_post(
154154
queued_from_profile=queued_from_profile,
155155
queue_id=queue_id,
156156
)
157-
return self._client._post("/v1/posts", data=payload)
157+
headers = {
158+
k: v for k, v in {"x-request-id": x_request_id}.items() if v is not None
159+
}
160+
return self._client._post("/v1/posts", data=payload, headers=headers)
158161

159162
def get_post(self, post_id: str) -> dict[str, Any]:
160163
"""Get post"""
@@ -359,7 +362,10 @@ async def acreate_post(
359362
queued_from_profile=queued_from_profile,
360363
queue_id=queue_id,
361364
)
362-
return await self._client._apost("/v1/posts", data=payload)
365+
headers = {
366+
k: v for k, v in {"x-request-id": x_request_id}.items() if v is not None
367+
}
368+
return await self._client._apost("/v1/posts", data=payload, headers=headers)
363369

364370
async def aget_post(self, post_id: str) -> dict[str, Any]:
365371
"""Get post (async)"""

0 commit comments

Comments
 (0)