2023-02-01 18:30:20 +01:00
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
2023-06-22 16:04:11 +02:00
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
2023-02-01 18:30:20 +01:00
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
2024-05-25 17:28:44 +02:00
Author : Adolfo Gómez , dkmaster at dkmon dot com
2023-02-01 18:30:20 +01:00
"""
import logging
import typing
2023-12-04 00:04:56 +01:00
import collections . abc
2024-03-17 23:22:39 +01:00
from unittest import mock
2023-02-01 18:30:20 +01:00
from django . db import models
2024-03-17 23:22:39 +01:00
from uds . core import ui
2023-02-01 18:30:20 +01:00
logger = logging . getLogger ( __name__ )
2024-03-17 04:33:12 +01:00
T = typing . TypeVar ( ' T ' )
2024-07-17 18:27:09 +02:00
V = typing . TypeVar ( ' V ' , bound = collections . abc . Mapping [ str , typing . Any ] )
2024-03-17 04:33:12 +01:00
2023-02-01 18:30:20 +01:00
def compare_dicts (
2023-12-04 00:28:19 +01:00
expected : collections . abc . Mapping [ str , typing . Any ] ,
actual : collections . abc . Mapping [ str , typing . Any ] ,
2023-12-04 00:04:56 +01:00
ignore_keys : typing . Optional [ list [ str ] ] = None ,
ignore_values : typing . Optional [ list [ str ] ] = None ,
ignore_keys_startswith : typing . Optional [ list [ str ] ] = None ,
ignore_values_startswith : typing . Optional [ list [ str ] ] = None ,
2023-12-04 01:07:56 +01:00
) - > list [ tuple [ str , str ] ] :
2023-02-01 18:30:20 +01:00
"""
Compares two dictionaries , returning a list of differences
"""
ignore_keys = ignore_keys or [ ]
ignore_values = ignore_values or [ ]
ignore_keys_startswith = ignore_keys_startswith or [ ]
ignore_values_startswith = ignore_values_startswith or [ ]
2024-07-17 18:27:09 +02:00
errors : list [ tuple [ str , str ] ] = [ ]
2023-02-01 18:30:20 +01:00
for k , v in expected . items ( ) :
if k in ignore_keys :
continue
if any ( k . startswith ( ik ) for ik in ignore_keys_startswith ) :
continue
if k not in actual :
errors . append ( ( k , f ' Key " { k } " not found in actual ' ) )
continue
if actual [ k ] in ignore_values :
continue
if any ( actual [ k ] . startswith ( iv ) for iv in ignore_values_startswith ) :
continue
if v != actual [ k ] :
errors . append ( ( k , f ' Value for key " { k } " is " { actual [ k ] } " instead of " { v } " ' ) )
return errors
def ensure_data (
item : models . Model ,
2023-12-04 00:28:19 +01:00
dct : collections . abc . Mapping [ str , typing . Any ] ,
2023-12-04 00:04:56 +01:00
ignore_keys : typing . Optional [ list [ str ] ] = None ,
ignore_values : typing . Optional [ list [ str ] ] = None ,
2023-02-01 18:30:20 +01:00
) - > bool :
"""
Reads model as dict , fix some fields if needed and compares to dct
"""
db_data = item . __class__ . objects . filter ( pk = item . pk ) . values ( ) [ 0 ]
# Remove if id and uuid in db_data, store uuid in id and remove uuid
if ' id ' in db_data and ' uuid ' in db_data :
db_data [ ' id ' ] = db_data [ ' uuid ' ]
del db_data [ ' uuid ' ]
2024-01-30 03:49:36 +01:00
errors = compare_dicts ( dct , db_data , ignore_keys = ignore_keys , ignore_values = ignore_values )
2023-02-02 17:49:49 +01:00
if errors :
logger . info ( ' Errors found: %s ' , errors )
return False
2024-01-30 03:49:36 +01:00
2023-02-02 17:49:49 +01:00
return True
2023-09-04 02:45:04 +02:00
2024-01-30 03:49:36 +01:00
2023-09-04 02:45:04 +02:00
def random_ip_v4 ( ) - > str :
"""
Returns a random ip v4 address
"""
import random
2023-09-05 16:48:31 +02:00
return ' . ' . join ( str ( random . randint ( 0 , 255 ) ) for _ in range ( 4 ) ) # nosec
2023-09-04 02:45:04 +02:00
2024-01-30 03:49:36 +01:00
2023-09-04 02:45:04 +02:00
def random_ip_v6 ( ) - > str :
"""
Returns a random ip v6 address
"""
import random
2023-09-05 16:48:31 +02:00
return ' : ' . join ( ' {:04x} ' . format ( random . randint ( 0 , 65535 ) ) for _ in range ( 8 ) ) # nosec
2023-09-04 02:45:04 +02:00
2024-01-30 03:49:36 +01:00
2023-09-04 02:45:04 +02:00
def random_mac ( ) - > str :
"""
Returns a random mac address
"""
import random
2023-09-05 16:48:31 +02:00
return ' : ' . join ( ' {:02x} ' . format ( random . randint ( 0 , 255 ) ) for _ in range ( 6 ) ) # nosec
2023-10-03 01:39:51 +02:00
2024-01-30 03:49:36 +01:00
2023-10-03 01:39:51 +02:00
def random_hostname ( ) - > str :
"""
Returns a random hostname
"""
import random
import string
2024-01-30 03:49:36 +01:00
return ' ' . join ( random . choice ( string . ascii_lowercase ) for _ in range ( 15 ) ) # nosec
2024-02-20 05:19:40 +01:00
# Just compare types
# This is a simple class that returns true if the types of the two objects are the same
class MustBeOfType :
2024-03-17 04:33:12 +01:00
_kind : type [ typing . Any ]
2024-07-17 18:27:09 +02:00
2024-02-20 05:19:40 +01:00
def __init__ ( self , kind : type ) - > None :
self . _kind = kind
2024-07-17 18:27:09 +02:00
2024-02-20 05:19:40 +01:00
def __eq__ ( self , other : typing . Any ) - > bool :
return isinstance ( other , self . _kind )
def __ne__ ( self , other : typing . Any ) - > bool :
return not self . __eq__ ( other )
def __str__ ( self ) - > str :
return f ' { self . __class__ . __name__ } ( { self . _kind . __name__ } ) '
def __repr__ ( self ) - > str :
return self . __str__ ( )
2024-07-17 18:27:09 +02:00
2024-07-17 19:41:52 +02:00
def search_item_by_attr ( lst : collections . abc . Iterable [ T ] , attribute : str , value : typing . Any , * * kwargs : typing . Any ) - > T :
2024-03-17 04:33:12 +01:00
"""
Returns an item from a list of items
2024-06-21 20:13:34 +02:00
kwargs are not used , just to let use it as partial on fixtures
2024-03-17 04:33:12 +01:00
"""
for item in lst :
if getattr ( item , attribute ) == value :
return item
2024-05-05 18:49:44 +02:00
raise ValueError ( f ' Item with { attribute } == " { value } " not found in list { str ( lst ) [ : 100 ] } ' )
2024-03-17 23:22:39 +01:00
2024-07-17 18:27:09 +02:00
2024-07-17 19:41:52 +02:00
def search_dict_by_attr ( lst : collections . abc . Iterable [ V ] , attribute : str , value : typing . Any , * * kwargs : typing . Any ) - > V :
2024-07-17 18:27:09 +02:00
"""
Returns an item from a list of items
kwargs are not used , just to let use it as partial on fixtures
"""
for item in lst :
if item . get ( attribute ) == value :
return item
raise ValueError ( f ' Item with { attribute } == " { value } " not found in list { str ( lst ) [ : 100 ] } ' )
def filter_list_by_attr (
2024-07-17 19:41:52 +02:00
lst : collections . abc . Iterable [ T ] , attribute : str , value : typing . Any , * , sorted_by : str = ' ' , * * kwargs : typing . Any
2024-07-17 18:27:09 +02:00
) - > list [ T ] :
2024-05-03 03:34:56 +02:00
"""
Returns a list of items from a list of items
2024-06-21 20:13:34 +02:00
kwargs are not used , just to let use it as partial on fixtures
2024-05-03 03:34:56 +02:00
"""
2024-06-30 14:30:56 +02:00
values = [ item for item in lst if getattr ( item , attribute ) == value or value is None ]
if sorted_by :
values . sort ( key = lambda x : getattr ( x , sorted_by ) )
return values
2024-05-03 03:34:56 +02:00
2024-07-17 18:27:09 +02:00
def filter_list_by_attr_list (
2024-07-17 19:41:52 +02:00
lst : collections . abc . Iterable [ T ] , attribute : str , values : list [ typing . Any ] , * , sorted_by : str = ' ' , * * kwargs : typing . Any
2024-07-17 18:27:09 +02:00
) - > list [ T ] :
2024-06-22 21:09:33 +02:00
"""
Returns a list of items from a list of items
kwargs are not used , just to let use it as partial on fixtures
"""
2024-06-30 14:30:56 +02:00
values = [ item for item in lst if getattr ( item , attribute ) in values ]
if sorted_by :
values . sort ( key = lambda x : getattr ( x , sorted_by ) )
return values
2024-06-22 21:09:33 +02:00
2024-07-17 18:27:09 +02:00
2024-03-17 23:22:39 +01:00
def check_userinterface_values ( obj : ui . UserInterface , values : ui . gui . ValuesDictType ) - > None :
"""
Checks that a user interface object has the values specified
"""
for k , v in values . items ( ) :
if isinstance ( v , MustBeOfType ) :
assert isinstance ( getattr ( obj , k ) , v . _kind )
elif v == mock . ANY :
pass
else :
assert getattr ( obj , k ) == v