cribl-control-plane 0.0.38__py3-none-any.whl → 0.4.0a6__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.

Potentially problematic release.


This version of cribl-control-plane might be problematic. Click here for more details.

Files changed (241) hide show
  1. cribl_control_plane/_hooks/clientcredentials.py +92 -42
  2. cribl_control_plane/_version.py +4 -4
  3. cribl_control_plane/acl.py +5 -3
  4. cribl_control_plane/auth_sdk.py +6 -3
  5. cribl_control_plane/basesdk.py +11 -1
  6. cribl_control_plane/commits.py +7 -5
  7. cribl_control_plane/destinations.py +6 -4
  8. cribl_control_plane/destinations_pq.py +2 -2
  9. cribl_control_plane/errors/__init__.py +23 -8
  10. cribl_control_plane/errors/apierror.py +2 -0
  11. cribl_control_plane/errors/criblcontrolplaneerror.py +11 -7
  12. cribl_control_plane/errors/error.py +4 -2
  13. cribl_control_plane/errors/healthserverstatus_error.py +41 -0
  14. cribl_control_plane/errors/no_response_error.py +5 -1
  15. cribl_control_plane/errors/responsevalidationerror.py +2 -0
  16. cribl_control_plane/groups_configs.py +8 -3
  17. cribl_control_plane/groups_sdk.py +64 -38
  18. cribl_control_plane/health.py +22 -12
  19. cribl_control_plane/httpclient.py +0 -1
  20. cribl_control_plane/lakedatasets.py +40 -12
  21. cribl_control_plane/models/__init__.py +1180 -54
  22. cribl_control_plane/models/authtoken.py +5 -1
  23. cribl_control_plane/models/{routecloneconf.py → branchinfo.py} +4 -4
  24. cribl_control_plane/models/cacheconnection.py +30 -2
  25. cribl_control_plane/models/cacheconnectionbackfillstatus.py +2 -1
  26. cribl_control_plane/models/cloudprovider.py +2 -1
  27. cribl_control_plane/models/configgroup.py +66 -11
  28. cribl_control_plane/models/configgroupcloud.py +17 -3
  29. cribl_control_plane/models/createconfiggroupbyproductop.py +27 -9
  30. cribl_control_plane/models/createinputhectokenbyidop.py +6 -5
  31. cribl_control_plane/models/createroutesappendbyidop.py +2 -2
  32. cribl_control_plane/models/createversionpushop.py +5 -5
  33. cribl_control_plane/models/createversionrevertop.py +2 -2
  34. cribl_control_plane/models/createversionundoop.py +3 -3
  35. cribl_control_plane/models/cribllakedataset.py +22 -2
  36. cribl_control_plane/models/cribllakedatasetupdate.py +95 -0
  37. cribl_control_plane/models/datasetmetadata.py +18 -2
  38. cribl_control_plane/models/deleteconfiggroupbyproductandidop.py +18 -2
  39. cribl_control_plane/models/deleteoutputpqbyidop.py +5 -5
  40. cribl_control_plane/models/deletepipelinebyidop.py +2 -2
  41. cribl_control_plane/models/difffiles.py +171 -0
  42. cribl_control_plane/models/distributedsummary.py +6 -0
  43. cribl_control_plane/models/getconfiggroupaclbyproductandidop.py +24 -2
  44. cribl_control_plane/models/getconfiggroupaclteamsbyproductandidop.py +24 -2
  45. cribl_control_plane/models/getconfiggroupbyproductandidop.py +14 -1
  46. cribl_control_plane/models/getconfiggroupconfigversionbyproductandidop.py +18 -2
  47. cribl_control_plane/models/getoutputpqbyidop.py +6 -5
  48. cribl_control_plane/models/getpipelinebyidop.py +2 -2
  49. cribl_control_plane/models/getroutesbyidop.py +2 -2
  50. cribl_control_plane/models/getsummaryop.py +18 -2
  51. cribl_control_plane/models/getversionbranchop.py +6 -5
  52. cribl_control_plane/models/getversioncountop.py +6 -5
  53. cribl_control_plane/models/getversiondiffop.py +6 -5
  54. cribl_control_plane/models/getversionshowop.py +6 -5
  55. cribl_control_plane/models/gitcountresult.py +13 -0
  56. cribl_control_plane/models/gitdiffresult.py +16 -0
  57. cribl_control_plane/models/gitinfo.py +14 -3
  58. cribl_control_plane/models/gitshowresult.py +19 -0
  59. cribl_control_plane/models/groupcreaterequest.py +171 -0
  60. cribl_control_plane/models/hbcriblinfo.py +39 -3
  61. cribl_control_plane/models/healthserverstatus.py +55 -0
  62. cribl_control_plane/models/heartbeatmetadata.py +3 -0
  63. cribl_control_plane/models/input.py +83 -78
  64. cribl_control_plane/models/inputappscope.py +126 -30
  65. cribl_control_plane/models/inputazureblob.py +62 -6
  66. cribl_control_plane/models/inputcloudflarehec.py +513 -0
  67. cribl_control_plane/models/inputcollection.py +47 -4
  68. cribl_control_plane/models/inputconfluentcloud.py +254 -30
  69. cribl_control_plane/models/inputcribl.py +47 -4
  70. cribl_control_plane/models/inputcriblhttp.py +121 -30
  71. cribl_control_plane/models/inputcribllakehttp.py +122 -30
  72. cribl_control_plane/models/inputcriblmetrics.py +48 -4
  73. cribl_control_plane/models/inputcribltcp.py +122 -24
  74. cribl_control_plane/models/inputcrowdstrike.py +92 -10
  75. cribl_control_plane/models/inputdatadogagent.py +98 -24
  76. cribl_control_plane/models/inputdatagen.py +47 -4
  77. cribl_control_plane/models/inputedgeprometheus.py +210 -50
  78. cribl_control_plane/models/inputelastic.py +167 -36
  79. cribl_control_plane/models/inputeventhub.py +209 -6
  80. cribl_control_plane/models/inputexec.py +59 -6
  81. cribl_control_plane/models/inputfile.py +78 -10
  82. cribl_control_plane/models/inputfirehose.py +97 -24
  83. cribl_control_plane/models/inputgooglepubsub.py +67 -6
  84. cribl_control_plane/models/inputgrafana.py +251 -71
  85. cribl_control_plane/models/inputhttp.py +97 -24
  86. cribl_control_plane/models/inputhttpraw.py +97 -24
  87. cribl_control_plane/models/inputjournalfiles.py +48 -4
  88. cribl_control_plane/models/inputkafka.py +248 -26
  89. cribl_control_plane/models/inputkinesis.py +130 -14
  90. cribl_control_plane/models/inputkubeevents.py +47 -4
  91. cribl_control_plane/models/inputkubelogs.py +61 -8
  92. cribl_control_plane/models/inputkubemetrics.py +61 -8
  93. cribl_control_plane/models/inputloki.py +113 -34
  94. cribl_control_plane/models/inputmetrics.py +97 -24
  95. cribl_control_plane/models/inputmodeldriventelemetry.py +107 -26
  96. cribl_control_plane/models/inputmsk.py +141 -30
  97. cribl_control_plane/models/inputnetflow.py +47 -4
  98. cribl_control_plane/models/inputoffice365mgmt.py +112 -14
  99. cribl_control_plane/models/inputoffice365msgtrace.py +114 -16
  100. cribl_control_plane/models/inputoffice365service.py +114 -16
  101. cribl_control_plane/models/inputopentelemetry.py +143 -32
  102. cribl_control_plane/models/inputprometheus.py +193 -44
  103. cribl_control_plane/models/inputprometheusrw.py +114 -27
  104. cribl_control_plane/models/inputrawudp.py +47 -4
  105. cribl_control_plane/models/inputs3.py +78 -8
  106. cribl_control_plane/models/inputs3inventory.py +92 -10
  107. cribl_control_plane/models/inputsecuritylake.py +93 -10
  108. cribl_control_plane/models/inputsnmp.py +68 -6
  109. cribl_control_plane/models/inputsplunk.py +130 -28
  110. cribl_control_plane/models/inputsplunkhec.py +111 -25
  111. cribl_control_plane/models/inputsplunksearch.py +108 -14
  112. cribl_control_plane/models/inputsqs.py +99 -16
  113. cribl_control_plane/models/inputsyslog.py +189 -47
  114. cribl_control_plane/models/inputsystemmetrics.py +202 -32
  115. cribl_control_plane/models/inputsystemstate.py +61 -8
  116. cribl_control_plane/models/inputtcp.py +122 -26
  117. cribl_control_plane/models/inputtcpjson.py +112 -26
  118. cribl_control_plane/models/inputwef.py +121 -15
  119. cribl_control_plane/models/inputwindowsmetrics.py +186 -33
  120. cribl_control_plane/models/inputwineventlogs.py +93 -11
  121. cribl_control_plane/models/inputwiz.py +78 -8
  122. cribl_control_plane/models/inputwizwebhook.py +97 -24
  123. cribl_control_plane/models/inputzscalerhec.py +111 -25
  124. cribl_control_plane/models/jobinfo.py +34 -0
  125. cribl_control_plane/models/jobstatus.py +48 -0
  126. cribl_control_plane/models/lakedatasetmetrics.py +17 -0
  127. cribl_control_plane/models/lakehouseconnectiontype.py +2 -1
  128. cribl_control_plane/models/listconfiggroupbyproductop.py +14 -1
  129. cribl_control_plane/models/logininfo.py +3 -3
  130. cribl_control_plane/models/masterworkerentry.py +17 -2
  131. cribl_control_plane/models/nodeactiveupgradestatus.py +2 -1
  132. cribl_control_plane/models/nodefailedupgradestatus.py +2 -1
  133. cribl_control_plane/models/nodeprovidedinfo.py +11 -1
  134. cribl_control_plane/models/nodeskippedupgradestatus.py +2 -1
  135. cribl_control_plane/models/nodeupgradestate.py +2 -1
  136. cribl_control_plane/models/nodeupgradestatus.py +51 -5
  137. cribl_control_plane/models/outpostnodeinfo.py +16 -0
  138. cribl_control_plane/models/output.py +104 -90
  139. cribl_control_plane/models/outputazureblob.py +171 -18
  140. cribl_control_plane/models/outputazuredataexplorer.py +514 -90
  141. cribl_control_plane/models/outputazureeventhub.py +315 -31
  142. cribl_control_plane/models/outputazurelogs.py +145 -26
  143. cribl_control_plane/models/outputchronicle.py +532 -0
  144. cribl_control_plane/models/outputclickhouse.py +205 -34
  145. cribl_control_plane/models/outputcloudflarer2.py +632 -0
  146. cribl_control_plane/models/outputcloudwatch.py +129 -23
  147. cribl_control_plane/models/outputconfluentcloud.py +384 -57
  148. cribl_control_plane/models/outputcriblhttp.py +199 -32
  149. cribl_control_plane/models/outputcribllake.py +156 -16
  150. cribl_control_plane/models/outputcribltcp.py +194 -29
  151. cribl_control_plane/models/outputcrowdstrikenextgensiem.py +172 -28
  152. cribl_control_plane/models/outputdatabricks.py +501 -0
  153. cribl_control_plane/models/outputdatadog.py +199 -31
  154. cribl_control_plane/models/outputdataset.py +181 -29
  155. cribl_control_plane/models/outputdiskspool.py +17 -2
  156. cribl_control_plane/models/outputdls3.py +233 -24
  157. cribl_control_plane/models/outputdynatracehttp.py +208 -34
  158. cribl_control_plane/models/outputdynatraceotlp.py +210 -36
  159. cribl_control_plane/models/outputelastic.py +199 -30
  160. cribl_control_plane/models/outputelasticcloud.py +171 -26
  161. cribl_control_plane/models/outputexabeam.py +96 -10
  162. cribl_control_plane/models/outputfilesystem.py +139 -14
  163. cribl_control_plane/models/outputgooglechronicle.py +216 -35
  164. cribl_control_plane/models/outputgooglecloudlogging.py +174 -31
  165. cribl_control_plane/models/outputgooglecloudstorage.py +215 -24
  166. cribl_control_plane/models/outputgooglepubsub.py +131 -23
  167. cribl_control_plane/models/outputgrafanacloud.py +376 -74
  168. cribl_control_plane/models/outputgraphite.py +128 -25
  169. cribl_control_plane/models/outputhoneycomb.py +145 -26
  170. cribl_control_plane/models/outputhumiohec.py +162 -28
  171. cribl_control_plane/models/outputinfluxdb.py +165 -28
  172. cribl_control_plane/models/outputkafka.py +375 -52
  173. cribl_control_plane/models/outputkinesis.py +165 -27
  174. cribl_control_plane/models/outputloki.py +164 -34
  175. cribl_control_plane/models/outputmicrosoftfabric.py +540 -0
  176. cribl_control_plane/models/outputminio.py +225 -25
  177. cribl_control_plane/models/outputmsk.py +267 -54
  178. cribl_control_plane/models/outputnewrelic.py +171 -29
  179. cribl_control_plane/models/outputnewrelicevents.py +163 -28
  180. cribl_control_plane/models/outputopentelemetry.py +240 -40
  181. cribl_control_plane/models/outputprometheus.py +145 -26
  182. cribl_control_plane/models/outputring.py +49 -8
  183. cribl_control_plane/models/outputs3.py +233 -26
  184. cribl_control_plane/models/outputsecuritylake.py +179 -18
  185. cribl_control_plane/models/outputsentinel.py +172 -29
  186. cribl_control_plane/models/outputsentineloneaisiem.py +181 -35
  187. cribl_control_plane/models/outputservicenow.py +223 -38
  188. cribl_control_plane/models/outputsignalfx.py +145 -26
  189. cribl_control_plane/models/outputsns.py +143 -25
  190. cribl_control_plane/models/outputsplunk.py +206 -36
  191. cribl_control_plane/models/outputsplunkhec.py +238 -26
  192. cribl_control_plane/models/outputsplunklb.py +253 -43
  193. cribl_control_plane/models/outputsqs.py +163 -33
  194. cribl_control_plane/models/outputstatsd.py +127 -25
  195. cribl_control_plane/models/outputstatsdext.py +128 -25
  196. cribl_control_plane/models/outputsumologic.py +146 -25
  197. cribl_control_plane/models/outputsyslog.py +318 -46
  198. cribl_control_plane/models/outputtcpjson.py +186 -32
  199. cribl_control_plane/models/outputwavefront.py +145 -26
  200. cribl_control_plane/models/outputwebhook.py +211 -33
  201. cribl_control_plane/models/outputxsiam.py +143 -26
  202. cribl_control_plane/models/packinfo.py +8 -5
  203. cribl_control_plane/models/packinstallinfo.py +11 -8
  204. cribl_control_plane/models/productscore.py +2 -1
  205. cribl_control_plane/models/rbacresource.py +2 -1
  206. cribl_control_plane/models/resourcepolicy.py +15 -2
  207. cribl_control_plane/models/routeconf.py +3 -4
  208. cribl_control_plane/models/runnablejob.py +27 -0
  209. cribl_control_plane/models/runnablejobcollection.py +669 -0
  210. cribl_control_plane/models/runnablejobexecutor.py +368 -0
  211. cribl_control_plane/models/runnablejobscheduledsearch.py +286 -0
  212. cribl_control_plane/models/updateconfiggroupbyproductandidop.py +19 -2
  213. cribl_control_plane/models/updateconfiggroupdeploybyproductandidop.py +19 -2
  214. cribl_control_plane/models/updatecribllakedatasetbylakeidandidop.py +9 -5
  215. cribl_control_plane/models/updateinputhectokenbyidandtokenop.py +6 -5
  216. cribl_control_plane/models/updatepacksop.py +25 -0
  217. cribl_control_plane/models/updatepipelinebyidop.py +6 -6
  218. cribl_control_plane/models/updateroutesbyidop.py +2 -2
  219. cribl_control_plane/models/uploadpackresponse.py +13 -0
  220. cribl_control_plane/models/workertypes.py +2 -1
  221. cribl_control_plane/nodes.py +5 -3
  222. cribl_control_plane/packs.py +202 -7
  223. cribl_control_plane/pipelines.py +18 -18
  224. cribl_control_plane/routes_sdk.py +22 -22
  225. cribl_control_plane/sdk.py +19 -6
  226. cribl_control_plane/sources.py +5 -3
  227. cribl_control_plane/tokens.py +23 -15
  228. cribl_control_plane/utils/__init__.py +15 -3
  229. cribl_control_plane/utils/annotations.py +32 -8
  230. cribl_control_plane/utils/eventstreaming.py +10 -0
  231. cribl_control_plane/utils/retries.py +69 -5
  232. cribl_control_plane/utils/unmarshal_json_response.py +15 -1
  233. cribl_control_plane/versions.py +11 -6
  234. {cribl_control_plane-0.0.38.dist-info → cribl_control_plane-0.4.0a6.dist-info}/METADATA +69 -23
  235. cribl_control_plane-0.4.0a6.dist-info/RECORD +336 -0
  236. {cribl_control_plane-0.0.38.dist-info → cribl_control_plane-0.4.0a6.dist-info}/WHEEL +1 -1
  237. cribl_control_plane-0.4.0a6.dist-info/licenses/LICENSE +201 -0
  238. cribl_control_plane/errors/healthstatus_error.py +0 -32
  239. cribl_control_plane/models/appmode.py +0 -13
  240. cribl_control_plane/models/healthstatus.py +0 -33
  241. cribl_control_plane-0.0.38.dist-info/RECORD +0 -315
@@ -21,6 +21,7 @@ class Credentials:
21
21
  client_id: str
22
22
  client_secret: str
23
23
  token_url: str
24
+ scopes: Optional[List[str]]
24
25
  additional_properties: Dict[str, str]
25
26
 
26
27
  def __init__(
@@ -28,25 +29,27 @@ class Credentials:
28
29
  client_id: str,
29
30
  client_secret: str,
30
31
  token_url: str,
32
+ scopes: Optional[List[str]],
31
33
  additional_properties: Optional[Dict[str, str]] = None,
32
34
  ):
33
35
  self.client_id = client_id
34
36
  self.client_secret = client_secret
35
37
  self.token_url = token_url
38
+ self.scopes = scopes
36
39
  self.additional_properties = additional_properties or {}
37
40
 
38
41
 
39
42
  class Session:
40
43
  credentials: Credentials
41
44
  token: str
42
- scopes: Optional[List[str]] = None
45
+ scopes: List[str]
43
46
  expires_at: Optional[int] = None
44
47
 
45
48
  def __init__(
46
49
  self,
47
50
  credentials: Credentials,
48
51
  token: str,
49
- scopes: Optional[List[str]] = None,
52
+ scopes: List[str],
50
53
  expires_at: Optional[int] = None,
51
54
  ):
52
55
  self.credentials = credentials
@@ -57,7 +60,7 @@ class Session:
57
60
 
58
61
  class ClientCredentialsHook(SDKInitHook, BeforeRequestHook, AfterErrorHook):
59
62
  client: HttpClient
60
- sessions: Dict[str, Session] = {}
63
+ sessions: Dict[str, Dict[str, Session]] = {}
61
64
 
62
65
  def sdk_init(self, config: SDKConfiguration) -> SDKConfiguration:
63
66
  if config.client is None:
@@ -69,8 +72,7 @@ class ClientCredentialsHook(SDKInitHook, BeforeRequestHook, AfterErrorHook):
69
72
  def before_request(
70
73
  self, hook_ctx: BeforeRequestContext, request: httpx.Request
71
74
  ) -> httpx.Request:
72
- if hook_ctx.oauth2_scopes is None:
73
- # OAuth2 not in use
75
+ if self.is_hook_disabled(hook_ctx):
74
76
  return request
75
77
 
76
78
  credentials = self.get_credentials(hook_ctx)
@@ -81,22 +83,24 @@ class ClientCredentialsHook(SDKInitHook, BeforeRequestHook, AfterErrorHook):
81
83
  credentials.client_id, credentials.client_secret
82
84
  )
83
85
 
84
- if (
85
- session_key not in self.sessions
86
- or not self.has_required_scopes(
87
- self.sessions[session_key].scopes, hook_ctx.oauth2_scopes
88
- )
89
- or self.has_token_expired(self.sessions[session_key].expires_at)
90
- ):
91
- sess = self.do_token_request(
86
+ scopes = self.get_required_scopes(credentials, hook_ctx)
87
+ session = self.get_existing_session(session_key, scopes)
88
+
89
+ if session is None:
90
+ # Create new session
91
+ session = self.do_token_request(
92
92
  hook_ctx,
93
93
  credentials,
94
- self.get_scopes(hook_ctx.oauth2_scopes, self.sessions.get(session_key)),
94
+ scopes,
95
95
  )
96
96
 
97
- self.sessions[session_key] = sess
97
+ if session_key not in self.sessions:
98
+ self.sessions[session_key] = {}
99
+
100
+ scope_key = self.get_scope_key(scopes)
101
+ self.sessions[session_key][scope_key] = session
98
102
 
99
- request.headers["Authorization"] = f"Bearer {self.sessions[session_key].token}"
103
+ request.headers["Authorization"] = f"Bearer {session.token}"
100
104
 
101
105
  return request
102
106
 
@@ -106,8 +110,7 @@ class ClientCredentialsHook(SDKInitHook, BeforeRequestHook, AfterErrorHook):
106
110
  response: Optional[httpx.Response],
107
111
  error: Optional[Exception],
108
112
  ) -> Union[Tuple[Optional[httpx.Response], Optional[Exception]], Exception]:
109
- if hook_ctx.oauth2_scopes is None:
110
- # OAuth2 not in use
113
+ if self.is_hook_disabled(hook_ctx):
111
114
  return (response, error)
112
115
 
113
116
  # We don't want to refresh the token if the error is not related to the token
@@ -122,12 +125,15 @@ class ClientCredentialsHook(SDKInitHook, BeforeRequestHook, AfterErrorHook):
122
125
  session_key = self.get_session_key(
123
126
  credentials.client_id, credentials.client_secret
124
127
  )
125
-
126
- if session_key in self.sessions:
127
- del self.sessions[session_key]
128
+ scopes = self.get_required_scopes(credentials, hook_ctx)
129
+ scope_key = self.get_scope_key(scopes)
130
+ self.remove_session(session_key, scope_key)
128
131
 
129
132
  return (response, error)
130
133
 
134
+ def is_hook_disabled(self, hook_ctx: HookContext) -> bool:
135
+ return hook_ctx.oauth2_scopes is None
136
+
131
137
  def get_credentials(self, hook_ctx: HookContext) -> Optional[Credentials]:
132
138
  source = hook_ctx.security_source
133
139
 
@@ -145,21 +151,19 @@ class ClientCredentialsHook(SDKInitHook, BeforeRequestHook, AfterErrorHook):
145
151
  # Extract additional properties from security object
146
152
  additional_properties = {}
147
153
  for key, value in dict(security.client_oauth).items():
148
- if key not in ["client_id", "client_secret", "token_url"]:
154
+ if key not in ["client_id", "client_secret", "token_url", "scopes"]:
149
155
  additional_properties[key] = value
150
156
 
151
157
  return Credentials(
152
158
  client_id=security.client_oauth.client_id,
153
159
  client_secret=security.client_oauth.client_secret,
154
160
  token_url=security.client_oauth.token_url,
161
+ scopes=None,
155
162
  additional_properties=additional_properties,
156
163
  )
157
164
 
158
165
  def do_token_request(
159
- self,
160
- hook_ctx: HookContext,
161
- credentials: Credentials,
162
- scopes: Optional[List[str]],
166
+ self, hook_ctx: HookContext, credentials: Credentials, scopes: List[str]
163
167
  ) -> Session:
164
168
  payload = {
165
169
  "grant_type": "client_credentials",
@@ -167,7 +171,7 @@ class ClientCredentialsHook(SDKInitHook, BeforeRequestHook, AfterErrorHook):
167
171
  "client_secret": credentials.client_secret,
168
172
  }
169
173
 
170
- if scopes is not None and len(scopes) > 0:
174
+ if len(scopes) > 0:
171
175
  payload["scope"] = " ".join(scopes)
172
176
 
173
177
  # Add additional properties to payload
@@ -188,7 +192,7 @@ class ClientCredentialsHook(SDKInitHook, BeforeRequestHook, AfterErrorHook):
188
192
 
189
193
  response_data = response.json()
190
194
 
191
- if response_data.get("token_type") != "Bearer":
195
+ if response_data.get("token_type", "").lower() != "bearer":
192
196
  raise Exception("Unexpected token type from token endpoint")
193
197
 
194
198
  expires_at = None
@@ -203,24 +207,70 @@ class ClientCredentialsHook(SDKInitHook, BeforeRequestHook, AfterErrorHook):
203
207
  )
204
208
 
205
209
  def get_session_key(self, client_id: str, client_secret: str) -> str:
210
+ """Generate a consistent session key for the given client ID and secret."""
206
211
  return hashlib.md5(f"{client_id}:{client_secret}".encode()).hexdigest()
207
212
 
213
+ def get_required_scopes(
214
+ self, credentials: Credentials, hook_ctx: HookContext
215
+ ) -> List[str]:
216
+ """Return the list of scopes that need to be requested."""
217
+ if credentials.scopes is not None:
218
+ return credentials.scopes
219
+ return hook_ctx.oauth2_scopes or []
220
+
221
+ def get_scope_key(self, scopes: List[str]) -> str:
222
+ """Generate a consistent scope key for the given scopes."""
223
+ if not scopes:
224
+ return ""
225
+
226
+ sorted_scopes = sorted(scopes)
227
+ return "&".join(sorted_scopes)
228
+
229
+ def remove_session(self, client_key: str, scope_key: str) -> None:
230
+ """Remove a session and clean up empty client session maps."""
231
+ if client_key in self.sessions and scope_key in self.sessions[client_key]:
232
+ del self.sessions[client_key][scope_key]
233
+
234
+ # Clean up empty client sessions
235
+ if not self.sessions[client_key]:
236
+ del self.sessions[client_key]
237
+
238
+ def get_existing_session(
239
+ self, client_key: str, required_scopes: List[str]
240
+ ) -> Optional[Session]:
241
+ """Find the best session for the required scopes."""
242
+ if client_key not in self.sessions:
243
+ return None
244
+
245
+ client_sessions = self.sessions[client_key]
246
+ scope_key = self.get_scope_key(required_scopes)
247
+
248
+ if scope_key in client_sessions:
249
+ exact_match = client_sessions[scope_key]
250
+ if self.has_token_expired(exact_match.expires_at):
251
+ self.remove_session(client_key, scope_key)
252
+ else:
253
+ return exact_match
254
+
255
+ # If no exact match was found, look for a superset match
256
+ for key, session in client_sessions.items():
257
+ if self.has_token_expired(session.expires_at):
258
+ self.remove_session(client_key, key)
259
+ elif self.has_required_scopes(session.scopes, required_scopes):
260
+ return session
261
+
262
+ return None
263
+
208
264
  def has_required_scopes(
209
- self, scopes: Optional[List[str]], required_scopes: List[str]
265
+ self, scopes: List[str], required_scopes: List[str]
210
266
  ) -> bool:
211
- if scopes is None:
212
- return False
213
-
267
+ """Check if all required scopes are present in the given scopes."""
214
268
  return all(scope in scopes for scope in required_scopes)
215
269
 
216
- def get_scopes(
217
- self, required_scopes: List[str], sess: Optional[Session]
218
- ) -> List[str]:
219
- scopes = required_scopes.copy()
220
- if sess is not None and sess.scopes is not None:
221
- scopes.extend(sess.scopes)
222
- scopes = list(set(scopes))
223
- return scopes
224
-
225
270
  def has_token_expired(self, expires_at: Optional[int]) -> bool:
226
- return expires_at is None or time.time() + 60 >= expires_at
271
+ """
272
+ Check if the token has expired.
273
+ If no expires_in field was returned by the authorization server, the token is considered to never expire.
274
+ A 60-second buffer is applied to refresh tokens before they actually expire.
275
+ """
276
+ return expires_at is not None and time.time() + 60 >= expires_at
@@ -3,10 +3,10 @@
3
3
  import importlib.metadata
4
4
 
5
5
  __title__: str = "cribl-control-plane"
6
- __version__: str = "0.0.38"
7
- __openapi_doc_version__: str = "4.14.0-alpha.1756226637429-1513a21f"
8
- __gen_version__: str = "2.686.7"
9
- __user_agent__: str = "speakeasy-sdk/python 0.0.38 2.686.7 4.14.0-alpha.1756226637429-1513a21f cribl-control-plane"
6
+ __version__: str = "0.4.0a6"
7
+ __openapi_doc_version__: str = "4.15.0-alpha.1763072414698-97546d75"
8
+ __gen_version__: str = "2.763.3"
9
+ __user_agent__: str = "speakeasy-sdk/python 0.4.0a6 2.763.3 4.15.0-alpha.1763072414698-97546d75 cribl-control-plane"
10
10
 
11
11
  try:
12
12
  if __package__ is not None:
@@ -14,13 +14,15 @@ from typing import Any, Mapping, Optional
14
14
  class ACL(BaseSDK):
15
15
  teams: Teams
16
16
 
17
- def __init__(self, sdk_config: SDKConfiguration) -> None:
18
- BaseSDK.__init__(self, sdk_config)
17
+ def __init__(
18
+ self, sdk_config: SDKConfiguration, parent_ref: Optional[object] = None
19
+ ) -> None:
20
+ BaseSDK.__init__(self, sdk_config, parent_ref=parent_ref)
19
21
  self.sdk_configuration = sdk_config
20
22
  self._init_sdks()
21
23
 
22
24
  def _init_sdks(self):
23
- self.teams = Teams(self.sdk_configuration)
25
+ self.teams = Teams(self.sdk_configuration, parent_ref=self.parent_ref)
24
26
 
25
27
  def get(
26
28
  self,
@@ -3,15 +3,18 @@
3
3
  from .basesdk import BaseSDK
4
4
  from .sdkconfiguration import SDKConfiguration
5
5
  from cribl_control_plane.tokens import Tokens
6
+ from typing import Optional
6
7
 
7
8
 
8
9
  class AuthSDK(BaseSDK):
9
10
  tokens: Tokens
10
11
 
11
- def __init__(self, sdk_config: SDKConfiguration) -> None:
12
- BaseSDK.__init__(self, sdk_config)
12
+ def __init__(
13
+ self, sdk_config: SDKConfiguration, parent_ref: Optional[object] = None
14
+ ) -> None:
15
+ BaseSDK.__init__(self, sdk_config, parent_ref=parent_ref)
13
16
  self.sdk_configuration = sdk_config
14
17
  self._init_sdks()
15
18
 
16
19
  def _init_sdks(self):
17
- self.tokens = Tokens(self.sdk_configuration)
20
+ self.tokens = Tokens(self.sdk_configuration, parent_ref=self.parent_ref)
@@ -19,9 +19,19 @@ from urllib.parse import parse_qs, urlparse
19
19
 
20
20
  class BaseSDK:
21
21
  sdk_configuration: SDKConfiguration
22
+ parent_ref: Optional[object] = None
23
+ """
24
+ Reference to the root SDK instance, if any. This will prevent it from
25
+ being garbage collected while there are active streams.
26
+ """
22
27
 
23
- def __init__(self, sdk_config: SDKConfiguration) -> None:
28
+ def __init__(
29
+ self,
30
+ sdk_config: SDKConfiguration,
31
+ parent_ref: Optional[object] = None,
32
+ ) -> None:
24
33
  self.sdk_configuration = sdk_config
34
+ self.parent_ref = parent_ref
25
35
 
26
36
  def _get_url(self, base_url, url_variables):
27
37
  sdk_url, sdk_variables = self.sdk_configuration.get_server_details()
@@ -14,13 +14,15 @@ from typing import Any, List, Mapping, Optional
14
14
  class Commits(BaseSDK):
15
15
  files: CommitsFiles
16
16
 
17
- def __init__(self, sdk_config: SDKConfiguration) -> None:
18
- BaseSDK.__init__(self, sdk_config)
17
+ def __init__(
18
+ self, sdk_config: SDKConfiguration, parent_ref: Optional[object] = None
19
+ ) -> None:
20
+ BaseSDK.__init__(self, sdk_config, parent_ref=parent_ref)
19
21
  self.sdk_configuration = sdk_config
20
22
  self._init_sdks()
21
23
 
22
24
  def _init_sdks(self):
23
- self.files = CommitsFiles(self.sdk_configuration)
25
+ self.files = CommitsFiles(self.sdk_configuration, parent_ref=self.parent_ref)
24
26
 
25
27
  def create(
26
28
  self,
@@ -779,7 +781,7 @@ class Commits(BaseSDK):
779
781
  Revert a commit in the local repository.
780
782
 
781
783
  :param commit:
782
- :param group_id: Group ID
784
+ :param group_id: The <code>id</code> of the Worker Group or Edge Fleet to revert the commit for. Required in Distributed deployments. Omit in Single-instance deployments.
783
785
  :param force:
784
786
  :param message:
785
787
  :param retries: Override the default retry configuration for this method
@@ -880,7 +882,7 @@ class Commits(BaseSDK):
880
882
  Revert a commit in the local repository.
881
883
 
882
884
  :param commit:
883
- :param group_id: Group ID
885
+ :param group_id: The <code>id</code> of the Worker Group or Edge Fleet to revert the commit for. Required in Distributed deployments. Omit in Single-instance deployments.
884
886
  :param force:
885
887
  :param message:
886
888
  :param retries: Override the default retry configuration for this method
@@ -18,14 +18,16 @@ class Destinations(BaseSDK):
18
18
  pq: DestinationsPq
19
19
  samples: Samples
20
20
 
21
- def __init__(self, sdk_config: SDKConfiguration) -> None:
22
- BaseSDK.__init__(self, sdk_config)
21
+ def __init__(
22
+ self, sdk_config: SDKConfiguration, parent_ref: Optional[object] = None
23
+ ) -> None:
24
+ BaseSDK.__init__(self, sdk_config, parent_ref=parent_ref)
23
25
  self.sdk_configuration = sdk_config
24
26
  self._init_sdks()
25
27
 
26
28
  def _init_sdks(self):
27
- self.pq = DestinationsPq(self.sdk_configuration)
28
- self.samples = Samples(self.sdk_configuration)
29
+ self.pq = DestinationsPq(self.sdk_configuration, parent_ref=self.parent_ref)
30
+ self.samples = Samples(self.sdk_configuration, parent_ref=self.parent_ref)
29
31
 
30
32
  def list(
31
33
  self,
@@ -83,7 +83,7 @@ class DestinationsPq(BaseSDK):
83
83
  )
84
84
 
85
85
  response_data: Any = None
86
- if utils.match_response(http_res, "200", "application/json"):
86
+ if utils.match_response(http_res, "201", "application/json"):
87
87
  return unmarshal_json_response(models.DeleteOutputPqByIDResponse, http_res)
88
88
  if utils.match_response(http_res, "500", "application/json"):
89
89
  response_data = unmarshal_json_response(errors.ErrorData, http_res)
@@ -170,7 +170,7 @@ class DestinationsPq(BaseSDK):
170
170
  )
171
171
 
172
172
  response_data: Any = None
173
- if utils.match_response(http_res, "200", "application/json"):
173
+ if utils.match_response(http_res, "201", "application/json"):
174
174
  return unmarshal_json_response(models.DeleteOutputPqByIDResponse, http_res)
175
175
  if utils.match_response(http_res, "500", "application/json"):
176
176
  response_data = unmarshal_json_response(errors.ErrorData, http_res)
@@ -1,14 +1,18 @@
1
1
  """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
2
2
 
3
+ from .criblcontrolplaneerror import CriblControlPlaneError
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 .apierror import APIError
9
- from .criblcontrolplaneerror import CriblControlPlaneError
10
11
  from .error import Error, ErrorData
11
- from .healthstatus_error import HealthStatusError, HealthStatusErrorData
12
+ from .healthserverstatus_error import (
13
+ HealthServerStatusError,
14
+ HealthServerStatusErrorData,
15
+ )
12
16
  from .no_response_error import NoResponseError
13
17
  from .responsevalidationerror import ResponseValidationError
14
18
 
@@ -17,24 +21,35 @@ __all__ = [
17
21
  "CriblControlPlaneError",
18
22
  "Error",
19
23
  "ErrorData",
20
- "HealthStatusError",
21
- "HealthStatusErrorData",
24
+ "HealthServerStatusError",
25
+ "HealthServerStatusErrorData",
22
26
  "NoResponseError",
23
27
  "ResponseValidationError",
24
28
  ]
25
29
 
26
30
  _dynamic_imports: dict[str, str] = {
27
31
  "APIError": ".apierror",
28
- "CriblControlPlaneError": ".criblcontrolplaneerror",
29
32
  "Error": ".error",
30
33
  "ErrorData": ".error",
31
- "HealthStatusError": ".healthstatus_error",
32
- "HealthStatusErrorData": ".healthstatus_error",
34
+ "HealthServerStatusError": ".healthserverstatus_error",
35
+ "HealthServerStatusErrorData": ".healthserverstatus_error",
33
36
  "NoResponseError": ".no_response_error",
34
37
  "ResponseValidationError": ".responsevalidationerror",
35
38
  }
36
39
 
37
40
 
41
+ def dynamic_import(modname, retries=3):
42
+ for attempt in range(retries):
43
+ try:
44
+ return import_module(modname, __package__)
45
+ except KeyError:
46
+ # Clear any half-initialized module and retry
47
+ sys.modules.pop(modname, None)
48
+ if attempt == retries - 1:
49
+ break
50
+ raise KeyError(f"Failed to import module '{modname}' after {retries} attempts")
51
+
52
+
38
53
  def __getattr__(attr_name: str) -> object:
39
54
  module_name = _dynamic_imports.get(attr_name)
40
55
  if module_name is None:
@@ -43,7 +58,7 @@ def __getattr__(attr_name: str) -> object:
43
58
  )
44
59
 
45
60
  try:
46
- module = import_module(module_name, __package__)
61
+ module = dynamic_import(module_name)
47
62
  result = getattr(module, attr_name)
48
63
  return result
49
64
  except ImportError as e:
@@ -2,12 +2,14 @@
2
2
 
3
3
  import httpx
4
4
  from typing import Optional
5
+ from dataclasses import dataclass
5
6
 
6
7
  from cribl_control_plane.errors import CriblControlPlaneError
7
8
 
8
9
  MAX_MESSAGE_LEN = 10_000
9
10
 
10
11
 
12
+ @dataclass(unsafe_hash=True)
11
13
  class APIError(CriblControlPlaneError):
12
14
  """The fallback error class if no more specific error class is matched."""
13
15
 
@@ -2,25 +2,29 @@
2
2
 
3
3
  import httpx
4
4
  from typing import Optional
5
+ from dataclasses import dataclass, field
5
6
 
6
7
 
8
+ @dataclass(unsafe_hash=True)
7
9
  class CriblControlPlaneError(Exception):
8
10
  """The base class for all HTTP error responses."""
9
11
 
10
12
  message: str
11
13
  status_code: int
12
14
  body: str
13
- headers: httpx.Headers
14
- raw_response: httpx.Response
15
+ headers: httpx.Headers = field(hash=False)
16
+ raw_response: httpx.Response = field(hash=False)
15
17
 
16
18
  def __init__(
17
19
  self, message: str, raw_response: httpx.Response, body: Optional[str] = None
18
20
  ):
19
- self.message = message
20
- self.status_code = raw_response.status_code
21
- self.body = body if body is not None else raw_response.text
22
- self.headers = raw_response.headers
23
- self.raw_response = raw_response
21
+ object.__setattr__(self, "message", message)
22
+ object.__setattr__(self, "status_code", raw_response.status_code)
23
+ object.__setattr__(
24
+ self, "body", body if body is not None else raw_response.text
25
+ )
26
+ object.__setattr__(self, "headers", raw_response.headers)
27
+ object.__setattr__(self, "raw_response", raw_response)
24
28
 
25
29
  def __str__(self):
26
30
  return self.message
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
  from cribl_control_plane.errors import CriblControlPlaneError
5
5
  from cribl_control_plane.types import BaseModel
6
+ from dataclasses import dataclass, field
6
7
  import httpx
7
8
  from typing import Optional
8
9
 
@@ -12,8 +13,9 @@ class ErrorData(BaseModel):
12
13
  r"""Error message"""
13
14
 
14
15
 
16
+ @dataclass(unsafe_hash=True)
15
17
  class Error(CriblControlPlaneError):
16
- data: ErrorData
18
+ data: ErrorData = field(hash=False)
17
19
 
18
20
  def __init__(
19
21
  self, data: ErrorData, raw_response: httpx.Response, body: Optional[str] = None
@@ -21,4 +23,4 @@ class Error(CriblControlPlaneError):
21
23
  fallback = body or raw_response.text
22
24
  message = str(data.message) or fallback
23
25
  super().__init__(message, raw_response, body)
24
- self.data = data
26
+ object.__setattr__(self, "data", data)
@@ -0,0 +1,41 @@
1
+ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
2
+
3
+ from __future__ import annotations
4
+ from cribl_control_plane.errors import CriblControlPlaneError
5
+ from cribl_control_plane.models import healthserverstatus as models_healthserverstatus
6
+ from cribl_control_plane.types import BaseModel
7
+ from cribl_control_plane.utils import validate_open_enum
8
+ from dataclasses import dataclass, field
9
+ import httpx
10
+ import pydantic
11
+ from pydantic.functional_validators import PlainValidator
12
+ from typing import Optional
13
+ from typing_extensions import Annotated
14
+
15
+
16
+ class HealthServerStatusErrorData(BaseModel):
17
+ start_time: Annotated[float, pydantic.Field(alias="startTime")]
18
+
19
+ status: Annotated[
20
+ models_healthserverstatus.Status, PlainValidator(validate_open_enum(False))
21
+ ]
22
+
23
+ role: Annotated[
24
+ Optional[models_healthserverstatus.Role],
25
+ PlainValidator(validate_open_enum(False)),
26
+ ] = None
27
+
28
+
29
+ @dataclass(unsafe_hash=True)
30
+ class HealthServerStatusError(CriblControlPlaneError):
31
+ data: HealthServerStatusErrorData = field(hash=False)
32
+
33
+ def __init__(
34
+ self,
35
+ data: HealthServerStatusErrorData,
36
+ raw_response: httpx.Response,
37
+ body: Optional[str] = None,
38
+ ):
39
+ message = body or raw_response.text
40
+ super().__init__(message, raw_response, body)
41
+ object.__setattr__(self, "data", data)
@@ -1,12 +1,16 @@
1
1
  """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
2
2
 
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(unsafe_hash=True)
3
7
  class NoResponseError(Exception):
4
8
  """Error raised when no HTTP response is received from the server."""
5
9
 
6
10
  message: str
7
11
 
8
12
  def __init__(self, message: str = "No response received"):
9
- self.message = message
13
+ object.__setattr__(self, "message", message)
10
14
  super().__init__(message)
11
15
 
12
16
  def __str__(self):
@@ -2,10 +2,12 @@
2
2
 
3
3
  import httpx
4
4
  from typing import Optional
5
+ from dataclasses import dataclass
5
6
 
6
7
  from cribl_control_plane.errors import CriblControlPlaneError
7
8
 
8
9
 
10
+ @dataclass(unsafe_hash=True)
9
11
  class ResponseValidationError(CriblControlPlaneError):
10
12
  """Error raised when there is a type mismatch between the response data and the expected Pydantic model."""
11
13
 
@@ -3,15 +3,20 @@
3
3
  from .basesdk import BaseSDK
4
4
  from .sdkconfiguration import SDKConfiguration
5
5
  from cribl_control_plane.configs_versions import ConfigsVersions
6
+ from typing import Optional
6
7
 
7
8
 
8
9
  class GroupsConfigs(BaseSDK):
9
10
  versions: ConfigsVersions
10
11
 
11
- def __init__(self, sdk_config: SDKConfiguration) -> None:
12
- BaseSDK.__init__(self, sdk_config)
12
+ def __init__(
13
+ self, sdk_config: SDKConfiguration, parent_ref: Optional[object] = None
14
+ ) -> None:
15
+ BaseSDK.__init__(self, sdk_config, parent_ref=parent_ref)
13
16
  self.sdk_configuration = sdk_config
14
17
  self._init_sdks()
15
18
 
16
19
  def _init_sdks(self):
17
- self.versions = ConfigsVersions(self.sdk_configuration)
20
+ self.versions = ConfigsVersions(
21
+ self.sdk_configuration, parent_ref=self.parent_ref
22
+ )