dub 0.28.0__tar.gz → 0.29.0__tar.gz

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 (145) hide show
  1. {dub-0.28.0 → dub-0.29.0}/PKG-INFO +1 -1
  2. {dub-0.28.0 → dub-0.29.0}/pyproject.toml +2 -1
  3. {dub-0.28.0 → dub-0.29.0}/src/dub/_version.py +3 -3
  4. {dub-0.28.0 → dub-0.29.0}/src/dub/basesdk.py +11 -1
  5. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/__init__.py +14 -1
  6. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/commissioncreatedevent.py +18 -0
  7. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/saleevent.py +8 -8
  8. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/__init__.py +15 -3
  9. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/__init__.py +14 -1
  10. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/tracklead.py +10 -9
  11. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/tracksale.py +8 -7
  12. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/updateworkspace.py +2 -13
  13. {dub-0.28.0 → dub-0.29.0}/src/dub/sdk.py +15 -2
  14. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/__init__.py +15 -3
  15. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/eventstreaming.py +10 -0
  16. {dub-0.28.0 → dub-0.29.0}/LICENSE +0 -0
  17. {dub-0.28.0 → dub-0.29.0}/README-PYPI.md +0 -0
  18. {dub-0.28.0 → dub-0.29.0}/py.typed +0 -0
  19. {dub-0.28.0 → dub-0.29.0}/src/dub/__init__.py +0 -0
  20. {dub-0.28.0 → dub-0.29.0}/src/dub/_hooks/__init__.py +0 -0
  21. {dub-0.28.0 → dub-0.29.0}/src/dub/_hooks/registration.py +0 -0
  22. {dub-0.28.0 → dub-0.29.0}/src/dub/_hooks/sdkhooks.py +0 -0
  23. {dub-0.28.0 → dub-0.29.0}/src/dub/_hooks/types.py +0 -0
  24. {dub-0.28.0 → dub-0.29.0}/src/dub/analytics.py +0 -0
  25. {dub-0.28.0 → dub-0.29.0}/src/dub/commissions.py +0 -0
  26. {dub-0.28.0 → dub-0.29.0}/src/dub/customers.py +0 -0
  27. {dub-0.28.0 → dub-0.29.0}/src/dub/domains.py +0 -0
  28. {dub-0.28.0 → dub-0.29.0}/src/dub/embed_tokens.py +0 -0
  29. {dub-0.28.0 → dub-0.29.0}/src/dub/events.py +0 -0
  30. {dub-0.28.0 → dub-0.29.0}/src/dub/folders.py +0 -0
  31. {dub-0.28.0 → dub-0.29.0}/src/dub/httpclient.py +0 -0
  32. {dub-0.28.0 → dub-0.29.0}/src/dub/links.py +0 -0
  33. {dub-0.28.0 → dub-0.29.0}/src/dub/models/__init__.py +0 -0
  34. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticsbrowsers.py +0 -0
  35. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticscities.py +0 -0
  36. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticscontinents.py +0 -0
  37. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticscount.py +0 -0
  38. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticscountries.py +0 -0
  39. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticsdevices.py +0 -0
  40. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticsos.py +0 -0
  41. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticsreferers.py +0 -0
  42. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticsrefererurls.py +0 -0
  43. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticsregions.py +0 -0
  44. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticstimeseries.py +0 -0
  45. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticstoplinks.py +0 -0
  46. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticstopurls.py +0 -0
  47. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/analyticstriggers.py +0 -0
  48. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/clickevent.py +0 -0
  49. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/continentcode.py +0 -0
  50. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/domainschema.py +1 -1
  51. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/folderschema.py +0 -0
  52. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/leadcreatedevent.py +0 -0
  53. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/leadevent.py +0 -0
  54. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/linkclickedevent.py +0 -0
  55. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/linkerrorschema.py +0 -0
  56. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/linkschema.py +0 -0
  57. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/linkwebhookevent.py +0 -0
  58. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/partneranalyticscount.py +0 -0
  59. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/partneranalyticstimeseries.py +0 -0
  60. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/partneranalyticstoplinks.py +0 -0
  61. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/partnerenrolledevent.py +0 -0
  62. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/salecreatedevent.py +0 -0
  63. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/security.py +0 -0
  64. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/tagschema.py +0 -0
  65. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/webhookevent.py +0 -0
  66. {dub-0.28.0 → dub-0.29.0}/src/dub/models/components/workspaceschema.py +0 -0
  67. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/badrequest.py +0 -0
  68. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/conflict.py +0 -0
  69. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/duberror.py +0 -0
  70. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/forbidden.py +0 -0
  71. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/internalservererror.py +0 -0
  72. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/inviteexpired.py +0 -0
  73. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/no_response_error.py +0 -0
  74. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/notfound.py +0 -0
  75. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/ratelimitexceeded.py +0 -0
  76. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/responsevalidationerror.py +0 -0
  77. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/sdkerror.py +0 -0
  78. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/unauthorized.py +0 -0
  79. {dub-0.28.0 → dub-0.29.0}/src/dub/models/errors/unprocessableentity.py +0 -0
  80. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/bulkcreatelinks.py +0 -0
  81. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/bulkdeletelinks.py +0 -0
  82. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/bulkupdatelinks.py +0 -0
  83. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/checkdomainstatus.py +0 -0
  84. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/createcustomer.py +0 -0
  85. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/createdomain.py +0 -0
  86. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/createfolder.py +0 -0
  87. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/createlink.py +0 -0
  88. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/createpartner.py +0 -0
  89. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/createpartnerlink.py +0 -0
  90. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/createreferralsembedtoken.py +0 -0
  91. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/createtag.py +0 -0
  92. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/deletecustomer.py +0 -0
  93. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/deletedomain.py +0 -0
  94. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/deletefolder.py +0 -0
  95. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/deletelink.py +0 -0
  96. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/deletetag.py +0 -0
  97. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/getcustomer.py +0 -0
  98. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/getcustomers.py +0 -0
  99. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/getlinkinfo.py +0 -0
  100. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/getlinks.py +0 -0
  101. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/getlinkscount.py +0 -0
  102. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/getqrcode.py +0 -0
  103. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/gettags.py +0 -0
  104. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/getworkspace.py +0 -0
  105. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/listcommissions.py +0 -0
  106. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/listdomains.py +0 -0
  107. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/listevents.py +0 -0
  108. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/listfolders.py +0 -0
  109. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/listpartners.py +0 -0
  110. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/registerdomain.py +0 -0
  111. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/retrieveanalytics.py +0 -0
  112. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/retrievelinks.py +0 -0
  113. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/retrievepartneranalytics.py +0 -0
  114. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/updatecommission.py +0 -0
  115. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/updatecustomer.py +0 -0
  116. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/updatedomain.py +0 -0
  117. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/updatefolder.py +0 -0
  118. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/updatelink.py +0 -0
  119. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/updatetag.py +0 -0
  120. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/upsertlink.py +0 -0
  121. {dub-0.28.0 → dub-0.29.0}/src/dub/models/operations/upsertpartnerlink.py +0 -0
  122. {dub-0.28.0 → dub-0.29.0}/src/dub/partners.py +0 -0
  123. {dub-0.28.0 → dub-0.29.0}/src/dub/py.typed +0 -0
  124. {dub-0.28.0 → dub-0.29.0}/src/dub/qr_codes.py +0 -0
  125. {dub-0.28.0 → dub-0.29.0}/src/dub/sdkconfiguration.py +0 -0
  126. {dub-0.28.0 → dub-0.29.0}/src/dub/tags.py +0 -0
  127. {dub-0.28.0 → dub-0.29.0}/src/dub/track.py +0 -0
  128. {dub-0.28.0 → dub-0.29.0}/src/dub/types/__init__.py +0 -0
  129. {dub-0.28.0 → dub-0.29.0}/src/dub/types/basemodel.py +0 -0
  130. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/annotations.py +0 -0
  131. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/datetimes.py +0 -0
  132. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/enums.py +0 -0
  133. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/forms.py +0 -0
  134. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/headers.py +0 -0
  135. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/logger.py +0 -0
  136. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/metadata.py +0 -0
  137. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/queryparams.py +0 -0
  138. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/requestbodies.py +0 -0
  139. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/retries.py +0 -0
  140. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/security.py +0 -0
  141. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/serializers.py +0 -0
  142. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/unmarshal_json_response.py +0 -0
  143. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/url.py +0 -0
  144. {dub-0.28.0 → dub-0.29.0}/src/dub/utils/values.py +0 -0
  145. {dub-0.28.0 → dub-0.29.0}/src/dub/workspaces.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dub
3
- Version: 0.28.0
3
+ Version: 0.29.0
4
4
  Summary: Python Client SDK Generated by Speakeasy
5
5
  Author: Speakeasy
6
6
  Requires-Python: >=3.9.2
@@ -1,7 +1,7 @@
1
1
 
2
2
  [project]
3
3
  name = "dub"
4
- version = "0.28.0"
4
+ version = "0.29.0"
5
5
  description = "Python Client SDK Generated by Speakeasy"
6
6
  authors = [{ name = "Speakeasy" },]
7
7
  readme = "README-PYPI.md"
@@ -36,6 +36,7 @@ build-backend = "poetry.core.masonry.api"
36
36
 
37
37
  [tool.pytest.ini_options]
38
38
  asyncio_default_fixture_loop_scope = "function"
39
+ asyncio_mode = "auto"
39
40
  pythonpath = ["src"]
40
41
 
41
42
  [tool.mypy]
@@ -3,10 +3,10 @@
3
3
  import importlib.metadata
4
4
 
5
5
  __title__: str = "dub"
6
- __version__: str = "0.28.0"
6
+ __version__: str = "0.29.0"
7
7
  __openapi_doc_version__: str = "0.0.1"
8
- __gen_version__: str = "2.684.0"
9
- __user_agent__: str = "speakeasy-sdk/python 0.28.0 2.684.0 0.0.1 dub"
8
+ __gen_version__: str = "2.698.0"
9
+ __user_agent__: str = "speakeasy-sdk/python 0.29.0 2.698.0 0.0.1 dub"
10
10
 
11
11
  try:
12
12
  if __package__ is not None:
@@ -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__(self, sdk_config: SDKConfiguration) -> None:
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 = import_module(module_name, __package__)
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 = []
@@ -28,29 +28,29 @@ class PaymentProcessor(str, Enum):
28
28
  class SaleTypedDict(TypedDict):
29
29
  amount: int
30
30
  r"""The amount of the sale in cents (for all two-decimal currencies). If the sale is in a zero-decimal currency, pass the full integer value (e.g. `1437` JPY). Learn more: https://d.to/currency"""
31
- payment_processor: PaymentProcessor
32
- r"""The payment processor via which the sale was made."""
33
31
  invoice_id: NotRequired[Nullable[str]]
34
32
  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."""
33
+ payment_processor: NotRequired[PaymentProcessor]
34
+ r"""The payment processor via which the sale was made."""
35
35
 
36
36
 
37
37
  class Sale(BaseModel):
38
38
  amount: int
39
39
  r"""The amount of the sale in cents (for all two-decimal currencies). If the sale is in a zero-decimal currency, pass the full integer value (e.g. `1437` JPY). Learn more: https://d.to/currency"""
40
40
 
41
- payment_processor: Annotated[
42
- PaymentProcessor, pydantic.Field(alias="paymentProcessor")
43
- ]
44
- r"""The payment processor via which the sale was made."""
45
-
46
41
  invoice_id: Annotated[OptionalNullable[str], pydantic.Field(alias="invoiceId")] = (
47
42
  None
48
43
  )
49
44
  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."""
50
45
 
46
+ payment_processor: Annotated[
47
+ Optional[PaymentProcessor], pydantic.Field(alias="paymentProcessor")
48
+ ] = PaymentProcessor.CUSTOM
49
+ r"""The payment processor via which the sale was made."""
50
+
51
51
  @model_serializer(mode="wrap")
52
52
  def serialize_model(self, handler):
53
- optional_fields = ["invoiceId"]
53
+ optional_fields = ["invoiceId", "paymentProcessor"]
54
54
  nullable_fields = ["invoiceId"]
55
55
  null_default_fields = ["invoiceId"]
56
56
 
@@ -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 = import_module(module_name, __package__)
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 = import_module(module_name, __package__)
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 = [
@@ -25,12 +25,12 @@ class TrackSaleRequestBodyTypedDict(TypedDict):
25
25
  r"""The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer."""
26
26
  amount: int
27
27
  r"""The amount of the sale in cents (for all two-decimal currencies). If the sale is in a zero-decimal currency, pass the full integer value (e.g. `1437` JPY). Learn more: https://d.to/currency"""
28
- payment_processor: PaymentProcessor
29
- r"""The payment processor via which the sale was made."""
30
28
  currency: NotRequired[str]
31
29
  r"""The currency of the sale. Accepts ISO 4217 currency codes. Sales will be automatically converted and stored as USD at the latest exchange rates. Learn more: https://d.to/currency"""
32
30
  event_name: NotRequired[str]
33
31
  r"""The name of the sale event. Recommended format: `Invoice paid` or `Subscription created`."""
32
+ payment_processor: NotRequired[PaymentProcessor]
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
36
  lead_event_name: NotRequired[Nullable[str]]
@@ -46,17 +46,17 @@ class TrackSaleRequestBody(BaseModel):
46
46
  amount: int
47
47
  r"""The amount of the sale in cents (for all two-decimal currencies). If the sale is in a zero-decimal currency, pass the full integer value (e.g. `1437` JPY). Learn more: https://d.to/currency"""
48
48
 
49
- payment_processor: Annotated[
50
- PaymentProcessor, pydantic.Field(alias="paymentProcessor")
51
- ]
52
- r"""The payment processor via which the sale was made."""
53
-
54
49
  currency: Optional[str] = "usd"
55
50
  r"""The currency of the sale. Accepts ISO 4217 currency codes. Sales will be automatically converted and stored as USD at the latest exchange rates. Learn more: https://d.to/currency"""
56
51
 
57
52
  event_name: Annotated[Optional[str], pydantic.Field(alias="eventName")] = "Purchase"
58
53
  r"""The name of the sale event. Recommended format: `Invoice paid` or `Subscription created`."""
59
54
 
55
+ payment_processor: Annotated[
56
+ Optional[PaymentProcessor], pydantic.Field(alias="paymentProcessor")
57
+ ] = PaymentProcessor.CUSTOM
58
+ r"""The payment processor via which the sale was made."""
59
+
60
60
  invoice_id: Annotated[OptionalNullable[str], pydantic.Field(alias="invoiceId")] = (
61
61
  None
62
62
  )
@@ -75,6 +75,7 @@ class TrackSaleRequestBody(BaseModel):
75
75
  optional_fields = [
76
76
  "currency",
77
77
  "eventName",
78
+ "paymentProcessor",
78
79
  "invoiceId",
79
80
  "leadEventName",
80
81
  "metadata",
@@ -5,7 +5,7 @@ from dub.types import BaseModel, Nullable, OptionalNullable, UNSET, UNSET_SENTIN
5
5
  from dub.utils import FieldMetadata, PathParamMetadata, RequestMetadata
6
6
  import pydantic
7
7
  from pydantic import model_serializer
8
- from typing import List, Optional
8
+ from typing import Optional
9
9
  from typing_extensions import Annotated, NotRequired, TypedDict
10
10
 
11
11
 
@@ -14,7 +14,6 @@ class UpdateWorkspaceRequestBodyTypedDict(TypedDict):
14
14
  slug: NotRequired[str]
15
15
  logo: NotRequired[Nullable[str]]
16
16
  conversion_enabled: NotRequired[bool]
17
- allowed_hostnames: NotRequired[List[str]]
18
17
 
19
18
 
20
19
  class UpdateWorkspaceRequestBody(BaseModel):
@@ -28,19 +27,9 @@ class UpdateWorkspaceRequestBody(BaseModel):
28
27
  Optional[bool], pydantic.Field(alias="conversionEnabled")
29
28
  ] = None
30
29
 
31
- allowed_hostnames: Annotated[
32
- Optional[List[str]], pydantic.Field(alias="allowedHostnames")
33
- ] = None
34
-
35
30
  @model_serializer(mode="wrap")
36
31
  def serialize_model(self, handler):
37
- optional_fields = [
38
- "name",
39
- "slug",
40
- "logo",
41
- "conversionEnabled",
42
- "allowedHostnames",
43
- ]
32
+ optional_fields = ["name", "slug", "logo", "conversionEnabled"]
44
33
  nullable_fields = ["logo"]
45
34
  null_default_fields = []
46
35
 
@@ -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 = importlib.import_module(module_path)
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:
@@ -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 = import_module(module_name, __package__)
171
- result = getattr(module, attr_name)
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}"
@@ -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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -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"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes