dub 0.32.0__py3-none-any.whl → 0.34.0__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.
- dub/_version.py +3 -3
- dub/analytics.py +6 -4
- dub/basesdk.py +6 -0
- dub/commissions.py +12 -8
- dub/customers.py +24 -313
- dub/domains.py +34 -26
- dub/embed_tokens.py +6 -4
- dub/events.py +6 -4
- dub/folders.py +24 -20
- dub/links.py +58 -54
- dub/models/components/__init__.py +81 -149
- dub/models/components/analyticstopurls.py +2 -2
- dub/models/components/leadcreatedevent.py +15 -14
- dub/models/components/linkclickedevent.py +19 -18
- dub/models/components/linkerrorschema.py +12 -12
- dub/models/components/linkschema.py +9 -3
- dub/models/components/linktagschema.py +3 -3
- dub/models/components/linktagschemaoutput.py +38 -0
- dub/models/components/linkwebhookevent.py +15 -16
- dub/models/components/partnerapplicationsubmittedevent.py +269 -0
- dub/models/components/partnerenrolledevent.py +68 -8
- dub/models/components/salecreatedevent.py +15 -14
- dub/models/components/webhookevent.py +6 -0
- dub/models/components/workspaceschema.py +6 -0
- dub/models/operations/__init__.py +147 -57
- dub/models/operations/banpartner.py +83 -0
- dub/models/operations/createpartner.py +68 -59
- dub/models/operations/createpartnerlink.py +0 -51
- dub/models/operations/createreferralsembedtoken.py +0 -51
- dub/models/operations/getcustomers.py +18 -0
- dub/models/operations/getlinkinfo.py +0 -2
- dub/models/operations/getlinks.py +2 -2
- dub/models/operations/getlinkscount.py +2 -2
- dub/models/operations/getqrcode.py +1 -1
- dub/models/operations/listcommissions.py +13 -2
- dub/models/operations/listdomains.py +1 -1
- dub/models/operations/listevents.py +2026 -21
- dub/models/operations/listpartners.py +75 -8
- dub/models/operations/retrieveanalytics.py +28 -5
- dub/models/operations/retrievelinks.py +44 -9
- dub/models/operations/retrievepartneranalytics.py +51 -11
- dub/models/operations/tracklead.py +4 -4
- dub/models/operations/updatecommission.py +7 -2
- dub/models/operations/updatecustomer.py +23 -11
- dub/models/operations/updatelink.py +0 -2
- dub/models/operations/updateworkspace.py +3 -3
- dub/models/operations/upsertpartnerlink.py +0 -51
- dub/partners.py +316 -24
- dub/qr_codes.py +4 -2
- dub/tags.py +24 -20
- dub/track.py +12 -16
- dub/types/basemodel.py +41 -3
- dub/utils/__init__.py +0 -3
- dub/utils/enums.py +60 -0
- dub/utils/forms.py +21 -10
- dub/utils/queryparams.py +14 -2
- dub/utils/requestbodies.py +3 -3
- dub/utils/retries.py +69 -5
- dub/utils/serializers.py +0 -20
- dub/utils/unmarshal_json_response.py +15 -1
- dub/workspaces.py +12 -16
- {dub-0.32.0.dist-info → dub-0.34.0.dist-info}/METADATA +15 -33
- {dub-0.32.0.dist-info → dub-0.34.0.dist-info}/RECORD +65 -67
- dub/models/components/clickevent.py +0 -556
- dub/models/components/continentcode.py +0 -16
- dub/models/components/leadevent.py +0 -680
- dub/models/components/saleevent.py +0 -779
- dub/models/operations/createcustomer.py +0 -382
- {dub-0.32.0.dist-info → dub-0.34.0.dist-info}/WHEEL +0 -0
- {dub-0.32.0.dist-info → dub-0.34.0.dist-info}/licenses/LICENSE +0 -0
dub/tags.py
CHANGED
|
@@ -23,7 +23,7 @@ class Tags(BaseSDK):
|
|
|
23
23
|
server_url: Optional[str] = None,
|
|
24
24
|
timeout_ms: Optional[int] = None,
|
|
25
25
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
26
|
-
) ->
|
|
26
|
+
) -> components.LinkTagSchemaOutput:
|
|
27
27
|
r"""Create a tag
|
|
28
28
|
|
|
29
29
|
Create a tag for the authenticated workspace.
|
|
@@ -66,6 +66,7 @@ class Tags(BaseSDK):
|
|
|
66
66
|
get_serialized_body=lambda: utils.serialize_request_body(
|
|
67
67
|
request, False, True, "json", Optional[operations.CreateTagRequestBody]
|
|
68
68
|
),
|
|
69
|
+
allow_empty_value=None,
|
|
69
70
|
timeout_ms=timeout_ms,
|
|
70
71
|
)
|
|
71
72
|
|
|
@@ -104,7 +105,7 @@ class Tags(BaseSDK):
|
|
|
104
105
|
|
|
105
106
|
response_data: Any = None
|
|
106
107
|
if utils.match_response(http_res, "201", "application/json"):
|
|
107
|
-
return unmarshal_json_response(
|
|
108
|
+
return unmarshal_json_response(components.LinkTagSchemaOutput, http_res)
|
|
108
109
|
if utils.match_response(http_res, "400", "application/json"):
|
|
109
110
|
response_data = unmarshal_json_response(errors.BadRequestData, http_res)
|
|
110
111
|
raise errors.BadRequest(response_data, http_res)
|
|
@@ -160,7 +161,7 @@ class Tags(BaseSDK):
|
|
|
160
161
|
server_url: Optional[str] = None,
|
|
161
162
|
timeout_ms: Optional[int] = None,
|
|
162
163
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
163
|
-
) ->
|
|
164
|
+
) -> components.LinkTagSchemaOutput:
|
|
164
165
|
r"""Create a tag
|
|
165
166
|
|
|
166
167
|
Create a tag for the authenticated workspace.
|
|
@@ -203,6 +204,7 @@ class Tags(BaseSDK):
|
|
|
203
204
|
get_serialized_body=lambda: utils.serialize_request_body(
|
|
204
205
|
request, False, True, "json", Optional[operations.CreateTagRequestBody]
|
|
205
206
|
),
|
|
207
|
+
allow_empty_value=None,
|
|
206
208
|
timeout_ms=timeout_ms,
|
|
207
209
|
)
|
|
208
210
|
|
|
@@ -241,7 +243,7 @@ class Tags(BaseSDK):
|
|
|
241
243
|
|
|
242
244
|
response_data: Any = None
|
|
243
245
|
if utils.match_response(http_res, "201", "application/json"):
|
|
244
|
-
return unmarshal_json_response(
|
|
246
|
+
return unmarshal_json_response(components.LinkTagSchemaOutput, http_res)
|
|
245
247
|
if utils.match_response(http_res, "400", "application/json"):
|
|
246
248
|
response_data = unmarshal_json_response(errors.BadRequestData, http_res)
|
|
247
249
|
raise errors.BadRequest(response_data, http_res)
|
|
@@ -292,7 +294,7 @@ class Tags(BaseSDK):
|
|
|
292
294
|
server_url: Optional[str] = None,
|
|
293
295
|
timeout_ms: Optional[int] = None,
|
|
294
296
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
295
|
-
) ->
|
|
297
|
+
) -> List[components.LinkTagSchemaOutput]:
|
|
296
298
|
r"""Retrieve a list of tags
|
|
297
299
|
|
|
298
300
|
Retrieve a list of tags for the authenticated workspace.
|
|
@@ -330,6 +332,7 @@ class Tags(BaseSDK):
|
|
|
330
332
|
accept_header_value="application/json",
|
|
331
333
|
http_headers=http_headers,
|
|
332
334
|
security=self.sdk_configuration.security,
|
|
335
|
+
allow_empty_value=None,
|
|
333
336
|
timeout_ms=timeout_ms,
|
|
334
337
|
)
|
|
335
338
|
|
|
@@ -369,7 +372,7 @@ class Tags(BaseSDK):
|
|
|
369
372
|
response_data: Any = None
|
|
370
373
|
if utils.match_response(http_res, "200", "application/json"):
|
|
371
374
|
return unmarshal_json_response(
|
|
372
|
-
|
|
375
|
+
List[components.LinkTagSchemaOutput], http_res
|
|
373
376
|
)
|
|
374
377
|
if utils.match_response(http_res, "400", "application/json"):
|
|
375
378
|
response_data = unmarshal_json_response(errors.BadRequestData, http_res)
|
|
@@ -421,7 +424,7 @@ class Tags(BaseSDK):
|
|
|
421
424
|
server_url: Optional[str] = None,
|
|
422
425
|
timeout_ms: Optional[int] = None,
|
|
423
426
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
424
|
-
) ->
|
|
427
|
+
) -> List[components.LinkTagSchemaOutput]:
|
|
425
428
|
r"""Retrieve a list of tags
|
|
426
429
|
|
|
427
430
|
Retrieve a list of tags for the authenticated workspace.
|
|
@@ -459,6 +462,7 @@ class Tags(BaseSDK):
|
|
|
459
462
|
accept_header_value="application/json",
|
|
460
463
|
http_headers=http_headers,
|
|
461
464
|
security=self.sdk_configuration.security,
|
|
465
|
+
allow_empty_value=None,
|
|
462
466
|
timeout_ms=timeout_ms,
|
|
463
467
|
)
|
|
464
468
|
|
|
@@ -498,7 +502,7 @@ class Tags(BaseSDK):
|
|
|
498
502
|
response_data: Any = None
|
|
499
503
|
if utils.match_response(http_res, "200", "application/json"):
|
|
500
504
|
return unmarshal_json_response(
|
|
501
|
-
|
|
505
|
+
List[components.LinkTagSchemaOutput], http_res
|
|
502
506
|
)
|
|
503
507
|
if utils.match_response(http_res, "400", "application/json"):
|
|
504
508
|
response_data = unmarshal_json_response(errors.BadRequestData, http_res)
|
|
@@ -556,7 +560,7 @@ class Tags(BaseSDK):
|
|
|
556
560
|
server_url: Optional[str] = None,
|
|
557
561
|
timeout_ms: Optional[int] = None,
|
|
558
562
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
559
|
-
) ->
|
|
563
|
+
) -> components.LinkTagSchemaOutput:
|
|
560
564
|
r"""Update a tag
|
|
561
565
|
|
|
562
566
|
Update a tag in the workspace.
|
|
@@ -605,6 +609,7 @@ class Tags(BaseSDK):
|
|
|
605
609
|
"json",
|
|
606
610
|
Optional[operations.UpdateTagRequestBody],
|
|
607
611
|
),
|
|
612
|
+
allow_empty_value=None,
|
|
608
613
|
timeout_ms=timeout_ms,
|
|
609
614
|
)
|
|
610
615
|
|
|
@@ -643,7 +648,7 @@ class Tags(BaseSDK):
|
|
|
643
648
|
|
|
644
649
|
response_data: Any = None
|
|
645
650
|
if utils.match_response(http_res, "200", "application/json"):
|
|
646
|
-
return unmarshal_json_response(
|
|
651
|
+
return unmarshal_json_response(components.LinkTagSchemaOutput, http_res)
|
|
647
652
|
if utils.match_response(http_res, "400", "application/json"):
|
|
648
653
|
response_data = unmarshal_json_response(errors.BadRequestData, http_res)
|
|
649
654
|
raise errors.BadRequest(response_data, http_res)
|
|
@@ -700,7 +705,7 @@ class Tags(BaseSDK):
|
|
|
700
705
|
server_url: Optional[str] = None,
|
|
701
706
|
timeout_ms: Optional[int] = None,
|
|
702
707
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
703
|
-
) ->
|
|
708
|
+
) -> components.LinkTagSchemaOutput:
|
|
704
709
|
r"""Update a tag
|
|
705
710
|
|
|
706
711
|
Update a tag in the workspace.
|
|
@@ -749,6 +754,7 @@ class Tags(BaseSDK):
|
|
|
749
754
|
"json",
|
|
750
755
|
Optional[operations.UpdateTagRequestBody],
|
|
751
756
|
),
|
|
757
|
+
allow_empty_value=None,
|
|
752
758
|
timeout_ms=timeout_ms,
|
|
753
759
|
)
|
|
754
760
|
|
|
@@ -787,7 +793,7 @@ class Tags(BaseSDK):
|
|
|
787
793
|
|
|
788
794
|
response_data: Any = None
|
|
789
795
|
if utils.match_response(http_res, "200", "application/json"):
|
|
790
|
-
return unmarshal_json_response(
|
|
796
|
+
return unmarshal_json_response(components.LinkTagSchemaOutput, http_res)
|
|
791
797
|
if utils.match_response(http_res, "400", "application/json"):
|
|
792
798
|
response_data = unmarshal_json_response(errors.BadRequestData, http_res)
|
|
793
799
|
raise errors.BadRequest(response_data, http_res)
|
|
@@ -838,7 +844,7 @@ class Tags(BaseSDK):
|
|
|
838
844
|
server_url: Optional[str] = None,
|
|
839
845
|
timeout_ms: Optional[int] = None,
|
|
840
846
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
841
|
-
) ->
|
|
847
|
+
) -> operations.DeleteTagResponseBody:
|
|
842
848
|
r"""Delete a tag
|
|
843
849
|
|
|
844
850
|
Delete a tag from the workspace. All existing links will still work, but they will no longer be associated with this tag.
|
|
@@ -876,6 +882,7 @@ class Tags(BaseSDK):
|
|
|
876
882
|
accept_header_value="application/json",
|
|
877
883
|
http_headers=http_headers,
|
|
878
884
|
security=self.sdk_configuration.security,
|
|
885
|
+
allow_empty_value=None,
|
|
879
886
|
timeout_ms=timeout_ms,
|
|
880
887
|
)
|
|
881
888
|
|
|
@@ -914,9 +921,7 @@ class Tags(BaseSDK):
|
|
|
914
921
|
|
|
915
922
|
response_data: Any = None
|
|
916
923
|
if utils.match_response(http_res, "200", "application/json"):
|
|
917
|
-
return unmarshal_json_response(
|
|
918
|
-
Optional[operations.DeleteTagResponseBody], http_res
|
|
919
|
-
)
|
|
924
|
+
return unmarshal_json_response(operations.DeleteTagResponseBody, http_res)
|
|
920
925
|
if utils.match_response(http_res, "400", "application/json"):
|
|
921
926
|
response_data = unmarshal_json_response(errors.BadRequestData, http_res)
|
|
922
927
|
raise errors.BadRequest(response_data, http_res)
|
|
@@ -967,7 +972,7 @@ class Tags(BaseSDK):
|
|
|
967
972
|
server_url: Optional[str] = None,
|
|
968
973
|
timeout_ms: Optional[int] = None,
|
|
969
974
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
970
|
-
) ->
|
|
975
|
+
) -> operations.DeleteTagResponseBody:
|
|
971
976
|
r"""Delete a tag
|
|
972
977
|
|
|
973
978
|
Delete a tag from the workspace. All existing links will still work, but they will no longer be associated with this tag.
|
|
@@ -1005,6 +1010,7 @@ class Tags(BaseSDK):
|
|
|
1005
1010
|
accept_header_value="application/json",
|
|
1006
1011
|
http_headers=http_headers,
|
|
1007
1012
|
security=self.sdk_configuration.security,
|
|
1013
|
+
allow_empty_value=None,
|
|
1008
1014
|
timeout_ms=timeout_ms,
|
|
1009
1015
|
)
|
|
1010
1016
|
|
|
@@ -1043,9 +1049,7 @@ class Tags(BaseSDK):
|
|
|
1043
1049
|
|
|
1044
1050
|
response_data: Any = None
|
|
1045
1051
|
if utils.match_response(http_res, "200", "application/json"):
|
|
1046
|
-
return unmarshal_json_response(
|
|
1047
|
-
Optional[operations.DeleteTagResponseBody], http_res
|
|
1048
|
-
)
|
|
1052
|
+
return unmarshal_json_response(operations.DeleteTagResponseBody, http_res)
|
|
1049
1053
|
if utils.match_response(http_res, "400", "application/json"):
|
|
1050
1054
|
response_data = unmarshal_json_response(errors.BadRequestData, http_res)
|
|
1051
1055
|
raise errors.BadRequest(response_data, http_res)
|
dub/track.py
CHANGED
|
@@ -23,7 +23,7 @@ class Track(BaseSDK):
|
|
|
23
23
|
server_url: Optional[str] = None,
|
|
24
24
|
timeout_ms: Optional[int] = None,
|
|
25
25
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
26
|
-
) ->
|
|
26
|
+
) -> operations.TrackLeadResponseBody:
|
|
27
27
|
r"""Track a lead
|
|
28
28
|
|
|
29
29
|
Track a lead for a short link.
|
|
@@ -66,6 +66,7 @@ class Track(BaseSDK):
|
|
|
66
66
|
get_serialized_body=lambda: utils.serialize_request_body(
|
|
67
67
|
request, False, True, "json", Optional[operations.TrackLeadRequestBody]
|
|
68
68
|
),
|
|
69
|
+
allow_empty_value=None,
|
|
69
70
|
timeout_ms=timeout_ms,
|
|
70
71
|
)
|
|
71
72
|
|
|
@@ -104,9 +105,7 @@ class Track(BaseSDK):
|
|
|
104
105
|
|
|
105
106
|
response_data: Any = None
|
|
106
107
|
if utils.match_response(http_res, "200", "application/json"):
|
|
107
|
-
return unmarshal_json_response(
|
|
108
|
-
Optional[operations.TrackLeadResponseBody], http_res
|
|
109
|
-
)
|
|
108
|
+
return unmarshal_json_response(operations.TrackLeadResponseBody, http_res)
|
|
110
109
|
if utils.match_response(http_res, "400", "application/json"):
|
|
111
110
|
response_data = unmarshal_json_response(errors.BadRequestData, http_res)
|
|
112
111
|
raise errors.BadRequest(response_data, http_res)
|
|
@@ -162,7 +161,7 @@ class Track(BaseSDK):
|
|
|
162
161
|
server_url: Optional[str] = None,
|
|
163
162
|
timeout_ms: Optional[int] = None,
|
|
164
163
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
165
|
-
) ->
|
|
164
|
+
) -> operations.TrackLeadResponseBody:
|
|
166
165
|
r"""Track a lead
|
|
167
166
|
|
|
168
167
|
Track a lead for a short link.
|
|
@@ -205,6 +204,7 @@ class Track(BaseSDK):
|
|
|
205
204
|
get_serialized_body=lambda: utils.serialize_request_body(
|
|
206
205
|
request, False, True, "json", Optional[operations.TrackLeadRequestBody]
|
|
207
206
|
),
|
|
207
|
+
allow_empty_value=None,
|
|
208
208
|
timeout_ms=timeout_ms,
|
|
209
209
|
)
|
|
210
210
|
|
|
@@ -243,9 +243,7 @@ class Track(BaseSDK):
|
|
|
243
243
|
|
|
244
244
|
response_data: Any = None
|
|
245
245
|
if utils.match_response(http_res, "200", "application/json"):
|
|
246
|
-
return unmarshal_json_response(
|
|
247
|
-
Optional[operations.TrackLeadResponseBody], http_res
|
|
248
|
-
)
|
|
246
|
+
return unmarshal_json_response(operations.TrackLeadResponseBody, http_res)
|
|
249
247
|
if utils.match_response(http_res, "400", "application/json"):
|
|
250
248
|
response_data = unmarshal_json_response(errors.BadRequestData, http_res)
|
|
251
249
|
raise errors.BadRequest(response_data, http_res)
|
|
@@ -301,7 +299,7 @@ class Track(BaseSDK):
|
|
|
301
299
|
server_url: Optional[str] = None,
|
|
302
300
|
timeout_ms: Optional[int] = None,
|
|
303
301
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
304
|
-
) ->
|
|
302
|
+
) -> operations.TrackSaleResponseBody:
|
|
305
303
|
r"""Track a sale
|
|
306
304
|
|
|
307
305
|
Track a sale for a short link.
|
|
@@ -344,6 +342,7 @@ class Track(BaseSDK):
|
|
|
344
342
|
get_serialized_body=lambda: utils.serialize_request_body(
|
|
345
343
|
request, False, True, "json", Optional[operations.TrackSaleRequestBody]
|
|
346
344
|
),
|
|
345
|
+
allow_empty_value=None,
|
|
347
346
|
timeout_ms=timeout_ms,
|
|
348
347
|
)
|
|
349
348
|
|
|
@@ -382,9 +381,7 @@ class Track(BaseSDK):
|
|
|
382
381
|
|
|
383
382
|
response_data: Any = None
|
|
384
383
|
if utils.match_response(http_res, "200", "application/json"):
|
|
385
|
-
return unmarshal_json_response(
|
|
386
|
-
Optional[operations.TrackSaleResponseBody], http_res
|
|
387
|
-
)
|
|
384
|
+
return unmarshal_json_response(operations.TrackSaleResponseBody, http_res)
|
|
388
385
|
if utils.match_response(http_res, "400", "application/json"):
|
|
389
386
|
response_data = unmarshal_json_response(errors.BadRequestData, http_res)
|
|
390
387
|
raise errors.BadRequest(response_data, http_res)
|
|
@@ -440,7 +437,7 @@ class Track(BaseSDK):
|
|
|
440
437
|
server_url: Optional[str] = None,
|
|
441
438
|
timeout_ms: Optional[int] = None,
|
|
442
439
|
http_headers: Optional[Mapping[str, str]] = None,
|
|
443
|
-
) ->
|
|
440
|
+
) -> operations.TrackSaleResponseBody:
|
|
444
441
|
r"""Track a sale
|
|
445
442
|
|
|
446
443
|
Track a sale for a short link.
|
|
@@ -483,6 +480,7 @@ class Track(BaseSDK):
|
|
|
483
480
|
get_serialized_body=lambda: utils.serialize_request_body(
|
|
484
481
|
request, False, True, "json", Optional[operations.TrackSaleRequestBody]
|
|
485
482
|
),
|
|
483
|
+
allow_empty_value=None,
|
|
486
484
|
timeout_ms=timeout_ms,
|
|
487
485
|
)
|
|
488
486
|
|
|
@@ -521,9 +519,7 @@ class Track(BaseSDK):
|
|
|
521
519
|
|
|
522
520
|
response_data: Any = None
|
|
523
521
|
if utils.match_response(http_res, "200", "application/json"):
|
|
524
|
-
return unmarshal_json_response(
|
|
525
|
-
Optional[operations.TrackSaleResponseBody], http_res
|
|
526
|
-
)
|
|
522
|
+
return unmarshal_json_response(operations.TrackSaleResponseBody, http_res)
|
|
527
523
|
if utils.match_response(http_res, "400", "application/json"):
|
|
528
524
|
response_data = unmarshal_json_response(errors.BadRequestData, http_res)
|
|
529
525
|
raise errors.BadRequest(response_data, http_res)
|
dub/types/basemodel.py
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from pydantic import ConfigDict, model_serializer
|
|
4
4
|
from pydantic import BaseModel as PydanticBaseModel
|
|
5
|
-
from
|
|
5
|
+
from pydantic_core import core_schema
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal, Optional, TypeVar, Union
|
|
6
7
|
from typing_extensions import TypeAliasType, TypeAlias
|
|
7
8
|
|
|
8
9
|
|
|
@@ -35,5 +36,42 @@ else:
|
|
|
35
36
|
"OptionalNullable", Union[Optional[Nullable[T]], Unset], type_params=(T,)
|
|
36
37
|
)
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
UnrecognizedStr:
|
|
39
|
+
|
|
40
|
+
class UnrecognizedStr(str):
|
|
41
|
+
@classmethod
|
|
42
|
+
def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> core_schema.CoreSchema:
|
|
43
|
+
# Make UnrecognizedStr only work in lax mode, not strict mode
|
|
44
|
+
# This makes it a "fallback" option when more specific types (like Literals) don't match
|
|
45
|
+
def validate_lax(v: Any) -> 'UnrecognizedStr':
|
|
46
|
+
if isinstance(v, cls):
|
|
47
|
+
return v
|
|
48
|
+
return cls(str(v))
|
|
49
|
+
|
|
50
|
+
# Use lax_or_strict_schema where strict always fails
|
|
51
|
+
# This forces Pydantic to prefer other union members in strict mode
|
|
52
|
+
# and only fall back to UnrecognizedStr in lax mode
|
|
53
|
+
return core_schema.lax_or_strict_schema(
|
|
54
|
+
lax_schema=core_schema.chain_schema([
|
|
55
|
+
core_schema.str_schema(),
|
|
56
|
+
core_schema.no_info_plain_validator_function(validate_lax)
|
|
57
|
+
]),
|
|
58
|
+
strict_schema=core_schema.none_schema(), # Always fails in strict mode
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class UnrecognizedInt(int):
|
|
63
|
+
@classmethod
|
|
64
|
+
def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> core_schema.CoreSchema:
|
|
65
|
+
# Make UnrecognizedInt only work in lax mode, not strict mode
|
|
66
|
+
# This makes it a "fallback" option when more specific types (like Literals) don't match
|
|
67
|
+
def validate_lax(v: Any) -> 'UnrecognizedInt':
|
|
68
|
+
if isinstance(v, cls):
|
|
69
|
+
return v
|
|
70
|
+
return cls(int(v))
|
|
71
|
+
return core_schema.lax_or_strict_schema(
|
|
72
|
+
lax_schema=core_schema.chain_schema([
|
|
73
|
+
core_schema.int_schema(),
|
|
74
|
+
core_schema.no_info_plain_validator_function(validate_lax)
|
|
75
|
+
]),
|
|
76
|
+
strict_schema=core_schema.none_schema(), # Always fails in strict mode
|
|
77
|
+
)
|
dub/utils/__init__.py
CHANGED
|
@@ -41,7 +41,6 @@ if TYPE_CHECKING:
|
|
|
41
41
|
validate_decimal,
|
|
42
42
|
validate_float,
|
|
43
43
|
validate_int,
|
|
44
|
-
validate_open_enum,
|
|
45
44
|
)
|
|
46
45
|
from .url import generate_url, template_url, remove_suffix
|
|
47
46
|
from .values import (
|
|
@@ -102,7 +101,6 @@ __all__ = [
|
|
|
102
101
|
"validate_const",
|
|
103
102
|
"validate_float",
|
|
104
103
|
"validate_int",
|
|
105
|
-
"validate_open_enum",
|
|
106
104
|
"cast_partial",
|
|
107
105
|
]
|
|
108
106
|
|
|
@@ -155,7 +153,6 @@ _dynamic_imports: dict[str, str] = {
|
|
|
155
153
|
"validate_const": ".serializers",
|
|
156
154
|
"validate_float": ".serializers",
|
|
157
155
|
"validate_int": ".serializers",
|
|
158
|
-
"validate_open_enum": ".serializers",
|
|
159
156
|
"cast_partial": ".values",
|
|
160
157
|
}
|
|
161
158
|
|
dub/utils/enums.py
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import enum
|
|
4
4
|
import sys
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic_core import core_schema
|
|
8
|
+
|
|
5
9
|
|
|
6
10
|
class OpenEnumMeta(enum.EnumMeta):
|
|
7
11
|
# The __call__ method `boundary` kwarg was added in 3.11 and must be present
|
|
@@ -72,3 +76,59 @@ class OpenEnumMeta(enum.EnumMeta):
|
|
|
72
76
|
)
|
|
73
77
|
except ValueError:
|
|
74
78
|
return value
|
|
79
|
+
|
|
80
|
+
def __new__(mcs, name, bases, namespace, **kwargs):
|
|
81
|
+
cls = super().__new__(mcs, name, bases, namespace, **kwargs)
|
|
82
|
+
|
|
83
|
+
# Add __get_pydantic_core_schema__ to make open enums work correctly
|
|
84
|
+
# in union discrimination. In strict mode (used by Pydantic for unions),
|
|
85
|
+
# only known enum values match. In lax mode, unknown values are accepted.
|
|
86
|
+
def __get_pydantic_core_schema__(
|
|
87
|
+
cls_inner: Any, _source_type: Any, _handler: Any
|
|
88
|
+
) -> core_schema.CoreSchema:
|
|
89
|
+
# Create a validator that only accepts known enum values (for strict mode)
|
|
90
|
+
def validate_strict(v: Any) -> Any:
|
|
91
|
+
if isinstance(v, cls_inner):
|
|
92
|
+
return v
|
|
93
|
+
# Use the parent EnumMeta's __call__ which raises ValueError for unknown values
|
|
94
|
+
return enum.EnumMeta.__call__(cls_inner, v)
|
|
95
|
+
|
|
96
|
+
# Create a lax validator that accepts unknown values
|
|
97
|
+
def validate_lax(v: Any) -> Any:
|
|
98
|
+
if isinstance(v, cls_inner):
|
|
99
|
+
return v
|
|
100
|
+
try:
|
|
101
|
+
return enum.EnumMeta.__call__(cls_inner, v)
|
|
102
|
+
except ValueError:
|
|
103
|
+
# Return the raw value for unknown enum values
|
|
104
|
+
return v
|
|
105
|
+
|
|
106
|
+
# Determine the base type schema (str or int)
|
|
107
|
+
is_int_enum = False
|
|
108
|
+
for base in cls_inner.__mro__:
|
|
109
|
+
if base is int:
|
|
110
|
+
is_int_enum = True
|
|
111
|
+
break
|
|
112
|
+
if base is str:
|
|
113
|
+
break
|
|
114
|
+
|
|
115
|
+
base_schema = (
|
|
116
|
+
core_schema.int_schema()
|
|
117
|
+
if is_int_enum
|
|
118
|
+
else core_schema.str_schema()
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Use lax_or_strict_schema:
|
|
122
|
+
# - strict mode: only known enum values match (raises ValueError for unknown)
|
|
123
|
+
# - lax mode: accept any value, return enum member or raw value
|
|
124
|
+
return core_schema.lax_or_strict_schema(
|
|
125
|
+
lax_schema=core_schema.chain_schema(
|
|
126
|
+
[base_schema, core_schema.no_info_plain_validator_function(validate_lax)]
|
|
127
|
+
),
|
|
128
|
+
strict_schema=core_schema.chain_schema(
|
|
129
|
+
[base_schema, core_schema.no_info_plain_validator_function(validate_strict)]
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
setattr(cls, "__get_pydantic_core_schema__", classmethod(__get_pydantic_core_schema__))
|
|
134
|
+
return cls
|
dub/utils/forms.py
CHANGED
|
@@ -142,16 +142,21 @@ def serialize_multipart_form(
|
|
|
142
142
|
if field_metadata.file:
|
|
143
143
|
if isinstance(val, List):
|
|
144
144
|
# Handle array of files
|
|
145
|
+
array_field_name = f_name + "[]"
|
|
145
146
|
for file_obj in val:
|
|
146
147
|
if not _is_set(file_obj):
|
|
147
148
|
continue
|
|
148
|
-
|
|
149
|
-
file_name, content, content_type = _extract_file_properties(
|
|
149
|
+
|
|
150
|
+
file_name, content, content_type = _extract_file_properties(
|
|
151
|
+
file_obj
|
|
152
|
+
)
|
|
150
153
|
|
|
151
154
|
if content_type is not None:
|
|
152
|
-
files.append(
|
|
155
|
+
files.append(
|
|
156
|
+
(array_field_name, (file_name, content, content_type))
|
|
157
|
+
)
|
|
153
158
|
else:
|
|
154
|
-
files.append((
|
|
159
|
+
files.append((array_field_name, (file_name, content)))
|
|
155
160
|
else:
|
|
156
161
|
# Handle single file
|
|
157
162
|
file_name, content, content_type = _extract_file_properties(val)
|
|
@@ -161,11 +166,16 @@ def serialize_multipart_form(
|
|
|
161
166
|
else:
|
|
162
167
|
files.append((f_name, (file_name, content)))
|
|
163
168
|
elif field_metadata.json:
|
|
164
|
-
files.append(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
+
files.append(
|
|
170
|
+
(
|
|
171
|
+
f_name,
|
|
172
|
+
(
|
|
173
|
+
None,
|
|
174
|
+
marshal_json(val, request_field_types[name]),
|
|
175
|
+
"application/json",
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
)
|
|
169
179
|
else:
|
|
170
180
|
if isinstance(val, List):
|
|
171
181
|
values = []
|
|
@@ -175,7 +185,8 @@ def serialize_multipart_form(
|
|
|
175
185
|
continue
|
|
176
186
|
values.append(_val_to_string(value))
|
|
177
187
|
|
|
178
|
-
|
|
188
|
+
array_field_name = f_name + "[]"
|
|
189
|
+
form[array_field_name] = values
|
|
179
190
|
else:
|
|
180
191
|
form[f_name] = _val_to_string(val)
|
|
181
192
|
return media_type, form, files
|
dub/utils/queryparams.py
CHANGED
|
@@ -27,12 +27,13 @@ from .forms import _populate_form
|
|
|
27
27
|
def get_query_params(
|
|
28
28
|
query_params: Any,
|
|
29
29
|
gbls: Optional[Any] = None,
|
|
30
|
+
allow_empty_value: Optional[List[str]] = None,
|
|
30
31
|
) -> Dict[str, List[str]]:
|
|
31
32
|
params: Dict[str, List[str]] = {}
|
|
32
33
|
|
|
33
|
-
globals_already_populated = _populate_query_params(query_params, gbls, params, [])
|
|
34
|
+
globals_already_populated = _populate_query_params(query_params, gbls, params, [], allow_empty_value)
|
|
34
35
|
if _is_set(gbls):
|
|
35
|
-
_populate_query_params(gbls, None, params, globals_already_populated)
|
|
36
|
+
_populate_query_params(gbls, None, params, globals_already_populated, allow_empty_value)
|
|
36
37
|
|
|
37
38
|
return params
|
|
38
39
|
|
|
@@ -42,6 +43,7 @@ def _populate_query_params(
|
|
|
42
43
|
gbls: Any,
|
|
43
44
|
query_param_values: Dict[str, List[str]],
|
|
44
45
|
skip_fields: List[str],
|
|
46
|
+
allow_empty_value: Optional[List[str]] = None,
|
|
45
47
|
) -> List[str]:
|
|
46
48
|
globals_already_populated: List[str] = []
|
|
47
49
|
|
|
@@ -69,6 +71,16 @@ def _populate_query_params(
|
|
|
69
71
|
globals_already_populated.append(name)
|
|
70
72
|
|
|
71
73
|
f_name = field.alias if field.alias is not None else name
|
|
74
|
+
|
|
75
|
+
allow_empty_set = set(allow_empty_value or [])
|
|
76
|
+
should_include_empty = f_name in allow_empty_set and (
|
|
77
|
+
value is None or value == [] or value == ""
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if should_include_empty:
|
|
81
|
+
query_param_values[f_name] = [""]
|
|
82
|
+
continue
|
|
83
|
+
|
|
72
84
|
serialization = metadata.serialization
|
|
73
85
|
if serialization is not None:
|
|
74
86
|
serialized_parms = _get_serialized_params(
|
dub/utils/requestbodies.py
CHANGED
|
@@ -44,15 +44,15 @@ def serialize_request_body(
|
|
|
44
44
|
|
|
45
45
|
serialized_request_body = SerializedRequestBody(media_type)
|
|
46
46
|
|
|
47
|
-
if re.match(r"(application|text)
|
|
47
|
+
if re.match(r"^(application|text)\/([^+]+\+)*json.*", media_type) is not None:
|
|
48
48
|
serialized_request_body.content = marshal_json(request_body, request_body_type)
|
|
49
|
-
elif re.match(r"multipart\/.*", media_type) is not None:
|
|
49
|
+
elif re.match(r"^multipart\/.*", media_type) is not None:
|
|
50
50
|
(
|
|
51
51
|
serialized_request_body.media_type,
|
|
52
52
|
serialized_request_body.data,
|
|
53
53
|
serialized_request_body.files,
|
|
54
54
|
) = serialize_multipart_form(media_type, request_body)
|
|
55
|
-
elif re.match(r"application\/x-www-form-urlencoded.*", media_type) is not None:
|
|
55
|
+
elif re.match(r"^application\/x-www-form-urlencoded.*", media_type) is not None:
|
|
56
56
|
serialized_request_body.data = serialize_form_data(request_body)
|
|
57
57
|
elif isinstance(request_body, (bytes, bytearray, io.BytesIO, io.BufferedReader)):
|
|
58
58
|
serialized_request_body.content = request_body
|