diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d646f52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +*.py[cod] + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +#lib +lib64 + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox +nosetests.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject \ No newline at end of file diff --git a/README.md b/README.md index 3d4db94..8b601ce 100644 --- a/README.md +++ b/README.md @@ -8,63 +8,72 @@ The source code can be found at: http://sourceforge.net/projects/kwalify/files/k The schema used in this library: http://www.kuwata-lab.com/kwalify/ruby/users-guide.01.html#schema + ## How to install Note: It is recomended allways to use a virtual-enviroment when using pyKwalify -1. Download the release you want to install from a tag, latest stable build or the dev branch. -2. Run "pip install pykwalify-x.x.x.tar.gz" +1. Download +2. Run "pip install pykwalify-x.y.z.tar.gz" 3. To use pykwalify run "pykwalify" in your terminal + ## Build from source -1. Download the release you want to install from a tag, latest stable build or the dev branch. +1. Clone the repo 2. Run "make sdist" -3. To install run "make pip install dist/pykwalify-x.x.x.tar.gz" +3. To install run "pip install dist/pykwalify-x.x.x.tar.gz" + ## Install from source -1. Download the release you want to install from a tag, latest stable build or the dev branch. +1. Clone the repo 2. Run "make install" + ## pyKwalify python dependencies - - argparse 1.2.1 + - docopt 0.6.0 - PyYaml 3.10 + ## Supported python version - - Python 2.7.x - (Not yet tested) + - Python 2.7.x - (Not supported, never will be) - Python 3.1.x - Yes - Python 3.2.x - Yes - - Python 3.3.x - (Not yet tested) + - Python 3.3.x - Yes + # Implemented validation rules + ``` type: Type of value. The followings are available: - str - int - - float [NYI] + - float - bool - - number (== int or float) [NYI] - - text (== str or number) [NYI] + - number (int or float) + - text (str or number) - date [NYI] - time [NYI] - timestamp [NYI] - seq - map - scalar (all but seq and map) - - any (means any data) [NYI] + - any (means any implemented type of data) required: - Value is required when true (default is false). This is similar to not-null constraint in RDBMS. + Value is required when true (default is false). This is similar to not-null constraint in RDBMS enum: List of available values. pattern: Specifies regular expression pattern of value. (Uses re.match() ) + pattern rule works in map to validate keys, it is usefull when allowempty is set to True. + Pattern also works on all scalar types. range: Range of value between max/max-ex and min/min-ex. @@ -72,21 +81,26 @@ range: 'min' means 'min-inclusive'. (a < b) 'max-ex' means 'max-exclusive'. (a >= b) 'min-ex' means 'min-exclusive'. (a <= b) - Type seq, map, bool and any are not available with range:. + Type seq, map, bool and any are not available with range length: - Range of length of value between max/max-ex and min/min-ex. Only type str and text are available with length:. + Range of length of value between max/max-ex and min/min-ex. Only type str and text are available with length unique: - Value is unique for mapping or sequence. This is similar to unique constraint of RDBMS. + Value is unique for mapping or sequence. This is similar to unique constraint of RDBMS name: Name of schema. desc: Description. This is not used for validation. + +allowempty: + NOTE: Experimental feature! + Only applies to map. It enables a dict to have items in it that is not validated. It can be combined with mapping to check for some fixed properties but still validate if any random properties exists. See example testfile 18a, 18b, 19a, 19b. ``` + ## Contributing 1. Fork it @@ -95,6 +109,7 @@ desc: 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request + ## License MIT. diff --git a/ReleaseNotes.rst b/ReleaseNotes.rst index 8428b10..10f7073 100644 --- a/ReleaseNotes.rst +++ b/ReleaseNotes.rst @@ -2,6 +2,28 @@ Release Notes ============= +v0.1.2 +====== + + - Added new and experimental validation rule allowempty. (See README for more info) + - Added TODO tracking file. + - Reworked the CLI to now use docopt and removede argparse. + - Implemented more typechecks, float, number, text, any + - Now suports python 3.3.x + - No longer support any python 2.x.y version + - Enabled pattern for map rule. It enables the validation of all keys in that map. (See README for more info) + - Alot more test files and now tests source_data and schema_data input arguments to core.py + - Alot of cleanup in the test suit + +v0.1.1 +====== + + - Reworked the structure of the project to be more clean and easy to find stuff. + - lib/ folder is now removed and all contents is placed in the root of the project + - All scripts is now moved to its own folder scripts/ (To use the script during dev the path to the root of the project must be in your python path somehow, recomended is to create a virtualenv and export the correct path when it activates) + - New make target 'cleanegg' + - Fixed path bugs in Makefile + - Fixed path bugs in Manifest v0.1.0 ====== diff --git a/TODO.rst b/TODO.rst new file mode 100644 index 0000000..c5fa37f --- /dev/null +++ b/TODO.rst @@ -0,0 +1,9 @@ + - It should be possible to support more then one object type in the same object. This should enable the user to check if a variable is a dict or a string and have different rules validating them both depending on the provided data. + + - Enums must have the ability to pick if it should be case sensetive or not. + + - Fix support for all NYI types + + - Test to implement lambda support in lib mode + + - Try to remove the requirement to have a list when defining a sequence: value. If pykwalify should support multiple types at the same time this might get removed because then it can just be implemented to support more then 1 type of sequence validation. \ No newline at end of file diff --git a/pykwalify/__init__.py b/pykwalify/__init__.py index 5f4de04..79b564b 100644 --- a/pykwalify/__init__.py +++ b/pykwalify/__init__.py @@ -4,7 +4,7 @@ __author__ = 'Grokzen ' #__version__ = '.'.join(map(str, __version_info__)) -__foobar__ = "0.1.1" +__foobar__ = "0.1.2" # Set to True to have revision from Version Control System in version string __devel__ = True @@ -156,9 +156,14 @@ def get_config_path(extra = ""): IN_VIRTUALENV = True if hasattr(sys, 'real_prefix') else False if IN_VIRTUALENV: path = os.path.join(sys.prefix, "etc", PACKAGE_NAME, extra) + if not os.path.exists(path): # we are not installed, running from source tree (prefix, bindir) = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) p = os.path.join(prefix, PACKAGE_NAME, "lib", "config", extra) + if os.path.exists(p): + pass + else: + p = os.path.join(prefix, "config", extra) else: p = path else: diff --git a/pykwalify/cli.py b/pykwalify/cli.py index 7c49b8c..fe9b24a 100644 --- a/pykwalify/cli.py +++ b/pykwalify/cli.py @@ -14,7 +14,7 @@ # 3rd party imports import yaml -import argparse +from docopt import docopt # Import pykwalify package import pykwalify @@ -31,60 +31,45 @@ def main(): """ The outline of this function needs to be like this: - # TODO: update 1. parse arguments 2. validate arguments only, dont go into other logic/code - 3. set up application - 4. update internal application state - 5. go into specific logic/code for various modes of operation + 3. run application logic """ ##### - ##### 1. parse arguments + ##### 1. parse cli arguments ##### - parser = argparse.ArgumentParser(description=__doc__.strip()) - - parser.add_argument("-d", "--data-file", - dest = "datafile", - action = "store", - default = None, - help = "schema definition file") - parser.add_argument("-q", "--quiet", - dest = 'quiet', - action = 'store_true', - default = False, - help = "suppress terminal output") - parser.add_argument("-s", "--schema-file", - dest = "schemafile", - action = "store", - default = None, - help = "the file to be tested") - parser.add_argument("-v", "--verbose", - dest = 'verbose', - action = 'count', - default = False, - help = "verbose terminal output (multiple -v increases verbosity)") - parser.add_argument("-V", "--version", - dest = 'version', - action = 'store_true', - default = False, - help = "display the version number and exit") - - args = parser.parse_args() + __docopt__ = """ +usage: pykwalify -d DATAFILE -s SCHEMAFILE [-q] [-v ...] + pykwalify -h | --help + pykwalify -V | --version + +pyKwalify - cli for pykwalify + +optional arguments: + -d DATAFILE, --data-file DATAFILE schema definition file + -s SCHEMAFILE, --schema-file SCHEMAFILE the file to be tested + -q, --quiet suppress terminal output + -v, --verbose verbose terminal output (multiple -v increases verbosity) + -V, --version display the version number and exit + -h, --help show this help message and exit +""" + + args = docopt(__docopt__, version=pykwalify.__foobar__) ##### ##### 2. validate arguments only, dont go into other code/logic ##### - if args.verbose: + if args["--verbose"]: # Calculates what level to set the root logger, # -vvvvvv (6) will be logging.DEBUG + logs of all subcommands runned via utils.runcmd() # -vvvvv (5) will be logging.DEBUG, # -v (1) will be logging.CRITICAL # HowTo log to -vvvvvv (6) veerbose level: Log.log(1, msg) # Dev Note: Cannot log to level 0 so use 1 if the logging should be above logging.DEBUG level - level = 60 - (args.verbose * 10) + level = 60 - (args["--verbose"] * 10) # Sets correct logging level to the root logger l = logging.getLogger() @@ -93,13 +78,13 @@ def main(): handler.level = level # If quiet then set the logging level above 50 so nothing is printed - if args.quiet: + if args["--quiet"]: l = logging.getLogger() l.setLevel(1337) for handler in l.handlers: handler.level = 1337 - Log.debug("Setting verbose level: %s" % args.verbose) + Log.debug("Setting verbose level: %s" % args["--verbose"]) Log.debug("Arguments from CLI: %s" % args) @@ -109,15 +94,14 @@ def main(): parser.print_help() sys.exit(retnames['optionerror']) - # quickly show version and exit - if args.version: - print(pykwalify.__foobar__) - sys.exit(retnames['noerror']) - - if not args.datafile and not args.schemafile: + if not args["--data-file"] and not args["--schema-file"]: print("ERROR: must provide both a data file and a schema file (use -f/--file and -s/--schema") parser.print_help() sys.exit(retnames["optionerror"]) - c = Core(source_file = args.datafile, schema_file = args.schemafile) + ##### + ##### 3. parse cli arguments + ##### + + c = Core(source_file = args["--data-file"], schema_file = args["--schema-file"]) c.run_core() diff --git a/pykwalify/core.py b/pykwalify/core.py index dcf17d3..acd11ed 100644 --- a/pykwalify/core.py +++ b/pykwalify/core.py @@ -107,10 +107,12 @@ def _validate(self, value, rule, path, errors, done): if rule._required and self.source is None: raise CoreError("required.novalue : %s" % path) + Log.debug(" ? ValidateRule: %s" % rule) + n = len(errors) if rule._sequence is not None: self._validate_sequence(value, rule, path, errors, done = None) - elif rule._mapping is not None: + elif rule._mapping is not None or rule._allowempty_map: self._validate_mapping(value, rule, path, errors, done = None) else: self._validate_scalar(value, rule, path, errors, done = None) @@ -120,10 +122,10 @@ def _validate(self, value, rule, path, errors, done): def _validate_sequence(self, value, rule, path, errors = [], done = None): Log.debug("Core Validate sequence") - Log.debug(" * %s" % value) - Log.debug(" * %s" % rule) - Log.debug(" * %s" % rule._type) - Log.debug(" * %s" % path) + Log.debug(" * Data: %s" % value) + Log.debug(" * Rule: %s" % rule) + Log.debug(" * RuleType: %s" % rule._type) + Log.debug(" * Path: %s" % path) Log.debug(" * Seq: %s" % rule._sequence) Log.debug(" * Map: %s" % rule._mapping) @@ -187,30 +189,50 @@ def _validate_sequence(self, value, rule, path, errors = [], done = None): def _validate_mapping(self, value, rule, path, errors = [], done = None): Log.debug("Validate mapping") - Log.debug(" + %s" % value) - Log.debug(" + %s" % rule) - Log.debug(" + %s" % rule._type) - Log.debug(" + %s" % path) + Log.debug(" + Data: %s" % value) + Log.debug(" + Rule: %s" % rule) + Log.debug(" + RuleType: %s" % rule._type) + Log.debug(" + Path: %s" % path) Log.debug(" + Seq: %s" % rule._sequence) Log.debug(" + Map: %s" % rule._mapping) + if rule._mapping is None: + Log.debug(" + No rule to apply, prolly because of allowempty: True") + return + assert isinstance(rule._mapping, dict), "mapping is not a valid dict object" if value is None: + Log.debug(" + Value is None, returning...") return m = rule._mapping - for k, rule in m.items(): - if rule._required and k not in value: + Log.debug(" + RuleMapping: %s" % m) + + for k, rr in m.items(): + if rr._required and k not in value: errors.append("required.nokey : %s : %s" % (k, path) ) for k, v in value.items(): - rule = m.get(k, None) - if rule is None: - errors.append("key.undefined : %s : %s" % (k, path) ) + r = m.get(k, None) + Log.debug(" + %s" % r) + + if rule._pattern: + res = re.match(rule._pattern, str(k) ) + Log.debug("Matching regexPattern: %s with value: %s" % (rule._pattern, k) ) + if res is None: # Not matching + errors.append("pattern.unmatch : %s --> %s : %s" % (rule._pattern, k, path) ) + + if r is None: + if not rule._allowempty_map: + errors.append("key.undefined : %s : %s" % (k, path) ) else: - # validate recursively - Log.debug("Core Map: validate recursively: %s" % rule) - self._validate(v, rule, "%s/%s" % (path, k), errors, done) + #if r._parent._mapping or r._mapping or not rule._allowempty_map: + if not r._schema: + # validate recursively + Log.debug("Core Map: validate recursively: %s" % r) + self._validate(v, r, "%s/%s" % (path, k), errors, done) + else: + print(" * Something is ignored Oo : %s" % r) def _validate_scalar(self, value, rule, path, errors = [], done = None): Log.debug("Validate scalar") @@ -287,14 +309,9 @@ def _validate_scalar_type(self, value, t, errors, path): Log.debug("Core scalar: validating scalar type") Log.debug("Core scalar: scalar type: %s" % type(value) ) - if t == "str": - if not isinstance(value, str): - errors.append("Value: %s is not of type 'str' : %s" % (value, path) ) - elif t == "int": - if not isinstance(value, int): - errors.append("Value: %s is not of type 'int' : %s" % (value, path) ) - elif t == "bool": - if not isinstance(value, bool): - errors.append("Value: %s is not of type 'bool' : %s" % (value, path) ) - else: - raise Exception("Unknown type check") + try: + if not tt[t](value): + errors.append("Value: %s is not of type '%s' : %s" % (value, t, path) ) + except Exception as e: + # Type not found in map + raise Exception("Unknown type check: %s : %s : %s" % (path, value, t) ) diff --git a/pykwalify/rule.py b/pykwalify/rule.py index c53425b..f7b207c 100644 --- a/pykwalify/rule.py +++ b/pykwalify/rule.py @@ -37,6 +37,7 @@ def __init__(self, schema = None, parent = None): self._length = None self._ident = None self._unique = None + self._allowempty_map = None self._parent = parent self._schema = schema @@ -44,6 +45,9 @@ def __init__(self, schema = None, parent = None): if isinstance(schema, dict): self.init(schema, "") + def __str__(self): + return "Rule: %s" % str(self._schema) + def init(self, schema, path): Log.debug("Init schema: %s" % schema) @@ -87,6 +91,8 @@ def init(self, schema, path): self.initIdentValue(v, rule, path) elif k == "unique": self.initUniqueValue(v, rule, path) + elif k == "allowempty": + self.initAllowEmptyMap(v, rule, path) elif k == "sequence": rule = self.initSequenceValue(v, rule, path) elif k == "mapping": @@ -96,6 +102,12 @@ def init(self, schema, path): self.checkConfliction(schema, rule, path) + def initAllowEmptyMap(self, v, rule, path): + Log.debug("Init allow empty value: %s" % path) + Log.debug("Type: %s : %s" % (v, rule) ) + + self._allowempty_map = v + def initTypeValue(self, v, rule, path): Log.debug("Init type value : %s" % path) Log.debug("Type: %s %s" % (v, rule) ) @@ -346,12 +358,10 @@ def checkConfliction(self, schema, rule, path): if self._length is not None: raise Exception("seq.conflict :: length: %s" % path) elif self._type == "map": - if "mapping" not in schema: + if "mapping" not in schema and not self._allowempty_map: raise Exception("map.nomapping") if self._enum is not None: raise Exception("map.conflict :: enum:") - if self._pattern is not None: - raise Exception("map.conflict :: pattern: %s" % path) if self._sequence is not None: raise Exception("map.conflict :: mapping: %s" % path) if self._range is not None: diff --git a/pykwalify/types.py b/pykwalify/types.py index 2bc5215..bc2f38f 100644 --- a/pykwalify/types.py +++ b/pykwalify/types.py @@ -14,6 +14,7 @@ _types = {"str": str, "int": int, "float": float, + "number": None, "bool": bool, "map": dict, "seq": list, @@ -21,6 +22,7 @@ "date": datetime.datetime, "symbol": str, "scalar": None, + "text": None, "any": object} def typeClass(type): @@ -42,5 +44,33 @@ def isScalar(obj): return not isCollection(obj) def isCorrectType(obj, type): - # TODO: not implemented proper yet - return isinstance(obj, type) \ No newline at end of file + return isinstance(obj, type) + +def isString(obj): + return isinstance(obj, str) + +def isInt(obj): + return isinstance(obj, int) + +def isBool(obj): + return isinstance(obj, bool) + +def isFloat(obj): + return isinstance(obj, float) + +def isNumber(obj): + return isInt(obj) or isFloat(obj) + +def isText(obj): + return (isString(obj) or isNumber(obj) ) and isBool(obj) == False + +def isAny(obj): + return isString(obj) or isInt(obj) or isBool(obj) or isFloat(obj) or isNumber(obj) or isText(obj) or isCollection(obj) + +tt = {"str": isString, + "int": isInt, + "bool": isBool, + "float": isFloat, + "number": isNumber, + "text": isText, + "any": isAny} diff --git a/releases/pykwalify-0.1.2.tar.gz b/releases/pykwalify-0.1.2.tar.gz new file mode 100644 index 0000000..c290bd5 Binary files /dev/null and b/releases/pykwalify-0.1.2.tar.gz differ diff --git a/setup.py b/setup.py index 1e2f5b3..a6d20b2 100755 --- a/setup.py +++ b/setup.py @@ -144,7 +144,7 @@ def get_share_path(*args): settings.update( name = PACKAGE_NAME, - version = _load_version(), + version = "0.1.2", description = 'Python lib/cli for JSON/YAML schema validation', long_description = 'Python lib/cli for JSON/YAML schema validation', author = "Grokzen", @@ -154,7 +154,7 @@ def get_share_path(*args): scripts = ['scripts/pykwalify'], data_files = [ (get_etc_path(), list_dir("config") ) ], install_requires = [ - 'argparse==1.2.1', + 'docopt==0.6.0', 'PyYAML==3.10', ], classifiers = ( diff --git a/tests/files/18a.yaml b/tests/files/18a.yaml new file mode 100644 index 0000000..9312ad9 --- /dev/null +++ b/tests/files/18a.yaml @@ -0,0 +1,3 @@ +datasources: + test1: test1.py + test2: test2.py diff --git a/tests/files/18b.yaml b/tests/files/18b.yaml new file mode 100644 index 0000000..fe7afaa --- /dev/null +++ b/tests/files/18b.yaml @@ -0,0 +1,5 @@ +type: map +mapping: + datasources: + type: map + allowempty: True diff --git a/tests/files/19a.yaml b/tests/files/19a.yaml new file mode 100644 index 0000000..9312ad9 --- /dev/null +++ b/tests/files/19a.yaml @@ -0,0 +1,3 @@ +datasources: + test1: test1.py + test2: test2.py diff --git a/tests/files/19b.yaml b/tests/files/19b.yaml new file mode 100644 index 0000000..c5393bf --- /dev/null +++ b/tests/files/19b.yaml @@ -0,0 +1,8 @@ +type: map +mapping: + datasources: + type: map + allowempty: True + mapping: + test1: + type: str \ No newline at end of file diff --git a/tests/files/20a.yaml b/tests/files/20a.yaml new file mode 100644 index 0000000..2c0cac5 --- /dev/null +++ b/tests/files/20a.yaml @@ -0,0 +1 @@ +3.14159 \ No newline at end of file diff --git a/tests/files/20b.yaml b/tests/files/20b.yaml new file mode 100644 index 0000000..ee9fe7b --- /dev/null +++ b/tests/files/20b.yaml @@ -0,0 +1 @@ +type: float \ No newline at end of file diff --git a/tests/files/21a.yaml b/tests/files/21a.yaml new file mode 100644 index 0000000..3e1a3ef --- /dev/null +++ b/tests/files/21a.yaml @@ -0,0 +1,2 @@ +- 1337 +- 3.14159 \ No newline at end of file diff --git a/tests/files/21b.yaml b/tests/files/21b.yaml new file mode 100644 index 0000000..3190d74 --- /dev/null +++ b/tests/files/21b.yaml @@ -0,0 +1,3 @@ +type: seq +sequence: + - type: number \ No newline at end of file diff --git a/tests/files/22a.yaml b/tests/files/22a.yaml new file mode 100644 index 0000000..adcc118 --- /dev/null +++ b/tests/files/22a.yaml @@ -0,0 +1,3 @@ +- 1337 +- 3.14159 +- abc \ No newline at end of file diff --git a/tests/files/22b.yaml b/tests/files/22b.yaml new file mode 100644 index 0000000..3190d74 --- /dev/null +++ b/tests/files/22b.yaml @@ -0,0 +1,3 @@ +type: seq +sequence: + - type: number \ No newline at end of file diff --git a/tests/files/23a.yaml b/tests/files/23a.yaml new file mode 100644 index 0000000..5291b54 --- /dev/null +++ b/tests/files/23a.yaml @@ -0,0 +1,3 @@ +- abc +- 123 +- 3.14159 \ No newline at end of file diff --git a/tests/files/23b.yaml b/tests/files/23b.yaml new file mode 100644 index 0000000..94ec8d1 --- /dev/null +++ b/tests/files/23b.yaml @@ -0,0 +1,3 @@ +type: seq +sequence: + - type: text \ No newline at end of file diff --git a/tests/files/24a.yaml b/tests/files/24a.yaml new file mode 100644 index 0000000..6f55fab --- /dev/null +++ b/tests/files/24a.yaml @@ -0,0 +1,4 @@ +- abc +- 123 +- 3.14159 +- True \ No newline at end of file diff --git a/tests/files/24b.yaml b/tests/files/24b.yaml new file mode 100644 index 0000000..94ec8d1 --- /dev/null +++ b/tests/files/24b.yaml @@ -0,0 +1,3 @@ +type: seq +sequence: + - type: text \ No newline at end of file diff --git a/tests/files/25b.yaml b/tests/files/25b.yaml new file mode 100644 index 0000000..60f4107 --- /dev/null +++ b/tests/files/25b.yaml @@ -0,0 +1,3 @@ +type: seq +sequence: + - type: any diff --git a/tests/files/26a.yaml b/tests/files/26a.yaml new file mode 100644 index 0000000..5c3de4e --- /dev/null +++ b/tests/files/26a.yaml @@ -0,0 +1,3 @@ +foobar: + barfoo: + opa: 1337 \ No newline at end of file diff --git a/tests/files/26b.yaml b/tests/files/26b.yaml new file mode 100644 index 0000000..29afbd5 --- /dev/null +++ b/tests/files/26b.yaml @@ -0,0 +1 @@ +type: any \ No newline at end of file diff --git a/tests/files/27a.yaml b/tests/files/27a.yaml new file mode 100644 index 0000000..5f6e70a --- /dev/null +++ b/tests/files/27a.yaml @@ -0,0 +1 @@ +na1me: foobar \ No newline at end of file diff --git a/tests/files/27b.yaml b/tests/files/27b.yaml new file mode 100644 index 0000000..d629738 --- /dev/null +++ b/tests/files/27b.yaml @@ -0,0 +1,6 @@ +type: map +pattern: "^[a-z]+$" +allowempty: True +mapping: + name: + type: str diff --git a/tests/files/28a.yaml b/tests/files/28a.yaml new file mode 100644 index 0000000..5f6e70a --- /dev/null +++ b/tests/files/28a.yaml @@ -0,0 +1 @@ +na1me: foobar \ No newline at end of file diff --git a/tests/files/28b.yaml b/tests/files/28b.yaml new file mode 100644 index 0000000..0c2e051 --- /dev/null +++ b/tests/files/28b.yaml @@ -0,0 +1,6 @@ +type: map +pattern: "^[a-z0-9]+$" +allowempty: True +mapping: + name: + type: str diff --git a/tests/files/8a.yaml b/tests/files/8a.yaml index ec5650d..71f90a0 100644 --- a/tests/files/8a.yaml +++ b/tests/files/8a.yaml @@ -1,4 +1,4 @@ name: foo email: foo@mail.com age: 20 -birth: "1985-01-01" \ No newline at end of file +birth: "1985-01-01" diff --git a/tests/files/9a.yaml b/tests/files/9a.yaml index 4291d07..c0551de 100644 --- a/tests/files/9a.yaml +++ b/tests/files/9a.yaml @@ -1,4 +1,4 @@ name: foo email: foo(at)mail.com age: twnty -birth: "jun 01, 1985" \ No newline at end of file +birth: "jun 01, 1985" diff --git a/tests/lib/testcore.py b/tests/lib/testcore.py index f22c14c..2ea956e 100644 --- a/tests/lib/testcore.py +++ b/tests/lib/testcore.py @@ -2,19 +2,10 @@ """ Unit test for pyKwalify - Core """ -# python std library -import re -import os -import sys -import unittest -import logging - # Testhelper class -from tests.testhelper import run as run -from tests.testhelper import TestHelper, Log, logging_regex, gettestcwd, _set_log_lv +from tests.testhelper import TestHelper, gettestcwd, _set_log_lv # pyKwalify imports -import pykwalify from pykwalify.core import Core class TestCore(TestHelper): @@ -22,80 +13,125 @@ class TestCore(TestHelper): def f(self, *args): return gettestcwd("tests", "files", *args) + def testCoreDataMode(self): + Core(source_data = 3.14159, schema_data = {"type": "number"} ).run_core() + Core(source_data = 3.14159, schema_data = {"type": "float"} ).run_core() + Core(source_data = 3, schema_data = {"type": "int"} ).run_core() + Core(source_data = True, schema_data = {"type": "bool"} ).run_core() + Core(source_data = "foobar", schema_data = {"type": "str"} ).run_core() + Core(source_data = "foobar", schema_data = {"type": "text"} ).run_core() + Core(source_data = "foobar", schema_data = {"type": "any"} ).run_core() + + with self.assertRaises(Exception): + Core(source_data = "abc", schema_data = {"type": "number"} ).run_core() + + with self.assertRaises(Exception): + Core(source_data = 3, schema_data = {"type": "float"} ).run_core() + + with self.assertRaises(Exception): + Core(source_data = 3.14159, schema_data = {"type": "int"} ).run_core() + + with self.assertRaises(Exception): + Core(source_data = 1337, schema_data = {"type": "bool"} ).run_core() + + with self.assertRaises(Exception): + Core(source_data = 1, schema_data = {"type": "str"} ).run_core() + + with self.assertRaises(Exception): + Core(source_data = True, schema_data = {"type": "text"} ).run_core() + + with self.assertRaises(Exception): + Core(source_data = dict, schema_data = {"type": "any"} ).run_core() + def testCore(self): # Test sequence with only string values - c = Core(source_file = self.f("1a.yaml"), schema_file = self.f("1b.yaml") ) - c.run_core() + Core(source_file = self.f("1a.yaml"), schema_file = self.f("1b.yaml") ).run_core() # Test sequence with defined string content type but data only has integers - c = Core(source_file = self.f("2a.yaml"), schema_file = self.f("2b.yaml") ) with self.assertRaises(Exception): - c.run_core() + Core(source_file = self.f("2a.yaml"), schema_file = self.f("2b.yaml") ).run_core() # Test sequence where the only valid items is integers - c = Core(source_file = self.f("3a.yaml"), schema_file = self.f("3b.yaml") ) - c.run_core() + Core(source_file = self.f("3a.yaml"), schema_file = self.f("3b.yaml") ).run_core() # Test sequence with only booleans - c = Core(source_file = self.f("4a.yaml"), schema_file = self.f("4b.yaml") ) - c.run_core() + Core(source_file = self.f("4a.yaml"), schema_file = self.f("4b.yaml") ).run_core() # Test sequence with defined string content type but data only has booleans - c = Core(source_file = self.f("5a.yaml"), schema_file = self.f("5b.yaml") ) with self.assertRaises(Exception): - c.run_core() + Core(source_file = self.f("5a.yaml"), schema_file = self.f("5b.yaml") ).run_core() # Test sequence with defined booleans but with one integer - c = Core(source_file = self.f("6a.yaml"), schema_file = self.f("6b.yaml") ) with self.assertRaises(Exception): - c.run_core() + Core(source_file = self.f("6a.yaml"), schema_file = self.f("6b.yaml") ).run_core() # Test sequence with strings and and lenght on each string - c = Core(source_file = self.f("7a.yaml"), schema_file = self.f("7b.yaml") ) with self.assertRaises(Exception): - c.run_core() + Core(source_file = self.f("7a.yaml"), schema_file = self.f("7b.yaml") ).run_core() # Test mapping with different types of data and some extra conditions - c = Core(source_file = self.f("8a.yaml"), schema_file = self.f("8b.yaml") ) - c.run_core() + Core(source_file = self.f("8a.yaml"), schema_file = self.f("8b.yaml") ).run_core() # Test mapping that do not work - c = Core(source_file = self.f("9a.yaml"), schema_file = self.f("8b.yaml") ) with self.assertRaises(Exception): - c.run_core() + Core(source_file = self.f("9a.yaml"), schema_file = self.f("8b.yaml") ).run_core() # Test sequence with mapping with valid mapping - c = Core(source_file = self.f("10a.yaml"), schema_file = self.f("10b.yaml") ) - c.run_core() + Core(source_file = self.f("10a.yaml"), schema_file = self.f("10b.yaml") ).run_core() # Test sequence with mapping with missing required key - c = Core(source_file = self.f("11a.yaml"), schema_file = self.f("10b.yaml") ) with self.assertRaises(Exception): - c.run_core() + Core(source_file = self.f("11a.yaml"), schema_file = self.f("10b.yaml") ).run_core() # Test mapping with sequence with mapping and valid data - c = Core(source_file = self.f("12a.yaml"), schema_file = self.f("12b.yaml") ) - c.run_core() + Core(source_file = self.f("12a.yaml"), schema_file = self.f("12b.yaml") ).run_core() # Test mapping with sequence with mapping and invalid data - c = Core(source_file = self.f("13a.yaml"), schema_file = self.f("12b.yaml") ) with self.assertRaises(Exception): - c.run_core() + Core(source_file = self.f("13a.yaml"), schema_file = self.f("12b.yaml") ).run_core() # Test most of the implemented functions - c = Core(source_file = self.f("14a.yaml"), schema_file = self.f("14b.yaml") ) - c.run_core() + Core(source_file = self.f("14a.yaml"), schema_file = self.f("14b.yaml") ).run_core() - c = Core(source_file = self.f("15a.yaml"), schema_file = self.f("14b.yaml") ) with self.assertRaises(Exception): - c.run_core() + Core(source_file = self.f("15a.yaml"), schema_file = self.f("14b.yaml") ).run_core() # This will test the unique constraint - c = Core(source_file = self.f("16a.yaml"), schema_file = self.f("16b.yaml") ) - c.run_core() + Core(source_file = self.f("16a.yaml"), schema_file = self.f("16b.yaml") ).run_core() # TODO: The reverse unique do not currently work proper # This will test the unique constraint but should fail - c = Core(source_file = self.f("17a.yaml"), schema_file = self.f("16b.yaml") ) with self.assertRaises(Exception): - c.run_core() + Core(source_file = self.f("17a.yaml"), schema_file = self.f("16b.yaml") ).run_core() + + Core(source_file = self.f("18a.yaml"), schema_file = self.f("18b.yaml") ).run_core() + + Core(source_file = self.f("19a.yaml"), schema_file = self.f("19b.yaml") ).run_core() + + Core(source_file = self.f("20a.yaml"), schema_file = self.f("20b.yaml") ).run_core() + + # This tests number validation rule + Core(source_file = self.f("21a.yaml"), schema_file = self.f("21b.yaml") ).run_core() + + # This tests number validation rule with wrong data + with self.assertRaises(Exception): + Core(source_file = self.f("22a.yaml"), schema_file = self.f("22b.yaml") ).run_core() + + # This test the text validation rule + Core(source_file = self.f("23a.yaml"), schema_file = self.f("23b.yaml") ).run_core() + + # This test the text validation rule with wrong data + with self.assertRaises(Exception): + Core(source_file = self.f("24a.yaml"), schema_file = self.f("24b.yaml") ).run_core() + + # This test the text validation rule + Core(source_file = self.f("24a.yaml"), schema_file = self.f("25b.yaml") ).run_core() + + Core(source_file = self.f("26a.yaml"), schema_file = self.f("26b.yaml") ).run_core() + + # This tests pattern matching on keys in a map + with self.assertRaises(Exception): + Core(source_file = self.f("27a.yaml"), schema_file = self.f("27b.yaml") ).run_core() + + Core(source_file = self.f("28a.yaml"), schema_file = self.f("28b.yaml") ) + \ No newline at end of file diff --git a/tests/lib/testrule.py b/tests/lib/testrule.py index 6f5106e..6555600 100644 --- a/tests/lib/testrule.py +++ b/tests/lib/testrule.py @@ -2,19 +2,10 @@ """ Unit test for pyKwalify - Rule """ -# python std library -import unittest -import re -import sys -import os - # Testhelper class -from tests.testhelper import run as run -from tests.testhelper import TestHelper, Log, logging_regex, gettestcwd, _set_log_lv -from tests.testhelper import Log +from tests.testhelper import TestHelper, _set_log_lv # pyKwalify imports -import pykwalify from pykwalify.rule import Rule class TestRule(TestHelper): diff --git a/tests/testhelper.py b/tests/testhelper.py index 1a7b318..888f3b7 100644 --- a/tests/testhelper.py +++ b/tests/testhelper.py @@ -22,8 +22,7 @@ path = os.path.join(sys.prefix, 'etc', "pykwalify", 'logging.ini') if not os.path.exists(path): # we are not installed, running from source tree (prefix, bindir) = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) - path = os.path.join(prefix, "pykwalify", "lib", "config", "logging.ini") - + path = os.path.join(prefix, "pykwalify", "config", "logging.ini") logging.config.fileConfig(path) else: logging.config.fileConfig(os.path.join(os.sep, 'etc', "pykwalify", 'logging.ini'))