dub 0.34.1__py3-none-any.whl → 0.35.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.
Files changed (93) hide show
  1. dub/_version.py +3 -3
  2. dub/basesdk.py +20 -6
  3. dub/models/components/__init__.py +108 -26
  4. dub/models/components/analyticsbrowsers.py +18 -1
  5. dub/models/components/analyticscities.py +18 -1
  6. dub/models/components/analyticscontinents.py +18 -1
  7. dub/models/components/analyticscount.py +18 -1
  8. dub/models/components/analyticscountries.py +20 -1
  9. dub/models/components/analyticsdevices.py +18 -1
  10. dub/models/components/analyticsos.py +18 -1
  11. dub/models/components/analyticsreferers.py +18 -1
  12. dub/models/components/analyticsrefererurls.py +18 -1
  13. dub/models/components/analyticsregions.py +18 -1
  14. dub/models/components/analyticstimeseries.py +18 -1
  15. dub/models/components/analyticstoplinks.py +16 -26
  16. dub/models/components/analyticstopurls.py +18 -1
  17. dub/models/components/analyticstriggers.py +18 -1
  18. dub/models/components/commissioncreatedevent.py +96 -64
  19. dub/models/components/domainschema.py +31 -50
  20. dub/models/components/folderschema.py +18 -19
  21. dub/models/components/leadcreatedevent.py +151 -134
  22. dub/models/components/linkclickedevent.py +57 -70
  23. dub/models/components/linkschema.py +63 -64
  24. dub/models/components/linkwebhookevent.py +43 -51
  25. dub/models/components/partneranalyticscount.py +18 -1
  26. dub/models/components/partneranalyticstimeseries.py +18 -1
  27. dub/models/components/partneranalyticstoplinks.py +16 -27
  28. dub/models/components/partnerapplicationsubmittedevent.py +42 -75
  29. dub/models/components/partnerenrolledevent.py +477 -83
  30. dub/models/components/salecreatedevent.py +152 -151
  31. dub/models/errors/badrequest.py +18 -1
  32. dub/models/errors/conflict.py +18 -1
  33. dub/models/errors/forbidden.py +18 -1
  34. dub/models/errors/internalservererror.py +18 -1
  35. dub/models/errors/inviteexpired.py +18 -1
  36. dub/models/errors/notfound.py +18 -1
  37. dub/models/errors/ratelimitexceeded.py +18 -1
  38. dub/models/errors/unauthorized.py +18 -1
  39. dub/models/errors/unprocessableentity.py +18 -1
  40. dub/models/operations/__init__.py +230 -19
  41. dub/models/operations/approvebountysubmission.py +71 -45
  42. dub/models/operations/banpartner.py +14 -19
  43. dub/models/operations/bulkcreatelinks.py +86 -87
  44. dub/models/operations/bulkupdatelinks.py +97 -82
  45. dub/models/operations/checkdomainstatus.py +1 -17
  46. dub/models/operations/createdomain.py +33 -34
  47. dub/models/operations/createfolder.py +18 -19
  48. dub/models/operations/createlink.py +86 -87
  49. dub/models/operations/createpartner.py +560 -168
  50. dub/models/operations/createpartnerlink.py +74 -85
  51. dub/models/operations/createreferralsembedtoken.py +99 -87
  52. dub/models/operations/createtag.py +18 -1
  53. dub/models/operations/deactivatepartner.py +65 -0
  54. dub/models/operations/getcustomer.py +106 -105
  55. dub/models/operations/getcustomers.py +123 -105
  56. dub/models/operations/getlinkinfo.py +18 -1
  57. dub/models/operations/getlinks.py +36 -1
  58. dub/models/operations/getlinkscount.py +32 -1
  59. dub/models/operations/getqrcode.py +29 -1
  60. dub/models/operations/gettags.py +20 -1
  61. dub/models/operations/listbountysubmissions.py +63 -26
  62. dub/models/operations/listcommissions.py +129 -64
  63. dub/models/operations/listdomains.py +18 -1
  64. dub/models/operations/listevents.py +414 -389
  65. dub/models/operations/listfolders.py +18 -1
  66. dub/models/operations/listpartners.py +510 -84
  67. dub/models/operations/registerdomain.py +1 -17
  68. dub/models/operations/rejectbountysubmission.py +71 -26
  69. dub/models/operations/retrieveanalytics.py +65 -66
  70. dub/models/operations/retrievelinks.py +30 -19
  71. dub/models/operations/retrievepartneranalytics.py +25 -28
  72. dub/models/operations/tracklead.py +38 -83
  73. dub/models/operations/tracksale.py +52 -95
  74. dub/models/operations/updatecommission.py +126 -64
  75. dub/models/operations/updatecustomer.py +122 -131
  76. dub/models/operations/updatedomain.py +50 -35
  77. dub/models/operations/updatefolder.py +34 -19
  78. dub/models/operations/updatelink.py +101 -86
  79. dub/models/operations/updatetag.py +34 -1
  80. dub/models/operations/upsertlink.py +86 -87
  81. dub/models/operations/upsertpartnerlink.py +72 -78
  82. dub/partners.py +288 -0
  83. dub/sdk.py +0 -3
  84. dub/utils/__init__.py +10 -1
  85. {dub-0.34.1.dist-info → dub-0.35.0.dist-info}/METADATA +4 -8
  86. dub-0.35.0.dist-info/RECORD +143 -0
  87. dub/models/components/workspaceschema.py +0 -328
  88. dub/models/operations/getworkspace.py +0 -21
  89. dub/models/operations/updateworkspace.py +0 -78
  90. dub/workspaces.py +0 -561
  91. dub-0.34.1.dist-info/RECORD +0 -146
  92. {dub-0.34.1.dist-info → dub-0.35.0.dist-info}/WHEEL +0 -0
  93. {dub-0.34.1.dist-info → dub-0.35.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,9 +1,10 @@
1
1
  """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
2
2
 
3
3
  from __future__ import annotations
4
- from dub.types import BaseModel
4
+ from dub.types import BaseModel, UNSET_SENTINEL
5
5
  from dub.utils import FieldMetadata, QueryParamMetadata
6
6
  import pydantic
7
+ from pydantic import model_serializer
7
8
  from typing import Optional
8
9
  from typing_extensions import Annotated, NotRequired, TypedDict
9
10
 
@@ -41,3 +42,19 @@ class GetLinkInfoRequest(BaseModel):
41
42
  FieldMetadata(query=QueryParamMetadata(style="form", explode=True)),
42
43
  ] = None
43
44
  r"""This is the ID of the link in the your database."""
45
+
46
+ @model_serializer(mode="wrap")
47
+ def serialize_model(self, handler):
48
+ optional_fields = set(["domain", "key", "linkId", "externalId"])
49
+ serialized = handler(self)
50
+ m = {}
51
+
52
+ for n, f in type(self).model_fields.items():
53
+ k = f.alias or n
54
+ val = serialized.get(k)
55
+
56
+ if val != UNSET_SENTINEL:
57
+ if val is not None or k not in optional_fields:
58
+ m[k] = val
59
+
60
+ return m
@@ -2,10 +2,11 @@
2
2
 
3
3
  from __future__ import annotations
4
4
  from dub.models.components import linkschema as components_linkschema
5
- from dub.types import BaseModel
5
+ from dub.types import BaseModel, UNSET_SENTINEL
6
6
  from dub.utils import FieldMetadata, QueryParamMetadata
7
7
  from enum import Enum
8
8
  import pydantic
9
+ from pydantic import model_serializer
9
10
  from typing import Callable, List, Optional, Union
10
11
  from typing_extensions import (
11
12
  Annotated,
@@ -199,6 +200,40 @@ class GetLinksRequest(BaseModel):
199
200
  ] = 100
200
201
  r"""The number of items per page."""
201
202
 
203
+ @model_serializer(mode="wrap")
204
+ def serialize_model(self, handler):
205
+ optional_fields = set(
206
+ [
207
+ "domain",
208
+ "tagId",
209
+ "tagIds",
210
+ "tagNames",
211
+ "folderId",
212
+ "search",
213
+ "userId",
214
+ "tenantId",
215
+ "showArchived",
216
+ "withTags",
217
+ "sortBy",
218
+ "sortOrder",
219
+ "sort",
220
+ "page",
221
+ "pageSize",
222
+ ]
223
+ )
224
+ serialized = handler(self)
225
+ m = {}
226
+
227
+ for n, f in type(self).model_fields.items():
228
+ k = f.alias or n
229
+ val = serialized.get(k)
230
+
231
+ if val != UNSET_SENTINEL:
232
+ if val is not None or k not in optional_fields:
233
+ m[k] = val
234
+
235
+ return m
236
+
202
237
 
203
238
  class GetLinksResponseTypedDict(TypedDict):
204
239
  result: List[components_linkschema.LinkSchemaTypedDict]
@@ -1,10 +1,11 @@
1
1
  """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
2
2
 
3
3
  from __future__ import annotations
4
- from dub.types import BaseModel
4
+ from dub.types import BaseModel, UNSET_SENTINEL
5
5
  from dub.utils import FieldMetadata, QueryParamMetadata
6
6
  from enum import Enum
7
7
  import pydantic
8
+ from pydantic import model_serializer
8
9
  from typing import List, Optional, Union
9
10
  from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict
10
11
 
@@ -157,3 +158,33 @@ class GetLinksCountRequest(BaseModel):
157
158
  FieldMetadata(query=QueryParamMetadata(style="form", explode=True)),
158
159
  ] = None
159
160
  r"""The field to group the links by."""
161
+
162
+ @model_serializer(mode="wrap")
163
+ def serialize_model(self, handler):
164
+ optional_fields = set(
165
+ [
166
+ "domain",
167
+ "tagId",
168
+ "tagIds",
169
+ "tagNames",
170
+ "folderId",
171
+ "search",
172
+ "userId",
173
+ "tenantId",
174
+ "showArchived",
175
+ "withTags",
176
+ "groupBy",
177
+ ]
178
+ )
179
+ serialized = handler(self)
180
+ m = {}
181
+
182
+ for n, f in type(self).model_fields.items():
183
+ k = f.alias or n
184
+ val = serialized.get(k)
185
+
186
+ if val != UNSET_SENTINEL:
187
+ if val is not None or k not in optional_fields:
188
+ m[k] = val
189
+
190
+ return m
@@ -1,10 +1,11 @@
1
1
  """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
2
2
 
3
3
  from __future__ import annotations
4
- from dub.types import BaseModel
4
+ from dub.types import BaseModel, UNSET_SENTINEL
5
5
  from dub.utils import FieldMetadata, QueryParamMetadata
6
6
  from enum import Enum
7
7
  import pydantic
8
+ from pydantic import model_serializer
8
9
  from typing import Optional
9
10
  from typing_extensions import Annotated, NotRequired, TypedDict
10
11
 
@@ -96,3 +97,30 @@ class GetQRCodeRequest(BaseModel):
96
97
  FieldMetadata(query=QueryParamMetadata(style="form", explode=True)),
97
98
  ] = True
98
99
  r"""DEPRECATED: Margin is included by default. Use the `margin` prop to customize the margin size."""
100
+
101
+ @model_serializer(mode="wrap")
102
+ def serialize_model(self, handler):
103
+ optional_fields = set(
104
+ [
105
+ "logo",
106
+ "size",
107
+ "level",
108
+ "fgColor",
109
+ "bgColor",
110
+ "hideLogo",
111
+ "margin",
112
+ "includeMargin",
113
+ ]
114
+ )
115
+ serialized = handler(self)
116
+ m = {}
117
+
118
+ for n, f in type(self).model_fields.items():
119
+ k = f.alias or n
120
+ val = serialized.get(k)
121
+
122
+ if val != UNSET_SENTINEL:
123
+ if val is not None or k not in optional_fields:
124
+ m[k] = val
125
+
126
+ return m
@@ -1,10 +1,11 @@
1
1
  """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
2
2
 
3
3
  from __future__ import annotations
4
- from dub.types import BaseModel
4
+ from dub.types import BaseModel, UNSET_SENTINEL
5
5
  from dub.utils import FieldMetadata, QueryParamMetadata
6
6
  from enum import Enum
7
7
  import pydantic
8
+ from pydantic import model_serializer
8
9
  from typing import List, Optional, Union
9
10
  from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict
10
11
 
@@ -85,3 +86,21 @@ class GetTagsRequest(BaseModel):
85
86
  FieldMetadata(query=QueryParamMetadata(style="form", explode=True)),
86
87
  ] = 100
87
88
  r"""The number of items per page."""
89
+
90
+ @model_serializer(mode="wrap")
91
+ def serialize_model(self, handler):
92
+ optional_fields = set(
93
+ ["sortBy", "sortOrder", "search", "ids", "page", "pageSize"]
94
+ )
95
+ serialized = handler(self)
96
+ m = {}
97
+
98
+ for n, f in type(self).model_fields.items():
99
+ k = f.alias or n
100
+ val = serialized.get(k)
101
+
102
+ if val != UNSET_SENTINEL:
103
+ if val is not None or k not in optional_fields:
104
+ m[k] = val
105
+
106
+ return m
@@ -35,6 +35,7 @@ class ListBountySubmissionsQueryParamSortOrder(str, Enum):
35
35
 
36
36
  class ListBountySubmissionsRequestTypedDict(TypedDict):
37
37
  bounty_id: str
38
+ r"""The ID of the bounty"""
38
39
  status: NotRequired[ListBountySubmissionsQueryParamStatus]
39
40
  r"""The status of the submissions to list."""
40
41
  group_id: NotRequired[str]
@@ -57,6 +58,7 @@ class ListBountySubmissionsRequest(BaseModel):
57
58
  pydantic.Field(alias="bountyId"),
58
59
  FieldMetadata(path=PathParamMetadata(style="simple", explode=False)),
59
60
  ]
61
+ r"""The ID of the bounty"""
60
62
 
61
63
  status: Annotated[
62
64
  Optional[ListBountySubmissionsQueryParamStatus],
@@ -105,22 +107,56 @@ class ListBountySubmissionsRequest(BaseModel):
105
107
  ] = 100
106
108
  r"""The number of items per page."""
107
109
 
110
+ @model_serializer(mode="wrap")
111
+ def serialize_model(self, handler):
112
+ optional_fields = set(
113
+ [
114
+ "status",
115
+ "groupId",
116
+ "partnerId",
117
+ "sortBy",
118
+ "sortOrder",
119
+ "page",
120
+ "pageSize",
121
+ ]
122
+ )
123
+ serialized = handler(self)
124
+ m = {}
125
+
126
+ for n, f in type(self).model_fields.items():
127
+ k = f.alias or n
128
+ val = serialized.get(k)
129
+
130
+ if val != UNSET_SENTINEL:
131
+ if val is not None or k not in optional_fields:
132
+ m[k] = val
133
+
134
+ return m
135
+
108
136
 
109
137
  class FilesTypedDict(TypedDict):
110
138
  url: str
139
+ r"""The URL of the uploaded file."""
111
140
  file_name: str
141
+ r"""The original file name."""
112
142
  size: float
143
+ r"""The file size in bytes."""
113
144
 
114
145
 
115
146
  class Files(BaseModel):
116
147
  url: str
148
+ r"""The URL of the uploaded file."""
117
149
 
118
150
  file_name: Annotated[str, pydantic.Field(alias="fileName")]
151
+ r"""The original file name."""
119
152
 
120
153
  size: float
154
+ r"""The file size in bytes."""
121
155
 
122
156
 
123
157
  class ListBountySubmissionsStatus(str, Enum):
158
+ r"""The status of the submission"""
159
+
124
160
  DRAFT = "draft"
125
161
  SUBMITTED = "submitted"
126
162
  APPROVED = "approved"
@@ -129,84 +165,85 @@ class ListBountySubmissionsStatus(str, Enum):
129
165
 
130
166
  class ListBountySubmissionsResponseBodyTypedDict(TypedDict):
131
167
  id: str
168
+ r"""The ID of the bounty submission"""
132
169
  bounty_id: str
170
+ r"""The ID of the bounty"""
133
171
  partner_id: str
172
+ r"""The ID of the partner"""
134
173
  description: Nullable[str]
174
+ r"""The description of the submission"""
135
175
  urls: Nullable[List[str]]
176
+ r"""The URLs submitted for the submission"""
136
177
  files: Nullable[List[FilesTypedDict]]
178
+ r"""The files uploaded for the submission"""
137
179
  status: ListBountySubmissionsStatus
180
+ r"""The status of the submission"""
138
181
  performance_count: Nullable[float]
182
+ r"""The performance count of the submission"""
139
183
  created_at: str
184
+ r"""The date and time the submission was created"""
140
185
  completed_at: Nullable[str]
186
+ r"""The date and time the submission was completed"""
141
187
  reviewed_at: Nullable[str]
188
+ r"""The date and time the submission was reviewed"""
142
189
  rejection_reason: Nullable[str]
190
+ r"""The reason for rejecting the submission"""
143
191
  rejection_note: Nullable[str]
192
+ r"""The note for rejecting the submission"""
144
193
 
145
194
 
146
195
  class ListBountySubmissionsResponseBody(BaseModel):
147
196
  id: str
197
+ r"""The ID of the bounty submission"""
148
198
 
149
199
  bounty_id: Annotated[str, pydantic.Field(alias="bountyId")]
200
+ r"""The ID of the bounty"""
150
201
 
151
202
  partner_id: Annotated[str, pydantic.Field(alias="partnerId")]
203
+ r"""The ID of the partner"""
152
204
 
153
205
  description: Nullable[str]
206
+ r"""The description of the submission"""
154
207
 
155
208
  urls: Nullable[List[str]]
209
+ r"""The URLs submitted for the submission"""
156
210
 
157
211
  files: Nullable[List[Files]]
212
+ r"""The files uploaded for the submission"""
158
213
 
159
214
  status: ListBountySubmissionsStatus
215
+ r"""The status of the submission"""
160
216
 
161
217
  performance_count: Annotated[
162
218
  Nullable[float], pydantic.Field(alias="performanceCount")
163
219
  ]
220
+ r"""The performance count of the submission"""
164
221
 
165
222
  created_at: Annotated[str, pydantic.Field(alias="createdAt")]
223
+ r"""The date and time the submission was created"""
166
224
 
167
225
  completed_at: Annotated[Nullable[str], pydantic.Field(alias="completedAt")]
226
+ r"""The date and time the submission was completed"""
168
227
 
169
228
  reviewed_at: Annotated[Nullable[str], pydantic.Field(alias="reviewedAt")]
229
+ r"""The date and time the submission was reviewed"""
170
230
 
171
231
  rejection_reason: Annotated[Nullable[str], pydantic.Field(alias="rejectionReason")]
232
+ r"""The reason for rejecting the submission"""
172
233
 
173
234
  rejection_note: Annotated[Nullable[str], pydantic.Field(alias="rejectionNote")]
235
+ r"""The note for rejecting the submission"""
174
236
 
175
237
  @model_serializer(mode="wrap")
176
238
  def serialize_model(self, handler):
177
- optional_fields = []
178
- nullable_fields = [
179
- "description",
180
- "urls",
181
- "files",
182
- "performanceCount",
183
- "completedAt",
184
- "reviewedAt",
185
- "rejectionReason",
186
- "rejectionNote",
187
- ]
188
- null_default_fields = []
189
-
190
239
  serialized = handler(self)
191
-
192
240
  m = {}
193
241
 
194
242
  for n, f in type(self).model_fields.items():
195
243
  k = f.alias or n
196
244
  val = serialized.get(k)
197
- serialized.pop(k, None)
198
245
 
199
- optional_nullable = k in optional_fields and k in nullable_fields
200
- is_set = (
201
- self.__pydantic_fields_set__.intersection({n})
202
- or k in null_default_fields
203
- ) # pylint: disable=no-member
204
-
205
- if val is not None and val != UNSET_SENTINEL:
206
- m[k] = val
207
- elif val != UNSET_SENTINEL and (
208
- not k in optional_fields or (optional_nullable and is_set)
209
- ):
246
+ if val != UNSET_SENTINEL:
210
247
  m[k] = val
211
248
 
212
249
  return m
@@ -194,6 +194,41 @@ class ListCommissionsRequest(BaseModel):
194
194
  ] = 100
195
195
  r"""The number of items per page."""
196
196
 
197
+ @model_serializer(mode="wrap")
198
+ def serialize_model(self, handler):
199
+ optional_fields = set(
200
+ [
201
+ "type",
202
+ "customerId",
203
+ "payoutId",
204
+ "partnerId",
205
+ "tenantId",
206
+ "groupId",
207
+ "invoiceId",
208
+ "status",
209
+ "sortBy",
210
+ "sortOrder",
211
+ "interval",
212
+ "start",
213
+ "end",
214
+ "timezone",
215
+ "page",
216
+ "pageSize",
217
+ ]
218
+ )
219
+ serialized = handler(self)
220
+ m = {}
221
+
222
+ for n, f in type(self).model_fields.items():
223
+ k = f.alias or n
224
+ val = serialized.get(k)
225
+
226
+ if val != UNSET_SENTINEL:
227
+ if val is not None or k not in optional_fields:
228
+ m[k] = val
229
+
230
+ return m
231
+
197
232
 
198
233
  class ListCommissionsType(str, Enum):
199
234
  CLICK = "click"
@@ -255,31 +290,28 @@ class ListCommissionsPartner(BaseModel):
255
290
 
256
291
  @model_serializer(mode="wrap")
257
292
  def serialize_model(self, handler):
258
- optional_fields = ["groupId"]
259
- nullable_fields = ["email", "image", "payoutsEnabledAt", "country", "groupId"]
260
- null_default_fields = []
261
-
293
+ optional_fields = set(["groupId"])
294
+ nullable_fields = set(
295
+ ["email", "image", "payoutsEnabledAt", "country", "groupId"]
296
+ )
262
297
  serialized = handler(self)
263
-
264
298
  m = {}
265
299
 
266
300
  for n, f in type(self).model_fields.items():
267
301
  k = f.alias or n
268
302
  val = serialized.get(k)
269
- serialized.pop(k, None)
270
-
271
- optional_nullable = k in optional_fields and k in nullable_fields
272
- is_set = (
273
- self.__pydantic_fields_set__.intersection({n})
274
- or k in null_default_fields
275
- ) # pylint: disable=no-member
276
-
277
- if val is not None and val != UNSET_SENTINEL:
278
- m[k] = val
279
- elif val != UNSET_SENTINEL and (
280
- not k in optional_fields or (optional_nullable and is_set)
281
- ):
282
- m[k] = val
303
+ is_nullable_and_explicitly_set = (
304
+ k in nullable_fields
305
+ and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
306
+ )
307
+
308
+ if val != UNSET_SENTINEL:
309
+ if (
310
+ val is not None
311
+ or k not in optional_fields
312
+ or is_nullable_and_explicitly_set
313
+ ):
314
+ m[k] = val
283
315
 
284
316
  return m
285
317
 
@@ -287,36 +319,42 @@ class ListCommissionsPartner(BaseModel):
287
319
  class ListCommissionsCustomerTypedDict(TypedDict):
288
320
  id: str
289
321
  r"""The unique ID of the customer. You may use either the customer's `id` on Dub (obtained via `/customers` endpoint) or their `externalId` (unique ID within your system, prefixed with `ext_`, e.g. `ext_123`)."""
290
- external_id: str
291
- r"""Unique identifier for the customer in the client's app."""
292
322
  name: str
293
323
  r"""Name of the customer."""
324
+ external_id: str
325
+ r"""Unique identifier for the customer in the client's app."""
294
326
  created_at: str
295
- r"""The date the customer was created."""
327
+ r"""The date the customer was created (usually the signup date or trial start date)."""
296
328
  email: NotRequired[Nullable[str]]
297
329
  r"""Email of the customer."""
298
330
  avatar: NotRequired[Nullable[str]]
299
331
  r"""Avatar URL of the customer."""
332
+ stripe_customer_id: NotRequired[Nullable[str]]
333
+ r"""The customer's Stripe customer ID. This is useful for attributing recurring sale events to the partner who referred the customer."""
300
334
  country: NotRequired[Nullable[str]]
301
335
  r"""Country of the customer."""
302
336
  sales: NotRequired[Nullable[float]]
303
337
  r"""Total number of sales for the customer."""
304
338
  sale_amount: NotRequired[Nullable[float]]
305
339
  r"""Total amount of sales for the customer."""
340
+ first_sale_at: NotRequired[Nullable[str]]
341
+ r"""The date the customer made their first sale. Useful for calculating the time to first sale and LTV."""
342
+ subscription_canceled_at: NotRequired[Nullable[str]]
343
+ r"""The date the customer canceled their subscription. Useful for calculating LTV and churn rate."""
306
344
 
307
345
 
308
346
  class ListCommissionsCustomer(BaseModel):
309
347
  id: str
310
348
  r"""The unique ID of the customer. You may use either the customer's `id` on Dub (obtained via `/customers` endpoint) or their `externalId` (unique ID within your system, prefixed with `ext_`, e.g. `ext_123`)."""
311
349
 
312
- external_id: Annotated[str, pydantic.Field(alias="externalId")]
313
- r"""Unique identifier for the customer in the client's app."""
314
-
315
350
  name: str
316
351
  r"""Name of the customer."""
317
352
 
353
+ external_id: Annotated[str, pydantic.Field(alias="externalId")]
354
+ r"""Unique identifier for the customer in the client's app."""
355
+
318
356
  created_at: Annotated[str, pydantic.Field(alias="createdAt")]
319
- r"""The date the customer was created."""
357
+ r"""The date the customer was created (usually the signup date or trial start date)."""
320
358
 
321
359
  email: OptionalNullable[str] = UNSET
322
360
  r"""Email of the customer."""
@@ -324,6 +362,11 @@ class ListCommissionsCustomer(BaseModel):
324
362
  avatar: OptionalNullable[str] = UNSET
325
363
  r"""Avatar URL of the customer."""
326
364
 
365
+ stripe_customer_id: Annotated[
366
+ OptionalNullable[str], pydantic.Field(alias="stripeCustomerId")
367
+ ] = UNSET
368
+ r"""The customer's Stripe customer ID. This is useful for attributing recurring sale events to the partner who referred the customer."""
369
+
327
370
  country: OptionalNullable[str] = UNSET
328
371
  r"""Country of the customer."""
329
372
 
@@ -335,33 +378,60 @@ class ListCommissionsCustomer(BaseModel):
335
378
  ] = UNSET
336
379
  r"""Total amount of sales for the customer."""
337
380
 
381
+ first_sale_at: Annotated[
382
+ OptionalNullable[str], pydantic.Field(alias="firstSaleAt")
383
+ ] = UNSET
384
+ r"""The date the customer made their first sale. Useful for calculating the time to first sale and LTV."""
385
+
386
+ subscription_canceled_at: Annotated[
387
+ OptionalNullable[str], pydantic.Field(alias="subscriptionCanceledAt")
388
+ ] = UNSET
389
+ r"""The date the customer canceled their subscription. Useful for calculating LTV and churn rate."""
390
+
338
391
  @model_serializer(mode="wrap")
339
392
  def serialize_model(self, handler):
340
- optional_fields = ["email", "avatar", "country", "sales", "saleAmount"]
341
- nullable_fields = ["email", "avatar", "country", "sales", "saleAmount"]
342
- null_default_fields = []
343
-
393
+ optional_fields = set(
394
+ [
395
+ "email",
396
+ "avatar",
397
+ "stripeCustomerId",
398
+ "country",
399
+ "sales",
400
+ "saleAmount",
401
+ "firstSaleAt",
402
+ "subscriptionCanceledAt",
403
+ ]
404
+ )
405
+ nullable_fields = set(
406
+ [
407
+ "email",
408
+ "avatar",
409
+ "stripeCustomerId",
410
+ "country",
411
+ "sales",
412
+ "saleAmount",
413
+ "firstSaleAt",
414
+ "subscriptionCanceledAt",
415
+ ]
416
+ )
344
417
  serialized = handler(self)
345
-
346
418
  m = {}
347
419
 
348
420
  for n, f in type(self).model_fields.items():
349
421
  k = f.alias or n
350
422
  val = serialized.get(k)
351
- serialized.pop(k, None)
352
-
353
- optional_nullable = k in optional_fields and k in nullable_fields
354
- is_set = (
355
- self.__pydantic_fields_set__.intersection({n})
356
- or k in null_default_fields
357
- ) # pylint: disable=no-member
358
-
359
- if val is not None and val != UNSET_SENTINEL:
360
- m[k] = val
361
- elif val != UNSET_SENTINEL and (
362
- not k in optional_fields or (optional_nullable and is_set)
363
- ):
364
- m[k] = val
423
+ is_nullable_and_explicitly_set = (
424
+ k in nullable_fields
425
+ and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
426
+ )
427
+
428
+ if val != UNSET_SENTINEL:
429
+ if (
430
+ val is not None
431
+ or k not in optional_fields
432
+ or is_nullable_and_explicitly_set
433
+ ):
434
+ m[k] = val
365
435
 
366
436
  return m
367
437
 
@@ -418,30 +488,25 @@ class ListCommissionsResponseBody(BaseModel):
418
488
 
419
489
  @model_serializer(mode="wrap")
420
490
  def serialize_model(self, handler):
421
- optional_fields = ["type", "userId", "customer"]
422
- nullable_fields = ["invoiceId", "description", "userId", "customer"]
423
- null_default_fields = []
424
-
491
+ optional_fields = set(["type", "userId", "customer"])
492
+ nullable_fields = set(["invoiceId", "description", "userId", "customer"])
425
493
  serialized = handler(self)
426
-
427
494
  m = {}
428
495
 
429
496
  for n, f in type(self).model_fields.items():
430
497
  k = f.alias or n
431
498
  val = serialized.get(k)
432
- serialized.pop(k, None)
433
-
434
- optional_nullable = k in optional_fields and k in nullable_fields
435
- is_set = (
436
- self.__pydantic_fields_set__.intersection({n})
437
- or k in null_default_fields
438
- ) # pylint: disable=no-member
439
-
440
- if val is not None and val != UNSET_SENTINEL:
441
- m[k] = val
442
- elif val != UNSET_SENTINEL and (
443
- not k in optional_fields or (optional_nullable and is_set)
444
- ):
445
- m[k] = val
499
+ is_nullable_and_explicitly_set = (
500
+ k in nullable_fields
501
+ and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member
502
+ )
503
+
504
+ if val != UNSET_SENTINEL:
505
+ if (
506
+ val is not None
507
+ or k not in optional_fields
508
+ or is_nullable_and_explicitly_set
509
+ ):
510
+ m[k] = val
446
511
 
447
512
  return m