catalystwan 2.0.0a1__py3-none-any.whl → 2.0.0a3__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.
@@ -1,2 +1,2 @@
1
1
  from catalystwan.core.client import create_client as create_client
2
- from catalystwan.core.client import create_thread_client as create_thread_client
2
+ from catalystwan.core.client import create_client_from_auth as create_client_from_auth
@@ -3,28 +3,74 @@ from __future__ import annotations
3
3
  import logging
4
4
  from contextlib import contextmanager
5
5
  from copy import copy
6
- from typing import TYPE_CHECKING, Generator, Optional, Union
6
+ from inspect import isclass
7
+ from typing import TYPE_CHECKING, Generator, Optional, Type, TypeVar, Union, cast, overload
7
8
 
8
9
  from catalystwan.core.apigw_auth import ApiGwAuth
10
+ from catalystwan.core.exceptions import CatalystwanException
9
11
  from catalystwan.core.loader import load_client
10
12
  from catalystwan.core.request_adapter import RequestAdapter
11
13
  from catalystwan.core.request_limiter import RequestLimiter
12
14
  from catalystwan.core.session import ManagerSession, create_base_url, create_manager_session
13
15
  from catalystwan.core.vmanage_auth import vManageAuth
16
+ from typing_extensions import TypeGuard
14
17
 
15
18
  if TYPE_CHECKING:
16
19
  from catalystwan.core.loader import ApiClient
17
20
 
18
21
 
22
+ class CatalystwanNotAClientException(CatalystwanException): ...
23
+
24
+
25
+ Client = TypeVar("Client")
26
+
27
+
28
+ # TODO: Better TypeGuards - it may be hard since we want to avoid direct imports
29
+ # For now, it's more of a hack for typing purposes
30
+ def _is_client_instance(obj: object) -> TypeGuard[ApiClient]:
31
+ return not isclass(obj) and hasattr(obj, "api_version")
32
+
33
+
34
+ def _is_client_class(obj: object) -> TypeGuard[Type[ApiClient]]:
35
+ return isclass(obj) and hasattr(obj, "api_version")
36
+
37
+
38
+ @overload
39
+ @contextmanager
40
+ def create_client_from_auth(
41
+ url: str,
42
+ auth: Union[vManageAuth, ApiGwAuth],
43
+ port: Optional[int] = None,
44
+ subdomain: Optional[str] = None,
45
+ logger: Optional[logging.Logger] = None,
46
+ request_limiter: Optional[RequestLimiter] = None,
47
+ ) -> Generator[ApiClient, None, None]: ...
48
+
49
+
50
+ @overload
51
+ @contextmanager
52
+ def create_client_from_auth(
53
+ url: str,
54
+ auth: Union[vManageAuth, ApiGwAuth],
55
+ port: Optional[int] = None,
56
+ subdomain: Optional[str] = None,
57
+ logger: Optional[logging.Logger] = None,
58
+ request_limiter: Optional[RequestLimiter] = None,
59
+ *,
60
+ api_client_class: Type[Client],
61
+ ) -> Generator[Client, None, None]: ...
62
+
63
+
19
64
  @contextmanager
20
- def create_thread_client(
65
+ def create_client_from_auth(
21
66
  url: str,
22
67
  auth: Union[vManageAuth, ApiGwAuth],
23
68
  port: Optional[int] = None,
24
69
  subdomain: Optional[str] = None,
25
70
  logger: Optional[logging.Logger] = None,
26
71
  request_limiter: Optional[RequestLimiter] = None,
27
- ) -> Generator[ApiClient, None, None]:
72
+ api_client_class: Optional[Type[Client]] = None,
73
+ ) -> Generator[Union[ApiClient, Client], None, None]:
28
74
  if logger is None:
29
75
  logger = logging.getLogger(__name__)
30
76
  session = ManagerSession(
@@ -36,12 +82,31 @@ def create_thread_client(
36
82
  )
37
83
  with session.login():
38
84
  version = session.api_version.base_version
39
- logger.debug(f"Choosing client for version {version}...")
40
- client = load_client(session.api_version.base_version)
41
- logger.debug(f"Client for version {version} loaded")
42
- yield client(RequestAdapter(session=session, logger=logger))
85
+ if api_client_class is None:
86
+ logger.debug(f"Choosing client for version {version}...")
87
+ client = load_client(session.api_version.base_version)
88
+ logger.debug(f"Client for version {version} loaded")
89
+ yield client(RequestAdapter(session=session, logger=logger))
90
+ elif _is_client_class(api_client_class):
91
+ logger.debug(f"Creating instance for client class {api_client_class}")
92
+ yield api_client_class(RequestAdapter(session=session, logger=logger))
93
+ else:
94
+ raise CatalystwanNotAClientException(f"{api_client_class} is not a client class")
95
+
96
+
97
+ @overload
98
+ @contextmanager
99
+ def create_client(
100
+ url: str,
101
+ username: str,
102
+ password: str,
103
+ port: Optional[int] = None,
104
+ subdomain: Optional[str] = None,
105
+ logger: Optional[logging.Logger] = None,
106
+ ) -> Generator[ApiClient, None, None]: ...
43
107
 
44
108
 
109
+ @overload
45
110
  @contextmanager
46
111
  def create_client(
47
112
  url: str,
@@ -50,22 +115,45 @@ def create_client(
50
115
  port: Optional[int] = None,
51
116
  subdomain: Optional[str] = None,
52
117
  logger: Optional[logging.Logger] = None,
53
- ) -> Generator[ApiClient, None, None]:
118
+ *,
119
+ api_client_class: Type[Client],
120
+ ) -> Generator[Client, None, None]: ...
121
+
122
+
123
+ @contextmanager
124
+ def create_client(
125
+ url: str,
126
+ username: str,
127
+ password: str,
128
+ port: Optional[int] = None,
129
+ subdomain: Optional[str] = None,
130
+ logger: Optional[logging.Logger] = None,
131
+ api_client_class: Optional[Type[Client]] = None,
132
+ ) -> Generator[Union[ApiClient, Client], None, None]:
54
133
  if logger is None:
55
134
  logger = logging.getLogger(__name__)
56
135
  with create_manager_session(url, username, password, port, subdomain, logger) as session:
57
- version = session.api_version.base_version
58
- logger.debug(f"Choosing client for version {version}...")
59
- client = load_client(session.api_version.base_version)
60
- logger.debug(f"Client for version {version} loaded")
61
- yield client(RequestAdapter(session=session, logger=logger))
136
+ if api_client_class is None:
137
+ version = session.api_version.base_version
138
+ logger.debug(f"Choosing client for version {version}...")
139
+ client = load_client(session.api_version.base_version)
140
+ logger.debug(f"Client for version {version} loaded")
141
+ yield client(RequestAdapter(session=session, logger=logger))
142
+ elif _is_client_class(api_client_class):
143
+ logger.debug(f"Creating instance for client class {api_client_class}")
144
+ yield api_client_class(RequestAdapter(session=session, logger=logger))
145
+ else:
146
+ raise CatalystwanNotAClientException(f"{api_client_class} is not a client class")
62
147
 
63
148
 
64
149
  @contextmanager
65
- def copy_client(client: ApiClient) -> Generator[ApiClient, None, None]:
66
- request_adapter = copy(client._request_adapter)
67
- session = request_adapter.session
68
- with session.login():
69
- new_client = load_client(session.api_version.base_version)(request_adapter)
70
- assert new_client.api_version == client.api_version
71
- yield new_client
150
+ def copy_client(client: Client) -> Generator[Client, None, None]:
151
+ if _is_client_instance(client):
152
+ request_adapter = copy(client._request_adapter)
153
+ session = request_adapter.session
154
+ with session.login():
155
+ new_client = load_client(session.api_version.base_version)(request_adapter)
156
+ assert new_client.api_version == client.api_version
157
+ yield cast(Client, new_client)
158
+ else:
159
+ raise CatalystwanNotAClientException(f"{client} is not a client instance")
@@ -6,32 +6,15 @@ import typing as t
6
6
 
7
7
  if t.TYPE_CHECKING:
8
8
  from catalystwan.versions.v20_15.api_client import ApiClient as ApiClientV20_15
9
- from catalystwan.versions.v20_16.api_client import ApiClient as ApiClientV20_16
10
9
 
11
- ApiClient = t.Union[ApiClientV20_15, ApiClientV20_16]
10
+ ApiClient = t.Union[ApiClientV20_15]
12
11
 
13
- VERSIONS = ["20.15", "20.16"]
12
+ VERSIONS = ["20.15"]
14
13
 
15
14
 
16
- @t.overload
17
- def load_client(version: t.Literal["20.15"]) -> t.Type[ApiClientV20_15]: ...
18
-
19
-
20
- @t.overload
21
- def load_client(version: t.Literal["20.16"]) -> t.Type[ApiClientV20_16]: ...
22
-
23
-
24
- @t.overload
25
- def load_client(version: str) -> t.Type[t.Union[ApiClientV20_15, ApiClientV20_16]]: ...
26
-
27
-
28
- def load_client(version: t.Union[str, t.Literal["20.15", "20.16"]]) -> t.Type[ApiClient]:
15
+ def load_client(version: str) -> t.Type[ApiClient]:
29
16
  if version == "20.15":
30
17
  from catalystwan.versions.v20_15.api_client import ApiClient as ApiClientV20_15
31
18
 
32
19
  return ApiClientV20_15
33
- if version == "20.16":
34
- from catalystwan.versions.v20_16.api_client import ApiClient as ApiClientV20_16
35
-
36
- return ApiClientV20_16
37
- raise RuntimeError("Unsupported version: {}".format(version))
20
+ raise RuntimeError("Unsupported version: {}".format(version))
@@ -1,14 +1,15 @@
1
1
  from collections import deque
2
2
  from copy import deepcopy
3
- from dataclasses import fields, is_dataclass
3
+ from dataclasses import dataclass, fields, is_dataclass
4
4
  from functools import reduce
5
5
  from inspect import isclass, unwrap
6
- from typing import Any, Dict, List, Literal, Protocol, Tuple, Type, TypeVar, Union
6
+ from typing import Any, Dict, List, Literal, Optional, Protocol, Tuple, Type, TypeVar, Union, cast
7
7
 
8
8
  from catalystwan.core.exceptions import (
9
9
  CatalystwanModelInputException,
10
10
  CatalystwanModelValidationError,
11
11
  )
12
+ from catalystwan.core.models.utils import count_matching_keys
12
13
  from catalystwan.core.types import MODEL_TYPES, AliasPath, DataclassInstance
13
14
  from typing_extensions import Annotated, get_args, get_origin, get_type_hints
14
15
 
@@ -19,6 +20,13 @@ class ValueExtractorCallable(Protocol):
19
20
  def __call__(self, field_value: Any) -> Any: ...
20
21
 
21
22
 
23
+ @dataclass
24
+ class ExtractedValue:
25
+ value: Any
26
+ exact_match: bool
27
+ matched_keys: Optional[int] = None
28
+
29
+
22
30
  class ModelDeserializer:
23
31
  def __init__(self, model: Type[T]) -> None:
24
32
  self.model = model
@@ -47,7 +55,6 @@ class ModelDeserializer:
47
55
 
48
56
  def __check_errors(self):
49
57
  if self._exceptions:
50
- print(self._exceptions)
51
58
  # Put exceptions from current model first
52
59
  self._exceptions.sort(key=lambda x: isinstance(x, CatalystwanModelValidationError))
53
60
  current_model_errors = sum(
@@ -58,67 +65,91 @@ class ModelDeserializer:
58
65
  message += f"{exc}\n"
59
66
  raise CatalystwanModelValidationError(message)
60
67
 
61
- def __is_optional(self, t: Any) -> bool:
62
- if get_origin(t) is Union and type(None) in get_args(t):
63
- return True
64
- return False
65
-
66
- def __extract_type(self, field_type: Any, field_value: Any, field_name: str) -> Any:
68
+ def __extract_type(self, field_type: Any, field_value: Any, field_name: str) -> ExtractedValue:
67
69
  origin = get_origin(field_type)
68
70
  # check for simple types and classes
69
71
  if origin is None:
70
- if field_type is Any:
71
- return field_value
72
- if isinstance(field_value, field_type):
73
- return field_value
72
+ if field_type is Any or isinstance(field_value, field_type):
73
+ return ExtractedValue(value=field_value, exact_match=True)
74
+ # Do not cast bool values
75
+ elif field_type is bool:
76
+ ...
77
+ # False/Empty values (like empty string or list) can match to None
78
+ elif field_type is type(None):
79
+ if not field_value:
80
+ return ExtractedValue(value=None, exact_match=False)
74
81
  elif is_dataclass(field_type):
75
- assert isinstance(field_type, type)
76
- return deserialize(field_type, **field_value)
82
+ model_instance = deserialize(
83
+ cast(Type[DataclassInstance], field_type), **field_value
84
+ )
85
+ return ExtractedValue(
86
+ value=model_instance,
87
+ exact_match=False,
88
+ matched_keys=count_matching_keys(model_instance, field_value),
89
+ )
77
90
  elif isclass(unwrap(field_type)):
78
91
  if isinstance(field_value, dict):
79
- return field_type(**field_value)
92
+ return ExtractedValue(value=field_type(**field_value), exact_match=False)
80
93
  else:
81
94
  try:
82
- return field_type(field_value)
95
+ return ExtractedValue(value=field_type(field_value), exact_match=False)
83
96
  except ValueError:
84
97
  raise CatalystwanModelInputException(
85
98
  f"Unable to match or cast input value for {field_name} [expected_type={unwrap(field_type)}, input={field_value}, input_type={type(field_value)}]"
86
99
  )
100
+ # List is an exact match only if all of its elements are
87
101
  elif origin is list:
88
102
  if isinstance(field_value, list):
89
- return [
90
- self.__extract_type(get_args(field_type)[0], value, field_name)
91
- for value in field_value
92
- ]
93
- elif self.__is_optional(field_type):
94
- if field_value is None:
95
- return None
96
- else:
97
- try:
98
- return self.__extract_type(get_args(field_type)[0], field_value, field_name)
99
- except CatalystwanModelInputException as e:
100
- if not field_value:
101
- return None
102
- raise e
103
+ values = []
104
+ exact_match = True
105
+ for value in field_value:
106
+ extracted_value = self.__extract_type(
107
+ get_args(field_type)[0], value, field_name
108
+ )
109
+ values.append(extracted_value.value)
110
+ if not extracted_value.exact_match:
111
+ exact_match = False
112
+ return ExtractedValue(value=values, exact_match=exact_match)
103
113
  elif origin is Literal:
104
114
  for arg in get_args(field_type):
105
115
  try:
106
116
  if type(arg)(field_value) == arg:
107
- return type(arg)(field_value)
117
+ return ExtractedValue(
118
+ value=type(arg)(field_value), exact_match=type(arg) is type(field_value)
119
+ )
108
120
  except Exception:
109
121
  continue
110
122
  elif origin is Annotated:
111
123
  validator, caster = field_type.__metadata__
112
124
  if validator(field_value):
113
- return field_value
114
- return caster(field_value)
115
- # TODO: Currently, casting is done left-to-right. Searching deeper for a better match may be the way to go.
125
+ return ExtractedValue(value=field_value, exact_match=True)
126
+ return ExtractedValue(value=caster(field_value), exact_match=False)
127
+ # When parsing Unions, try to find the best match. Currently, it involves:
128
+ # 1. Finding the exact match
129
+ # 2. If not found, favors dataclasses - sorted by number of matched keys, then None values
130
+ # 3. If no dataclasses are present, return the leftmost matched argument
116
131
  elif origin is Union:
132
+ matches: List[ExtractedValue] = []
117
133
  for arg in get_args(field_type):
118
134
  try:
119
- return self.__extract_type(arg, field_value, field_name)
135
+ extracted_value = self.__extract_type(arg, field_value, field_name)
136
+ # exact match, return
137
+ if extracted_value.exact_match:
138
+ return extracted_value
139
+ else:
140
+ matches.append(extracted_value)
120
141
  except Exception:
121
142
  continue
143
+ # Only one element matched, return
144
+ if len(matches) == 1:
145
+ return matches[0]
146
+ # Only non-exact matches left, sort and return first element
147
+ elif len(matches) > 1:
148
+ matches.sort(
149
+ key=lambda x: (x.matched_keys is not None, x.matched_keys, x.value is None),
150
+ reverse=True,
151
+ )
152
+ return matches[0]
122
153
  # Correct type not found, add exception
123
154
  raise CatalystwanModelInputException(
124
155
  f"Unable to match or cast input value for {field_name} [expected_type={unwrap(field_type)}, input={field_value}, input_type={type(field_value)}]"
@@ -131,7 +162,7 @@ class ModelDeserializer:
131
162
  kwargs_copy = deepcopy(kwargs)
132
163
  new_args = []
133
164
  new_kwargs = {}
134
- field_types = get_type_hints(cls)
165
+ field_types = get_type_hints(cls, include_extras=True)
135
166
  for field in fields(cls):
136
167
  if not field.init:
137
168
  continue
@@ -141,7 +172,9 @@ class ModelDeserializer:
141
172
  field_value = args_copy.popleft()
142
173
  try:
143
174
  new_args.append(
144
- self.__extract_type(field_type, value_extractor(field_value), field.name)
175
+ self.__extract_type(
176
+ field_type, value_extractor(field_value), field.name
177
+ ).value
145
178
  )
146
179
  except (
147
180
  CatalystwanModelInputException,
@@ -165,7 +198,7 @@ class ModelDeserializer:
165
198
  try:
166
199
  new_kwargs[field.name] = self.__extract_type(
167
200
  field_type, value_extractor(field_value), field.name
168
- )
201
+ ).value
169
202
  except (
170
203
  CatalystwanModelInputException,
171
204
  CatalystwanModelValidationError,
@@ -0,0 +1,27 @@
1
+ from dataclasses import is_dataclass
2
+ from typing import TypeVar, cast
3
+
4
+ from catalystwan.core.types import DataclassInstance
5
+
6
+ DataclassType = TypeVar("DataclassType", bound=DataclassInstance)
7
+
8
+
9
+ def count_matching_keys(model: DataclassType, model_payload: dict):
10
+ matched_keys = 0
11
+ for key, value in model_payload.items():
12
+ try:
13
+ model_value = getattr(model, key)
14
+ matched_keys += 1
15
+ if is_dataclass(model_value) and isinstance(value, dict):
16
+ matched_keys += count_matching_keys(cast(DataclassType, model_value), value)
17
+ elif (
18
+ isinstance(model_value, list)
19
+ and all([is_dataclass(element) for element in model_value])
20
+ and isinstance(value, list)
21
+ ):
22
+ for model_v, input_v in zip(model_value, value):
23
+ matched_keys += count_matching_keys(model_v, input_v)
24
+ except AttributeError:
25
+ continue
26
+
27
+ return matched_keys
@@ -4,7 +4,7 @@ import logging
4
4
  from copy import copy
5
5
  from dataclasses import dataclass, field, fields, is_dataclass
6
6
  from string import Formatter
7
- from typing import Any, Dict, List, Optional, Type, TypeVar, Union, cast
7
+ from typing import Any, Dict, List, Literal, Optional, Tuple, Type, TypeVar, Union, cast
8
8
 
9
9
  from catalystwan.abc import RequestAdapterInterface, ResponseInterface, SessionInterface
10
10
  from catalystwan.abc.types import HTTP_METHOD, JSON
@@ -14,6 +14,7 @@ from catalystwan.core.exceptions import (
14
14
  )
15
15
  from catalystwan.core.models.deserialize import deserialize
16
16
  from catalystwan.core.models.serialize import serialize
17
+ from catalystwan.core.models.utils import count_matching_keys
17
18
  from catalystwan.core.types import DataclassInstance
18
19
  from typing_extensions import get_args, get_origin
19
20
 
@@ -213,34 +214,44 @@ class RequestAdapter(RequestAdapterInterface):
213
214
  # return model that matches best with the input
214
215
  valid_models.sort(
215
216
  key=lambda x: (
216
- self.__count_matching_keys(x.model, cast(dict, x.payload.data)),
217
+ count_matching_keys(x.model, cast(dict, x.payload.data)),
217
218
  x.payload.priority,
218
219
  ),
219
220
  reverse=True,
220
221
  )
221
222
  return valid_models[0].model
222
223
 
223
- def __count_matching_keys(self, model: DataclassType, model_payload: dict):
224
- matched_keys = 0
225
- for key, value in model_payload.items():
226
- try:
227
- model_value = getattr(model, key)
228
- matched_keys += 1
229
- if is_dataclass(model_value) and isinstance(value, dict):
230
- matched_keys += self.__count_matching_keys(
231
- cast(DataclassType, model_value), value
232
- )
233
- elif (
234
- isinstance(model_value, list)
235
- and all([is_dataclass(element) for element in model_value])
236
- and isinstance(value, list)
237
- ):
238
- for model_v, input_v in zip(model_value, value):
239
- matched_keys += self.__count_matching_keys(model_v, input_v)
240
- except AttributeError:
224
+ def param_checker(self, required_params: List[Tuple[Any, Any]], excluded_params: List[Any]):
225
+ for param in excluded_params:
226
+ if param is not None:
227
+ return False
228
+ for param_value, expected_type in required_params:
229
+ if param_value is None:
230
+ return False
231
+ origin = get_origin(expected_type)
232
+ if origin is Any:
241
233
  continue
242
-
243
- return matched_keys
234
+ elif origin is None:
235
+ if type(param_value) is not expected_type:
236
+ return False
237
+ elif origin is Literal:
238
+ if param_value not in get_args(expected_type):
239
+ return False
240
+ # This part assumes List and Unions are not overly complex, allowing get_args to flatten the list of types
241
+ elif origin is list:
242
+ if type(param_value) is not list:
243
+ return False
244
+ args = get_args(expected_type)
245
+ if Any in args:
246
+ continue
247
+ if type(param_value) not in args:
248
+ return False
249
+ elif origin is Union:
250
+ if type(param_value) not in get_args(expected_type):
251
+ return False
252
+ else:
253
+ continue
254
+ return True
244
255
 
245
256
  def __copy__(self) -> RequestAdapter:
246
257
  return RequestAdapter(session=copy(self.session), logger=self.logger)
@@ -0,0 +1,15 @@
1
+ import os
2
+ import pytest
3
+
4
+ from catalystwan.core.session import create_manager_session
5
+ from catalystwan.core.request_adapter import RequestAdapter
6
+
7
+ @pytest.fixture(scope="package")
8
+ def catalystwan_requests():
9
+ url = os.environ["SDWAN_URL"]
10
+ port = int(os.environ["SDWAN_PORT"])
11
+ username = os.environ["SDWAN_USERNAME"]
12
+ password = os.environ["SDWAN_PASSWORD"]
13
+ print(f"Connecting to {url}:{port}...")
14
+ with create_manager_session(url, username, password, port) as session:
15
+ yield RequestAdapter(session=session)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: catalystwan
3
- Version: 2.0.0a1
3
+ Version: 2.0.0a3
4
4
  Summary: Cisco Catalyst WAN SDK for Python
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -207,7 +207,6 @@ License: Apache License
207
207
  Project-URL: Homepage, https://github.com/cisco-en-programmability/catalystwan-sdk-next-python
208
208
  Classifier: Development Status :: 3 - Alpha
209
209
  Classifier: Intended Audience :: Developers
210
- Classifier: License :: OSI Approved :: Apache Software License
211
210
  Classifier: Programming Language :: Python
212
211
  Classifier: Programming Language :: Python :: 3
213
212
  Classifier: Programming Language :: Python :: 3.8
@@ -237,3 +236,108 @@ Provides-Extra: ver-all
237
236
  Requires-Dist: catalystwan-v20-15 ==2.0.0a1 ; extra == 'ver-all'
238
237
  Requires-Dist: catalystwan-v20-16 ==2.0.0a1 ; extra == 'ver-all'
239
238
 
239
+ Cisco Catalyst WAN SDK 2.0
240
+ ==========================
241
+
242
+ Welcome to the official documentation for the Cisco Catalyst WAN SDK, a package designed for creating simple and parallel automatic requests via the official SD-WAN Manager API.
243
+
244
+ Overview
245
+ --------
246
+
247
+ Cisco Catalyst WAN SDK serves as a multiple session handler (provider, provider as a tenant, tenant) and is environment-independent. You just need a connection to any SD-WAN Manager.
248
+
249
+ Supported Catalystwan WAN Server Versions
250
+ -----------------------------------------
251
+
252
+ - 20.15
253
+ - 20.16
254
+
255
+ Cisco Catalyst WAN SDK – Early Access Release
256
+ ---------------------------------------------
257
+
258
+ We are excited to introduce the Cisco Catalyst WAN SDK in its early access release phase,
259
+ marking an important step in enabling developers to harness the full potential of Cisco's
260
+ networking solutions. This release provides a unique opportunity to explore and experiment
261
+ with the SDK's capabilities as we continue to refine and enhance its features.
262
+
263
+ As this version is part of an early development stage, it is provided "as is" and is still
264
+ undergoing active testing and iteration. While we are committed to supporting your experience
265
+ on a best-effort basis, we recommend exercising caution and conducting thorough testing before
266
+ deploying it in a production environment.
267
+
268
+ Your feedback during this phase is invaluable in shaping the SDK to meet the needs of our developer
269
+ community. Thank you for partnering with us on this journey to innovate and advance networking automation.
270
+
271
+ Not recommend to use in production environments.
272
+ ------------------------------------------------
273
+ Cisco Catalyst WAN SDK in its `pre-alpha` release phase. This marks a significant milestone
274
+ in empowering developers to unlock the full capabilities of Cisco's networking solutions.
275
+ Please note that, as a pre-alpha release, this version of the SDK is still in active development
276
+ and testing. It is provided "as is," with limited support offered on a best-effort basis.
277
+
278
+
279
+ Supported Python Versions
280
+ -------------------------
281
+
282
+ Python >= 3.8
283
+
284
+ > If you don't have a specific version, you can just use [Pyenv](https://github.com/pyenv/pyenv) to manage Python versions.
285
+
286
+
287
+ Installation
288
+ ------------
289
+
290
+ To install the SDK, run the following command:
291
+
292
+ ```bash
293
+ pip install catalystwan==2.0.0a0
294
+ ```
295
+
296
+ To manually install the necessary Python packages in editable mode, you can use the `pip install -e` command.
297
+
298
+ ```bash
299
+ pip install -e ./packages/catalystwan-types \
300
+ -e ./packages/catalystwan-core \
301
+ -e ./versions/catalystwan-v20_15 \
302
+ -e ./versions/catalystwan-v20_16
303
+ ```
304
+
305
+
306
+ Getting Started
307
+ ---------------
308
+
309
+ To execute SDK APIs, you need to create a `ApiClient`. Use the `create_client()` method to configure a session, perform authentication, and obtain a `ApiClient` instance in an operational state.
310
+
311
+ ### Example Usage
312
+
313
+ Here's a quick example of how to use the SDK:
314
+
315
+ ```python
316
+ from catalystwan.core import create_client
317
+
318
+ url = "example.com"
319
+ username = "admin"
320
+ password = "password123"
321
+
322
+ with create_client(url=url, username=username, password=password) as client:
323
+ result = client.health.devices.get_devices_health()
324
+ print(result)
325
+ ```
326
+
327
+ If you need to preform more complex operations that require models, they can utilize an alias: `m`.
328
+ ```python
329
+
330
+ with create_client(...) as client:
331
+ result = client.admin.aaa.update_aaa_config(
332
+ client.admin.aaa.m.Aaa(
333
+ accounting: True,
334
+ admin_auth_order: False,
335
+ audit_disable: False,
336
+ auth_fallback: False,
337
+ auth_order: ["local"]
338
+ )
339
+ )
340
+ print(result)
341
+ ```
342
+
343
+ Using an alias allows for easier access and management of models, simplifying workflows and improving efficiency. This approach helps streamline operations without requiring direct integration with underlying models, making them more user-friendly and scalable.
@@ -1,24 +1,27 @@
1
- catalystwan/core/__init__.py,sha256=BtFD4803qN7cxzpl1hrUMMfV29mBEPH3DbCWcsJAhYM,148
1
+ catalystwan/core/__init__.py,sha256=KRpud-erJoWEkgvlT6ZIEcrwx6lN96KSPyFLHEIfpAI,154
2
2
  catalystwan/core/abstractions.py,sha256=-Srp20skc9WDu6m2zGUjA34rEJPd12XQgDTGf_qicXk,738
3
3
  catalystwan/core/apigw_auth.py,sha256=-ZnoXzMB86SuRhJ5o7OL9BuVJTZvKQp9EW1zWu_4Iao,4706
4
- catalystwan/core/client.py,sha256=FPQEAqyLK-NQDOExEYEBzKQvghQ4Y7h2l6VY1IENSRI,2599
4
+ catalystwan/core/client.py,sha256=skQ-ZrwFEn7w5MpS2lWew74cy5fJm3IeLD0MkWnR27c,5527
5
5
  catalystwan/core/encoder.py,sha256=hY_Osk9yyqvDgveB2pHmH5kzAzIO11vNT-kmSEmvgGA,243
6
6
  catalystwan/core/exceptions.py,sha256=Fhs69uEe-Uq4G4q3XS4N4oSQZJcjmlbL9jKtNkRWspQ,6680
7
- catalystwan/core/loader.py,sha256=AGjXJEt3qptG-B7iCKhXG0Y9p-wfG_-xD4H_6aSEiQ4,1133
7
+ catalystwan/core/loader.py,sha256=4lokgUCc6JAp33xApwJd6YRYQAQneJqRFk8XvK9OPE8,553
8
8
  catalystwan/core/metadata.py,sha256=JIXC3um36dWLkEWCADo8pxsCsq8jjPguHF1u-4Ws2FQ,2102
9
9
  catalystwan/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- catalystwan/core/request_adapter.py,sha256=90bQcK6hiMpaqW3mxCM4HHsT9GTwcDHNz3Bu62oME6g,9783
10
+ catalystwan/core/request_adapter.py,sha256=q1qOSJ5Cv9Tfiw2EED6WLMeT3cy2YPvRCQWm4HIDRmk,10218
11
11
  catalystwan/core/request_limiter.py,sha256=l0afY5HCpXCC_M6RoQSlg-X3k_bnTLieHGFfopuBDCk,521
12
12
  catalystwan/core/response.py,sha256=xj3MwOUt8lj4M3rElR5vnRThHlN1lU6xsNOCBsY1QXc,7824
13
13
  catalystwan/core/session.py,sha256=3uqHslMzq4CD_N0IEzygnEgbTGEi0NT1ljqA0CmuE8U,21504
14
+ catalystwan/core/tests.py,sha256=2XLXUxNJ14Hp-1Ix9ITWMvpvC-1NgtCwixOItmmcWWw,535
14
15
  catalystwan/core/types.py,sha256=X-u8OdRkCmJFA7NWZIgsrxvu83vFoDey5Z4Bqha0fdc,702
15
16
  catalystwan/core/version.py,sha256=cVKyfW_COPiF961Wlo3Gf5JL-2ZoFC1TfEfh49EUBEg,2959
16
17
  catalystwan/core/vmanage_auth.py,sha256=dJwxtA6XgOSghlrhW4G2kBbNaTHBPsTBSmg4-kuMLGc,11019
17
18
  catalystwan/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- catalystwan/core/models/deserialize.py,sha256=eYuLmn04j5kzvMs8MXlPmBf9B1uFaW-utksthbViKk4,8308
19
+ catalystwan/core/models/deserialize.py,sha256=5_vq7Ay7eCbntG0Mf9_BnNTqo_uIqDxq64ahrhaqwLY,10135
19
20
  catalystwan/core/models/serialize.py,sha256=RW8zJ4NUMwIBtOaoLLuFTx5WS2rQdcfKiARjA3XX-W0,5110
20
- catalystwan-2.0.0a1.dist-info/LICENSE,sha256=97ROi91Vxrj_Jio2v8FI9E8-y6x2uYdQRaFlrEbVjkc,11375
21
- catalystwan-2.0.0a1.dist-info/METADATA,sha256=wAB7GAuqsLAsrv9kK8eC6yYZt20EavOUG8kHKZ0EjBU,14487
22
- catalystwan-2.0.0a1.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
23
- catalystwan-2.0.0a1.dist-info/top_level.txt,sha256=0mv3rTOajNOAxvPHv_tHJpukVsP0plO5rD1ORKVDUM4,12
24
- catalystwan-2.0.0a1.dist-info/RECORD,,
21
+ catalystwan/core/models/utils.py,sha256=9crw0Nrm05sJkKjcyDwPU5eM5QgTUUmYf-asxdkkYe0,995
22
+ catalystwan-2.0.0a3.dist-info/LICENSE,sha256=97ROi91Vxrj_Jio2v8FI9E8-y6x2uYdQRaFlrEbVjkc,11375
23
+ catalystwan-2.0.0a3.dist-info/METADATA,sha256=2l3dfe-2f_yZ0PcThm80L0hq_gDgRpjslxGt9DqKzVE,18299
24
+ catalystwan-2.0.0a3.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
25
+ catalystwan-2.0.0a3.dist-info/entry_points.txt,sha256=bisxNMmzts6IKVBQQ9XjnnjGAbBBTDoeYLuZnkBwKZU,48
26
+ catalystwan-2.0.0a3.dist-info/top_level.txt,sha256=0mv3rTOajNOAxvPHv_tHJpukVsP0plO5rD1ORKVDUM4,12
27
+ catalystwan-2.0.0a3.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [pytest11]
2
+ catalystwan = catalystwan.core.tests