luna-quantum 0.0.16__py3-none-any.whl → 0.0.33__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 luna-quantum might be problematic. Click here for more details.

Files changed (66) hide show
  1. {luna_quantum-0.0.16.dist-info → luna_quantum-0.0.33.dist-info}/METADATA +2 -1
  2. {luna_quantum-0.0.16.dist-info → luna_quantum-0.0.33.dist-info}/RECORD +64 -58
  3. luna_sdk/controllers/luna_http_client.py +27 -0
  4. luna_sdk/controllers/luna_platform_client.py +41 -23
  5. luna_sdk/controllers/luna_q.py +11 -16
  6. luna_sdk/controllers/luna_solve.py +12 -17
  7. luna_sdk/controllers/luna_transform.py +14 -15
  8. luna_sdk/error/http_error_utils.py +10 -3
  9. luna_sdk/interfaces/circuit_repo_i.py +18 -12
  10. luna_sdk/interfaces/cplex_repo_i.py +25 -10
  11. luna_sdk/interfaces/info_repo_i.py +10 -3
  12. luna_sdk/interfaces/lp_repo_i.py +20 -8
  13. luna_sdk/interfaces/optimization_repo_i.py +35 -60
  14. luna_sdk/interfaces/qpu_token_repo_i.py +40 -38
  15. luna_sdk/interfaces/solutions_repo_i.py +44 -24
  16. luna_sdk/repositories/circuit_repo.py +11 -44
  17. luna_sdk/repositories/cplex_repo.py +32 -20
  18. luna_sdk/repositories/info_repo.py +4 -7
  19. luna_sdk/repositories/lp_repo.py +21 -15
  20. luna_sdk/repositories/optimization_repo.py +36 -210
  21. luna_sdk/repositories/qpu_token_repo.py +52 -128
  22. luna_sdk/repositories/solutions_repo.py +109 -181
  23. luna_sdk/schemas/create/solution.py +2 -2
  24. luna_sdk/schemas/enums/optimization.py +8 -7
  25. luna_sdk/schemas/enums/qpu_token_type.py +1 -1
  26. luna_sdk/schemas/optimization.py +15 -24
  27. luna_sdk/schemas/optimization_formats/qubo.py +8 -0
  28. luna_sdk/schemas/pretty_base.py +10 -3
  29. luna_sdk/schemas/qpu_token.py +4 -5
  30. luna_sdk/schemas/rest/qpu_token/qpu_token_source.py +18 -0
  31. luna_sdk/schemas/rest/qpu_token/token_provider.py +47 -15
  32. luna_sdk/schemas/solution.py +7 -6
  33. luna_sdk/schemas/solver_info.py +31 -1
  34. luna_sdk/schemas/solver_parameters/aws/optimizer_params.py +40 -0
  35. luna_sdk/schemas/solver_parameters/aws/qaoa.py +36 -4
  36. luna_sdk/schemas/solver_parameters/base_parameter.py +5 -0
  37. luna_sdk/schemas/solver_parameters/dwave/base.py +15 -14
  38. luna_sdk/schemas/solver_parameters/dwave/dialectic_search.py +3 -2
  39. luna_sdk/schemas/solver_parameters/dwave/kerberos.py +2 -3
  40. luna_sdk/schemas/solver_parameters/dwave/leap_hybrid_bqm.py +2 -2
  41. luna_sdk/schemas/solver_parameters/dwave/leap_hybrid_cqm.py +2 -2
  42. luna_sdk/schemas/solver_parameters/dwave/parallel_tempering.py +2 -3
  43. luna_sdk/schemas/solver_parameters/dwave/parallel_tempering_qpu.py +2 -3
  44. luna_sdk/schemas/solver_parameters/dwave/population_annealing.py +2 -3
  45. luna_sdk/schemas/solver_parameters/dwave/population_annealing_qpu.py +2 -1
  46. luna_sdk/schemas/solver_parameters/dwave/qaga.py +4 -2
  47. luna_sdk/schemas/solver_parameters/dwave/qbsolv_like_qpu.py +2 -3
  48. luna_sdk/schemas/solver_parameters/dwave/qbsolv_like_simulated_annealing.py +2 -3
  49. luna_sdk/schemas/solver_parameters/dwave/qbsolv_like_tabu_search.py +2 -3
  50. luna_sdk/schemas/solver_parameters/dwave/quantum_annealing.py +2 -3
  51. luna_sdk/schemas/solver_parameters/dwave/repeated_reverse_quantum_annealing.py +4 -2
  52. luna_sdk/schemas/solver_parameters/dwave/repeated_reverse_simulated_annealing.py +3 -2
  53. luna_sdk/schemas/solver_parameters/dwave/saga.py +1 -1
  54. luna_sdk/schemas/solver_parameters/dwave/tabu_search.py +3 -1
  55. luna_sdk/schemas/solver_parameters/fujitsu/base.py +5 -4
  56. luna_sdk/schemas/solver_parameters/fujitsu/partial_config.py +7 -5
  57. luna_sdk/schemas/solver_parameters/ibm/standard_parameters.py +121 -7
  58. luna_sdk/schemas/solver_parameters/qctrl/qaoa.py +2 -2
  59. luna_sdk/schemas/wrappers/__init__.py +1 -0
  60. luna_sdk/schemas/wrappers/datetime_wrapper.py +31 -0
  61. luna_sdk/utils/parameter_finder.py +90 -0
  62. luna_sdk/utils/qpu_tokens.py +14 -13
  63. luna_sdk/constants.py +0 -1
  64. luna_sdk/controllers/custom_login_client.py +0 -61
  65. {luna_quantum-0.0.16.dist-info → luna_quantum-0.0.33.dist-info}/LICENSE +0 -0
  66. {luna_quantum-0.0.16.dist-info → luna_quantum-0.0.33.dist-info}/WHEEL +0 -0
@@ -1,11 +1,12 @@
1
1
  from typing import Literal, Optional, Tuple, Union
2
2
 
3
- from pydantic import BaseModel, ConfigDict, Field
3
+ from pydantic import ConfigDict, Field
4
4
 
5
+ from luna_sdk.schemas.solver_parameters.base_parameter import BaseParameter
5
6
  from luna_sdk.schemas.solver_parameters.fujitsu.partial_config import PartialConfig
6
7
 
7
8
 
8
- class CommonParams(BaseModel):
9
+ class CommonParams(BaseParameter):
9
10
  auto_tuning: Literal[
10
11
  "NOTHING",
11
12
  "SCALING",
@@ -24,7 +25,7 @@ class CommonParams(BaseModel):
24
25
  model_config = ConfigDict(arbitrary_types_allowed=True)
25
26
 
26
27
 
27
- class V2Params(BaseModel):
28
+ class V2Params(BaseParameter):
28
29
  optimization_method: Literal["annealing", "parallel_tempering"] = "annealing"
29
30
  temperature_start: float = Field(default=1_000.0, ge=0.0, le=1e20)
30
31
  temperature_end: float = Field(default=1.0, ge=0.0, le=1e20)
@@ -37,7 +38,7 @@ class V2Params(BaseModel):
37
38
  sampling_runs: int = 100
38
39
 
39
40
 
40
- class ConnectionParams(BaseModel):
41
+ class ConnectionParams(BaseParameter):
41
42
  annealer_protocol: Literal["http", "https"] = "https"
42
43
  annealer_address: str = "cloud.ts.fujitsu.com"
43
44
  annealer_port: int = Field(default=443, ge=0, le=2**16)
@@ -1,13 +1,15 @@
1
1
  from typing import Any, List, Literal, Optional, Tuple, Union
2
2
 
3
3
  from numpy.typing import NDArray
4
- from pydantic import BaseModel, ConfigDict, Field, field_serializer
4
+ from pydantic import ConfigDict, Field, field_serializer
5
5
  from typing_extensions import Annotated
6
6
 
7
+ from luna_sdk.schemas.solver_parameters.base_parameter import BaseParameter
8
+
7
9
  OneHot = Literal["no_way", "one_way", "two_way"]
8
10
 
9
11
 
10
- class VarShapeBase(BaseModel):
12
+ class VarShapeBase(BaseParameter):
11
13
  name: str
12
14
  one_hot: OneHot = "no_way"
13
15
 
@@ -110,7 +112,7 @@ VarDef = Annotated[
110
112
  ]
111
113
 
112
114
 
113
- class OneHotGroup(BaseModel):
115
+ class OneHotGroup(BaseParameter):
114
116
  """O:class:`dadk.BinPol.OneHotGroup` is used to define a one-way-one-hot group within a set of bit variables.
115
117
  In particular, the entries of the one-way-one-hot group are specified as tuples including the name of the variable
116
118
  specified in a BitArrayShape and (optionally) the indices of the variable in every dimension. Here, a single number
@@ -129,7 +131,7 @@ class OneHotGroup(BaseModel):
129
131
  entries: List[Union[List, Tuple]] = []
130
132
 
131
133
 
132
- class VarShapeSet(BaseModel):
134
+ class VarShapeSet(BaseParameter):
133
135
  """:class:`dadk.BinPol.VarShapeSet` defines an indexing structure for the bits of a BinPol polynomial. Plain BinPol
134
136
  polynomials are defined on a set of bits indexed by a ``range(N)`` for some integer ``N``. The ``VarShapeSet`` lays
135
137
  a sequence of disjoint named sections over this linear structure. Bits within a section can be addressed by the
@@ -150,7 +152,7 @@ class VarShapeSet(BaseModel):
150
152
  one_hot_groups: Optional[List[OneHotGroup]] = None
151
153
 
152
154
 
153
- class PartialConfig(BaseModel):
155
+ class PartialConfig(BaseParameter):
154
156
  """:class:`PartialConfig` produces a dictionary that can be used to initialize bits for the annealing algorithm.
155
157
 
156
158
  The start state for an annealing or parallel tempering optimization can be specified.
@@ -1,9 +1,104 @@
1
1
  from typing import Any, Dict, Literal, Optional, Union
2
2
 
3
- from pydantic import BaseModel, ConfigDict
3
+ from pydantic import Field
4
4
 
5
+ from luna_sdk.schemas.solver_parameters.base_parameter import BaseParameter
6
+
7
+
8
+ class Simulator(BaseParameter):
9
+ """Use a simulator as backend. The QAOA is executed completely on our server, and
10
+ no IBM token is required.
11
+
12
+ Parameters
13
+ ----------
14
+ backend_name: Literal['aer', 'statevector']
15
+ Which simulator to use. Currently, `AerSimulator` from `qiskit_aer` and the statevector simulator from `qiskit.primitives` are available.
16
+ """
17
+
18
+ backend_type: Literal["simulator"] = "simulator"
19
+ backend_name: Literal["aer", "statevector"] = "aer"
20
+
21
+
22
+ class FakeProvider(BaseParameter):
23
+ """Use a V2 fake backend from `qiskit_ibm_runtime.fake_provider`. The QAOA is
24
+ executed entirely on our server, and no IBM token is required.
25
+
26
+ Parameters
27
+ ----------
28
+ backend_name: str
29
+ Which backend to use
30
+ """
31
+
32
+ backend_type: Literal["fake_provider"] = "fake_provider"
33
+ backend_name: str
34
+
35
+
36
+ class IbmBackend(BaseParameter):
37
+ """Use an online backend from `ibm_quantum` or `ibm_cloud`. As IBM hardware is used,
38
+ this method requires an IBM token.
39
+
40
+ Parameters
41
+ ----------
42
+ backend_name: str
43
+ Which backend to use.
44
+ """
45
+
46
+ backend_type: Literal["ibm_backend"] = "ibm_backend"
47
+ backend_name: str
48
+
49
+
50
+ class LeastBusyQuery(BaseParameter):
51
+ """Filter parameters when querying the least busy backend.
52
+
53
+ Parameters
54
+ ----------
55
+ min_num_qubits: Optional[int]
56
+ Minimum number of qubits the backend has to have.
57
+ instance: Optional[str]
58
+ This is only supported for `ibm_quantum` runtime and is in the hub/group/project format.
59
+ filters: Dict[str, Any]
60
+ Simple filters that require a specific value for an attribute in backend configuration or status. Example: `{'operational': True}`"""
61
+
62
+ min_num_qubits: Optional[int] = None
63
+ instance: Optional[str] = None
64
+ filters: Dict[str, Any] = {}
65
+
66
+
67
+ class LeastBusy(BaseParameter):
68
+ """Use the least busy online backend from `ibm_quantum` or `ibm_cloud`. As IBM
69
+ hardware is used, this method requires an IBM token.
70
+
71
+ Parameters
72
+ ----------
73
+ query_params: LeastBusyQuery
74
+ Filter parameters when querying the least busy backend.
75
+ """
76
+
77
+ backend_type: Literal["least_busy"] = "least_busy"
78
+ query_params: LeastBusyQuery = LeastBusyQuery()
79
+
80
+
81
+ class ServiceConfig(BaseParameter):
82
+ """Parameters to be passed to the `QiskitRuntimeService` object
83
+
84
+ Parameters
85
+ ----------
86
+ channel: Optional[Literal['ibm_cloud', 'ibm_quantum']]
87
+ The channel type for the service
88
+ url: Optional[str]
89
+ The URL of the service
90
+ name: Optional[str]
91
+ The name of the service
92
+ instance: Optional[str]
93
+ The instance identifier
94
+ proxies: Optional[dict]
95
+ Proxy settings for the service
96
+ verify: Optional[bool]
97
+ SSL verification setting
98
+ channel_strategy: Optional[str]
99
+ The strategy for the channel
100
+ """
5
101
 
6
- class ServiceConfig(BaseModel):
7
102
  channel: Optional[Literal["ibm_cloud", "ibm_quantum"]] = "ibm_quantum"
8
103
  url: Optional[str] = None
9
104
  name: Optional[str] = None
@@ -12,16 +107,35 @@ class ServiceConfig(BaseModel):
12
107
  verify: Optional[bool] = None
13
108
  channel_strategy: Optional[str] = None
14
109
 
15
- model_config = ConfigDict(extra="forbid")
16
110
 
111
+ class StandardParameters(BaseParameter):
112
+ """
113
+ Standard parameters for the optimizer.
114
+
115
+ Parameters
116
+ ----------
117
+ backend: Union[Simulator, FakeProvider, IbmBackend, LeastBusy]
118
+ Which backend to use. If None, will use no backend and StatevectorSampler and StatevectorEstimator. If dict, will call `runtime_service.least_busy` with the params given in the dict. If str: If 'AerSimulator', use AerSimulator. If string starts with 'Fake', will use the corresponding fake backend from qiskit_ibm_runtime.fake_provider. Otherwise, will try to use a real backend with this name.
119
+ shots: int
120
+ Shots for the optimizer
121
+ dynamical_decoupling: Dict[str, Any]
122
+ Dynamical decoupling options for the optimizer
123
+ optimizer: str
124
+ Name of the optimizer to use in scipy minimize
125
+ maxiter: int
126
+ Maximum number of iterations for the algorithm
127
+ optimization_level: int
128
+ Optimization level for the pass manager
129
+ service_config: ServiceConfig
130
+ Parameters to be passed to the `QiskitRuntimeService` object
131
+ """
17
132
 
18
- class StandardParameters(BaseModel):
19
- backend: Optional[Union[str, Dict[str, Any]]] = "AerSimulator"
133
+ backend: Union[Simulator, FakeProvider, IbmBackend, LeastBusy] = Field(
134
+ default=Simulator(), discriminator="backend_type"
135
+ )
20
136
  shots: int = 1024
21
137
  dynamical_decoupling: Dict[str, Any] = {}
22
138
  optimizer: str = "COBYLA"
23
139
  maxiter: int = 10
24
140
  optimization_level: int = 2
25
141
  service_config: ServiceConfig = ServiceConfig()
26
-
27
- model_config = ConfigDict(extra="forbid")
@@ -1,9 +1,9 @@
1
1
  from typing import Any, Optional
2
2
 
3
- from pydantic import BaseModel
3
+ from luna_sdk.schemas.solver_parameters.base_parameter import BaseParameter
4
4
 
5
5
 
6
- class QaoaParameters(BaseModel):
6
+ class QaoaParameters(BaseParameter):
7
7
  """
8
8
  QAOA is a popular algorithm that can be applied to a wide range of optimization problems that are out of reach today
9
9
  like portfolio optimization, efficient logistics routing, and asset liability management.
@@ -0,0 +1 @@
1
+ from .datetime_wrapper import PydanticDatetimeWrapper
@@ -0,0 +1,31 @@
1
+ from datetime import datetime
2
+ from typing import Union
3
+
4
+ from dateutil.parser import parse
5
+ from pydantic import BeforeValidator
6
+ from typing_extensions import Annotated
7
+
8
+
9
+ def validate_datetime(date_string: Union[str, datetime]) -> datetime:
10
+ """Validate an ISO date string and return the resulting datetime in the local timezone.
11
+
12
+ Parameters
13
+ ----------
14
+ date_string : str
15
+ The ISO date string
16
+
17
+ Returns
18
+ -------
19
+ datetime
20
+ The datetime in the user's local timezone
21
+
22
+ Raises
23
+ ------
24
+ ValueError
25
+ If `date_string` does not have a valid format.
26
+ """
27
+ dt = date_string if isinstance(date_string, datetime) else parse(date_string)
28
+ return dt.astimezone()
29
+
30
+
31
+ PydanticDatetimeWrapper = Annotated[datetime, BeforeValidator(validate_datetime)]
@@ -0,0 +1,90 @@
1
+ from typing import Dict, Optional, Type
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from luna_sdk.schemas.solver_parameters.aws import QaoaParameters as AwsQaoaParameters
6
+ from luna_sdk.schemas.solver_parameters.dwave import (
7
+ DialecticSearchParameters,
8
+ KerberosParameters,
9
+ LeapHybridBqmParameters,
10
+ LeapHybridCqmParameters,
11
+ ParallelTemperingParameters,
12
+ ParallelTemperingQpuParameters,
13
+ PopulationAnnealingParameters,
14
+ PopulationAnnealingQpuParameters,
15
+ QAGAParameters,
16
+ QbSolvLikeQpuParameters,
17
+ QbSolvLikeSimulatedAnnealingParameters,
18
+ QbSolvLikeTabuSearchParameters,
19
+ QuantumAnnealingParameters,
20
+ RepeatedReverseQuantumAnnealingParameters,
21
+ RepeatedReverseSimulatedAnnealingParameters,
22
+ SAGAParameters,
23
+ SimulatedAnnealingParameters,
24
+ TabuSearchParameters,
25
+ )
26
+ from luna_sdk.schemas.solver_parameters.fujitsu import (
27
+ DigitalAnnealerCPUParameters,
28
+ DigitalAnnealerV2Parameters,
29
+ DigitalAnnealerV3Parameters,
30
+ )
31
+ from luna_sdk.schemas.solver_parameters.ibm import VqeParameters
32
+ from luna_sdk.schemas.solver_parameters.ibm import QaoaParameters as IbmQaoaParameters
33
+ from luna_sdk.schemas.solver_parameters.qctrl import (
34
+ QaoaParameters as QctrlQaoaParameters,
35
+ )
36
+
37
+ _provider_solver_param_dict: Dict[str, Dict[str, Optional[Type[BaseModel]]]] = {
38
+ "aws": {
39
+ "QAOA": AwsQaoaParameters,
40
+ },
41
+ "dwave": {
42
+ "BF": None,
43
+ "DS": DialecticSearchParameters,
44
+ "K": KerberosParameters,
45
+ "LBQM": LeapHybridBqmParameters,
46
+ "LCQM": LeapHybridCqmParameters,
47
+ "PT": ParallelTemperingParameters,
48
+ "PTQ": ParallelTemperingQpuParameters,
49
+ "PA": PopulationAnnealingParameters,
50
+ "PAQ": PopulationAnnealingQpuParameters,
51
+ "QLQ": QbSolvLikeQpuParameters,
52
+ "QLSA": QbSolvLikeSimulatedAnnealingParameters,
53
+ "QLTS": QbSolvLikeTabuSearchParameters,
54
+ "QA": QuantumAnnealingParameters,
55
+ "RRQA": RepeatedReverseQuantumAnnealingParameters,
56
+ "RRSA": RepeatedReverseSimulatedAnnealingParameters,
57
+ "SA": SimulatedAnnealingParameters,
58
+ "TS": TabuSearchParameters,
59
+ "SAGA": SAGAParameters,
60
+ "SAGAPW": None,
61
+ "SAGAMP": None,
62
+ "QAGA": QAGAParameters,
63
+ "QAGAPW": None,
64
+ "QAGAMP": None,
65
+ },
66
+ "fujitsu": {
67
+ "DACPU": DigitalAnnealerCPUParameters,
68
+ "DAV3": DigitalAnnealerV3Parameters,
69
+ "DAV2": DigitalAnnealerV2Parameters,
70
+ },
71
+ "ibm": {
72
+ "QAOA": IbmQaoaParameters,
73
+ "VQE": VqeParameters,
74
+ },
75
+ "qctrl": {
76
+ "QCTRLQAOA": QctrlQaoaParameters,
77
+ },
78
+ "zib": {
79
+ "SCIP": None,
80
+ },
81
+ }
82
+
83
+
84
+ def get_parameter_by_solver(solver: str, provider: str) -> Optional[Type[BaseModel]]:
85
+ if provider not in _provider_solver_param_dict:
86
+ return None
87
+
88
+ if solver not in _provider_solver_param_dict[provider]:
89
+ return None
90
+ return _provider_solver_param_dict[provider][solver]
@@ -1,9 +1,10 @@
1
1
  import os
2
2
 
3
- from luna_sdk.schemas import QpuToken, QpuTokenSource
3
+ from luna_sdk.schemas.rest.qpu_token.qpu_token_source import _RESTQpuTokenSource
4
4
  from luna_sdk.schemas.rest.qpu_token.token_provider import (
5
5
  RestAPITokenProvider,
6
6
  AWSQpuTokens,
7
+ _RestQpuToken,
7
8
  )
8
9
 
9
10
 
@@ -15,37 +16,37 @@ def extract_qpu_tokens_from_env() -> RestAPITokenProvider:
15
16
  aws_access_key = os.environ.get("LUNA_AWS_ACCESS_KEY")
16
17
  aws_access_secret_key = os.environ.get("LUNA_AWS_SECRET_ACCESS_KEY")
17
18
  return RestAPITokenProvider(
18
- ibm=QpuToken(
19
- source=QpuTokenSource.INLINE,
19
+ ibm=_RestQpuToken(
20
+ source=_RESTQpuTokenSource.INLINE,
20
21
  token=ibm_token,
21
22
  )
22
23
  if ibm_token
23
24
  else None,
24
- dwave=QpuToken(
25
- source=QpuTokenSource.INLINE,
25
+ dwave=_RestQpuToken(
26
+ source=_RESTQpuTokenSource.INLINE,
26
27
  token=dwave_token,
27
28
  )
28
29
  if dwave_token
29
30
  else None,
30
- qctrl=QpuToken(
31
- source=QpuTokenSource.INLINE,
31
+ qctrl=_RestQpuToken(
32
+ source=_RESTQpuTokenSource.INLINE,
32
33
  token=qctrl_token,
33
34
  )
34
35
  if qctrl_token
35
36
  else None,
36
- fujitsu=QpuToken(
37
- source=QpuTokenSource.INLINE,
37
+ fujitsu=_RestQpuToken(
38
+ source=_RESTQpuTokenSource.INLINE,
38
39
  token=fujitsu_token,
39
40
  )
40
41
  if fujitsu_token
41
42
  else None,
42
43
  aws=AWSQpuTokens(
43
- aws_access_key=QpuToken(
44
- source=QpuTokenSource.INLINE,
44
+ aws_access_key=_RestQpuToken(
45
+ source=_RESTQpuTokenSource.INLINE,
45
46
  token=aws_access_key,
46
47
  ),
47
- aws_secret_access_key=QpuToken(
48
- source=QpuTokenSource.INLINE,
48
+ aws_secret_access_key=_RestQpuToken(
49
+ source=_RESTQpuTokenSource.INLINE,
49
50
  token=aws_access_secret_key,
50
51
  ),
51
52
  ),
luna_sdk/constants.py DELETED
@@ -1 +0,0 @@
1
- LUNA_SDK_VERSION: str = "0.0.11"
@@ -1,61 +0,0 @@
1
- import logging
2
- from http.client import UNAUTHORIZED
3
-
4
- from httpx import Client, ReadTimeout, Response
5
-
6
- from luna_sdk.constants import LUNA_SDK_VERSION
7
- from luna_sdk.error.http_error_utils import HttpErrorUtils
8
- from luna_sdk.exceptions.timeout_exception import TimeoutException
9
-
10
-
11
- class CustomLoginClient(Client):
12
- _login_url: str
13
- _email: str
14
- _password: str
15
- _bearer_token: str
16
-
17
- _user_agent: str = f"LunaSDK/{LUNA_SDK_VERSION}"
18
-
19
- def __init__(self, email: str, password: str, login_url: str, *args, **kwargs):
20
- super().__init__(*args, **kwargs)
21
- self._email = email
22
- self._password = password
23
- self._login_url = login_url
24
-
25
- self.headers["User-Agent"] = self._user_agent
26
-
27
- def login(self, email: str, password: str):
28
- with Client() as client:
29
- headers = {
30
- "accept": "application/json",
31
- "Content-Type": "application/x-www-form-urlencoded",
32
- "User-Agent": self._user_agent,
33
- }
34
-
35
- response: Response = client.post(
36
- self._login_url,
37
- data={"username": email, "password": password},
38
- headers=headers,
39
- )
40
- HttpErrorUtils.check_for_error(response)
41
-
42
- self._bearer_token = response.json()["access_token"]
43
-
44
- def request(self, *args, **kwargs) -> Response:
45
- try:
46
- response: Response = super().request(*args, **kwargs)
47
- if response.status_code == UNAUTHORIZED:
48
- logging.info("Unauthorized - trying to login")
49
-
50
- self.login(self._email, self._password)
51
-
52
- logging.info("Re-login successful")
53
- self.headers.update(
54
- headers={"authorization": f"Bearer {self._bearer_token}"}
55
- )
56
- logging.info("Trying request again")
57
- response = super().request(*args, **kwargs)
58
- except ReadTimeout as e:
59
- raise TimeoutException()
60
- HttpErrorUtils.check_for_error(response)
61
- return response