add tclint

This commit is contained in:
2025-07-24 21:31:08 +02:00
parent e842d9d74e
commit 666c537f07
31 changed files with 6721 additions and 308 deletions
+88
View File
@@ -0,0 +1,88 @@
"""Schema validation for Python data structures.
Given eg. a nested data structure like this:
{
'exclude': ['Users', 'Uptime'],
'include': [],
'set': {
'snmp_community': 'public',
'snmp_timeout': 15,
'snmp_version': '2c',
},
'targets': {
'localhost': {
'exclude': ['Uptime'],
'features': {
'Uptime': {
'retries': 3,
},
'Users': {
'snmp_community': 'monkey',
'snmp_port': 15,
},
},
'include': ['Users'],
'set': {
'snmp_community': 'monkeys',
},
},
},
}
A schema like this:
>>> settings = {
... 'snmp_community': str,
... 'retries': int,
... 'snmp_version': All(Coerce(str), Any('3', '2c', '1')),
... }
>>> features = ['Ping', 'Uptime', 'Http']
>>> schema = Schema({
... 'exclude': features,
... 'include': features,
... 'set': settings,
... 'targets': {
... 'exclude': features,
... 'include': features,
... 'features': {
... str: settings,
... },
... },
... })
Validate like so:
>>> schema({
... 'set': {
... 'snmp_community': 'public',
... 'snmp_version': '2c',
... },
... 'targets': {
... 'exclude': ['Ping'],
... 'features': {
... 'Uptime': {'retries': 3},
... 'Users': {'snmp_community': 'monkey'},
... },
... },
... }) == {
... 'set': {'snmp_version': '2c', 'snmp_community': 'public'},
... 'targets': {
... 'exclude': ['Ping'],
... 'features': {'Uptime': {'retries': 3},
... 'Users': {'snmp_community': 'monkey'}}}}
True
"""
# flake8: noqa
# fmt: off
from voluptuous.schema_builder import *
from voluptuous.util import *
from voluptuous.validators import *
from voluptuous.error import * # isort: skip
# fmt: on
__version__ = '0.15.2'
__author__ = 'alecthomas'
+219
View File
@@ -0,0 +1,219 @@
# fmt: off
import typing
# fmt: on
class Error(Exception):
"""Base validation exception."""
class SchemaError(Error):
"""An error was encountered in the schema."""
class Invalid(Error):
"""The data was invalid.
:attr msg: The error message.
:attr path: The path to the error, as a list of keys in the source data.
:attr error_message: The actual error message that was raised, as a
string.
"""
def __init__(
self,
message: str,
path: typing.Optional[typing.List[typing.Hashable]] = None,
error_message: typing.Optional[str] = None,
error_type: typing.Optional[str] = None,
) -> None:
Error.__init__(self, message)
self._path = path or []
self._error_message = error_message or message
self.error_type = error_type
@property
def msg(self) -> str:
return self.args[0]
@property
def path(self) -> typing.List[typing.Hashable]:
return self._path
@property
def error_message(self) -> str:
return self._error_message
def __str__(self) -> str:
path = ' @ data[%s]' % ']['.join(map(repr, self.path)) if self.path else ''
output = Exception.__str__(self)
if self.error_type:
output += ' for ' + self.error_type
return output + path
def prepend(self, path: typing.List[typing.Hashable]) -> None:
self._path = path + self.path
class MultipleInvalid(Invalid):
def __init__(self, errors: typing.Optional[typing.List[Invalid]] = None) -> None:
self.errors = errors[:] if errors else []
def __repr__(self) -> str:
return 'MultipleInvalid(%r)' % self.errors
@property
def msg(self) -> str:
return self.errors[0].msg
@property
def path(self) -> typing.List[typing.Hashable]:
return self.errors[0].path
@property
def error_message(self) -> str:
return self.errors[0].error_message
def add(self, error: Invalid) -> None:
self.errors.append(error)
def __str__(self) -> str:
return str(self.errors[0])
def prepend(self, path: typing.List[typing.Hashable]) -> None:
for error in self.errors:
error.prepend(path)
class RequiredFieldInvalid(Invalid):
"""Required field was missing."""
class ObjectInvalid(Invalid):
"""The value we found was not an object."""
class DictInvalid(Invalid):
"""The value found was not a dict."""
class ExclusiveInvalid(Invalid):
"""More than one value found in exclusion group."""
class InclusiveInvalid(Invalid):
"""Not all values found in inclusion group."""
class SequenceTypeInvalid(Invalid):
"""The type found is not a sequence type."""
class TypeInvalid(Invalid):
"""The value was not of required type."""
class ValueInvalid(Invalid):
"""The value was found invalid by evaluation function."""
class ContainsInvalid(Invalid):
"""List does not contain item"""
class ScalarInvalid(Invalid):
"""Scalars did not match."""
class CoerceInvalid(Invalid):
"""Impossible to coerce value to type."""
class AnyInvalid(Invalid):
"""The value did not pass any validator."""
class AllInvalid(Invalid):
"""The value did not pass all validators."""
class MatchInvalid(Invalid):
"""The value does not match the given regular expression."""
class RangeInvalid(Invalid):
"""The value is not in given range."""
class TrueInvalid(Invalid):
"""The value is not True."""
class FalseInvalid(Invalid):
"""The value is not False."""
class BooleanInvalid(Invalid):
"""The value is not a boolean."""
class UrlInvalid(Invalid):
"""The value is not a URL."""
class EmailInvalid(Invalid):
"""The value is not an email address."""
class FileInvalid(Invalid):
"""The value is not a file."""
class DirInvalid(Invalid):
"""The value is not a directory."""
class PathInvalid(Invalid):
"""The value is not a path."""
class LiteralInvalid(Invalid):
"""The literal values do not match."""
class LengthInvalid(Invalid):
pass
class DatetimeInvalid(Invalid):
"""The value is not a formatted datetime string."""
class DateInvalid(Invalid):
"""The value is not a formatted date string."""
class InInvalid(Invalid):
pass
class NotInInvalid(Invalid):
pass
class ExactSequenceInvalid(Invalid):
pass
class NotEnoughValid(Invalid):
"""The value did not pass enough validations."""
pass
class TooManyValid(Invalid):
"""The value passed more than expected validations."""
pass
+57
View File
@@ -0,0 +1,57 @@
# fmt: off
import typing
from voluptuous import Invalid, MultipleInvalid
from voluptuous.error import Error
from voluptuous.schema_builder import Schema
# fmt: on
MAX_VALIDATION_ERROR_ITEM_LENGTH = 500
def _nested_getitem(
data: typing.Any, path: typing.List[typing.Hashable]
) -> typing.Optional[typing.Any]:
for item_index in path:
try:
data = data[item_index]
except (KeyError, IndexError, TypeError):
# The index is not present in the dictionary, list or other
# indexable or data is not subscriptable
return None
return data
def humanize_error(
data,
validation_error: Invalid,
max_sub_error_length: int = MAX_VALIDATION_ERROR_ITEM_LENGTH,
) -> str:
"""Provide a more helpful + complete validation error message than that provided automatically
Invalid and MultipleInvalid do not include the offending value in error messages,
and MultipleInvalid.__str__ only provides the first error.
"""
if isinstance(validation_error, MultipleInvalid):
return '\n'.join(
sorted(
humanize_error(data, sub_error, max_sub_error_length)
for sub_error in validation_error.errors
)
)
else:
offending_item_summary = repr(_nested_getitem(data, validation_error.path))
if len(offending_item_summary) > max_sub_error_length:
offending_item_summary = (
offending_item_summary[: max_sub_error_length - 3] + '...'
)
return '%s. Got %s' % (validation_error, offending_item_summary)
def validate_with_humanized_errors(
data, schema: Schema, max_sub_error_length: int = MAX_VALIDATION_ERROR_ITEM_LENGTH
) -> typing.Any:
try:
return schema(data)
except (Invalid, MultipleInvalid) as e:
raise Error(humanize_error(data, e, max_sub_error_length))
View File
File diff suppressed because it is too large Load Diff
+149
View File
@@ -0,0 +1,149 @@
# F401: "imported but unused"
# fmt: off
import typing
from voluptuous import validators # noqa: F401
from voluptuous.error import Invalid, LiteralInvalid, TypeInvalid # noqa: F401
from voluptuous.schema_builder import DefaultFactory # noqa: F401
from voluptuous.schema_builder import Schema, default_factory, raises # noqa: F401
# fmt: on
__author__ = 'tusharmakkar08'
def Lower(v: str) -> str:
"""Transform a string to lower case.
>>> s = Schema(Lower)
>>> s('HI')
'hi'
"""
return str(v).lower()
def Upper(v: str) -> str:
"""Transform a string to upper case.
>>> s = Schema(Upper)
>>> s('hi')
'HI'
"""
return str(v).upper()
def Capitalize(v: str) -> str:
"""Capitalise a string.
>>> s = Schema(Capitalize)
>>> s('hello world')
'Hello world'
"""
return str(v).capitalize()
def Title(v: str) -> str:
"""Title case a string.
>>> s = Schema(Title)
>>> s('hello world')
'Hello World'
"""
return str(v).title()
def Strip(v: str) -> str:
"""Strip whitespace from a string.
>>> s = Schema(Strip)
>>> s(' hello world ')
'hello world'
"""
return str(v).strip()
class DefaultTo(object):
"""Sets a value to default_value if none provided.
>>> s = Schema(DefaultTo(42))
>>> s(None)
42
>>> s = Schema(DefaultTo(list))
>>> s(None)
[]
"""
def __init__(self, default_value, msg: typing.Optional[str] = None) -> None:
self.default_value = default_factory(default_value)
self.msg = msg
def __call__(self, v):
if v is None:
v = self.default_value()
return v
def __repr__(self):
return 'DefaultTo(%s)' % (self.default_value(),)
class SetTo(object):
"""Set a value, ignoring any previous value.
>>> s = Schema(validators.Any(int, SetTo(42)))
>>> s(2)
2
>>> s("foo")
42
"""
def __init__(self, value) -> None:
self.value = default_factory(value)
def __call__(self, v):
return self.value()
def __repr__(self):
return 'SetTo(%s)' % (self.value(),)
class Set(object):
"""Convert a list into a set.
>>> s = Schema(Set())
>>> s([]) == set([])
True
>>> s([1, 2]) == set([1, 2])
True
>>> with raises(Invalid, regex="^cannot be presented as set: "):
... s([set([1, 2]), set([3, 4])])
"""
def __init__(self, msg: typing.Optional[str] = None) -> None:
self.msg = msg
def __call__(self, v):
try:
set_v = set(v)
except Exception as e:
raise TypeInvalid(self.msg or 'cannot be presented as set: {0}'.format(e))
return set_v
def __repr__(self):
return 'Set()'
class Literal(object):
def __init__(self, lit) -> None:
self.lit = lit
def __call__(self, value, msg: typing.Optional[str] = None):
if self.lit != value:
raise LiteralInvalid(msg or '%s not match for %s' % (value, self.lit))
else:
return self.lit
def __str__(self):
return str(self.lit)
def __repr__(self):
return repr(self.lit)
File diff suppressed because it is too large Load Diff