classiq 0.93.0__py3-none-any.whl → 0.99.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. classiq/__init__.py +11 -19
  2. classiq/_analyzer_extras/_ipywidgets_async_extension.py +7 -7
  3. classiq/_analyzer_extras/interactive_hardware.py +19 -12
  4. classiq/_internals/api_wrapper.py +31 -142
  5. classiq/_internals/async_utils.py +4 -7
  6. classiq/_internals/authentication/auth0.py +41 -15
  7. classiq/_internals/authentication/authorization_code.py +9 -0
  8. classiq/_internals/authentication/authorization_flow.py +41 -0
  9. classiq/_internals/authentication/device.py +33 -52
  10. classiq/_internals/authentication/hybrid_flow.py +19 -0
  11. classiq/_internals/authentication/password_manager.py +13 -13
  12. classiq/_internals/authentication/token_manager.py +9 -9
  13. classiq/_internals/client.py +17 -44
  14. classiq/_internals/config.py +19 -5
  15. classiq/_internals/help.py +1 -2
  16. classiq/_internals/host_checker.py +3 -3
  17. classiq/_internals/jobs.py +14 -14
  18. classiq/_internals/type_validation.py +3 -3
  19. classiq/analyzer/analyzer.py +18 -18
  20. classiq/analyzer/rb.py +17 -8
  21. classiq/analyzer/show_interactive_hack.py +1 -1
  22. classiq/applications/__init__.py +2 -2
  23. classiq/applications/chemistry/__init__.py +0 -30
  24. classiq/applications/chemistry/op_utils.py +4 -4
  25. classiq/applications/chemistry/problems.py +3 -3
  26. classiq/applications/chemistry/ucc.py +1 -2
  27. classiq/applications/chemistry/z2_symmetries.py +4 -4
  28. classiq/applications/combinatorial_helpers/allowed_constraints.py +1 -3
  29. classiq/applications/combinatorial_helpers/arithmetic/arithmetic_expression.py +2 -1
  30. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +2 -2
  31. classiq/applications/combinatorial_helpers/encoding_mapping.py +2 -3
  32. classiq/applications/combinatorial_helpers/encoding_utils.py +2 -2
  33. classiq/applications/combinatorial_helpers/optimization_model.py +3 -4
  34. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_sparsing.py +2 -2
  35. classiq/applications/combinatorial_helpers/pyomo_utils.py +8 -8
  36. classiq/applications/combinatorial_helpers/sympy_utils.py +1 -3
  37. classiq/applications/combinatorial_helpers/transformations/encoding.py +3 -3
  38. classiq/applications/combinatorial_helpers/transformations/fixed_variables.py +1 -2
  39. classiq/applications/combinatorial_optimization/combinatorial_optimization_config.py +2 -3
  40. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +4 -6
  41. classiq/applications/combinatorial_optimization/combinatorial_problem.py +15 -10
  42. classiq/applications/hamiltonian/pauli_decomposition.py +6 -4
  43. classiq/applications/iqae/iqae.py +14 -11
  44. classiq/applications/qnn/datasets/dataset_base_classes.py +6 -6
  45. classiq/applications/qnn/datasets/dataset_parity.py +6 -6
  46. classiq/applications/qnn/gradients/simple_quantum_gradient.py +1 -1
  47. classiq/applications/qnn/qlayer.py +9 -8
  48. classiq/applications/qnn/torch_utils.py +5 -6
  49. classiq/applications/qnn/types.py +2 -1
  50. classiq/applications/qsp/__init__.py +20 -2
  51. classiq/applications/qsp/qsp.py +238 -10
  52. classiq/applications/qsvm/qsvm_data_generation.py +1 -2
  53. classiq/evaluators/classical_expression.py +0 -4
  54. classiq/evaluators/parameter_types.py +10 -8
  55. classiq/evaluators/qmod_annotated_expression.py +31 -26
  56. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +14 -14
  57. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +2 -1
  58. classiq/evaluators/qmod_expression_visitors/sympy_wrappers.py +8 -8
  59. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +4 -4
  60. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +14 -4
  61. classiq/evaluators/qmod_node_evaluators/list_evaluation.py +2 -2
  62. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +3 -3
  63. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +9 -9
  64. classiq/evaluators/qmod_node_evaluators/utils.py +6 -6
  65. classiq/evaluators/qmod_type_inference/classical_type_inference.py +9 -10
  66. classiq/evaluators/qmod_type_inference/quantum_type_inference.py +5 -5
  67. classiq/execution/__init__.py +0 -3
  68. classiq/execution/execution_session.py +28 -21
  69. classiq/execution/jobs.py +26 -26
  70. classiq/execution/qnn.py +1 -2
  71. classiq/execution/user_budgets.py +71 -37
  72. classiq/executor.py +1 -3
  73. classiq/interface/_version.py +1 -1
  74. classiq/interface/analyzer/analysis_params.py +4 -4
  75. classiq/interface/analyzer/cytoscape_graph.py +3 -3
  76. classiq/interface/analyzer/result.py +4 -4
  77. classiq/interface/ast_node.py +3 -3
  78. classiq/interface/backend/backend_preferences.py +26 -50
  79. classiq/interface/backend/ionq/ionq_quantum_program.py +5 -5
  80. classiq/interface/backend/provider_config/__init__.py +0 -0
  81. classiq/interface/backend/provider_config/provider_config.py +8 -0
  82. classiq/interface/backend/provider_config/providers/__init__.py +0 -0
  83. classiq/interface/backend/provider_config/providers/alice_bob.py +47 -0
  84. classiq/interface/backend/provider_config/providers/aqt.py +16 -0
  85. classiq/interface/backend/provider_config/providers/azure.py +37 -0
  86. classiq/interface/backend/provider_config/providers/braket.py +39 -0
  87. classiq/interface/backend/provider_config/providers/ibm.py +26 -0
  88. classiq/interface/backend/provider_config/providers/ionq.py +22 -0
  89. classiq/interface/backend/quantum_backend_providers.py +20 -2
  90. classiq/interface/chemistry/ansatz_library.py +3 -5
  91. classiq/interface/chemistry/operator.py +3 -3
  92. classiq/interface/combinatorial_optimization/examples/knapsack.py +2 -4
  93. classiq/interface/combinatorial_optimization/examples/tsp_digraph.py +1 -2
  94. classiq/interface/compression_utils.py +2 -3
  95. classiq/interface/debug_info/debug_info.py +8 -7
  96. classiq/interface/exceptions.py +6 -7
  97. classiq/interface/execution/primitives.py +6 -6
  98. classiq/interface/executor/estimate_cost.py +1 -1
  99. classiq/interface/executor/execution_preferences.py +3 -5
  100. classiq/interface/executor/execution_request.py +10 -10
  101. classiq/interface/executor/execution_result.py +1 -2
  102. classiq/interface/executor/quantum_code.py +8 -8
  103. classiq/interface/executor/result.py +28 -18
  104. classiq/interface/executor/user_budget.py +25 -17
  105. classiq/interface/executor/vqe_result.py +5 -6
  106. classiq/interface/generator/ansatz_library.py +6 -8
  107. classiq/interface/generator/application_apis/__init__.py +0 -3
  108. classiq/interface/generator/arith/arithmetic.py +2 -2
  109. classiq/interface/generator/arith/arithmetic_arg_type_validator.py +2 -3
  110. classiq/interface/generator/arith/arithmetic_expression_abc.py +4 -5
  111. classiq/interface/generator/arith/arithmetic_expression_parser.py +11 -4
  112. classiq/interface/generator/arith/arithmetic_expression_validator.py +12 -15
  113. classiq/interface/generator/arith/arithmetic_operations.py +4 -6
  114. classiq/interface/generator/arith/arithmetic_param_getters.py +70 -107
  115. classiq/interface/generator/arith/arithmetic_result_builder.py +4 -4
  116. classiq/interface/generator/arith/ast_node_rewrite.py +8 -4
  117. classiq/interface/generator/arith/binary_ops.py +15 -40
  118. classiq/interface/generator/arith/logical_ops.py +2 -3
  119. classiq/interface/generator/arith/number_utils.py +2 -2
  120. classiq/interface/generator/arith/register_user_input.py +3 -3
  121. classiq/interface/generator/arith/unary_ops.py +2 -2
  122. classiq/interface/generator/circuit_code/circuit_code.py +8 -10
  123. classiq/interface/generator/circuit_code/types_and_constants.py +1 -1
  124. classiq/interface/generator/complex_type.py +2 -2
  125. classiq/interface/generator/copy.py +1 -3
  126. classiq/interface/generator/expressions/atomic_expression_functions.py +0 -5
  127. classiq/interface/generator/expressions/evaluated_expression.py +2 -3
  128. classiq/interface/generator/expressions/expression.py +2 -2
  129. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +4 -7
  130. classiq/interface/generator/function_param_list.py +0 -40
  131. classiq/interface/generator/function_params.py +5 -6
  132. classiq/interface/generator/functions/classical_function_declaration.py +2 -2
  133. classiq/interface/generator/functions/classical_type.py +3 -3
  134. classiq/interface/generator/functions/type_modifier.py +0 -15
  135. classiq/interface/generator/functions/type_name.py +2 -2
  136. classiq/interface/generator/generated_circuit_data.py +14 -18
  137. classiq/interface/generator/hamiltonian_evolution/exponentiation.py +2 -4
  138. classiq/interface/generator/hardware/hardware_data.py +8 -8
  139. classiq/interface/generator/hardware_efficient_ansatz.py +9 -9
  140. classiq/interface/generator/mcu.py +3 -3
  141. classiq/interface/generator/mcx.py +3 -3
  142. classiq/interface/generator/model/constraints.py +34 -5
  143. classiq/interface/generator/model/preferences/preferences.py +15 -21
  144. classiq/interface/generator/model/quantum_register.py +7 -10
  145. classiq/interface/generator/noise_properties.py +3 -7
  146. classiq/interface/generator/parameters.py +1 -1
  147. classiq/interface/generator/partitioned_register.py +1 -2
  148. classiq/interface/generator/preferences/qasm_to_qmod_params.py +11 -0
  149. classiq/interface/generator/quantum_function_call.py +9 -12
  150. classiq/interface/generator/quantum_program.py +10 -23
  151. classiq/interface/generator/range_types.py +3 -3
  152. classiq/interface/generator/slice_parsing_utils.py +4 -5
  153. classiq/interface/generator/standard_gates/standard_gates.py +2 -4
  154. classiq/interface/generator/synthesis_execution_parameter.py +1 -3
  155. classiq/interface/generator/synthesis_metadata/synthesis_duration.py +9 -0
  156. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +2 -3
  157. classiq/interface/generator/transpiler_basis_gates.py +12 -4
  158. classiq/interface/generator/types/builtin_enum_declarations.py +0 -145
  159. classiq/interface/generator/types/compilation_metadata.py +12 -1
  160. classiq/interface/generator/types/enum_declaration.py +2 -1
  161. classiq/interface/generator/validations/flow_graph.py +3 -3
  162. classiq/interface/generator/visitor.py +10 -12
  163. classiq/interface/hardware.py +2 -3
  164. classiq/interface/helpers/classproperty.py +2 -2
  165. classiq/interface/helpers/custom_encoders.py +2 -1
  166. classiq/interface/helpers/custom_pydantic_types.py +1 -1
  167. classiq/interface/helpers/text_utils.py +1 -4
  168. classiq/interface/ide/visual_model.py +6 -5
  169. classiq/interface/interface_version.py +1 -1
  170. classiq/interface/jobs.py +3 -3
  171. classiq/interface/model/allocate.py +4 -4
  172. classiq/interface/model/block.py +6 -2
  173. classiq/interface/model/bounds.py +3 -3
  174. classiq/interface/model/classical_if.py +4 -0
  175. classiq/interface/model/control.py +8 -1
  176. classiq/interface/model/inplace_binary_operation.py +2 -2
  177. classiq/interface/model/invert.py +4 -0
  178. classiq/interface/model/model.py +4 -4
  179. classiq/interface/model/model_visitor.py +40 -1
  180. classiq/interface/model/parameter.py +1 -3
  181. classiq/interface/model/port_declaration.py +1 -1
  182. classiq/interface/model/power.py +4 -0
  183. classiq/interface/model/quantum_expressions/quantum_expression.py +1 -2
  184. classiq/interface/model/quantum_function_call.py +3 -6
  185. classiq/interface/model/quantum_function_declaration.py +1 -0
  186. classiq/interface/model/quantum_lambda_function.py +4 -4
  187. classiq/interface/model/quantum_statement.py +11 -4
  188. classiq/interface/model/quantum_type.py +14 -14
  189. classiq/interface/model/repeat.py +4 -0
  190. classiq/interface/model/skip_control.py +4 -0
  191. classiq/interface/model/validation_handle.py +2 -3
  192. classiq/interface/model/variable_declaration_statement.py +2 -2
  193. classiq/interface/model/within_apply_operation.py +4 -0
  194. classiq/interface/pretty_print/expression_to_qmod.py +3 -4
  195. classiq/interface/server/routes.py +0 -16
  196. classiq/interface/source_reference.py +3 -4
  197. classiq/model_expansions/arithmetic.py +11 -7
  198. classiq/model_expansions/arithmetic_compute_result_attrs.py +30 -27
  199. classiq/model_expansions/capturing/captured_vars.py +3 -3
  200. classiq/model_expansions/capturing/mangling_utils.py +1 -2
  201. classiq/model_expansions/closure.py +12 -11
  202. classiq/model_expansions/function_builder.py +14 -6
  203. classiq/model_expansions/generative_functions.py +7 -12
  204. classiq/model_expansions/interpreters/base_interpreter.py +3 -7
  205. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +2 -1
  206. classiq/model_expansions/interpreters/generative_interpreter.py +5 -3
  207. classiq/model_expansions/quantum_operations/allocate.py +4 -4
  208. classiq/model_expansions/quantum_operations/assignment_result_processor.py +2 -4
  209. classiq/model_expansions/quantum_operations/call_emitter.py +31 -37
  210. classiq/model_expansions/quantum_operations/declarative_call_emitter.py +2 -2
  211. classiq/model_expansions/quantum_operations/emitter.py +3 -5
  212. classiq/model_expansions/quantum_operations/expression_evaluator.py +3 -3
  213. classiq/model_expansions/quantum_operations/skip_control_verifier.py +1 -2
  214. classiq/model_expansions/quantum_operations/variable_decleration.py +2 -2
  215. classiq/model_expansions/scope.py +7 -7
  216. classiq/model_expansions/scope_initialization.py +4 -0
  217. classiq/model_expansions/visitors/symbolic_param_inference.py +6 -6
  218. classiq/model_expansions/visitors/uncomputation_signature_inference.py +328 -0
  219. classiq/model_expansions/visitors/variable_references.py +15 -14
  220. classiq/open_library/functions/__init__.py +28 -11
  221. classiq/open_library/functions/amplitude_loading.py +81 -0
  222. classiq/open_library/functions/discrete_sine_cosine_transform.py +5 -5
  223. classiq/open_library/functions/grover.py +8 -10
  224. classiq/open_library/functions/lcu.py +47 -18
  225. classiq/open_library/functions/modular_exponentiation.py +93 -8
  226. classiq/open_library/functions/qsvt.py +66 -79
  227. classiq/open_library/functions/qsvt_temp.py +536 -0
  228. classiq/open_library/functions/state_preparation.py +130 -27
  229. classiq/qmod/__init__.py +6 -4
  230. classiq/qmod/builtins/classical_execution_primitives.py +4 -23
  231. classiq/qmod/builtins/classical_functions.py +1 -42
  232. classiq/qmod/builtins/enums.py +15 -153
  233. classiq/qmod/builtins/functions/__init__.py +9 -18
  234. classiq/qmod/builtins/functions/allocation.py +25 -4
  235. classiq/qmod/builtins/functions/arithmetic.py +22 -27
  236. classiq/qmod/builtins/functions/exponentiation.py +51 -2
  237. classiq/qmod/builtins/functions/mcx_func.py +7 -0
  238. classiq/qmod/builtins/functions/standard_gates.py +46 -27
  239. classiq/qmod/builtins/operations.py +165 -79
  240. classiq/qmod/builtins/structs.py +24 -91
  241. classiq/qmod/cfunc.py +3 -2
  242. classiq/qmod/classical_function.py +2 -1
  243. classiq/qmod/cparam.py +2 -8
  244. classiq/qmod/create_model_function.py +7 -7
  245. classiq/qmod/declaration_inferrer.py +33 -30
  246. classiq/qmod/expression_query.py +7 -4
  247. classiq/qmod/model_state_container.py +2 -2
  248. classiq/qmod/native/pretty_printer.py +25 -14
  249. classiq/qmod/pretty_print/expression_to_python.py +5 -3
  250. classiq/qmod/pretty_print/pretty_printer.py +39 -17
  251. classiq/qmod/python_classical_type.py +40 -13
  252. classiq/qmod/qfunc.py +124 -19
  253. classiq/qmod/qmod_constant.py +2 -2
  254. classiq/qmod/qmod_parameter.py +5 -2
  255. classiq/qmod/qmod_variable.py +47 -46
  256. classiq/qmod/quantum_callable.py +18 -13
  257. classiq/qmod/quantum_expandable.py +31 -26
  258. classiq/qmod/quantum_function.py +84 -36
  259. classiq/qmod/semantics/annotation/call_annotation.py +5 -5
  260. classiq/qmod/semantics/error_manager.py +12 -14
  261. classiq/qmod/semantics/lambdas.py +1 -2
  262. classiq/qmod/semantics/validation/types_validation.py +1 -2
  263. classiq/qmod/symbolic.py +2 -4
  264. classiq/qmod/utilities.py +13 -20
  265. classiq/qmod/write_qmod.py +3 -4
  266. classiq/quantum_program.py +1 -3
  267. classiq/synthesis.py +11 -7
  268. {classiq-0.93.0.dist-info → classiq-0.99.0.dist-info}/METADATA +2 -3
  269. {classiq-0.93.0.dist-info → classiq-0.99.0.dist-info}/RECORD +271 -299
  270. {classiq-0.93.0.dist-info → classiq-0.99.0.dist-info}/WHEEL +1 -1
  271. classiq/applications/chemistry/ansatz_parameters.py +0 -29
  272. classiq/applications/chemistry/chemistry_execution_parameters.py +0 -16
  273. classiq/applications/chemistry/chemistry_model_constructor.py +0 -532
  274. classiq/applications/chemistry/ground_state_problem.py +0 -42
  275. classiq/applications/qsvm/__init__.py +0 -8
  276. classiq/applications/qsvm/qsvm.py +0 -11
  277. classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +0 -129
  278. classiq/execution/iqcc.py +0 -128
  279. classiq/interface/applications/qsvm.py +0 -117
  280. classiq/interface/chemistry/elements.py +0 -120
  281. classiq/interface/chemistry/fermionic_operator.py +0 -208
  282. classiq/interface/chemistry/ground_state_problem.py +0 -132
  283. classiq/interface/chemistry/ground_state_result.py +0 -8
  284. classiq/interface/chemistry/molecule.py +0 -71
  285. classiq/interface/execution/iqcc.py +0 -44
  286. classiq/interface/generator/application_apis/chemistry_declarations.py +0 -69
  287. classiq/interface/generator/application_apis/entangler_declarations.py +0 -29
  288. classiq/interface/generator/application_apis/qsvm_declarations.py +0 -6
  289. classiq/interface/generator/chemistry_function_params.py +0 -50
  290. classiq/interface/generator/entangler_params.py +0 -72
  291. classiq/interface/generator/entanglers.py +0 -14
  292. classiq/interface/generator/hamiltonian_evolution/qdrift.py +0 -27
  293. classiq/interface/generator/hartree_fock.py +0 -26
  294. classiq/interface/generator/hva.py +0 -22
  295. classiq/interface/generator/linear_pauli_rotations.py +0 -92
  296. classiq/interface/generator/qft.py +0 -37
  297. classiq/interface/generator/qsvm.py +0 -96
  298. classiq/interface/generator/state_preparation/__init__.py +0 -14
  299. classiq/interface/generator/state_preparation/bell_state_preparation.py +0 -27
  300. classiq/interface/generator/state_preparation/computational_basis_state_preparation.py +0 -28
  301. classiq/interface/generator/state_preparation/distributions.py +0 -53
  302. classiq/interface/generator/state_preparation/exponential_state_preparation.py +0 -14
  303. classiq/interface/generator/state_preparation/ghz_state_preparation.py +0 -14
  304. classiq/interface/generator/state_preparation/metrics.py +0 -41
  305. classiq/interface/generator/state_preparation/state_preparation.py +0 -113
  306. classiq/interface/generator/state_preparation/state_preparation_abc.py +0 -24
  307. classiq/interface/generator/state_preparation/uniform_distibution_state_preparation.py +0 -13
  308. classiq/interface/generator/state_preparation/w_state_preparation.py +0 -13
  309. classiq/interface/generator/ucc.py +0 -74
  310. classiq/interface/helpers/backward_compatibility.py +0 -9
  311. classiq/model_expansions/transformers/type_modifier_inference.py +0 -392
  312. classiq/open_library/functions/lookup_table.py +0 -58
  313. classiq/qmod/builtins/functions/chemistry.py +0 -123
  314. classiq/qmod/builtins/functions/qsvm.py +0 -24
  315. {classiq-0.93.0.dist-info → classiq-0.99.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  import urllib.parse
2
+ import warnings
2
3
  from dataclasses import dataclass
3
- from typing import Any, Optional, Union
4
+ from typing import Any
4
5
 
5
6
  from httpx import AsyncClient, Response, codes
6
7
  from pydantic import Field
@@ -8,6 +9,8 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
8
9
 
9
10
  from classiq.interface.exceptions import ClassiqAuthenticationError
10
11
 
12
+ from classiq._internals.config import CONFIG_ENV_FILES
13
+
11
14
 
12
15
  class AuthSettings(BaseSettings):
13
16
  domain: str = Field(
@@ -20,22 +23,18 @@ class AuthSettings(BaseSettings):
20
23
  default="f6721qMOVoDAOVkzrv8YaWassRKSFX6Y",
21
24
  validation_alias="CLASSIQ_AUTH_CLIENT_ID",
22
25
  )
26
+ organization: str = Field(default="", validation_alias="CLASSIQ_AUTH_ORGANIZATION")
23
27
 
24
- model_config = SettingsConfigDict(extra="allow")
25
-
26
- def __init__(self, **data: Any) -> None:
27
- initial_data = {
28
- field: data[field] for field in data if field in self.model_fields
29
- }
30
- super().__init__(**data)
31
- for field, value in initial_data.items():
32
- setattr(self, field, value)
28
+ model_config = SettingsConfigDict(
29
+ env_file=CONFIG_ENV_FILES,
30
+ extra="ignore",
31
+ )
33
32
 
34
33
 
35
34
  @dataclass
36
35
  class Tokens:
37
36
  access_token: str
38
- refresh_token: Optional[str]
37
+ refresh_token: str | None
39
38
 
40
39
 
41
40
  class Auth0:
@@ -53,11 +52,15 @@ class Auth0:
53
52
  def _client_id(self) -> str:
54
53
  return self._auth_settings.client_id
55
54
 
55
+ @property
56
+ def _organization(self) -> str:
57
+ return self._auth_settings.organization
58
+
56
59
  async def _make_request(
57
60
  self,
58
61
  url: str,
59
62
  payload: dict[str, str],
60
- allow_error: Union[bool, int] = False,
63
+ allow_error: bool | int = False,
61
64
  ) -> dict[str, Any]:
62
65
  encoded_payload = urllib.parse.urlencode(payload)
63
66
  client: AsyncClient
@@ -76,13 +79,20 @@ class Auth0:
76
79
  f"Request to Auth0 failed with error code {code}: {data.get('error')}"
77
80
  )
78
81
 
79
- async def get_device_data(self, get_refresh_token: bool = True) -> dict[str, Any]:
82
+ async def get_device_data(
83
+ self, require_refresh_token: bool = True
84
+ ) -> dict[str, Any]:
80
85
  payload = {
81
- "client_id": self._auth_settings.client_id,
86
+ "client_id": self._client_id,
82
87
  "audience": self._auth_settings.audience,
83
88
  }
84
- if get_refresh_token:
89
+ if require_refresh_token:
85
90
  payload["scope"] = "offline_access"
91
+ if self._organization:
92
+ warnings.warn(
93
+ "Organizations are not supported in device auth flow.",
94
+ stacklevel=1,
95
+ )
86
96
 
87
97
  return await self._make_request(
88
98
  url="/oauth/device/code",
@@ -102,6 +112,22 @@ class Auth0:
102
112
  allow_error=codes.FORBIDDEN,
103
113
  )
104
114
 
115
+ def get_authorize_url(
116
+ self, redirect_uri: str, require_refresh_token: bool = True
117
+ ) -> str:
118
+ params = {
119
+ "client_id": self._client_id,
120
+ "response_type": "code",
121
+ "audience": self._auth_settings.audience,
122
+ "redirect_uri": redirect_uri,
123
+ }
124
+ if require_refresh_token:
125
+ params["scope"] = "offline_access"
126
+ if self._organization:
127
+ # Otherwise, let the Auth0 handle
128
+ params["organization"] = self._organization
129
+ return f"{self._base_url}/authorize?{urllib.parse.urlencode(params)}"
130
+
105
131
  async def refresh_access_token(self, refresh_token: str) -> Tokens:
106
132
  # TODO handle failure
107
133
  payload = {
@@ -0,0 +1,9 @@
1
+ from classiq._internals.authentication.authorization_flow import AuthorizationFlow
2
+
3
+
4
+ class AuthorizationCodeFlow(AuthorizationFlow):
5
+ async def authorize(self, redirect_uri: str) -> None:
6
+ auth_url = self.auth0_client.get_authorize_url(
7
+ redirect_uri, self.require_refresh_token
8
+ )
9
+ self.open_url(auth_url)
@@ -0,0 +1,41 @@
1
+ import webbrowser
2
+ from typing import Any
3
+
4
+ from classiq.interface.exceptions import ClassiqAuthenticationError
5
+
6
+ from classiq._internals.authentication.auth0 import Auth0, Tokens
7
+
8
+
9
+ class AuthorizationFlow:
10
+ def __init__(self, require_refresh_token: bool = True, text_only: bool = False):
11
+ self.require_refresh_token = require_refresh_token
12
+ self.text_only = text_only
13
+ self.auth0_client = Auth0()
14
+
15
+ async def get_tokens(self) -> Tokens:
16
+ raise NotImplementedError
17
+
18
+ def handle_ready_data(self, data: dict[str, Any]) -> Tokens:
19
+ access_token: str | None = data.get("access_token") or None
20
+ # If refresh token was not requested, this would be None
21
+ refresh_token: str | None = data.get("refresh_token") or None
22
+
23
+ if access_token is None or (
24
+ self.require_refresh_token is True and refresh_token is None
25
+ ):
26
+ raise ClassiqAuthenticationError(
27
+ "Token generation failed for unknown reason (missing access token or refresh token)."
28
+ )
29
+
30
+ return Tokens(access_token=access_token, refresh_token=refresh_token)
31
+
32
+ def open_url(self, url: str) -> None:
33
+ if self.text_only:
34
+ print( # noqa: T201
35
+ f"Please visit this URL from any trusted device to authenticate: {url}"
36
+ )
37
+ else:
38
+ webbrowser.open(url)
39
+ print( # noqa: T201
40
+ f"If a browser doesn't automatically open, please visit this URL from any trusted device to authenticate: {url}"
41
+ )
@@ -1,8 +1,8 @@
1
1
  import asyncio
2
- import webbrowser
3
2
  from collections.abc import Iterable
3
+ from dataclasses import dataclass
4
4
  from datetime import timedelta
5
- from typing import Any, Optional, TypeVar
5
+ from typing import Any, TypeVar
6
6
 
7
7
  from classiq.interface.exceptions import (
8
8
  ClassiqAuthenticationError,
@@ -10,74 +10,55 @@ from classiq.interface.exceptions import (
10
10
  )
11
11
 
12
12
  from classiq._internals.async_utils import poll_for
13
- from classiq._internals.authentication.auth0 import Auth0, Tokens
13
+ from classiq._internals.authentication.auth0 import Tokens
14
+ from classiq._internals.authentication.authorization_flow import AuthorizationFlow
14
15
 
15
16
  T = TypeVar("T")
16
17
 
17
18
 
18
- class DeviceRegistrar:
19
+ @dataclass
20
+ class DeviceData:
21
+ user_code: str
22
+ device_code: str
23
+ interval: float
24
+ expires_in: float
25
+ verification_uri: str
26
+ verification_uri_complete: str
27
+
28
+
29
+ class DeviceCodeFlow(AuthorizationFlow):
19
30
  _TIMEOUT_ERROR = (
20
31
  "Device registration timed out. Please re-initiate the flow and "
21
32
  "authorize the device within the timeout."
22
33
  )
23
34
  _TIMEOUT_SEC: float = timedelta(minutes=15).total_seconds()
24
35
 
25
- @classmethod
26
- async def register(
27
- cls, get_refresh_token: bool = True, text_only: bool = False
28
- ) -> Tokens:
29
- auth0_client = Auth0()
30
- data: dict[str, Any] = await auth0_client.get_device_data(
31
- get_refresh_token=get_refresh_token
36
+ async def get_device_data(self) -> DeviceData:
37
+ device_data: dict[str, Any] = await self.auth0_client.get_device_data(
38
+ require_refresh_token=self.require_refresh_token
32
39
  )
40
+ return DeviceData(**device_data)
33
41
 
34
- print(f"Your user code: {data['user_code']}") # noqa: T201
35
- verification_url = data["verification_uri_complete"]
36
- print( # noqa: T201
37
- f"If a browser doesn't automatically open, please visit this URL from any trusted device: {verification_url}"
38
- )
39
- if not text_only:
40
- webbrowser.open(verification_url)
41
- timeout = min(data["expires_in"], cls._TIMEOUT_SEC)
42
- return await cls._poll_tokens(
43
- auth0_client=auth0_client,
44
- device_code=data["device_code"],
45
- interval=data["interval"],
42
+ async def poll_tokens(self, device_data: DeviceData) -> Tokens:
43
+ interval = device_data.interval
44
+ timeout = min(device_data.expires_in, self._TIMEOUT_SEC)
45
+ return await self._poll_tokens(
46
+ device_code=device_data.device_code,
47
+ interval=interval,
46
48
  timeout=timeout,
47
- get_refresh_token=get_refresh_token,
48
49
  )
49
50
 
50
- @classmethod
51
- def _handle_ready_data(
52
- cls, data: dict[str, Any], get_refresh_token: bool
53
- ) -> Tokens:
54
- access_token: Optional[str] = data.get("access_token")
55
- # If refresh token was not requested, this would be None
56
- refresh_token: Optional[str] = data.get("refresh_token")
57
-
58
- if access_token is None or (
59
- get_refresh_token is True and refresh_token is None
60
- ):
61
- raise ClassiqAuthenticationError(
62
- "Token generation failed for unknown reason."
63
- )
64
-
65
- return Tokens(access_token=access_token, refresh_token=refresh_token)
66
-
67
- @classmethod
68
51
  async def _poll_tokens(
69
- cls,
70
- auth0_client: Auth0,
52
+ self,
71
53
  device_code: str,
72
- interval: int,
54
+ interval: float,
73
55
  timeout: float,
74
- get_refresh_token: bool = True,
75
56
  ) -> Tokens:
76
57
  async def poller() -> dict[str, Any]:
77
58
  nonlocal device_code
78
- return await auth0_client.poll_tokens(device_code=device_code)
59
+ return await self.auth0_client.poll_tokens(device_code=device_code)
79
60
 
80
- def interval_coro() -> Iterable[int]:
61
+ def interval_coro() -> Iterable[float]:
81
62
  nonlocal interval
82
63
  while True:
83
64
  yield interval
@@ -86,16 +67,16 @@ class DeviceRegistrar:
86
67
  async for data in poll_for(
87
68
  poller=poller, timeout_sec=timeout, interval_sec=interval_coro()
88
69
  ):
89
- error_code: Optional[str] = data.get("error")
70
+ error_code: str | None = data.get("error")
90
71
  if error_code is None:
91
- return cls._handle_ready_data(data, get_refresh_token)
72
+ return self.handle_ready_data(data)
92
73
  elif error_code == "authorization_pending":
93
74
  pass
94
75
  elif error_code == "slow_down":
95
76
  # This value is used by poll_for via interval_coro
96
77
  interval *= 2
97
78
  elif error_code == "expired_token":
98
- raise ClassiqExpiredTokenError(cls._TIMEOUT_ERROR)
79
+ raise ClassiqExpiredTokenError(self._TIMEOUT_ERROR)
99
80
  elif error_code == "access_denied":
100
81
  error_description = data.get("error_description")
101
82
  if error_description is None:
@@ -109,4 +90,4 @@ class DeviceRegistrar:
109
90
  f"Device registration failed with an unknown error: {error_code}."
110
91
  )
111
92
  else:
112
- raise ClassiqAuthenticationError(cls._TIMEOUT_ERROR)
93
+ raise ClassiqAuthenticationError(self._TIMEOUT_ERROR)
@@ -0,0 +1,19 @@
1
+ from classiq._internals.authentication.auth0 import Tokens
2
+ from classiq._internals.authentication.authorization_code import AuthorizationCodeFlow
3
+ from classiq._internals.authentication.authorization_flow import AuthorizationFlow
4
+ from classiq._internals.authentication.device import DeviceCodeFlow
5
+
6
+
7
+ class HybridFlow(AuthorizationFlow):
8
+ def __init__(
9
+ self, require_refresh_token: bool = True, text_only: bool = False
10
+ ) -> None:
11
+ super().__init__(require_refresh_token, text_only)
12
+ self.device_flow = DeviceCodeFlow(require_refresh_token, text_only)
13
+ self.auth_code_flow = AuthorizationCodeFlow(require_refresh_token, text_only)
14
+
15
+ async def get_tokens(self) -> Tokens:
16
+ device_data = await self.device_flow.get_device_data()
17
+ await self.auth_code_flow.authorize(device_data.verification_uri_complete)
18
+ print(f"Your user code: {device_data.user_code}") # noqa: T201
19
+ return await self.device_flow.poll_tokens(device_data)
@@ -5,7 +5,7 @@ import os
5
5
  import pathlib
6
6
  import platform
7
7
  import stat
8
- from typing import Any, Optional
8
+ from typing import Any
9
9
 
10
10
  import keyring
11
11
  from keyring.backends import fail
@@ -42,27 +42,27 @@ class PasswordManager(abc.ABC):
42
42
  self._settings = PasswordManagerSettings()
43
43
 
44
44
  @property
45
- def access_token(self) -> Optional[str]:
45
+ def access_token(self) -> str | None:
46
46
  return self._get(key=self._settings.ACCESS_TOKEN_KEY)
47
47
 
48
48
  @access_token.setter
49
- def access_token(self, access_token: Optional[str]) -> None:
49
+ def access_token(self, access_token: str | None) -> None:
50
50
  self._set(key=self._settings.ACCESS_TOKEN_KEY, value=access_token)
51
51
 
52
52
  @property
53
- def refresh_token(self) -> Optional[str]:
53
+ def refresh_token(self) -> str | None:
54
54
  return self._get(key=self._settings.REFRESH_TOKEN_KEY)
55
55
 
56
56
  @refresh_token.setter
57
- def refresh_token(self, refresh_token: Optional[str]) -> None:
57
+ def refresh_token(self, refresh_token: str | None) -> None:
58
58
  self._set(key=self._settings.REFRESH_TOKEN_KEY, value=refresh_token)
59
59
 
60
60
  @abc.abstractmethod
61
- def _get(self, key: str) -> Optional[str]:
61
+ def _get(self, key: str) -> str | None:
62
62
  pass
63
63
 
64
64
  @abc.abstractmethod
65
- def _set(self, key: str, value: Optional[str]) -> None:
65
+ def _set(self, key: str, value: str | None) -> None:
66
66
  pass
67
67
 
68
68
  @abc.abstractmethod
@@ -76,10 +76,10 @@ class PasswordManager(abc.ABC):
76
76
 
77
77
 
78
78
  class KeyringPasswordManager(PasswordManager):
79
- def _get(self, key: str) -> Optional[str]:
79
+ def _get(self, key: str) -> str | None:
80
80
  return keyring.get_password(service_name=self._SERVICE_NAME, username=key)
81
81
 
82
- def _set(self, key: str, value: Optional[str]) -> None:
82
+ def _set(self, key: str, value: str | None) -> None:
83
83
  if value is None:
84
84
  self._clear(key)
85
85
  return
@@ -101,10 +101,10 @@ class KeyringPasswordManager(PasswordManager):
101
101
 
102
102
 
103
103
  class DummyPasswordManager(PasswordManager):
104
- def _get(self, key: str) -> Optional[str]:
104
+ def _get(self, key: str) -> str | None:
105
105
  return None
106
106
 
107
- def _set(self, key: str, value: Optional[str]) -> None:
107
+ def _set(self, key: str, value: str | None) -> None:
108
108
  return
109
109
 
110
110
  def _clear(self, key: str) -> None:
@@ -134,13 +134,13 @@ class FilePasswordManager(PasswordManager):
134
134
  return json.loads(self.credentials_file.read_text())
135
135
  return {}
136
136
 
137
- def _get(self, key: str) -> Optional[str]:
137
+ def _get(self, key: str) -> str | None:
138
138
  token_dict = self._get_token_dict()
139
139
  if key in token_dict:
140
140
  return token_dict[key]
141
141
  return None
142
142
 
143
- def _set(self, key: str, value: Optional[str]) -> None:
143
+ def _set(self, key: str, value: str | None) -> None:
144
144
  token_dict = self._get_token_dict()
145
145
  token_dict[key] = value
146
146
  self._update_file(token_dict)
@@ -3,7 +3,6 @@ import logging
3
3
  import threading
4
4
  import warnings
5
5
  from collections.abc import Sequence
6
- from typing import Optional
7
6
 
8
7
  from classiq.interface.exceptions import (
9
8
  ClassiqAuthenticationError,
@@ -11,8 +10,8 @@ from classiq.interface.exceptions import (
11
10
  )
12
11
 
13
12
  from classiq._internals.authentication import password_manager as pm
14
- from classiq._internals.authentication.auth0 import Auth0
15
- from classiq._internals.authentication.device import DeviceRegistrar, Tokens
13
+ from classiq._internals.authentication.auth0 import Auth0, Tokens
14
+ from classiq._internals.authentication.hybrid_flow import HybridFlow
16
15
  from classiq._internals.config import Configuration
17
16
 
18
17
  PASSWORD_MANAGERS: Sequence[type[pm.PasswordManager]] = [
@@ -26,7 +25,7 @@ class TokenManager:
26
25
  def __init__(
27
26
  self,
28
27
  config: Configuration,
29
- password_manager: Optional[pm.PasswordManager] = None,
28
+ password_manager: pm.PasswordManager | None = None,
30
29
  ) -> None:
31
30
  self._config = config
32
31
  if password_manager is None:
@@ -35,8 +34,8 @@ class TokenManager:
35
34
  # to a specific event loop, which is undesirable
36
35
  self._lock = threading.Lock()
37
36
  self._password_manager: pm.PasswordManager = password_manager
38
- self._access_token: Optional[str] = self._password_manager.access_token
39
- self._refresh_token: Optional[str] = self._password_manager.refresh_token
37
+ self._access_token: str | None = self._password_manager.access_token
38
+ self._refresh_token: str | None = self._password_manager.refresh_token
40
39
  if self._access_token is None and self._refresh_token is not None:
41
40
  _logger.info(
42
41
  "Inconsistent state, access token is absent and refresh token is present."
@@ -67,7 +66,7 @@ class TokenManager:
67
66
  "Password Manager not found, we could not store your credentials securely. Please contact support@classiq.io"
68
67
  )
69
68
 
70
- def get_access_token(self) -> Optional[str]:
69
+ def get_access_token(self) -> str | None:
71
70
  return self._access_token
72
71
 
73
72
  async def _refresh(self) -> None:
@@ -127,7 +126,8 @@ class TokenManager:
127
126
  async def _authentication_helper(self) -> None:
128
127
  # TODO: consider using refresh token rotation
129
128
  # (https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation)
130
- tokens = await DeviceRegistrar.register(
131
- get_refresh_token=True, text_only=self._config.text_only
129
+ authorization_flow = HybridFlow(
130
+ require_refresh_token=True, text_only=self._config.text_only
132
131
  )
132
+ tokens = await authorization_flow.get_tokens()
133
133
  self._save_tokens(tokens, force_override_refresh_token=True)
@@ -7,9 +7,8 @@ import os
7
7
  import platform
8
8
  import sys
9
9
  import time
10
- from collections.abc import Awaitable
11
- from types import TracebackType
12
- from typing import Any, Callable, NoReturn, Optional, TypeVar, Union
10
+ from collections.abc import Awaitable, Callable
11
+ from typing import Any, NoReturn, TypeVar
13
12
 
14
13
  import httpx
15
14
  from typing_extensions import ParamSpec
@@ -146,27 +145,6 @@ def try_again_on_failure(
146
145
  return wrapper
147
146
 
148
147
 
149
- class _AsyncNullContext(contextlib.AbstractAsyncContextManager):
150
- """
151
- This class is meant to replace `contextlib.nullcontext`, which hadn't supported
152
- async context manager until python 3.10.
153
- """
154
-
155
- def __init__(self, enter_result: Any = None) -> None:
156
- self._enter_result = enter_result
157
-
158
- async def __aenter__(self) -> Any:
159
- return self._enter_result
160
-
161
- async def __aexit__(
162
- self,
163
- exc_type: Optional[type[BaseException]],
164
- exc_val: Optional[BaseException],
165
- exc_tb: Optional[TracebackType],
166
- ) -> None:
167
- pass
168
-
169
-
170
148
  class Client:
171
149
  _UNKNOWN_VERSION = HostChecker._UNKNOWN_VERSION
172
150
  _SESSION_HEADER = "Classiq-Session"
@@ -177,7 +155,7 @@ class Client:
177
155
  self._config = conf
178
156
  self._token_manager = token_manager.TokenManager(config=self._config)
179
157
  self._api_prefix = self._make_api_prefix()
180
- self._session_id: Optional[str] = None
158
+ self._session_id: str | None = None
181
159
 
182
160
  @staticmethod
183
161
  def _make_api_prefix() -> str:
@@ -229,9 +207,9 @@ class Client:
229
207
  http_client: httpx.AsyncClient,
230
208
  method: str,
231
209
  url: str,
232
- json: Optional[dict] = None,
233
- params: Optional[dict] = None,
234
- headers: Optional[dict[str, str]] = None,
210
+ json: dict | None = None,
211
+ params: dict | None = None,
212
+ headers: dict[str, str] | None = None,
235
213
  ) -> httpx.Response:
236
214
  http_client.headers.update(self._get_headers())
237
215
 
@@ -265,12 +243,12 @@ class Client:
265
243
  self,
266
244
  http_method: str,
267
245
  url: str,
268
- body: Optional[dict] = None,
269
- params: Optional[dict] = None,
246
+ body: dict | None = None,
247
+ params: dict | None = None,
270
248
  use_versioned_url: bool = True,
271
- headers: Optional[dict[str, str]] = None,
272
- http_client: Optional[httpx.AsyncClient] = None,
273
- ) -> Union[dict, list, str]:
249
+ headers: dict[str, str] | None = None,
250
+ http_client: httpx.AsyncClient | None = None,
251
+ ) -> dict | list | str:
274
252
  if use_versioned_url:
275
253
  url = self.make_versioned_url(url)
276
254
  async with self.use_client_or_create(http_client) as async_client:
@@ -285,26 +263,21 @@ class Client:
285
263
  return response.json()
286
264
 
287
265
  def use_client_or_create(
288
- self, http_client: Optional[httpx.AsyncClient]
266
+ self, http_client: httpx.AsyncClient | None
289
267
  ) -> contextlib.AbstractAsyncContextManager[httpx.AsyncClient]:
290
268
  if http_client is None:
291
269
  return self.async_client()
292
270
  else:
293
- if sys.version_info[0:2] < (3, 10):
294
- # remove this `if` and the `_AsyncNullContext` class when we stop
295
- # supporting python 3.9
296
- return _AsyncNullContext(enter_result=http_client)
297
- else:
298
- return contextlib.nullcontext(enter_result=http_client)
271
+ return contextlib.nullcontext(enter_result=http_client)
299
272
 
300
273
  def sync_call_api(
301
274
  self,
302
275
  http_method: str,
303
276
  url: str,
304
- body: Optional[dict] = None,
305
- headers: Optional[dict] = None,
277
+ body: dict | None = None,
278
+ headers: dict | None = None,
306
279
  use_versioned_url: bool = True,
307
- ) -> Union[dict, str]:
280
+ ) -> dict | str:
308
281
  if use_versioned_url:
309
282
  url = self.make_versioned_url(url)
310
283
  with httpx.Client(**self._make_client_args()) as sync_client:
@@ -356,7 +329,7 @@ class Client:
356
329
  return self._config
357
330
 
358
331
 
359
- DEFAULT_CLIENT: Optional[Client] = None
332
+ DEFAULT_CLIENT: Client | None = None
360
333
 
361
334
 
362
335
  def client() -> Client:
@@ -2,7 +2,6 @@
2
2
 
3
3
  import os
4
4
  import pathlib
5
- from typing import Optional, Union
6
5
 
7
6
  import configargparse # type: ignore[import]
8
7
  import pydantic
@@ -37,7 +36,21 @@ class Configuration(BaseModel):
37
36
  )
38
37
 
39
38
 
40
- _DEFAULT_CONFIG_FILES = [str(pathlib.Path("classiq", "config.ini"))]
39
+ home_path = pathlib.Path.home()
40
+ if os.name == "posix":
41
+ config_path = home_path / ".config"
42
+ else: # assuming "nt"
43
+ app_data = os.getenv("APPDATA")
44
+ if app_data:
45
+ config_path = pathlib.Path(app_data)
46
+ else:
47
+ config_path = home_path / "AppData" / "Roaming"
48
+
49
+ _DEFAULT_CONFIG_FILES = [
50
+ str(config_path / "classiq" / "config.ini"),
51
+ str(config_path / "classiq.conf"),
52
+ str(pathlib.Path("classiq", "config.ini")),
53
+ ]
41
54
  if os.name == "posix":
42
55
  # Unix convensions:
43
56
  # System-wide configuration rests in "/etc"
@@ -48,12 +61,13 @@ if os.name == "posix":
48
61
  _DEFAULT_CONFIG_FILES = [
49
62
  "/etc/classiq/config.ini",
50
63
  "/etc/classiq.conf",
51
- "~/.config/classiq/config.ini",
52
- "~/.config/classiq.conf",
53
64
  ] + _DEFAULT_CONFIG_FILES
54
65
 
55
66
 
56
- def init(args: Optional[Union[str, list[str]]] = None) -> Configuration:
67
+ CONFIG_ENV_FILES = (config_path / "classiq" / "config.env",)
68
+
69
+
70
+ def init(args: str | list[str] | None = None) -> Configuration:
57
71
  """Initialize the configuration object.
58
72
 
59
73
  Args:
@@ -1,12 +1,11 @@
1
1
  import webbrowser
2
- from typing import Optional
3
2
 
4
3
  from classiq.interface._version import SEMVER_VERSION
5
4
 
6
5
  DOCS_BASE_URL = "https://docs.classiq.io/"
7
6
 
8
7
 
9
- def open_help(version: Optional[str] = None) -> None:
8
+ def open_help(version: str | None = None) -> None:
10
9
  if version is None:
11
10
  version = SEMVER_VERSION
12
11
  if version == "0.0.0":
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  import warnings
5
5
  from datetime import datetime
6
- from typing import TYPE_CHECKING, Optional
6
+ from typing import TYPE_CHECKING
7
7
 
8
8
  import httpx
9
9
  import pydantic
@@ -38,7 +38,7 @@ class HostChecker:
38
38
  self._client_version = client_version
39
39
  self._interface_version = interface_version
40
40
 
41
- def _get_interface_version(self) -> Optional[str]:
41
+ def _get_interface_version(self) -> str | None:
42
42
  global_interfaces = GlobalVersions.model_validate(
43
43
  self._client.sync_call_api(
44
44
  "get", "/interface_versions", use_versioned_url=False
@@ -52,7 +52,7 @@ class HostChecker:
52
52
  )
53
53
  return host.classiq_interface
54
54
 
55
- def _get_deprecation_info(self) -> Optional[DeprecationInfo]:
55
+ def _get_deprecation_info(self) -> DeprecationInfo | None:
56
56
  global_versions = GlobalVersions.model_validate(
57
57
  self._client.sync_call_api("get", "/versions", use_versioned_url=False)
58
58
  )