dub 0.28.1__py3-none-any.whl → 0.29.1__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/basesdk.py +11 -1
- dub/models/components/__init__.py +14 -1
- dub/models/components/commissioncreatedevent.py +18 -0
- dub/models/components/domainschema.py +1 -1
- dub/models/errors/__init__.py +15 -3
- dub/models/operations/__init__.py +14 -1
- dub/models/operations/tracklead.py +10 -9
- dub/models/operations/tracksale.py +51 -7
- dub/sdk.py +15 -2
- dub/utils/__init__.py +15 -3
- dub/utils/eventstreaming.py +10 -0
- {dub-0.28.1.dist-info → dub-0.29.1.dist-info}/METADATA +1 -1
- {dub-0.28.1.dist-info → dub-0.29.1.dist-info}/RECORD +16 -16
- {dub-0.28.1.dist-info → dub-0.29.1.dist-info}/LICENSE +0 -0
- {dub-0.28.1.dist-info → dub-0.29.1.dist-info}/WHEEL +0 -0
dub/_version.py
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import importlib.metadata
|
|
4
4
|
|
|
5
5
|
__title__: str = "dub"
|
|
6
|
-
__version__: str = "0.
|
|
6
|
+
__version__: str = "0.29.1"
|
|
7
7
|
__openapi_doc_version__: str = "0.0.1"
|
|
8
|
-
__gen_version__: str = "2.
|
|
9
|
-
__user_agent__: str = "speakeasy-sdk/python 0.
|
|
8
|
+
__gen_version__: str = "2.698.0"
|
|
9
|
+
__user_agent__: str = "speakeasy-sdk/python 0.29.1 2.698.0 0.0.1 dub"
|
|
10
10
|
|
|
11
11
|
try:
|
|
12
12
|
if __package__ is not None:
|
dub/basesdk.py
CHANGED
|
@@ -12,9 +12,19 @@ from urllib.parse import parse_qs, urlparse
|
|
|
12
12
|
|
|
13
13
|
class BaseSDK:
|
|
14
14
|
sdk_configuration: SDKConfiguration
|
|
15
|
+
parent_ref: Optional[object] = None
|
|
16
|
+
"""
|
|
17
|
+
Reference to the root SDK instance, if any. This will prevent it from
|
|
18
|
+
being garbage collected while there are active streams.
|
|
19
|
+
"""
|
|
15
20
|
|
|
16
|
-
def __init__(
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
sdk_config: SDKConfiguration,
|
|
24
|
+
parent_ref: Optional[object] = None,
|
|
25
|
+
) -> None:
|
|
17
26
|
self.sdk_configuration = sdk_config
|
|
27
|
+
self.parent_ref = parent_ref
|
|
18
28
|
|
|
19
29
|
def _get_url(self, base_url, url_variables):
|
|
20
30
|
sdk_url, sdk_variables = self.sdk_configuration.get_server_details()
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
from importlib import import_module
|
|
5
5
|
import builtins
|
|
6
|
+
import sys
|
|
6
7
|
|
|
7
8
|
if TYPE_CHECKING:
|
|
8
9
|
from .analyticsbrowsers import AnalyticsBrowsers, AnalyticsBrowsersTypedDict
|
|
@@ -557,6 +558,18 @@ _dynamic_imports: dict[str, str] = {
|
|
|
557
558
|
}
|
|
558
559
|
|
|
559
560
|
|
|
561
|
+
def dynamic_import(modname, retries=3):
|
|
562
|
+
for attempt in range(retries):
|
|
563
|
+
try:
|
|
564
|
+
return import_module(modname, __package__)
|
|
565
|
+
except KeyError:
|
|
566
|
+
# Clear any half-initialized module and retry
|
|
567
|
+
sys.modules.pop(modname, None)
|
|
568
|
+
if attempt == retries - 1:
|
|
569
|
+
break
|
|
570
|
+
raise KeyError(f"Failed to import module '{modname}' after {retries} attempts")
|
|
571
|
+
|
|
572
|
+
|
|
560
573
|
def __getattr__(attr_name: str) -> object:
|
|
561
574
|
module_name = _dynamic_imports.get(attr_name)
|
|
562
575
|
if module_name is None:
|
|
@@ -565,7 +578,7 @@ def __getattr__(attr_name: str) -> object:
|
|
|
565
578
|
)
|
|
566
579
|
|
|
567
580
|
try:
|
|
568
|
-
module =
|
|
581
|
+
module = dynamic_import(module_name)
|
|
569
582
|
result = getattr(module, attr_name)
|
|
570
583
|
return result
|
|
571
584
|
except ImportError as e:
|
|
@@ -43,6 +43,12 @@ class PartnerTypedDict(TypedDict):
|
|
|
43
43
|
r"""The date when the partner enabled payouts."""
|
|
44
44
|
country: Nullable[str]
|
|
45
45
|
r"""The partner's country (required for tax purposes)."""
|
|
46
|
+
total_clicks: float
|
|
47
|
+
total_leads: float
|
|
48
|
+
total_conversions: float
|
|
49
|
+
total_sales: float
|
|
50
|
+
total_sale_amount: float
|
|
51
|
+
total_commissions: float
|
|
46
52
|
|
|
47
53
|
|
|
48
54
|
class Partner(BaseModel):
|
|
@@ -66,6 +72,18 @@ class Partner(BaseModel):
|
|
|
66
72
|
country: Nullable[str]
|
|
67
73
|
r"""The partner's country (required for tax purposes)."""
|
|
68
74
|
|
|
75
|
+
total_clicks: Annotated[float, pydantic.Field(alias="totalClicks")]
|
|
76
|
+
|
|
77
|
+
total_leads: Annotated[float, pydantic.Field(alias="totalLeads")]
|
|
78
|
+
|
|
79
|
+
total_conversions: Annotated[float, pydantic.Field(alias="totalConversions")]
|
|
80
|
+
|
|
81
|
+
total_sales: Annotated[float, pydantic.Field(alias="totalSales")]
|
|
82
|
+
|
|
83
|
+
total_sale_amount: Annotated[float, pydantic.Field(alias="totalSaleAmount")]
|
|
84
|
+
|
|
85
|
+
total_commissions: Annotated[float, pydantic.Field(alias="totalCommissions")]
|
|
86
|
+
|
|
69
87
|
@model_serializer(mode="wrap")
|
|
70
88
|
def serialize_model(self, handler):
|
|
71
89
|
optional_fields = []
|
|
@@ -167,9 +167,9 @@ class DomainSchema(BaseModel):
|
|
|
167
167
|
"placeholder",
|
|
168
168
|
"expiredUrl",
|
|
169
169
|
"notFoundUrl",
|
|
170
|
+
"logo",
|
|
170
171
|
"assetLinks",
|
|
171
172
|
"appleAppSiteAssociation",
|
|
172
|
-
"logo",
|
|
173
173
|
"registeredDomain",
|
|
174
174
|
]
|
|
175
175
|
null_default_fields = ["assetLinks", "appleAppSiteAssociation"]
|
dub/models/errors/__init__.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
|
|
2
2
|
|
|
3
|
+
from .duberror import DubError
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
from importlib import import_module
|
|
5
6
|
import builtins
|
|
7
|
+
import sys
|
|
6
8
|
|
|
7
9
|
if TYPE_CHECKING:
|
|
8
10
|
from .badrequest import BadRequest, BadRequestData, Code, Error, ErrorTypedDict
|
|
@@ -13,7 +15,6 @@ if TYPE_CHECKING:
|
|
|
13
15
|
ConflictError,
|
|
14
16
|
ConflictErrorTypedDict,
|
|
15
17
|
)
|
|
16
|
-
from .duberror import DubError
|
|
17
18
|
from .forbidden import (
|
|
18
19
|
Forbidden,
|
|
19
20
|
ForbiddenCode,
|
|
@@ -130,7 +131,6 @@ _dynamic_imports: dict[str, str] = {
|
|
|
130
131
|
"ConflictData": ".conflict",
|
|
131
132
|
"ConflictError": ".conflict",
|
|
132
133
|
"ConflictErrorTypedDict": ".conflict",
|
|
133
|
-
"DubError": ".duberror",
|
|
134
134
|
"Forbidden": ".forbidden",
|
|
135
135
|
"ForbiddenCode": ".forbidden",
|
|
136
136
|
"ForbiddenData": ".forbidden",
|
|
@@ -172,6 +172,18 @@ _dynamic_imports: dict[str, str] = {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
|
|
175
|
+
def dynamic_import(modname, retries=3):
|
|
176
|
+
for attempt in range(retries):
|
|
177
|
+
try:
|
|
178
|
+
return import_module(modname, __package__)
|
|
179
|
+
except KeyError:
|
|
180
|
+
# Clear any half-initialized module and retry
|
|
181
|
+
sys.modules.pop(modname, None)
|
|
182
|
+
if attempt == retries - 1:
|
|
183
|
+
break
|
|
184
|
+
raise KeyError(f"Failed to import module '{modname}' after {retries} attempts")
|
|
185
|
+
|
|
186
|
+
|
|
175
187
|
def __getattr__(attr_name: str) -> object:
|
|
176
188
|
module_name = _dynamic_imports.get(attr_name)
|
|
177
189
|
if module_name is None:
|
|
@@ -180,7 +192,7 @@ def __getattr__(attr_name: str) -> object:
|
|
|
180
192
|
)
|
|
181
193
|
|
|
182
194
|
try:
|
|
183
|
-
module =
|
|
195
|
+
module = dynamic_import(module_name)
|
|
184
196
|
result = getattr(module, attr_name)
|
|
185
197
|
return result
|
|
186
198
|
except ImportError as e:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
from importlib import import_module
|
|
5
5
|
import builtins
|
|
6
|
+
import sys
|
|
6
7
|
|
|
7
8
|
if TYPE_CHECKING:
|
|
8
9
|
from .bulkcreatelinks import (
|
|
@@ -1094,6 +1095,18 @@ _dynamic_imports: dict[str, str] = {
|
|
|
1094
1095
|
}
|
|
1095
1096
|
|
|
1096
1097
|
|
|
1098
|
+
def dynamic_import(modname, retries=3):
|
|
1099
|
+
for attempt in range(retries):
|
|
1100
|
+
try:
|
|
1101
|
+
return import_module(modname, __package__)
|
|
1102
|
+
except KeyError:
|
|
1103
|
+
# Clear any half-initialized module and retry
|
|
1104
|
+
sys.modules.pop(modname, None)
|
|
1105
|
+
if attempt == retries - 1:
|
|
1106
|
+
break
|
|
1107
|
+
raise KeyError(f"Failed to import module '{modname}' after {retries} attempts")
|
|
1108
|
+
|
|
1109
|
+
|
|
1097
1110
|
def __getattr__(attr_name: str) -> object:
|
|
1098
1111
|
module_name = _dynamic_imports.get(attr_name)
|
|
1099
1112
|
if module_name is None:
|
|
@@ -1102,7 +1115,7 @@ def __getattr__(attr_name: str) -> object:
|
|
|
1102
1115
|
)
|
|
1103
1116
|
|
|
1104
1117
|
try:
|
|
1105
|
-
module =
|
|
1118
|
+
module = dynamic_import(module_name)
|
|
1106
1119
|
result = getattr(module, attr_name)
|
|
1107
1120
|
return result
|
|
1108
1121
|
except ImportError as e:
|
|
@@ -10,15 +10,16 @@ from typing_extensions import Annotated, NotRequired, TypedDict
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class Mode(str, Enum):
|
|
13
|
-
r"""The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub."""
|
|
13
|
+
r"""The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub; `deferred` will defer the lead event creation to a subsequent request."""
|
|
14
14
|
|
|
15
15
|
ASYNC = "async"
|
|
16
16
|
WAIT = "wait"
|
|
17
|
+
DEFERRED = "deferred"
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class TrackLeadRequestBodyTypedDict(TypedDict):
|
|
20
21
|
click_id: str
|
|
21
|
-
r"""The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie."""
|
|
22
|
+
r"""The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie. If an empty string is provided, Dub will try to find an existing customer with the provided `customerExternalId` and use the `clickId` from the customer if found."""
|
|
22
23
|
event_name: str
|
|
23
24
|
r"""The name of the lead event to track. Can also be used as a unique identifier to associate a given lead event for a customer for a subsequent sale event (via the `leadEventName` prop in `/track/sale`)."""
|
|
24
25
|
customer_external_id: str
|
|
@@ -29,17 +30,17 @@ class TrackLeadRequestBodyTypedDict(TypedDict):
|
|
|
29
30
|
r"""The email address of the customer."""
|
|
30
31
|
customer_avatar: NotRequired[Nullable[str]]
|
|
31
32
|
r"""The avatar URL of the customer."""
|
|
33
|
+
mode: NotRequired[Mode]
|
|
34
|
+
r"""The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub; `deferred` will defer the lead event creation to a subsequent request."""
|
|
32
35
|
event_quantity: NotRequired[Nullable[float]]
|
|
33
36
|
r"""The numerical value associated with this lead event (e.g., number of provisioned seats in a free trial). If defined as N, the lead event will be tracked N times."""
|
|
34
|
-
mode: NotRequired[Mode]
|
|
35
|
-
r"""The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub."""
|
|
36
37
|
metadata: NotRequired[Nullable[Dict[str, Any]]]
|
|
37
38
|
r"""Additional metadata to be stored with the lead event. Max 10,000 characters."""
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
class TrackLeadRequestBody(BaseModel):
|
|
41
42
|
click_id: Annotated[str, pydantic.Field(alias="clickId")]
|
|
42
|
-
r"""The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie."""
|
|
43
|
+
r"""The unique ID of the click that the lead conversion event is attributed to. You can read this value from `dub_id` cookie. If an empty string is provided, Dub will try to find an existing customer with the provided `customerExternalId` and use the `clickId` from the customer if found."""
|
|
43
44
|
|
|
44
45
|
event_name: Annotated[str, pydantic.Field(alias="eventName")]
|
|
45
46
|
r"""The name of the lead event to track. Can also be used as a unique identifier to associate a given lead event for a customer for a subsequent sale event (via the `leadEventName` prop in `/track/sale`)."""
|
|
@@ -62,14 +63,14 @@ class TrackLeadRequestBody(BaseModel):
|
|
|
62
63
|
] = None
|
|
63
64
|
r"""The avatar URL of the customer."""
|
|
64
65
|
|
|
66
|
+
mode: Optional[Mode] = Mode.ASYNC
|
|
67
|
+
r"""The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub; `deferred` will defer the lead event creation to a subsequent request."""
|
|
68
|
+
|
|
65
69
|
event_quantity: Annotated[
|
|
66
70
|
OptionalNullable[float], pydantic.Field(alias="eventQuantity")
|
|
67
71
|
] = UNSET
|
|
68
72
|
r"""The numerical value associated with this lead event (e.g., number of provisioned seats in a free trial). If defined as N, the lead event will be tracked N times."""
|
|
69
73
|
|
|
70
|
-
mode: Optional[Mode] = Mode.ASYNC
|
|
71
|
-
r"""The mode to use for tracking the lead event. `async` will not block the request; `wait` will block the request until the lead event is fully recorded in Dub."""
|
|
72
|
-
|
|
73
74
|
metadata: OptionalNullable[Dict[str, Any]] = UNSET
|
|
74
75
|
r"""Additional metadata to be stored with the lead event. Max 10,000 characters."""
|
|
75
76
|
|
|
@@ -79,8 +80,8 @@ class TrackLeadRequestBody(BaseModel):
|
|
|
79
80
|
"customerName",
|
|
80
81
|
"customerEmail",
|
|
81
82
|
"customerAvatar",
|
|
82
|
-
"eventQuantity",
|
|
83
83
|
"mode",
|
|
84
|
+
"eventQuantity",
|
|
84
85
|
"metadata",
|
|
85
86
|
]
|
|
86
87
|
nullable_fields = [
|
|
@@ -33,10 +33,18 @@ class TrackSaleRequestBodyTypedDict(TypedDict):
|
|
|
33
33
|
r"""The payment processor via which the sale was made."""
|
|
34
34
|
invoice_id: NotRequired[Nullable[str]]
|
|
35
35
|
r"""The invoice ID of the sale. Can be used as a idempotency key – only one sale event can be recorded for a given invoice ID."""
|
|
36
|
-
lead_event_name: NotRequired[Nullable[str]]
|
|
37
|
-
r"""The name of the lead event that occurred before the sale (case-sensitive). This is used to associate the sale event with a particular lead event (instead of the latest lead event for a link-customer combination, which is the default behavior)."""
|
|
38
36
|
metadata: NotRequired[Nullable[Dict[str, Any]]]
|
|
39
37
|
r"""Additional metadata to be stored with the sale event. Max 10,000 characters when stringified."""
|
|
38
|
+
lead_event_name: NotRequired[Nullable[str]]
|
|
39
|
+
r"""The name of the lead event that occurred before the sale (case-sensitive). This is used to associate the sale event with a particular lead event (instead of the latest lead event for a link-customer combination, which is the default behavior). For sale tracking without a pre-existing lead event, this field can also be used to specify the lead event name."""
|
|
40
|
+
click_id: NotRequired[Nullable[str]]
|
|
41
|
+
r"""[For sale tracking without a pre-existing lead event]: The unique ID of the click that the sale conversion event is attributed to. You can read this value from `dub_id` cookie."""
|
|
42
|
+
customer_name: NotRequired[Nullable[str]]
|
|
43
|
+
r"""[For sale tracking without a pre-existing lead event]: The name of the customer. If not passed, a random name will be generated (e.g. “Big Red Caribou”)."""
|
|
44
|
+
customer_email: NotRequired[Nullable[str]]
|
|
45
|
+
r"""[For sale tracking without a pre-existing lead event]: The email address of the customer."""
|
|
46
|
+
customer_avatar: NotRequired[Nullable[str]]
|
|
47
|
+
r"""[For sale tracking without a pre-existing lead event]: The avatar URL of the customer."""
|
|
40
48
|
|
|
41
49
|
|
|
42
50
|
class TrackSaleRequestBody(BaseModel):
|
|
@@ -62,13 +70,31 @@ class TrackSaleRequestBody(BaseModel):
|
|
|
62
70
|
)
|
|
63
71
|
r"""The invoice ID of the sale. Can be used as a idempotency key – only one sale event can be recorded for a given invoice ID."""
|
|
64
72
|
|
|
73
|
+
metadata: OptionalNullable[Dict[str, Any]] = UNSET
|
|
74
|
+
r"""Additional metadata to be stored with the sale event. Max 10,000 characters when stringified."""
|
|
75
|
+
|
|
65
76
|
lead_event_name: Annotated[
|
|
66
77
|
OptionalNullable[str], pydantic.Field(alias="leadEventName")
|
|
67
78
|
] = None
|
|
68
|
-
r"""The name of the lead event that occurred before the sale (case-sensitive). This is used to associate the sale event with a particular lead event (instead of the latest lead event for a link-customer combination, which is the default behavior)."""
|
|
79
|
+
r"""The name of the lead event that occurred before the sale (case-sensitive). This is used to associate the sale event with a particular lead event (instead of the latest lead event for a link-customer combination, which is the default behavior). For sale tracking without a pre-existing lead event, this field can also be used to specify the lead event name."""
|
|
69
80
|
|
|
70
|
-
|
|
71
|
-
r"""
|
|
81
|
+
click_id: Annotated[OptionalNullable[str], pydantic.Field(alias="clickId")] = UNSET
|
|
82
|
+
r"""[For sale tracking without a pre-existing lead event]: The unique ID of the click that the sale conversion event is attributed to. You can read this value from `dub_id` cookie."""
|
|
83
|
+
|
|
84
|
+
customer_name: Annotated[
|
|
85
|
+
OptionalNullable[str], pydantic.Field(alias="customerName")
|
|
86
|
+
] = None
|
|
87
|
+
r"""[For sale tracking without a pre-existing lead event]: The name of the customer. If not passed, a random name will be generated (e.g. “Big Red Caribou”)."""
|
|
88
|
+
|
|
89
|
+
customer_email: Annotated[
|
|
90
|
+
OptionalNullable[str], pydantic.Field(alias="customerEmail")
|
|
91
|
+
] = None
|
|
92
|
+
r"""[For sale tracking without a pre-existing lead event]: The email address of the customer."""
|
|
93
|
+
|
|
94
|
+
customer_avatar: Annotated[
|
|
95
|
+
OptionalNullable[str], pydantic.Field(alias="customerAvatar")
|
|
96
|
+
] = None
|
|
97
|
+
r"""[For sale tracking without a pre-existing lead event]: The avatar URL of the customer."""
|
|
72
98
|
|
|
73
99
|
@model_serializer(mode="wrap")
|
|
74
100
|
def serialize_model(self, handler):
|
|
@@ -77,11 +103,29 @@ class TrackSaleRequestBody(BaseModel):
|
|
|
77
103
|
"eventName",
|
|
78
104
|
"paymentProcessor",
|
|
79
105
|
"invoiceId",
|
|
106
|
+
"metadata",
|
|
80
107
|
"leadEventName",
|
|
108
|
+
"clickId",
|
|
109
|
+
"customerName",
|
|
110
|
+
"customerEmail",
|
|
111
|
+
"customerAvatar",
|
|
112
|
+
]
|
|
113
|
+
nullable_fields = [
|
|
114
|
+
"invoiceId",
|
|
81
115
|
"metadata",
|
|
116
|
+
"leadEventName",
|
|
117
|
+
"clickId",
|
|
118
|
+
"customerName",
|
|
119
|
+
"customerEmail",
|
|
120
|
+
"customerAvatar",
|
|
121
|
+
]
|
|
122
|
+
null_default_fields = [
|
|
123
|
+
"invoiceId",
|
|
124
|
+
"leadEventName",
|
|
125
|
+
"customerName",
|
|
126
|
+
"customerEmail",
|
|
127
|
+
"customerAvatar",
|
|
82
128
|
]
|
|
83
|
-
nullable_fields = ["invoiceId", "leadEventName", "metadata"]
|
|
84
|
-
null_default_fields = ["invoiceId", "leadEventName"]
|
|
85
129
|
|
|
86
130
|
serialized = handler(self)
|
|
87
131
|
|
dub/sdk.py
CHANGED
|
@@ -11,6 +11,7 @@ from dub.models import components
|
|
|
11
11
|
from dub.types import OptionalNullable, UNSET
|
|
12
12
|
import httpx
|
|
13
13
|
import importlib
|
|
14
|
+
import sys
|
|
14
15
|
from typing import Any, Callable, Dict, Optional, TYPE_CHECKING, Union, cast
|
|
15
16
|
import weakref
|
|
16
17
|
|
|
@@ -131,6 +132,7 @@ class Dub(BaseSDK):
|
|
|
131
132
|
timeout_ms=timeout_ms,
|
|
132
133
|
debug_logger=debug_logger,
|
|
133
134
|
),
|
|
135
|
+
parent_ref=self,
|
|
134
136
|
)
|
|
135
137
|
|
|
136
138
|
hooks = SDKHooks()
|
|
@@ -155,13 +157,24 @@ class Dub(BaseSDK):
|
|
|
155
157
|
self.sdk_configuration.async_client_supplied,
|
|
156
158
|
)
|
|
157
159
|
|
|
160
|
+
def dynamic_import(self, modname, retries=3):
|
|
161
|
+
for attempt in range(retries):
|
|
162
|
+
try:
|
|
163
|
+
return importlib.import_module(modname)
|
|
164
|
+
except KeyError:
|
|
165
|
+
# Clear any half-initialized module and retry
|
|
166
|
+
sys.modules.pop(modname, None)
|
|
167
|
+
if attempt == retries - 1:
|
|
168
|
+
break
|
|
169
|
+
raise KeyError(f"Failed to import module '{modname}' after {retries} attempts")
|
|
170
|
+
|
|
158
171
|
def __getattr__(self, name: str):
|
|
159
172
|
if name in self._sub_sdk_map:
|
|
160
173
|
module_path, class_name = self._sub_sdk_map[name]
|
|
161
174
|
try:
|
|
162
|
-
module =
|
|
175
|
+
module = self.dynamic_import(module_path)
|
|
163
176
|
klass = getattr(module, class_name)
|
|
164
|
-
instance = klass(self.sdk_configuration)
|
|
177
|
+
instance = klass(self.sdk_configuration, parent_ref=self)
|
|
165
178
|
setattr(self, name, instance)
|
|
166
179
|
return instance
|
|
167
180
|
except ImportError as e:
|
dub/utils/__init__.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
from importlib import import_module
|
|
5
5
|
import builtins
|
|
6
|
+
import sys
|
|
6
7
|
|
|
7
8
|
if TYPE_CHECKING:
|
|
8
9
|
from .annotations import get_discriminator
|
|
@@ -159,6 +160,18 @@ _dynamic_imports: dict[str, str] = {
|
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
|
|
163
|
+
def dynamic_import(modname, retries=3):
|
|
164
|
+
for attempt in range(retries):
|
|
165
|
+
try:
|
|
166
|
+
return import_module(modname, __package__)
|
|
167
|
+
except KeyError:
|
|
168
|
+
# Clear any half-initialized module and retry
|
|
169
|
+
sys.modules.pop(modname, None)
|
|
170
|
+
if attempt == retries - 1:
|
|
171
|
+
break
|
|
172
|
+
raise KeyError(f"Failed to import module '{modname}' after {retries} attempts")
|
|
173
|
+
|
|
174
|
+
|
|
162
175
|
def __getattr__(attr_name: str) -> object:
|
|
163
176
|
module_name = _dynamic_imports.get(attr_name)
|
|
164
177
|
if module_name is None:
|
|
@@ -167,9 +180,8 @@ def __getattr__(attr_name: str) -> object:
|
|
|
167
180
|
)
|
|
168
181
|
|
|
169
182
|
try:
|
|
170
|
-
module =
|
|
171
|
-
|
|
172
|
-
return result
|
|
183
|
+
module = dynamic_import(module_name)
|
|
184
|
+
return getattr(module, attr_name)
|
|
173
185
|
except ImportError as e:
|
|
174
186
|
raise ImportError(
|
|
175
187
|
f"Failed to import {attr_name} from {module_name}: {e}"
|
dub/utils/eventstreaming.py
CHANGED
|
@@ -17,6 +17,9 @@ T = TypeVar("T")
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class EventStream(Generic[T]):
|
|
20
|
+
# Holds a reference to the SDK client to avoid it being garbage collected
|
|
21
|
+
# and cause termination of the underlying httpx client.
|
|
22
|
+
client_ref: Optional[object]
|
|
20
23
|
response: httpx.Response
|
|
21
24
|
generator: Generator[T, None, None]
|
|
22
25
|
|
|
@@ -25,9 +28,11 @@ class EventStream(Generic[T]):
|
|
|
25
28
|
response: httpx.Response,
|
|
26
29
|
decoder: Callable[[str], T],
|
|
27
30
|
sentinel: Optional[str] = None,
|
|
31
|
+
client_ref: Optional[object] = None,
|
|
28
32
|
):
|
|
29
33
|
self.response = response
|
|
30
34
|
self.generator = stream_events(response, decoder, sentinel)
|
|
35
|
+
self.client_ref = client_ref
|
|
31
36
|
|
|
32
37
|
def __iter__(self):
|
|
33
38
|
return self
|
|
@@ -43,6 +48,9 @@ class EventStream(Generic[T]):
|
|
|
43
48
|
|
|
44
49
|
|
|
45
50
|
class EventStreamAsync(Generic[T]):
|
|
51
|
+
# Holds a reference to the SDK client to avoid it being garbage collected
|
|
52
|
+
# and cause termination of the underlying httpx client.
|
|
53
|
+
client_ref: Optional[object]
|
|
46
54
|
response: httpx.Response
|
|
47
55
|
generator: AsyncGenerator[T, None]
|
|
48
56
|
|
|
@@ -51,9 +59,11 @@ class EventStreamAsync(Generic[T]):
|
|
|
51
59
|
response: httpx.Response,
|
|
52
60
|
decoder: Callable[[str], T],
|
|
53
61
|
sentinel: Optional[str] = None,
|
|
62
|
+
client_ref: Optional[object] = None,
|
|
54
63
|
):
|
|
55
64
|
self.response = response
|
|
56
65
|
self.generator = stream_events_async(response, decoder, sentinel)
|
|
66
|
+
self.client_ref = client_ref
|
|
57
67
|
|
|
58
68
|
def __aiter__(self):
|
|
59
69
|
return self
|
|
@@ -3,9 +3,9 @@ dub/_hooks/__init__.py,sha256=9_7W5jAYw8rcO8Kfc-Ty-lB82BHfksAJJpVFb_UeU1c,146
|
|
|
3
3
|
dub/_hooks/registration.py,sha256=tT-1Cjp5ax1DL-84HBNWPy4wAwgP-0aI4-asLfnkIlw,625
|
|
4
4
|
dub/_hooks/sdkhooks.py,sha256=2rLEjSz1xFGWabNs1voFn0lXSCqkS38bdKVFdnBJufE,2553
|
|
5
5
|
dub/_hooks/types.py,sha256=5vcNbFBNpCxqI7ZebiBtut7T_Gz2i36L5MjTqGvxV7Y,3035
|
|
6
|
-
dub/_version.py,sha256=
|
|
6
|
+
dub/_version.py,sha256=35aRnNE1PgZV6Lun1z5FraXsyMg7SI4_MxNb7dJx2hY,450
|
|
7
7
|
dub/analytics.py,sha256=D4s6aPCiCVxwbG2bIvanBiaDtYZgN1xMwu5DOnuRrVg,12342
|
|
8
|
-
dub/basesdk.py,sha256=
|
|
8
|
+
dub/basesdk.py,sha256=ZpolQ0D1eZ93wq3jecXpg7RHj8NuFodyWkk9l4eIcqY,12108
|
|
9
9
|
dub/commissions.py,sha256=OzDAs372f4VszeKJNkR4pR7q5SNI4JiCwz-bzny0YMc,24346
|
|
10
10
|
dub/customers.py,sha256=iqlDeXGgkqXC4amT7M2RCnUnPcnpmDY4VSWyQsMVHdA,59897
|
|
11
11
|
dub/domains.py,sha256=cH01GZMhvJHULm7cJxXII--vcDR7DuSginIcKkbpp7M,73956
|
|
@@ -15,7 +15,7 @@ dub/folders.py,sha256=6hlJheqGnEu8noq3A-iahury-TxtLJ3e_YiIPr8NDl8,47602
|
|
|
15
15
|
dub/httpclient.py,sha256=Eu73urOAiZQtdUIyOUnPccxCiBbWEKrXG-JrRG3SLM4,3946
|
|
16
16
|
dub/links.py,sha256=vnP7Uu41PtsOr2mqzROSNxQB3InnOoxq4_zNw3_q5L8,121880
|
|
17
17
|
dub/models/__init__.py,sha256=wIW9sbvSKlrGyoPY4mXvHqw-_Inpl6zqpN6U6j-w6SU,83
|
|
18
|
-
dub/models/components/__init__.py,sha256=
|
|
18
|
+
dub/models/components/__init__.py,sha256=xlsKHs_fvV-8wF-pLFpT5x7XH6jjjDvrj4-Z4F3c9aU,20822
|
|
19
19
|
dub/models/components/analyticsbrowsers.py,sha256=f6qMrkPFf38u3_PIovvdIc0hsX1YpYEaPxNwbXzkoeY,1172
|
|
20
20
|
dub/models/components/analyticscities.py,sha256=zef22QucFtrOCkPDrpvpNlrbX465_0dFHyZ5va_LRRI,1666
|
|
21
21
|
dub/models/components/analyticscontinents.py,sha256=D_SQTm1Xp_pOt7qZTLJVo2B3RQUP8k3MQmsYRQYbjlI,1616
|
|
@@ -31,9 +31,9 @@ dub/models/components/analyticstoplinks.py,sha256=RvhyKQcmDT47F-4sPojhhDgvFYRZCA
|
|
|
31
31
|
dub/models/components/analyticstopurls.py,sha256=WN4oF32SSXUlFPpWvx7jKype0SfdBsX1BvrkaKjClRM,1122
|
|
32
32
|
dub/models/components/analyticstriggers.py,sha256=reWQ1cQDNgPc_cDhGrMv5EplFviiyWZ0nYTvU7bm3C0,1484
|
|
33
33
|
dub/models/components/clickevent.py,sha256=0n1Cwbq9A12Dw8sUIYoHLtsMIFsAUXJ0PH8fPIjzrsQ,18709
|
|
34
|
-
dub/models/components/commissioncreatedevent.py,sha256=
|
|
34
|
+
dub/models/components/commissioncreatedevent.py,sha256=JGQ862mEC9CqSt03JX5pzrM36DacHYNiDhAY1tp95qg,9193
|
|
35
35
|
dub/models/components/continentcode.py,sha256=YFw3_x0w7CxCQijsbfiiOoS9FbNATeHyGLcw-LEsXYw,315
|
|
36
|
-
dub/models/components/domainschema.py,sha256=
|
|
36
|
+
dub/models/components/domainschema.py,sha256=m0QHTiOcO20Ne77d1VanWGSdOxdi5Zt4kIgtQFpXbgI,6688
|
|
37
37
|
dub/models/components/folderschema.py,sha256=tfVy46SHPHMKl-_bOTr6_j5KHtx6aOJiFniYntBOVe4,2415
|
|
38
38
|
dub/models/components/leadcreatedevent.py,sha256=HzgsTjqEIDO-xSa0Y87xRCx_dn-koCUqSb5dvuFvbEU,18047
|
|
39
39
|
dub/models/components/leadevent.py,sha256=G21I9c3dEx0dhuU3hTqQ0OS1Y6Hp-tDdXEmj_xAuNcc,22953
|
|
@@ -51,7 +51,7 @@ dub/models/components/security.py,sha256=be_cng1n5ULto_xGGPBKH1ZE5LrtmBTg6kX2uPJ
|
|
|
51
51
|
dub/models/components/tagschema.py,sha256=jZ2MFrE9ctCqR18S0GV-czRTcViglbpl-9h7pUt81VQ,759
|
|
52
52
|
dub/models/components/webhookevent.py,sha256=12SgBns1nVcb0Efs8JR9JO8vmvK25bXMROCT-s0Ue5c,1268
|
|
53
53
|
dub/models/components/workspaceschema.py,sha256=hS846LkmNv5ZdYZvT1eY7wCQ6-xs_5XMAc2VmdBjPtk,10898
|
|
54
|
-
dub/models/errors/__init__.py,sha256=
|
|
54
|
+
dub/models/errors/__init__.py,sha256=puGRbwSI00uEfD5rs_zvN4GtHhVFXRqrkK60R_nSI24,6462
|
|
55
55
|
dub/models/errors/badrequest.py,sha256=G5UFXCXOZy5raqCb_UpY409hSNT0V8lCiQxZs5iCri8,1646
|
|
56
56
|
dub/models/errors/conflict.py,sha256=tDqlH_1HgU17AgVE2RNH3vnFo1XY03ptSanRtvx3qK0,1562
|
|
57
57
|
dub/models/errors/duberror.py,sha256=wD61h62qi9-hdtxHDO6w7cbWIEoqgO0x32ZovrP8dsQ,710
|
|
@@ -65,7 +65,7 @@ dub/models/errors/responsevalidationerror.py,sha256=SRWiFq2Ip-M7luHULq3POe4JSWzO
|
|
|
65
65
|
dub/models/errors/sdkerror.py,sha256=iWxT-KvUgOlqID84qtbl7jG88f3xF9bxVCnMrtshtA0,1216
|
|
66
66
|
dub/models/errors/unauthorized.py,sha256=gTybq5vCh3nFKAnERxwMwETlQZ3T3SR01RKooIreVdo,1710
|
|
67
67
|
dub/models/errors/unprocessableentity.py,sha256=EkqNdp5lZQHA8hJgNdA3gviqMmyjcFx02cTxl3yhyrs,1693
|
|
68
|
-
dub/models/operations/__init__.py,sha256=
|
|
68
|
+
dub/models/operations/__init__.py,sha256=E4E9CC-KMmY16v02O1LRExzMUkgF6dM_ltzVaw6vhac,42255
|
|
69
69
|
dub/models/operations/bulkcreatelinks.py,sha256=OZNtpJ96LE_33GGD3XsaXN43w3GxA2Oqwmspu1wrGvI,17832
|
|
70
70
|
dub/models/operations/bulkdeletelinks.py,sha256=u_hEFC9TZ1UnGGgLhQ-Mf3HNDO98Ur49MtdBnNVIRsE,1151
|
|
71
71
|
dub/models/operations/bulkupdatelinks.py,sha256=TCkPBC7wD-EZdJm0m9XSznY7sNF8pixyXPXokb3S4XY,15879
|
|
@@ -100,8 +100,8 @@ dub/models/operations/registerdomain.py,sha256=fjozn1tFU-cNarHdAqN_flQoGAE498ob-
|
|
|
100
100
|
dub/models/operations/retrieveanalytics.py,sha256=4dRnXcathBqM34MPZV6pE_fMYe3P3CVIb53-i8TRemo,20421
|
|
101
101
|
dub/models/operations/retrievelinks.py,sha256=rMp0VPEdwLT5ekQ3g2eAHwlr8-4EaEw699yLzDqTXzk,2855
|
|
102
102
|
dub/models/operations/retrievepartneranalytics.py,sha256=up_lKTeJBLQBmVaPOU9r06t-TysfnePx9eM5h6sFci4,5328
|
|
103
|
-
dub/models/operations/tracklead.py,sha256=
|
|
104
|
-
dub/models/operations/tracksale.py,sha256=
|
|
103
|
+
dub/models/operations/tracklead.py,sha256=iJLteV9tKPphqrN9tgtZvF3xIFpx1tJ2tlZLfVfmn4A,7446
|
|
104
|
+
dub/models/operations/tracksale.py,sha256=rY_YMRP7ttbxvtdFcQfO3NWYSZd_1s1Qv6pSShsyge8,11442
|
|
105
105
|
dub/models/operations/updatecommission.py,sha256=N_okp7jc6jqI4CnGRvTEKTw-QPb5DEwGVGfKmOSRQrY,11019
|
|
106
106
|
dub/models/operations/updatecustomer.py,sha256=xlW-W99WgdlAsKD4fNhIEw3f1Sipnb4ahPnZFOcisSY,13517
|
|
107
107
|
dub/models/operations/updatedomain.py,sha256=rexCga7uNxgBZLPiCMcaudc2cQGB0E_qX2HI0DgG_3M,4519
|
|
@@ -114,17 +114,17 @@ dub/models/operations/upsertpartnerlink.py,sha256=Z0xtZHGzePne4wM2XaouR8f_pJrHA6
|
|
|
114
114
|
dub/partners.py,sha256=y-lObDvW0e7UMFkmF2bjrikimU0zb1Lf2U-KSANCvnU,72639
|
|
115
115
|
dub/py.typed,sha256=zrp19r0G21lr2yRiMC0f8MFkQFGj9wMpSbboePMg8KM,59
|
|
116
116
|
dub/qr_codes.py,sha256=r1kxjwW54UDxdVZ74l5U1bxbkvMcrSC9nlPUHDZYBsI,11572
|
|
117
|
-
dub/sdk.py,sha256=
|
|
117
|
+
dub/sdk.py,sha256=fu-8vmD6fO8Qoj4O_0z1fXel6nMRT5YwY1RIURt_7EE,8085
|
|
118
118
|
dub/sdkconfiguration.py,sha256=2aIgzM94TIYQe5zkQmHhDsdlxJdV6cfhWX0pspYMHow,1605
|
|
119
119
|
dub/tags.py,sha256=qpa2kajz07L6MiaEKlrGAOE55wF1M3eSWAx2J6X04l4,47163
|
|
120
120
|
dub/track.py,sha256=y42M2SOOiM0fboywGCEvkcZJDrZp0x9Ck4GInfx-5zw,24510
|
|
121
121
|
dub/types/__init__.py,sha256=RArOwSgeeTIva6h-4ttjXwMUeCkz10nAFBL9D-QljI4,377
|
|
122
122
|
dub/types/basemodel.py,sha256=L79WXvTECbSqaJzs8D3ud_KdIWkU7Cx2wbohDAktE9E,1127
|
|
123
|
-
dub/utils/__init__.py,sha256=
|
|
123
|
+
dub/utils/__init__.py,sha256=88uXYJ3d7C29IuuhPMgWfu4i00tNl555vaqrLA61hds,5766
|
|
124
124
|
dub/utils/annotations.py,sha256=aR7mZG34FzgRdew7WZPYEu9QGBerpuKxCF4sek5Z_5Y,1699
|
|
125
125
|
dub/utils/datetimes.py,sha256=oppAA5e3V35pQov1-FNLKxAaNF1_XWi-bQtyjjql3H8,855
|
|
126
126
|
dub/utils/enums.py,sha256=REU6ydF8gsVL3xaeGX4sMNyiL3q5P9h29-f6Sa6luAE,2633
|
|
127
|
-
dub/utils/eventstreaming.py,sha256=
|
|
127
|
+
dub/utils/eventstreaming.py,sha256=SgFqMcUOYKlrTQ4gAp_dNcKLvDXukeiEMNU3DP8mXk8,6692
|
|
128
128
|
dub/utils/forms.py,sha256=EJdnrfIkuwpDtekyHutla0HjI_FypTYcmYNyPKEu_W0,6874
|
|
129
129
|
dub/utils/headers.py,sha256=cPxWSmUILrefTGDzTH1Hdj7_Hlsj-EY6K5Tyc4iH4dk,3663
|
|
130
130
|
dub/utils/logger.py,sha256=9nUtlKHo3RFsIVyMw5jq3wEKZMVwHnZMSc6xLp-otC0,520
|
|
@@ -138,7 +138,7 @@ dub/utils/unmarshal_json_response.py,sha256=FcgE-IWPMAHWDdw6QEZeLeD5G_rflScZbT10
|
|
|
138
138
|
dub/utils/url.py,sha256=BgGPgcTA6MRK4bF8fjP2dUopN3NzEzxWMXPBVg8NQUA,5254
|
|
139
139
|
dub/utils/values.py,sha256=CcaCXEa3xHhkUDROyXZocN8f0bdITftv9Y0P9lTf0YM,3517
|
|
140
140
|
dub/workspaces.py,sha256=qqg3JuPFsC14D9OqUeMxYvXHOoIJOJ9To1faAG7x8kM,24373
|
|
141
|
-
dub-0.
|
|
142
|
-
dub-0.
|
|
143
|
-
dub-0.
|
|
144
|
-
dub-0.
|
|
141
|
+
dub-0.29.1.dist-info/LICENSE,sha256=kc_aZ6YHHcdSsRy-mGsT0Ehji0ZgR_zevXiUt05V2KY,1079
|
|
142
|
+
dub-0.29.1.dist-info/METADATA,sha256=z8sF9nrz9c-60JBlsPNvawbqaPi1Sl519JTw9KXsJAk,30669
|
|
143
|
+
dub-0.29.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
144
|
+
dub-0.29.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|