mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
consolidate name & display name fields, closes #115
This commit is contained in:
@@ -101,6 +101,7 @@ async def query(query_data: Query, request: Request, background_tasks: Backgroun
|
||||
json_output = True
|
||||
|
||||
cached = False
|
||||
runtime = 65535
|
||||
if cache_response:
|
||||
log.debug("Query {} exists in cache", cache_key)
|
||||
|
||||
@@ -195,7 +196,7 @@ async def import_certificate(encoded_request: EncodedRequest):
|
||||
# Write certificate to file
|
||||
import_public_key(
|
||||
app_path=APP_PATH,
|
||||
device_name=matched_device.name,
|
||||
device_name=matched_device._id,
|
||||
keystring=decoded_request,
|
||||
)
|
||||
except RuntimeError as err:
|
||||
|
@@ -189,52 +189,6 @@ except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def _build_frontend_networks():
|
||||
"""Build filtered JSON structure of networks for frontend.
|
||||
|
||||
Schema:
|
||||
{
|
||||
"device.network.display_name": {
|
||||
"device.name": {
|
||||
"display_name": "device.display_name",
|
||||
"vrfs": [
|
||||
"Global",
|
||||
"vrf.display_name"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Raises:
|
||||
ConfigError: Raised if parsing/building error occurs.
|
||||
|
||||
Returns:
|
||||
{dict} -- Frontend networks
|
||||
"""
|
||||
frontend_dict = {}
|
||||
for device in devices.objects:
|
||||
if device.network.display_name in frontend_dict:
|
||||
frontend_dict[device.network.display_name].update(
|
||||
{
|
||||
device.name: {
|
||||
"display_name": device.network.display_name,
|
||||
"vrfs": [vrf.display_name for vrf in device.vrfs],
|
||||
}
|
||||
}
|
||||
)
|
||||
elif device.network.display_name not in frontend_dict:
|
||||
frontend_dict[device.network.display_name] = {
|
||||
device.name: {
|
||||
"display_name": device.network.display_name,
|
||||
"vrfs": [vrf.display_name for vrf in device.vrfs],
|
||||
}
|
||||
}
|
||||
frontend_dict["default_vrf"] = devices.default_vrf
|
||||
if not frontend_dict:
|
||||
raise ConfigError(error_msg="Unable to build network to device mapping")
|
||||
return frontend_dict
|
||||
|
||||
|
||||
def _build_frontend_devices():
|
||||
"""Build filtered JSON structure of devices for frontend.
|
||||
|
||||
@@ -310,8 +264,8 @@ def _build_networks():
|
||||
if device.network.display_name == _network:
|
||||
network_def["locations"].append(
|
||||
{
|
||||
"_id": device._id,
|
||||
"name": device.name,
|
||||
"display_name": device.display_name,
|
||||
"network": device.network.display_name,
|
||||
"vrfs": [
|
||||
{
|
||||
@@ -417,7 +371,6 @@ content_credit = CREDIT.format(version=__version__)
|
||||
|
||||
vrfs = _build_vrfs()
|
||||
networks = _build_networks()
|
||||
frontend_networks = _build_frontend_networks()
|
||||
frontend_devices = _build_frontend_devices()
|
||||
_include_fields = {
|
||||
"cache": {"show_text", "timeout"},
|
||||
@@ -443,7 +396,6 @@ _frontend_params.update(
|
||||
{
|
||||
"hyperglass_version": __version__,
|
||||
"queries": {**params.queries.map, "list": params.queries.list},
|
||||
"devices": frontend_devices,
|
||||
"networks": networks,
|
||||
"vrfs": vrfs,
|
||||
"parsed_data_fields": PARSED_RESPONSE_FIELDS,
|
||||
|
@@ -42,7 +42,7 @@ class AgentConnection(Connection):
|
||||
raise RestError(
|
||||
"SSL Certificate for device {d} has not been imported",
|
||||
level="danger",
|
||||
d=self.device.display_name,
|
||||
d=self.device.name,
|
||||
)
|
||||
http_protocol = "https"
|
||||
client_params.update({"verify": str(self.device.ssl.cert)})
|
||||
@@ -90,8 +90,7 @@ class AgentConnection(Connection):
|
||||
|
||||
elif raw_response.status_code == 204:
|
||||
raise ResponseEmpty(
|
||||
params.messages.no_output,
|
||||
device_name=self.device.display_name,
|
||||
params.messages.no_output, device_name=self.device.name,
|
||||
)
|
||||
|
||||
else:
|
||||
@@ -102,14 +101,14 @@ class AgentConnection(Connection):
|
||||
log.error("Error connecting to device {}: {}", self.device.name, msg)
|
||||
raise RestError(
|
||||
params.messages.connection_error,
|
||||
device_name=self.device.display_name,
|
||||
device_name=self.device.name,
|
||||
error=msg,
|
||||
)
|
||||
except OSError as ose:
|
||||
log.critical(str(ose))
|
||||
raise RestError(
|
||||
params.messages.connection_error,
|
||||
device_name=self.device.display_name,
|
||||
device_name=self.device.name,
|
||||
error="System error",
|
||||
)
|
||||
except CertificateError as cert_error:
|
||||
@@ -117,7 +116,7 @@ class AgentConnection(Connection):
|
||||
msg = parse_exception(cert_error)
|
||||
raise RestError(
|
||||
params.messages.connection_error,
|
||||
device_name=self.device.display_name,
|
||||
device_name=self.device.name,
|
||||
error=f"{msg}: {cert_error}",
|
||||
)
|
||||
|
||||
@@ -125,7 +124,7 @@ class AgentConnection(Connection):
|
||||
log.error("Response code is {}", raw_response.status_code)
|
||||
raise RestError(
|
||||
params.messages.connection_error,
|
||||
device_name=self.device.display_name,
|
||||
device_name=self.device.name,
|
||||
error=params.messages.general,
|
||||
)
|
||||
|
||||
@@ -133,7 +132,7 @@ class AgentConnection(Connection):
|
||||
log.error("No response from device {}", self.device.name)
|
||||
raise RestError(
|
||||
params.messages.connection_error,
|
||||
device_name=self.device.display_name,
|
||||
device_name=self.device.name,
|
||||
error=params.messages.no_response,
|
||||
)
|
||||
|
||||
|
@@ -54,7 +54,7 @@ class SSHConnection(Connection):
|
||||
)
|
||||
raise ScrapeError(
|
||||
params.messages.connection_error,
|
||||
device_name=self.device.display_name,
|
||||
device_name=self.device.name,
|
||||
proxy=proxy.name,
|
||||
error=str(scrape_proxy_error),
|
||||
)
|
||||
|
@@ -51,7 +51,7 @@ async def execute(query: Query) -> Union[str, Sequence[Dict]]:
|
||||
|
||||
timeout_args = {
|
||||
"unformatted_msg": params.messages.connection_error,
|
||||
"device_name": query.device.display_name,
|
||||
"device_name": query.device.name,
|
||||
"error": params.messages.request_timeout,
|
||||
}
|
||||
|
||||
@@ -73,9 +73,7 @@ async def execute(query: Query) -> Union[str, Sequence[Dict]]:
|
||||
output = await driver.parsed_response(response)
|
||||
|
||||
if output == "" or output == "\n":
|
||||
raise ResponseEmpty(
|
||||
params.messages.no_output, device_name=query.device.display_name
|
||||
)
|
||||
raise ResponseEmpty(params.messages.no_output, device_name=query.device.name)
|
||||
|
||||
log.debug("Output for query: {}:\n{}", query.json(), repr(output))
|
||||
signal.alarm(0)
|
||||
|
@@ -136,7 +136,7 @@ class Query(BaseModel):
|
||||
"""Create dictionary representation of instance."""
|
||||
if pretty:
|
||||
items = {
|
||||
"query_location": self.device.display_name,
|
||||
"query_location": self.device.name,
|
||||
"query_type": self.query.display_name,
|
||||
"query_vrf": self.query_vrf.display_name,
|
||||
"query_target": str(self.query_target),
|
||||
@@ -189,7 +189,7 @@ class Query(BaseModel):
|
||||
Returns:
|
||||
{str} -- Valid query_location
|
||||
"""
|
||||
if value not in devices.hostnames:
|
||||
if value not in devices._ids:
|
||||
raise InputInvalid(
|
||||
params.messages.invalid_field,
|
||||
level="warning",
|
||||
@@ -222,7 +222,7 @@ class Query(BaseModel):
|
||||
raise InputInvalid(
|
||||
params.messages.vrf_not_associated,
|
||||
vrf_name=vrf_object.display_name,
|
||||
device_name=device.display_name,
|
||||
device_name=device.name,
|
||||
)
|
||||
return device_vrf
|
||||
|
||||
|
@@ -180,7 +180,6 @@ class RoutersResponse(BaseModel):
|
||||
|
||||
name: StrictStr
|
||||
network: Network
|
||||
display_name: StrictStr
|
||||
vrfs: List[Vrf]
|
||||
|
||||
class Config:
|
||||
@@ -188,15 +187,7 @@ class RoutersResponse(BaseModel):
|
||||
|
||||
title = "Device"
|
||||
description = "Per-device attributes"
|
||||
schema_extra = {
|
||||
"examples": [
|
||||
{
|
||||
"name": "router01-nyc01",
|
||||
"location": "nyc01",
|
||||
"display_name": "New York City, NY",
|
||||
}
|
||||
]
|
||||
}
|
||||
schema_extra = {"examples": [{"name": "router01-nyc01", "location": "nyc01"}]}
|
||||
|
||||
|
||||
class CommunityResponse(BaseModel):
|
||||
|
@@ -3,12 +3,19 @@
|
||||
# Standard Library
|
||||
import os
|
||||
import re
|
||||
from typing import Any, Dict, List, Union, Optional
|
||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
||||
from pathlib import Path
|
||||
from ipaddress import IPv4Address, IPv6Address
|
||||
|
||||
# Third Party
|
||||
from pydantic import StrictInt, StrictStr, StrictBool, validator, root_validator
|
||||
from pydantic import (
|
||||
StrictInt,
|
||||
StrictStr,
|
||||
StrictBool,
|
||||
PrivateAttr,
|
||||
validator,
|
||||
root_validator,
|
||||
)
|
||||
|
||||
# Project
|
||||
from hyperglass.log import log
|
||||
@@ -41,15 +48,43 @@ _default_vrf = {
|
||||
}
|
||||
|
||||
|
||||
def find_device_id(values: Dict) -> Tuple[str, Dict]:
|
||||
"""Generate device id & handle legacy display_name field."""
|
||||
|
||||
def generate_id(name: str) -> str:
|
||||
scrubbed = re.sub(r"[^A-Za-z0-9\_\-\s]", "", name)
|
||||
return "_".join(scrubbed.split()).lower()
|
||||
|
||||
name = values.pop("name", None)
|
||||
|
||||
if name is None:
|
||||
raise ValueError("name is required.")
|
||||
|
||||
legacy_display_name = values.pop("display_name", None)
|
||||
|
||||
if legacy_display_name is not None:
|
||||
log.warning(
|
||||
"The 'display_name' field is deprecated. Use the 'name' field instead."
|
||||
)
|
||||
device_id = generate_id(legacy_display_name)
|
||||
display_name = legacy_display_name
|
||||
else:
|
||||
device_id = generate_id(name)
|
||||
display_name = name
|
||||
|
||||
return device_id, {"name": display_name, "display_name": None, **values}
|
||||
|
||||
|
||||
class Device(HyperglassModel):
|
||||
"""Validation model for per-router config in devices.yaml."""
|
||||
|
||||
_id: StrictStr = PrivateAttr()
|
||||
name: StrictStr
|
||||
address: Union[IPv4Address, IPv6Address, StrictStr]
|
||||
network: Network
|
||||
credential: Credential
|
||||
proxy: Optional[Proxy]
|
||||
display_name: StrictStr
|
||||
display_name: Optional[StrictStr]
|
||||
port: StrictInt = 22
|
||||
ssl: Optional[Ssl]
|
||||
nos: StrictStr
|
||||
@@ -59,6 +94,12 @@ class Device(HyperglassModel):
|
||||
vrf_names: List[StrictStr] = []
|
||||
structured_output: Optional[StrictBool]
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
"""Set the device ID."""
|
||||
_id, values = find_device_id(kwargs)
|
||||
super().__init__(**values)
|
||||
self._id = _id
|
||||
|
||||
def __hash__(self) -> int:
|
||||
"""Make device object hashable so the object can be deduplicated with set()."""
|
||||
return hash((self.name,))
|
||||
@@ -245,6 +286,7 @@ class Device(HyperglassModel):
|
||||
class Devices(HyperglassModelExtra):
|
||||
"""Validation model for device configurations."""
|
||||
|
||||
_ids: List[StrictStr] = []
|
||||
hostnames: List[StrictStr] = []
|
||||
vrfs: List[StrictStr] = []
|
||||
display_vrfs: List[StrictStr] = []
|
||||
@@ -272,6 +314,7 @@ class Devices(HyperglassModelExtra):
|
||||
all_nos = set()
|
||||
objects = set()
|
||||
hostnames = set()
|
||||
_ids = set()
|
||||
|
||||
init_kwargs = {}
|
||||
|
||||
@@ -284,6 +327,7 @@ class Devices(HyperglassModelExtra):
|
||||
# list with `devices.hostnames`, same for all router
|
||||
# classes, for when iteration over all routers is required.
|
||||
hostnames.add(device.name)
|
||||
_ids.add(device._id)
|
||||
objects.add(device)
|
||||
all_nos.add(device.commands)
|
||||
|
||||
@@ -320,19 +364,20 @@ class Devices(HyperglassModelExtra):
|
||||
|
||||
# Convert the de-duplicated sets to a standard list, add lists
|
||||
# as class attributes. Sort router list by router name attribute
|
||||
init_kwargs["_ids"] = list(_ids)
|
||||
init_kwargs["hostnames"] = list(hostnames)
|
||||
init_kwargs["all_nos"] = list(all_nos)
|
||||
init_kwargs["vrfs"] = list(vrfs)
|
||||
init_kwargs["display_vrfs"] = list(vrfs)
|
||||
init_kwargs["vrf_objects"] = list(vrf_objects)
|
||||
init_kwargs["objects"] = sorted(objects, key=lambda x: x.display_name)
|
||||
init_kwargs["objects"] = sorted(objects, key=lambda x: x.name)
|
||||
|
||||
super().__init__(**init_kwargs)
|
||||
|
||||
def __getitem__(self, accessor: str) -> Device:
|
||||
"""Get a device by its name."""
|
||||
for device in self.objects:
|
||||
if device.name == accessor:
|
||||
if device._id == accessor:
|
||||
return device
|
||||
|
||||
raise AttributeError(f"No device named '{accessor}'")
|
||||
|
@@ -11,8 +11,8 @@ function buildOptions(networks: TNetwork[]) {
|
||||
return networks.map(net => {
|
||||
const label = net.display_name;
|
||||
const options = net.locations.map(loc => ({
|
||||
label: loc.display_name,
|
||||
value: loc.name,
|
||||
label: loc.name,
|
||||
value: loc._id,
|
||||
group: net.display_name,
|
||||
}));
|
||||
return { label, options };
|
||||
|
@@ -45,7 +45,7 @@ export const Results: React.FC = () => {
|
||||
<Result
|
||||
index={i}
|
||||
device={device}
|
||||
key={device.name}
|
||||
key={device._id}
|
||||
queryLocation={loc.value}
|
||||
queryVrf={queryVrf.value}
|
||||
queryType={queryType.value}
|
||||
|
@@ -66,7 +66,7 @@ const _Result: React.ForwardRefRenderFunction<HTMLDivElement, TResult> = (props:
|
||||
]);
|
||||
|
||||
if (typeof data !== 'undefined') {
|
||||
responses.merge({ [device.name]: data });
|
||||
responses.merge({ [device._id]: data });
|
||||
}
|
||||
|
||||
const cacheLabel = useStrf(web.text.cache_icon, { time: data?.timestamp }, [data?.timestamp]);
|
||||
@@ -148,8 +148,8 @@ const _Result: React.ForwardRefRenderFunction<HTMLDivElement, TResult> = (props:
|
||||
|
||||
return (
|
||||
<AnimatedAccordionItem
|
||||
id={device.name}
|
||||
ref={ref}
|
||||
id={device._id}
|
||||
isDisabled={isLoading}
|
||||
exit={{ opacity: 0, y: 300 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
@@ -169,12 +169,12 @@ const _Result: React.ForwardRefRenderFunction<HTMLDivElement, TResult> = (props:
|
||||
errorMsg={errorMsg}
|
||||
errorLevel={errorLevel}
|
||||
runtime={data?.runtime ?? 0}
|
||||
title={device.display_name}
|
||||
title={device.name}
|
||||
/>
|
||||
</AccordionButton>
|
||||
<HStack py={2} spacing={1}>
|
||||
{isStructuredOutput(data) && data.level === 'success' && tableComponent && (
|
||||
<Path device={device.name} />
|
||||
<Path device={device._id} />
|
||||
)}
|
||||
<CopyButton copyValue={copyValue} isDisabled={isLoading} />
|
||||
<RequeryButton requery={refetch} isDisabled={isLoading} />
|
||||
|
@@ -13,7 +13,7 @@ export function useDevice(): TUseDevice {
|
||||
const devices = useMemo(() => networks.map(n => n.locations).flat(), []);
|
||||
|
||||
function getDevice(id: string): TDevice {
|
||||
return devices.filter(dev => dev.name === id)[0];
|
||||
return devices.filter(dev => dev._id === id)[0];
|
||||
}
|
||||
|
||||
return useCallback(getDevice, []);
|
||||
|
@@ -114,9 +114,9 @@ export interface TDeviceVrf extends TDeviceVrfBase {
|
||||
}
|
||||
|
||||
interface TDeviceBase {
|
||||
_id: string;
|
||||
name: string;
|
||||
network: string;
|
||||
display_name: string;
|
||||
}
|
||||
|
||||
export interface TDevice extends TDeviceBase {
|
||||
|
43
poetry.lock
generated
43
poetry.lock
generated
@@ -927,7 +927,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.6.1"
|
||||
version = "1.7.3"
|
||||
description = "Data validation and settings management using python 3.6 type hinting"
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -1427,7 +1427,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = ">=3.6.1,<4.0"
|
||||
content-hash = "020be9857d03222d73418b6c7e4b966cd42d5a020f4b3e3a3dddeef6d8f29535"
|
||||
content-hash = "cb0743e8f0e89938e116d9e1c2e64b260b52759ffaeacc521ce5f79d492d0b99"
|
||||
|
||||
[metadata.files]
|
||||
aiocontextvars = [
|
||||
@@ -1883,23 +1883,28 @@ pycparser = [
|
||||
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
|
||||
]
|
||||
pydantic = [
|
||||
{file = "pydantic-1.6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:418b84654b60e44c0cdd5384294b0e4bc1ebf42d6e873819424f3b78b8690614"},
|
||||
{file = "pydantic-1.6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4900b8820b687c9a3ed753684337979574df20e6ebe4227381d04b3c3c628f99"},
|
||||
{file = "pydantic-1.6.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:b49c86aecde15cde33835d5d6360e55f5e0067bb7143a8303bf03b872935c75b"},
|
||||
{file = "pydantic-1.6.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2de562a456c4ecdc80cf1a8c3e70c666625f7d02d89a6174ecf63754c734592e"},
|
||||
{file = "pydantic-1.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f769141ab0abfadf3305d4fcf36660e5cf568a666dd3efab7c3d4782f70946b1"},
|
||||
{file = "pydantic-1.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2dc946b07cf24bee4737ced0ae77e2ea6bc97489ba5a035b603bd1b40ad81f7e"},
|
||||
{file = "pydantic-1.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:36dbf6f1be212ab37b5fda07667461a9219c956181aa5570a00edfb0acdfe4a1"},
|
||||
{file = "pydantic-1.6.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:1783c1d927f9e1366e0e0609ae324039b2479a1a282a98ed6a6836c9ed02002c"},
|
||||
{file = "pydantic-1.6.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:cf3933c98cb5e808b62fae509f74f209730b180b1e3c3954ee3f7949e083a7df"},
|
||||
{file = "pydantic-1.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f8af9b840a9074e08c0e6dc93101de84ba95df89b267bf7151d74c553d66833b"},
|
||||
{file = "pydantic-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:40d765fa2d31d5be8e29c1794657ad46f5ee583a565c83cea56630d3ae5878b9"},
|
||||
{file = "pydantic-1.6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3fa799f3cfff3e5f536cbd389368fc96a44bb30308f258c94ee76b73bd60531d"},
|
||||
{file = "pydantic-1.6.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:6c3f162ba175678218629f446a947e3356415b6b09122dcb364e58c442c645a7"},
|
||||
{file = "pydantic-1.6.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:eb75dc1809875d5738df14b6566ccf9fd9c0bcde4f36b72870f318f16b9f5c20"},
|
||||
{file = "pydantic-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:530d7222a2786a97bc59ee0e0ebbe23728f82974b1f1ad9a11cd966143410633"},
|
||||
{file = "pydantic-1.6.1-py36.py37.py38-none-any.whl", hash = "sha256:b5b3489cb303d0f41ad4a7390cf606a5f2c7a94dcba20c051cd1c653694cb14d"},
|
||||
{file = "pydantic-1.6.1.tar.gz", hash = "sha256:54122a8ed6b75fe1dd80797f8251ad2063ea348a03b77218d73ea9fe19bd4e73"},
|
||||
{file = "pydantic-1.7.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c59ea046aea25be14dc22d69c97bee629e6d48d2b2ecb724d7fe8806bf5f61cd"},
|
||||
{file = "pydantic-1.7.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a4143c8d0c456a093387b96e0f5ee941a950992904d88bc816b4f0e72c9a0009"},
|
||||
{file = "pydantic-1.7.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:d8df4b9090b595511906fa48deda47af04e7d092318bfb291f4d45dfb6bb2127"},
|
||||
{file = "pydantic-1.7.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:514b473d264671a5c672dfb28bdfe1bf1afd390f6b206aa2ec9fed7fc592c48e"},
|
||||
{file = "pydantic-1.7.3-cp36-cp36m-win_amd64.whl", hash = "sha256:dba5c1f0a3aeea5083e75db9660935da90216f8a81b6d68e67f54e135ed5eb23"},
|
||||
{file = "pydantic-1.7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:59e45f3b694b05a69032a0d603c32d453a23f0de80844fb14d55ab0c6c78ff2f"},
|
||||
{file = "pydantic-1.7.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:5b24e8a572e4b4c18f614004dda8c9f2c07328cb5b6e314d6e1bbd536cb1a6c1"},
|
||||
{file = "pydantic-1.7.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:b2b054d095b6431cdda2f852a6d2f0fdec77686b305c57961b4c5dd6d863bf3c"},
|
||||
{file = "pydantic-1.7.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:025bf13ce27990acc059d0c5be46f416fc9b293f45363b3d19855165fee1874f"},
|
||||
{file = "pydantic-1.7.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6e3874aa7e8babd37b40c4504e3a94cc2023696ced5a0500949f3347664ff8e2"},
|
||||
{file = "pydantic-1.7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e682f6442ebe4e50cb5e1cfde7dda6766fb586631c3e5569f6aa1951fd1a76ef"},
|
||||
{file = "pydantic-1.7.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:185e18134bec5ef43351149fe34fda4758e53d05bb8ea4d5928f0720997b79ef"},
|
||||
{file = "pydantic-1.7.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:f5b06f5099e163295b8ff5b1b71132ecf5866cc6e7f586d78d7d3fd6e8084608"},
|
||||
{file = "pydantic-1.7.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:24ca47365be2a5a3cc3f4a26dcc755bcdc9f0036f55dcedbd55663662ba145ec"},
|
||||
{file = "pydantic-1.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:d1fe3f0df8ac0f3a9792666c69a7cd70530f329036426d06b4f899c025aca74e"},
|
||||
{file = "pydantic-1.7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f6864844b039805add62ebe8a8c676286340ba0c6d043ae5dea24114b82a319e"},
|
||||
{file = "pydantic-1.7.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ecb54491f98544c12c66ff3d15e701612fc388161fd455242447083350904730"},
|
||||
{file = "pydantic-1.7.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:ffd180ebd5dd2a9ac0da4e8b995c9c99e7c74c31f985ba090ee01d681b1c4b95"},
|
||||
{file = "pydantic-1.7.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8d72e814c7821125b16f1553124d12faba88e85405b0864328899aceaad7282b"},
|
||||
{file = "pydantic-1.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:475f2fa134cf272d6631072554f845d0630907fce053926ff634cc6bc45bf1af"},
|
||||
{file = "pydantic-1.7.3-py3-none-any.whl", hash = "sha256:38be427ea01a78206bcaf9a56f835784afcba9e5b88fbdce33bbbfbcd7841229"},
|
||||
{file = "pydantic-1.7.3.tar.gz", hash = "sha256:213125b7e9e64713d16d988d10997dabc6a1f73f3991e1ff8e35ebb1409c7dc9"},
|
||||
]
|
||||
pydocstyle = [
|
||||
{file = "pydocstyle-5.1.1-py3-none-any.whl", hash = "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"},
|
||||
|
@@ -47,7 +47,7 @@ netmiko = "^3.3.2"
|
||||
paramiko = "^2.7.1"
|
||||
psutil = "^5.7.2"
|
||||
py-cpuinfo = "^7.0.0"
|
||||
pydantic = "^1.4"
|
||||
pydantic = "^1.7.3"
|
||||
python = ">=3.6.1,<4.0"
|
||||
redis = "^3.5.3"
|
||||
scrapli = {extras = ["asyncssh"], version = "^2020.9.26"}
|
||||
|
Reference in New Issue
Block a user