scalekit-sdk-python 2.2.2__tar.gz → 2.3.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 (141) hide show
  1. {scalekit_sdk_python-2.2.2/scalekit_sdk_python.egg-info → scalekit_sdk_python-2.3.0}/PKG-INFO +1 -1
  2. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/client.py +97 -21
  3. scalekit_sdk_python-2.3.0/scalekit/common/exceptions.py +260 -0
  4. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/common/scalekit.py +15 -2
  5. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/core.py +24 -37
  6. scalekit_sdk_python-2.3.0/scalekit/passwordless.py +145 -0
  7. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0/scalekit_sdk_python.egg-info}/PKG-INFO +1 -1
  8. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit_sdk_python.egg-info/SOURCES.txt +2 -0
  9. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/setup.py +1 -1
  10. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/LICENSE +0 -0
  11. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/README.md +0 -0
  12. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/buf/__init__.py +0 -0
  13. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/buf/validate/__init__.py +0 -0
  14. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/buf/validate/expression_pb2.py +0 -0
  15. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/buf/validate/expression_pb2.pyi +0 -0
  16. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/buf/validate/expression_pb2_grpc.py +0 -0
  17. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/buf/validate/priv/__init__.py +0 -0
  18. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/buf/validate/priv/private_pb2.py +0 -0
  19. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/buf/validate/priv/private_pb2.pyi +0 -0
  20. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/buf/validate/priv/private_pb2_grpc.py +0 -0
  21. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/buf/validate/validate_pb2.py +0 -0
  22. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/buf/validate/validate_pb2.pyi +0 -0
  23. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/buf/validate/validate_pb2_grpc.py +0 -0
  24. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/__init__.py +0 -0
  25. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/common/__init__.py +0 -0
  26. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/common/user.py +0 -0
  27. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/connection.py +0 -0
  28. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/constants/__init__.py +0 -0
  29. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/constants/user.py +0 -0
  30. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/directory.py +0 -0
  31. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/domain.py +0 -0
  32. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/m2m_client.py +0 -0
  33. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/organization.py +0 -0
  34. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/role.py +0 -0
  35. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/users.py +0 -0
  36. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/utils/__init__.py +0 -0
  37. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/utils/directory.py +0 -0
  38. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/__init__.py +0 -0
  39. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/auditlogs/__init__.py +0 -0
  40. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/auditlogs/auditlogs_pb2.py +0 -0
  41. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/auditlogs/auditlogs_pb2.pyi +0 -0
  42. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/auditlogs/auditlogs_pb2_grpc.py +0 -0
  43. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/auth/__init__.py +0 -0
  44. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/auth/auth_pb2.py +0 -0
  45. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/auth/auth_pb2.pyi +0 -0
  46. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/auth/auth_pb2_grpc.py +0 -0
  47. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/auth/passwordless_pb2.py +0 -0
  48. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/auth/passwordless_pb2.pyi +0 -0
  49. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/auth/passwordless_pb2_grpc.py +0 -0
  50. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/clients/__init__.py +0 -0
  51. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/clients/clients_pb2.py +0 -0
  52. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/clients/clients_pb2.pyi +0 -0
  53. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/clients/clients_pb2_grpc.py +0 -0
  54. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/commons/__init__.py +0 -0
  55. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/commons/commons_pb2.py +0 -0
  56. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/commons/commons_pb2.pyi +0 -0
  57. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/commons/commons_pb2_grpc.py +0 -0
  58. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/connected_accounts/__init__.py +0 -0
  59. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/connected_accounts/connected_accounts_pb2.py +0 -0
  60. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/connected_accounts/connected_accounts_pb2.pyi +0 -0
  61. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/connected_accounts/connected_accounts_pb2_grpc.py +0 -0
  62. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/connections/__init__.py +0 -0
  63. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/connections/connections_pb2.py +0 -0
  64. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/connections/connections_pb2.pyi +0 -0
  65. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/connections/connections_pb2_grpc.py +0 -0
  66. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/directories/__init__.py +0 -0
  67. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/directories/directories_pb2.py +0 -0
  68. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/directories/directories_pb2.pyi +0 -0
  69. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/directories/directories_pb2_grpc.py +0 -0
  70. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/domains/__init__.py +0 -0
  71. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/domains/domains_pb2.py +0 -0
  72. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/domains/domains_pb2.pyi +0 -0
  73. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/domains/domains_pb2_grpc.py +0 -0
  74. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/emails/__init__.py +0 -0
  75. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/emails/emails_pb2.py +0 -0
  76. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/emails/emails_pb2.pyi +0 -0
  77. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/emails/emails_pb2_grpc.py +0 -0
  78. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/environments/__init__.py +0 -0
  79. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/environments/environments_pb2.py +0 -0
  80. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/environments/environments_pb2.pyi +0 -0
  81. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/environments/environments_pb2_grpc.py +0 -0
  82. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/errdetails/__init__.py +0 -0
  83. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/errdetails/errdetails_pb2.py +0 -0
  84. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/errdetails/errdetails_pb2.pyi +0 -0
  85. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/errdetails/errdetails_pb2_grpc.py +0 -0
  86. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/events/__init__.py +0 -0
  87. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/events/events_pb2.py +0 -0
  88. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/events/events_pb2.pyi +0 -0
  89. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/events/events_pb2_grpc.py +0 -0
  90. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/invites/__init__.py +0 -0
  91. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/invites/invites_pb2.py +0 -0
  92. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/invites/invites_pb2.pyi +0 -0
  93. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/invites/invites_pb2_grpc.py +0 -0
  94. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/members/__init__.py +0 -0
  95. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/members/members_pb2.py +0 -0
  96. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/members/members_pb2.pyi +0 -0
  97. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/members/members_pb2_grpc.py +0 -0
  98. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/migrations/__init__.py +0 -0
  99. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/migrations/migrations_pb2.py +0 -0
  100. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/migrations/migrations_pb2.pyi +0 -0
  101. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/migrations/migrations_pb2_grpc.py +0 -0
  102. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/options/__init__.py +0 -0
  103. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/options/options_pb2.py +0 -0
  104. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/options/options_pb2.pyi +0 -0
  105. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/options/options_pb2_grpc.py +0 -0
  106. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/organizations/__init__.py +0 -0
  107. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/organizations/organizations_pb2.py +0 -0
  108. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/organizations/organizations_pb2.pyi +0 -0
  109. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/organizations/organizations_pb2_grpc.py +0 -0
  110. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/providers/__init__.py +0 -0
  111. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/providers/providers_pb2.py +0 -0
  112. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/providers/providers_pb2.pyi +0 -0
  113. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/providers/providers_pb2_grpc.py +0 -0
  114. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/roles/__init__.py +0 -0
  115. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/roles/roles_pb2.py +0 -0
  116. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/roles/roles_pb2.pyi +0 -0
  117. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/roles/roles_pb2_grpc.py +0 -0
  118. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/tools/__init__.py +0 -0
  119. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/tools/tools_pb2.py +0 -0
  120. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/tools/tools_pb2.pyi +0 -0
  121. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/tools/tools_pb2_grpc.py +0 -0
  122. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/user_attributes/__init__.py +0 -0
  123. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/user_attributes/user_attributes_pb2.py +0 -0
  124. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/user_attributes/user_attributes_pb2.pyi +0 -0
  125. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/user_attributes/user_attributes_pb2_grpc.py +0 -0
  126. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/users/__init__.py +0 -0
  127. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/users/users_pb2.py +0 -0
  128. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/users/users_pb2.pyi +0 -0
  129. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/users/users_pb2_grpc.py +0 -0
  130. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/webhooks/__init__.py +0 -0
  131. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/webhooks/webhooks_pb2.py +0 -0
  132. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/webhooks/webhooks_pb2.pyi +0 -0
  133. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/webhooks/webhooks_pb2_grpc.py +0 -0
  134. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/workspaces/__init__.py +0 -0
  135. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/workspaces/workspaces_pb2.py +0 -0
  136. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/workspaces/workspaces_pb2.pyi +0 -0
  137. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit/v1/workspaces/workspaces_pb2_grpc.py +0 -0
  138. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit_sdk_python.egg-info/dependency_links.txt +0 -0
  139. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit_sdk_python.egg-info/requires.txt +0 -0
  140. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/scalekit_sdk_python.egg-info/top_level.txt +0 -0
  141. {scalekit_sdk_python-2.2.2 → scalekit_sdk_python-2.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scalekit-sdk-python
3
- Version: 2.2.2
3
+ Version: 2.3.0
4
4
  Summary: Scalekit official Python SDK
5
5
  Home-page: https://github.com/scalekit-inc/scalekit-sdk-python
6
6
  Author: Team Scalekit
@@ -16,14 +16,18 @@ from scalekit.organization import OrganizationClient
16
16
  from scalekit.directory import DirectoryClient
17
17
  from scalekit.users import UserClient
18
18
  from scalekit.role import RoleClient
19
+ from scalekit.passwordless import PasswordlessClient
19
20
  from scalekit.common.scalekit import (
20
21
  AuthorizationUrlOptions,
21
22
  CodeAuthenticationOptions,
22
23
  GrantType,
23
24
  IdpInitiatedLoginClaims,
24
25
  LogoutUrlOptions,
26
+ TokenValidationOptions,
25
27
  )
26
28
  from scalekit.constants.user import id_token_claim_to_user_map
29
+ from scalekit.common.exceptions import (WebhookVerificationError,
30
+ ScalekitValidateTokenFailureException)
27
31
 
28
32
  AUTHORIZE_ENDPOINT = "oauth/authorize"
29
33
  LOGOUT_ENDPOINT = "oidc/logout"
@@ -31,10 +35,6 @@ webhook_tolerance_in_seconds = timedelta(minutes=5)
31
35
  webhook_signature_version = "v1"
32
36
 
33
37
 
34
- class WebhookVerificationError(Exception):
35
- pass
36
-
37
-
38
38
  class ScalekitClient:
39
39
  """ Class definition for scalekit client """
40
40
 
@@ -54,8 +54,7 @@ class ScalekitClient:
54
54
  """
55
55
  try:
56
56
  self.core_client = CoreClient(
57
- env_url=env_url, client_id=client_id, client_secret=client_secret
58
- )
57
+ env_url=env_url, client_id=client_id, client_secret=client_secret)
59
58
  self.domain = DomainClient(self.core_client)
60
59
  self.connection = ConnectionClient(self.core_client)
61
60
  self.organization = OrganizationClient(self.core_client)
@@ -63,6 +62,7 @@ class ScalekitClient:
63
62
  self.m2m_client = M2MClient(self.core_client)
64
63
  self.users = UserClient(self.core_client)
65
64
  self.roles = RoleClient(self.core_client)
65
+ self.passwordless = PasswordlessClient(self.core_client)
66
66
  except Exception as exp:
67
67
  raise exp
68
68
 
@@ -140,7 +140,7 @@ class ScalekitClient:
140
140
  access_token = response["access_token"]
141
141
  refresh_token = response.get("refresh_token")
142
142
  # Validate id_token
143
- claims = self.__validate_token(id_token, {"verify_aud": False})
143
+ claims = self.__validate_token(id_token, options=None)
144
144
  user = {}
145
145
  amr_claims = claims.get('amr', [])
146
146
  connection_id = amr_claims[0] if amr_claims else None
@@ -158,21 +158,25 @@ class ScalekitClient:
158
158
  "organization_id": organization_id
159
159
  }
160
160
 
161
+ except jwt.exceptions.InvalidTokenError as exp:
162
+ raise ScalekitValidateTokenFailureException(exp)
161
163
  except Exception as exp:
162
164
  raise exp
163
165
 
164
- def validate_access_token(self, token: str, options: Optional[Dict] = None, audience = None) -> bool:
166
+ def validate_access_token(self, token: str, options: Optional[TokenValidationOptions] = None, audience = None) -> bool:
165
167
  """
166
168
  Method to validate access token
167
169
 
168
170
  :param token : access token
169
171
  :type : ``` str ```
172
+ :param options : Optional validation options for issuer, audience, and scopes
173
+ :type : ``` TokenValidationOptions ```
174
+ :param audience : audience for validation (deprecated, use options.audience instead)
175
+ :type : ``` str ```
170
176
 
171
177
  :returns:
172
178
  bool
173
179
  """
174
- options = options if options else {}
175
- options["verify_aud"] = False if not audience else True
176
180
  try:
177
181
  self.__validate_token(token, options=options, audience=audience)
178
182
  return True
@@ -205,59 +209,131 @@ class ScalekitClient:
205
209
  except Exception as exp:
206
210
  raise exp
207
211
 
208
- def validate_access_token_and_get_claims(self, token: str, options: Optional[Dict] = None, audience = None) -> Dict[str, Any]:
212
+ def validate_access_token_and_get_claims(self, token: str, options: Optional[TokenValidationOptions] = None, audience = None) -> Dict[str, Any]:
209
213
  """
210
214
  Method to validate access token and get claims
211
215
 
212
216
  :param token : access token
213
217
  :type : ``` str ```
218
+ :param options : Optional validation options for issuer, audience, and scopes
219
+ :type : ``` TokenValidationOptions ```
220
+ :param audience : audience for validation (deprecated, use options.audience instead)
221
+ :type : ``` str ```
214
222
 
215
223
  :returns:
216
224
  claims
217
225
  """
218
-
219
- options = options if options else {}
220
- options["verify_aud"] = False if not audience else True
221
226
 
222
227
  try:
223
228
  claims = self.__validate_token(token, options=options, audience=audience)
224
229
  return claims
230
+ except jwt.exceptions.InvalidTokenError as exp:
231
+ raise ScalekitValidateTokenFailureException(exp)
225
232
  except Exception as exp:
226
- raise exp
233
+ raise exp
227
234
 
228
- def get_idp_initiated_login_claims(self, idp_initiated_login_token: str) -> IdpInitiatedLoginClaims:
235
+ def get_idp_initiated_login_claims(self, idp_initiated_login_token: str, options: Optional[TokenValidationOptions] = None, audience = None) -> IdpInitiatedLoginClaims:
229
236
  """
230
237
  Method to get IDP initiated login claims
231
238
 
232
239
  :param idp_initiated_login_token : IDP initiated login token
233
240
  :type : ``` str ```
241
+ :param options : Optional validation options for issuer and audience
242
+ :type : ``` TokenValidationOptions ```
243
+ :param audience : audience for validation (deprecated, use options.audience instead)
244
+ :type : ``` str ```
234
245
 
235
246
  :returns:
236
247
  ``` IdpInitiatedLoginClaims ```
237
248
  """
238
249
  try:
239
- claims = self.__validate_token(idp_initiated_login_token, {"verify_aud": False})
250
+ claims = self.__validate_token(idp_initiated_login_token, options=options, audience=audience)
240
251
  return claims
252
+ except jwt.exceptions.InvalidTokenError as exp:
253
+ raise ScalekitValidateTokenFailureException(exp)
241
254
  except Exception as exp:
242
255
  raise exp
243
256
 
244
257
  def __validate_token(
245
- self, token: str, options: Optional[Dict] = None, audience: Optional[str] = None
258
+ self, token: str, options: Optional[TokenValidationOptions] = None, audience: Optional[str] = None
246
259
  ) -> Dict[str, Any]:
247
260
  """
248
261
  Method to validate token
249
262
 
250
263
  :param token : token
251
264
  :type : ``` str ```
265
+ :param options : validation options for issuer, audience, and scopes
266
+ :type : ``` TokenValidationOptions ```
267
+ :param audience : audience for validation
268
+ :type : ``` str ```
252
269
 
253
270
  :returns:
254
271
  payload
255
272
  """
273
+ # Convert TokenValidationOptions to jwt decode options if provided
274
+ jwt_options = {}
275
+ if options:
276
+ if options.issuer:
277
+ jwt_options["issuer"] = options.issuer
278
+ if options.audience:
279
+ jwt_options["audience"] = options.audience
280
+ jwt_options["verify_aud"] = True
281
+ elif audience is not None:
282
+ jwt_options["audience"] = [audience]
283
+ jwt_options["verify_aud"] = True
284
+ else:
285
+ jwt_options["verify_aud"] = False
286
+
256
287
  self.core_client.get_jwks()
257
288
  kid = jwt.get_unverified_header(token)["kid"]
258
289
  key = self.core_client.keys[kid]
259
290
 
260
- return jwt.decode(token, key=key, algorithms="RS256", options=options, audience=audience)
291
+ payload = jwt.decode(token, key=key, algorithms="RS256", options=jwt_options)
292
+
293
+ # Validate scopes if required
294
+ if options and options.required_scopes:
295
+ self.verify_scopes(token, options.required_scopes)
296
+
297
+ return payload
298
+
299
+
300
+
301
+ def verify_scopes(self, token: str, required_scopes: list[str]) -> bool:
302
+ """
303
+ Verify that the token contains the required scopes
304
+
305
+ :param token : The token to verify
306
+ :type : ``` str ```
307
+ :param required_scopes : The scopes that must be present in the token
308
+ :type : ``` list[str] ```
309
+
310
+ :returns:
311
+ bool: Returns True if all required scopes are present
312
+ :raises:
313
+ Error: If required scopes are missing, with details about which scopes are missing
314
+ """
315
+ payload = jwt.decode(token, options={"verify_signature": False})
316
+ scopes = self.__extract_scopes_from_payload(payload)
317
+
318
+ missing_scopes = [scope for scope in required_scopes if scope not in scopes]
319
+
320
+ if missing_scopes:
321
+ raise ScalekitValidateTokenFailureException(f"Token missing required scopes: {', '.join(missing_scopes)}")
322
+
323
+ return True
324
+
325
+ def __extract_scopes_from_payload(self, payload: Dict[str, Any]) -> list[str]:
326
+ """
327
+ Extract scopes from token payload
328
+
329
+ :param payload : The token payload
330
+ :type : ``` Dict[str, Any] ```
331
+
332
+ :returns:
333
+ list[str]: Array of scopes found in the token
334
+ """
335
+ scopes = payload.get("scopes", [])
336
+ return [scope for scope in scopes if scope and scope.strip()]
261
337
 
262
338
  def verify_webhook_payload(self, secret: str, headers: Dict[str, str], payload: [str | bytes]) -> bool:
263
339
  """
@@ -334,10 +410,10 @@ class ScalekitClient:
334
410
  raise WebhookVerificationError("Invalid Signature Headers")
335
411
 
336
412
  if timestamp < (now - webhook_tolerance_in_seconds):
337
- raise Exception("Message timestamp too old")
413
+ raise WebhookVerificationError("Message timestamp too old")
338
414
 
339
415
  if timestamp > (now + webhook_tolerance_in_seconds):
340
- raise Exception("Message timestamp too new")
416
+ raise WebhookVerificationError("Message timestamp too new")
341
417
 
342
418
  return timestamp
343
419
 
@@ -0,0 +1,260 @@
1
+
2
+ import grpc
3
+ from grpc import StatusCode
4
+ from http import HTTPStatus
5
+ from grpc_status import rpc_status
6
+ from requests.models import Response
7
+ from scalekit.v1.errdetails.errdetails_pb2 import ErrorInfo
8
+
9
+
10
+ GRPC_TO_HTTP = {
11
+ StatusCode.OK: HTTPStatus.OK,
12
+ StatusCode.INVALID_ARGUMENT: HTTPStatus.BAD_REQUEST,
13
+ StatusCode.FAILED_PRECONDITION: HTTPStatus.BAD_REQUEST,
14
+ StatusCode.OUT_OF_RANGE: HTTPStatus.BAD_REQUEST,
15
+ StatusCode.UNAUTHENTICATED: HTTPStatus.UNAUTHORIZED,
16
+ StatusCode.PERMISSION_DENIED: HTTPStatus.FORBIDDEN,
17
+ StatusCode.NOT_FOUND: HTTPStatus.NOT_FOUND,
18
+ StatusCode.ALREADY_EXISTS: HTTPStatus.CONFLICT,
19
+ StatusCode.ABORTED: HTTPStatus.CONFLICT,
20
+ StatusCode.RESOURCE_EXHAUSTED: HTTPStatus.TOO_MANY_REQUESTS,
21
+ StatusCode.CANCELLED: 499,
22
+ StatusCode.DATA_LOSS: HTTPStatus.INTERNAL_SERVER_ERROR,
23
+ StatusCode.UNKNOWN: HTTPStatus.INTERNAL_SERVER_ERROR,
24
+ StatusCode.INTERNAL: HTTPStatus.INTERNAL_SERVER_ERROR,
25
+ StatusCode.UNIMPLEMENTED: HTTPStatus.NOT_IMPLEMENTED,
26
+ StatusCode.UNAVAILABLE: HTTPStatus.SERVICE_UNAVAILABLE,
27
+ StatusCode.DEADLINE_EXCEEDED: HTTPStatus.GATEWAY_TIMEOUT,
28
+ }
29
+
30
+ HTTP_TO_GRPC = {
31
+ HTTPStatus.OK: StatusCode.OK,
32
+ HTTPStatus.BAD_REQUEST: StatusCode.INVALID_ARGUMENT,
33
+ HTTPStatus.UNAUTHORIZED: StatusCode.UNAUTHENTICATED,
34
+ HTTPStatus.FORBIDDEN: StatusCode.PERMISSION_DENIED,
35
+ HTTPStatus.NOT_FOUND: StatusCode.NOT_FOUND,
36
+ HTTPStatus.CONFLICT: StatusCode.ALREADY_EXISTS,
37
+ HTTPStatus.TOO_MANY_REQUESTS: StatusCode.RESOURCE_EXHAUSTED,
38
+ HTTPStatus.INTERNAL_SERVER_ERROR: StatusCode.INTERNAL,
39
+ HTTPStatus.NOT_IMPLEMENTED: StatusCode.UNIMPLEMENTED,
40
+ HTTPStatus.SERVICE_UNAVAILABLE: StatusCode.UNAVAILABLE,
41
+ HTTPStatus.GATEWAY_TIMEOUT: StatusCode.DEADLINE_EXCEEDED,
42
+ }
43
+
44
+
45
+ HTTP_STATUS = {
46
+ 'OK': HTTPStatus.OK,
47
+ 'BAD_REQUEST': HTTPStatus.BAD_REQUEST,
48
+ 'UNAUTHORIZED': HTTPStatus.UNAUTHORIZED,
49
+ 'FORBIDDEN': HTTPStatus.FORBIDDEN,
50
+ 'NOT_FOUND': HTTPStatus.NOT_FOUND,
51
+ 'CONFLICT': HTTPStatus.CONFLICT,
52
+ 'TOO_MANY_REQUESTS': HTTPStatus.TOO_MANY_REQUESTS,
53
+ 'INTERNAL_SERVER_ERROR': HTTPStatus.INTERNAL_SERVER_ERROR,
54
+ 'NOT_IMPLEMENTED': HTTPStatus.NOT_IMPLEMENTED,
55
+ 'SERVICE_UNAVAILABLE': HTTPStatus.SERVICE_UNAVAILABLE,
56
+ 'GATEWAY_TIMEOUT': HTTPStatus.GATEWAY_TIMEOUT,
57
+ }
58
+
59
+
60
+ class ScalekitException(Exception):
61
+ """ Base class for all scalekit exceptions """
62
+ def __init__(self, error):
63
+ super().__init__(error)
64
+
65
+
66
+ class WebhookVerificationError(ScalekitException):
67
+ """ Exception raised for webhook verification failure """
68
+ def __init__(self, error):
69
+ super().__init__(error)
70
+
71
+
72
+ class ScalekitValidateTokenFailureException(ScalekitException):
73
+ """ Exception raised for token validation failure """
74
+ def __init__(self, error):
75
+ super().__init__(error)
76
+
77
+
78
+ class ScalekitServerException(ScalekitException):
79
+ """ Base class for all scalekit server exceptions """
80
+ def __init__(self, error: Response | grpc.RpcError):
81
+ super().__init__(error)
82
+ self._unpacked_details = list()
83
+ if isinstance(error, Response):
84
+ if error.reason and isinstance(error.reason, str):
85
+ self._http_status = HTTP_STATUS.get(error.reason.upper(), HTTPStatus.INTERNAL_SERVER_ERROR)
86
+ else:
87
+ self._http_status = HTTP_STATUS.get('INTERNAL_SERVER_ERROR')
88
+ self._grpc_status = HTTP_TO_GRPC.get(error.status_code, StatusCode.UNKNOWN)
89
+ self._error_code = error.reason
90
+ self._err_details = error.text
91
+ self._message = None
92
+ elif isinstance(error, grpc.RpcError):
93
+ self._grpc_status = error.code()
94
+ self._http_status = GRPC_TO_HTTP.get(self._grpc_status)
95
+ self._message = rpc_status.from_call(error).message
96
+ self._err_details = rpc_status.from_call(error).details
97
+ self._error_code = None
98
+
99
+ for detail in self._err_details:
100
+ info = ErrorInfo()
101
+ detail.Unpack(info)
102
+ self._unpacked_details.append(info)
103
+ if not self._error_code:
104
+ self._error_code = info.error_code
105
+
106
+ @staticmethod
107
+ def promote(error: Response | grpc.RpcError):
108
+ """ Promote a ScalekitServerException (Response or RpcError) to a specific error type """
109
+ grpc_status = HTTP_TO_GRPC.get(error.status_code) if isinstance(error, Response) else error.code()
110
+
111
+ if grpc_status == StatusCode.INVALID_ARGUMENT:
112
+ return ScalekitBadRequestException(error)
113
+ elif grpc_status == StatusCode.FAILED_PRECONDITION:
114
+ return ScalekitBadRequestException(error)
115
+ elif grpc_status == StatusCode.OUT_OF_RANGE:
116
+ return ScalekitBadRequestException(error)
117
+ elif grpc_status == StatusCode.UNAUTHENTICATED:
118
+ return ScalekitUnauthorizedException(error)
119
+ elif grpc_status == StatusCode.PERMISSION_DENIED:
120
+ return ScalekitForbiddenException(error)
121
+ elif grpc_status == StatusCode.NOT_FOUND:
122
+ return ScalekitNotFoundException(error)
123
+ elif grpc_status == StatusCode.ALREADY_EXISTS:
124
+ return ScalekitConflictException(error)
125
+ elif grpc_status == StatusCode.ABORTED:
126
+ return ScalekitConflictException(error)
127
+ elif grpc_status == StatusCode.RESOURCE_EXHAUSTED:
128
+ return ScalekitTooManyRequestsException(error)
129
+ elif grpc_status == StatusCode.CANCELLED:
130
+ return ScalekitCancelledException(error)
131
+ elif grpc_status == StatusCode.DATA_LOSS:
132
+ return ScalekitInternalServerException(error)
133
+ elif grpc_status == StatusCode.UNKNOWN:
134
+ return ScalekitInternalServerException(error)
135
+ elif grpc_status == StatusCode.INTERNAL:
136
+ return ScalekitInternalServerException(error)
137
+ elif grpc_status == StatusCode.UNIMPLEMENTED:
138
+ return ScalekitNotImplementedException(error)
139
+ elif grpc_status == StatusCode.UNAVAILABLE:
140
+ return ScalekitServiceUnavailableException(error)
141
+ elif grpc_status == StatusCode.DEADLINE_EXCEEDED:
142
+ return ScalekitGatewayTimeoutException(error)
143
+ else:
144
+ return ScalekitUnknownException(error)
145
+
146
+ def __str__(self):
147
+ if self._unpacked_details:
148
+ border = "=" * 40
149
+ details_str = str(self._unpacked_details)
150
+ if details_str.startswith("[") and "\n" in details_str:
151
+ details_str = details_str.replace("[", "[\n", 1)
152
+ return (f"\n{border}\n"
153
+ f"Error Code: {self._error_code}\n"
154
+ f"GRPC: ({self._grpc_status.name}: {self._grpc_status.value})\n"
155
+ f"HTTP: ({self._http_status.name}: {self._http_status.value})\n"
156
+ f"Error Details:\n"
157
+ f"{self._message}: {details_str}\n{border}\n")
158
+ else:
159
+ border = "=" * 40
160
+ return (f"\n{border}\n"
161
+ f"Error Code: {self._error_code}\n"
162
+ f"GRPC: ({self._grpc_status.name}: {self._grpc_status.value})\n"
163
+ f"HTTP: ({self._http_status.name}: {self._http_status.value})\n"
164
+ f"Error Details: {self._err_details}\n{border}\n")
165
+
166
+ @property
167
+ def http_status(self):
168
+ """ Getter for HTTP status code """
169
+ return self._http_status
170
+
171
+ @property
172
+ def error_code(self):
173
+ """ Getter for Error code """
174
+ return self._error_code
175
+
176
+ @property
177
+ def err_details(self):
178
+ """ Getter for Error details object """
179
+ return self._err_details
180
+
181
+ @property
182
+ def grpc_status(self):
183
+ """ Getter for GRPC status code """
184
+ return self._grpc_status
185
+
186
+ @property
187
+ def message(self):
188
+ """ Getter for Exception message """
189
+ return self._message
190
+
191
+
192
+ class ScalekitBadRequestException(ScalekitServerException):
193
+ """ Scalekit Exception raised for bad requests """
194
+ def __init__(self, error: Response | grpc.RpcError):
195
+ super().__init__(error)
196
+
197
+
198
+ class ScalekitUnauthorizedException(ScalekitServerException):
199
+ """ Scalekit Exception raised for unauthorized access """
200
+ def __init__(self, error: Response | grpc.RpcError):
201
+ super().__init__(error)
202
+
203
+
204
+ class ScalekitForbiddenException(ScalekitServerException):
205
+ """ Scalekit Exception raised for forbidden access """
206
+ def __init__(self, error: Response | grpc.RpcError):
207
+ super().__init__(error)
208
+
209
+
210
+ class ScalekitNotFoundException(ScalekitServerException):
211
+ """ Scalekit Exception raised when a resource is not found """
212
+ def __init__(self, error: Response | grpc.RpcError):
213
+ super().__init__(error)
214
+
215
+
216
+ class ScalekitConflictException(ScalekitServerException):
217
+ """ Scalekit Exception raised for conflicts, such as duplicate resources """
218
+ def __init__(self, error: Response | grpc.RpcError):
219
+ super().__init__(error)
220
+
221
+
222
+ class ScalekitTooManyRequestsException(ScalekitServerException):
223
+ """ Scalekit Exception raised when too many requests are made in a short time """
224
+ def __init__(self, error: Response | grpc.RpcError):
225
+ super().__init__(error)
226
+
227
+
228
+ class ScalekitInternalServerException(ScalekitServerException):
229
+ """ Scalekit Exception raised for internal server errors """
230
+ def __init__(self, error: Response | grpc.RpcError):
231
+ super().__init__(error)
232
+
233
+
234
+ class ScalekitNotImplementedException(ScalekitServerException):
235
+ """ Scalekit Exception raised when a feature is not implemented """
236
+ def __init__(self, error: Response | grpc.RpcError):
237
+ super().__init__(error)
238
+
239
+
240
+ class ScalekitServiceUnavailableException(ScalekitServerException):
241
+ """ Scalekit Exception raised when the service is unavailable """
242
+ def __init__(self, error: Response | grpc.RpcError):
243
+ super().__init__(error)
244
+
245
+
246
+ class ScalekitGatewayTimeoutException(ScalekitServerException):
247
+ """ Scalekit Exception raised when a gateway timeout occurs """
248
+ def __init__(self, error: Response | grpc.RpcError):
249
+ super().__init__(error)
250
+
251
+
252
+ class ScalekitCancelledException(ScalekitServerException):
253
+ """ Scalekit Exception raised when an operation is cancelled """
254
+ def __init__(self, error: Response | grpc.RpcError):
255
+ super().__init__(error)
256
+
257
+
258
+ class ScalekitUnknownException(ScalekitServerException):
259
+ def __init__(self, error: Response | grpc.RpcError):
260
+ super().__init__(error)
@@ -1,5 +1,5 @@
1
1
  from enum import Enum
2
- from typing import Optional
2
+ from typing import Optional, List
3
3
 
4
4
 
5
5
  class GrantType(Enum):
@@ -71,4 +71,17 @@ class LogoutUrlOptions:
71
71
  ):
72
72
  self.id_token_hint = id_token_hint
73
73
  self.post_logout_redirect_uri = post_logout_redirect_uri
74
- self.state = state
74
+ self.state = state
75
+
76
+
77
+ class TokenValidationOptions:
78
+ """Options for token validation including issuer, audience, and scope validation"""
79
+ def __init__(
80
+ self,
81
+ issuer: Optional[str] = None,
82
+ audience: Optional[List[str]] = None,
83
+ required_scopes: Optional[List[str]] = None
84
+ ):
85
+ self.issuer = issuer
86
+ self.audience = audience
87
+ self.required_scopes = required_scopes
@@ -8,9 +8,8 @@ import platform
8
8
  from urllib.parse import urlparse
9
9
 
10
10
  from cryptography.hazmat.primitives import serialization
11
- from grpc_status import rpc_status
12
11
  from scalekit.common.scalekit import GrantType
13
- from scalekit.v1.errdetails.errdetails_pb2 import ErrorInfo
12
+ from scalekit.common.exceptions import ScalekitServerException, ScalekitException
14
13
 
15
14
  TRequest = TypeVar("TRequest")
16
15
  TResponse = TypeVar("TResponse")
@@ -27,7 +26,7 @@ class WithCall(Protocol):
27
26
  class CoreClient:
28
27
  """Class definition for Core Client"""
29
28
 
30
- sdk_version = "Scalekit-Python/2.2.2"
29
+ sdk_version = "Scalekit-Python/2.3.0"
31
30
  api_version = "20250718"
32
31
  user_agent = f"{sdk_version} Python/{platform.python_version()} ({platform.system()}; {platform.architecture()}"
33
32
 
@@ -86,7 +85,7 @@ class CoreClient:
86
85
 
87
86
  response = self.authenticate(data=json.dumps(params))
88
87
  if response.status_code != 200:
89
- raise Exception(response.content)
88
+ raise ScalekitServerException.promote(response)
90
89
  response = json.loads(response.content)
91
90
  self.access_token = response["access_token"]
92
91
 
@@ -105,7 +104,7 @@ class CoreClient:
105
104
  verify=True,
106
105
  )
107
106
  if response.status_code != 200:
108
- raise Exception(response.content)
107
+ raise ScalekitServerException.promote(response)
109
108
  return response
110
109
 
111
110
  def get_jwks(self):
@@ -129,38 +128,6 @@ class CoreClient:
129
128
 
130
129
  self.keys[kid] = pem_key.decode("utf-8")
131
130
 
132
- def grpc_exec(
133
- self,
134
- func: WithCall,
135
- data: TRequest,
136
- retry=1,
137
- ) -> TResponse:
138
- try:
139
- resp = func(
140
- data,
141
- metadata=tuple(self.get_headers().items()),
142
- )
143
- return resp
144
- except grpc.RpcError as exp:
145
- if retry > 0:
146
- return self.grpc_exec(func, data, retry=retry - 1)
147
- else:
148
- status_code = exp.code()
149
- status = rpc_status.from_call(exp)
150
- messages = [status.message]
151
- if status_code == grpc.StatusCode.INVALID_ARGUMENT:
152
- for detail in status.details:
153
- if detail.Is(ErrorInfo.DESCRIPTOR):
154
- info = ErrorInfo()
155
- detail.Unpack(info)
156
- if info.validation_error_info:
157
- for fv in info.validation_error_info.field_violations:
158
- messages.append(f"{fv.field}: {fv.description}")
159
-
160
- raise Exception("\n".join(messages))
161
- except Exception as exp:
162
- raise exp
163
-
164
131
  def get_headers(self, headers: Optional[dict] = None) -> dict:
165
132
  """
166
133
  Method to get user defined headers and returns collated header params
@@ -180,3 +147,23 @@ class CoreClient:
180
147
  if headers:
181
148
  return {**default_headers, **headers}
182
149
  return default_headers
150
+
151
+ def grpc_exec(
152
+ self,
153
+ func: WithCall,
154
+ data: TRequest,
155
+ retry=1,
156
+ ) -> TResponse:
157
+ try:
158
+ resp = func(
159
+ data,
160
+ metadata=tuple(self.get_headers().items()),
161
+ )
162
+ return resp
163
+ except grpc.RpcError as exp:
164
+ if retry > 0:
165
+ return self.grpc_exec(func, data, retry=retry - 1)
166
+ else:
167
+ raise ScalekitServerException.promote(exp)
168
+ except Exception as exp:
169
+ raise ScalekitException(exp)