Skip to content

Commit e110299

Browse files
authored
Remove PY3.8 support; Add PY3.13 support (#979)
* Remove PY3.8 support; Add PY3.13 support * Use better for Generic type enforcement and mypy * Dev dependency; pin to earlier version of ncclient due to Windows breakage * Work around of PyEZ breakage using a fixed PyEZ (dev only) * Use a PY3.13 fixed version of NAPALM (dev only) * Fixing nbval test issue with dictionary sort order and threads
1 parent 9380759 commit e110299

File tree

6 files changed

+1677
-1416
lines changed

6 files changed

+1677
-1416
lines changed

.github/workflows/main.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
shell: bash
5353
strategy:
5454
matrix:
55-
python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ]
55+
python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ]
5656
platform: [ubuntu-latest, macos-13, windows-2019]
5757
runs-on: ${{ matrix.platform }}
5858
steps:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ What Nornir brings to the table is that it takes care of dealing with your inven
1616
Install
1717
=======
1818

19-
Please note that Nornir requires Python 3.8 or higher. Install Nornir with pip.
19+
Please note that Nornir requires Python 3.9 or higher. Install Nornir with pip.
2020

2121
```
2222
pip install nornir

docs/tutorial/processors.ipynb

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -234,117 +234,117 @@
234234
"output_type": "stream",
235235
"text": [
236236
"{\n",
237-
" \"hi!\": {\n",
238-
" \"started\": true,\n",
237+
" \"bye!\": {\n",
238+
" \"completed\": true,\n",
239+
" \"host1.bma\": {\n",
240+
" \"completed\": true,\n",
241+
" \"result\": \"bye! my name is host1.bma\"\n",
242+
" },\n",
239243
" \"host1.cmh\": {\n",
240244
" \"completed\": true,\n",
241-
" \"result\": \"hi! my name is host1.cmh\"\n",
245+
" \"result\": \"bye! my name is host1.cmh\"\n",
242246
" },\n",
243-
" \"host2.cmh\": {\n",
247+
" \"host2.bma\": {\n",
244248
" \"completed\": true,\n",
245-
" \"result\": \"hi! my name is host2.cmh\"\n",
249+
" \"result\": \"bye! my name is host2.bma\"\n",
246250
" },\n",
247-
" \"spine00.cmh\": {\n",
251+
" \"host2.cmh\": {\n",
248252
" \"completed\": true,\n",
249-
" \"result\": \"hi! my name is spine00.cmh\"\n",
253+
" \"result\": \"bye! my name is host2.cmh\"\n",
250254
" },\n",
251-
" \"spine01.cmh\": {\n",
255+
" \"leaf00.bma\": {\n",
252256
" \"completed\": true,\n",
253-
" \"result\": \"hi! my name is spine01.cmh\"\n",
257+
" \"result\": \"bye! my name is leaf00.bma\"\n",
254258
" },\n",
255259
" \"leaf00.cmh\": {\n",
256260
" \"completed\": true,\n",
257-
" \"result\": \"hi! my name is leaf00.cmh\"\n",
261+
" \"result\": \"bye! my name is leaf00.cmh\"\n",
258262
" },\n",
259-
" \"leaf01.cmh\": {\n",
263+
" \"leaf01.bma\": {\n",
260264
" \"completed\": true,\n",
261-
" \"result\": \"hi! my name is leaf01.cmh\"\n",
265+
" \"result\": \"bye! my name is leaf01.bma\"\n",
262266
" },\n",
263-
" \"host1.bma\": {\n",
267+
" \"leaf01.cmh\": {\n",
264268
" \"completed\": true,\n",
265-
" \"result\": \"hi! my name is host1.bma\"\n",
269+
" \"result\": \"bye! my name is leaf01.cmh\"\n",
266270
" },\n",
267-
" \"host2.bma\": {\n",
271+
" \"spine00.bma\": {\n",
268272
" \"completed\": true,\n",
269-
" \"result\": \"hi! my name is host2.bma\"\n",
273+
" \"result\": \"bye! my name is spine00.bma\"\n",
270274
" },\n",
271-
" \"spine00.bma\": {\n",
275+
" \"spine00.cmh\": {\n",
272276
" \"completed\": true,\n",
273-
" \"result\": \"hi! my name is spine00.bma\"\n",
277+
" \"result\": \"bye! my name is spine00.cmh\"\n",
274278
" },\n",
275279
" \"spine01.bma\": {\n",
276280
" \"completed\": true,\n",
277-
" \"result\": \"hi! my name is spine01.bma\"\n",
281+
" \"result\": \"bye! my name is spine01.bma\"\n",
278282
" },\n",
279-
" \"leaf00.bma\": {\n",
283+
" \"spine01.cmh\": {\n",
280284
" \"completed\": true,\n",
281-
" \"result\": \"hi! my name is leaf00.bma\"\n",
285+
" \"result\": \"bye! my name is spine01.cmh\"\n",
282286
" },\n",
283-
" \"leaf01.bma\": {\n",
287+
" \"started\": true\n",
288+
" },\n",
289+
" \"hi!\": {\n",
290+
" \"completed\": true,\n",
291+
" \"host1.bma\": {\n",
284292
" \"completed\": true,\n",
285-
" \"result\": \"hi! my name is leaf01.bma\"\n",
293+
" \"result\": \"hi! my name is host1.bma\"\n",
286294
" },\n",
287-
" \"completed\": true\n",
288-
" },\n",
289-
" \"bye!\": {\n",
290-
" \"started\": true,\n",
291295
" \"host1.cmh\": {\n",
292296
" \"completed\": true,\n",
293-
" \"result\": \"bye! my name is host1.cmh\"\n",
297+
" \"result\": \"hi! my name is host1.cmh\"\n",
294298
" },\n",
295-
" \"host2.cmh\": {\n",
299+
" \"host2.bma\": {\n",
296300
" \"completed\": true,\n",
297-
" \"result\": \"bye! my name is host2.cmh\"\n",
301+
" \"result\": \"hi! my name is host2.bma\"\n",
298302
" },\n",
299-
" \"spine00.cmh\": {\n",
303+
" \"host2.cmh\": {\n",
300304
" \"completed\": true,\n",
301-
" \"result\": \"bye! my name is spine00.cmh\"\n",
305+
" \"result\": \"hi! my name is host2.cmh\"\n",
302306
" },\n",
303-
" \"spine01.cmh\": {\n",
307+
" \"leaf00.bma\": {\n",
304308
" \"completed\": true,\n",
305-
" \"result\": \"bye! my name is spine01.cmh\"\n",
309+
" \"result\": \"hi! my name is leaf00.bma\"\n",
306310
" },\n",
307311
" \"leaf00.cmh\": {\n",
308312
" \"completed\": true,\n",
309-
" \"result\": \"bye! my name is leaf00.cmh\"\n",
310-
" },\n",
311-
" \"leaf01.cmh\": {\n",
312-
" \"completed\": true,\n",
313-
" \"result\": \"bye! my name is leaf01.cmh\"\n",
313+
" \"result\": \"hi! my name is leaf00.cmh\"\n",
314314
" },\n",
315-
" \"host1.bma\": {\n",
315+
" \"leaf01.bma\": {\n",
316316
" \"completed\": true,\n",
317-
" \"result\": \"bye! my name is host1.bma\"\n",
317+
" \"result\": \"hi! my name is leaf01.bma\"\n",
318318
" },\n",
319-
" \"host2.bma\": {\n",
319+
" \"leaf01.cmh\": {\n",
320320
" \"completed\": true,\n",
321-
" \"result\": \"bye! my name is host2.bma\"\n",
321+
" \"result\": \"hi! my name is leaf01.cmh\"\n",
322322
" },\n",
323323
" \"spine00.bma\": {\n",
324324
" \"completed\": true,\n",
325-
" \"result\": \"bye! my name is spine00.bma\"\n",
325+
" \"result\": \"hi! my name is spine00.bma\"\n",
326326
" },\n",
327-
" \"spine01.bma\": {\n",
327+
" \"spine00.cmh\": {\n",
328328
" \"completed\": true,\n",
329-
" \"result\": \"bye! my name is spine01.bma\"\n",
329+
" \"result\": \"hi! my name is spine00.cmh\"\n",
330330
" },\n",
331-
" \"leaf00.bma\": {\n",
331+
" \"spine01.bma\": {\n",
332332
" \"completed\": true,\n",
333-
" \"result\": \"bye! my name is leaf00.bma\"\n",
333+
" \"result\": \"hi! my name is spine01.bma\"\n",
334334
" },\n",
335-
" \"leaf01.bma\": {\n",
335+
" \"spine01.cmh\": {\n",
336336
" \"completed\": true,\n",
337-
" \"result\": \"bye! my name is leaf01.bma\"\n",
337+
" \"result\": \"hi! my name is spine01.cmh\"\n",
338338
" },\n",
339-
" \"completed\": true\n",
339+
" \"started\": true\n",
340340
" }\n",
341341
"}\n"
342342
]
343343
}
344344
],
345345
"source": [
346346
"import json\n",
347-
"print(json.dumps(data, indent=4))"
347+
"print(json.dumps(data, indent=4, sort_keys=True))"
348348
]
349349
},
350350
{

nornir/core/configuration.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sys
66
import warnings
77
from pathlib import Path
8-
from typing import Any, Dict, List, Optional, Type, TypeVar
8+
from typing import Any, Dict, Generic, List, Optional, Type, TypeVar
99

1010
import ruamel.yaml
1111

@@ -16,7 +16,7 @@
1616
T = TypeVar("T")
1717

1818

19-
class Parameter:
19+
class Parameter(Generic[T]):
2020
def __init__(
2121
self,
2222
envvar: str,
@@ -47,14 +47,18 @@ def resolve(self, value: Optional[T]) -> T:
4747

4848
if v is None:
4949
v = self.default
50+
51+
if not isinstance(v, self.type):
52+
raise TypeError(f"Expected type {self.type}, got {type(v)}")
53+
5054
return v
5155

5256

5357
class SSHConfig:
5458
__slots__ = ("config_file",)
5559

5660
class Parameters:
57-
config_file = Parameter(default=DEFAULT_SSH_CONFIG, envvar="NORNIR_SSH_CONFIG_FILE")
61+
config_file = Parameter[str](default=DEFAULT_SSH_CONFIG, envvar="NORNIR_SSH_CONFIG_FILE")
5862

5963
def __init__(self, config_file: Optional[str] = None) -> None:
6064
self.config_file = self.Parameters.config_file.resolve(config_file)
@@ -67,10 +71,12 @@ class InventoryConfig:
6771
__slots__ = "options", "plugin", "transform_function", "transform_function_options"
6872

6973
class Parameters:
70-
plugin = Parameter(typ=str, default="SimpleInventory", envvar="NORNIR_INVENTORY_PLUGIN")
71-
options = Parameter(default={}, envvar="NORNIR_INVENTORY_OPTIONS")
72-
transform_function = Parameter(typ=str, envvar="NORNIR_INVENTORY_TRANSFORM_FUNCTION")
73-
transform_function_options = Parameter(
74+
plugin = Parameter[str](
75+
typ=str, default="SimpleInventory", envvar="NORNIR_INVENTORY_PLUGIN"
76+
)
77+
options = Parameter[Dict[str, Any]](default={}, envvar="NORNIR_INVENTORY_OPTIONS")
78+
transform_function = Parameter[str](typ=str, envvar="NORNIR_INVENTORY_TRANSFORM_FUNCTION")
79+
transform_function_options = Parameter[Dict[str, Any]](
7480
default={}, envvar="NORNIR_INVENTORY_TRANSFORM_FUNCTION_OPTIONS"
7581
)
7682

@@ -101,15 +107,15 @@ class LoggingConfig:
101107
__slots__ = "enabled", "format", "level", "log_file", "loggers", "to_console"
102108

103109
class Parameters:
104-
enabled = Parameter(default=True, envvar="NORNIR_LOGGING_ENABLED")
105-
level = Parameter(default="INFO", envvar="NORNIR_LOGGING_LEVEL")
106-
log_file = Parameter(default="nornir.log", envvar="NORNIR_LOGGING_LOG_FILE")
107-
format = Parameter(
110+
enabled = Parameter[bool](default=True, envvar="NORNIR_LOGGING_ENABLED")
111+
level = Parameter[str](default="INFO", envvar="NORNIR_LOGGING_LEVEL")
112+
log_file = Parameter[str](default="nornir.log", envvar="NORNIR_LOGGING_LOG_FILE")
113+
format = Parameter[str](
108114
default="%(asctime)s - %(name)12s - %(levelname)8s - %(funcName)10s() - %(message)s",
109115
envvar="NORNIR_LOGGING_FORMAT",
110116
)
111-
to_console = Parameter(default=False, envvar="NORNIR_LOGGING_TO_CONSOLE")
112-
loggers = Parameter(default=["nornir"], envvar="NORNIR_LOGGING_LOGGERS")
117+
to_console = Parameter[bool](default=False, envvar="NORNIR_LOGGING_TO_CONSOLE")
118+
loggers = Parameter[List[str]](default=["nornir"], envvar="NORNIR_LOGGING_LOGGERS")
113119

114120
def __init__(
115121
self,
@@ -194,8 +200,8 @@ class RunnerConfig:
194200
__slots__ = ("options", "plugin")
195201

196202
class Parameters:
197-
plugin = Parameter(default="threaded", envvar="NORNIR_RUNNER_PLUGIN")
198-
options = Parameter(default={}, envvar="NORNIR_RUNNER_OPTIONS")
203+
plugin = Parameter[str](default="threaded", envvar="NORNIR_RUNNER_PLUGIN")
204+
options = Parameter[Dict[str, Any]](default={}, envvar="NORNIR_RUNNER_OPTIONS")
199205

200206
def __init__(
201207
self, plugin: Optional[str] = None, options: Optional[Dict[str, Any]] = None
@@ -214,7 +220,7 @@ class CoreConfig:
214220
__slots__ = ("raise_on_error",)
215221

216222
class Parameters:
217-
raise_on_error = Parameter(default=False, envvar="NORNIR_CORE_RAISE_ON_ERROR")
223+
raise_on_error = Parameter[bool](default=False, envvar="NORNIR_CORE_RAISE_ON_ERROR")
218224

219225
def __init__(self, raise_on_error: Optional[bool] = None) -> None:
220226
self.raise_on_error = self.Parameters.raise_on_error.resolve(raise_on_error)

0 commit comments

Comments
 (0)