Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor validation #42

Merged
merged 26 commits into from
Mar 24, 2019
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a6b76e4
Rename documentation to docstring
robertodr Mar 14, 2019
62e0c2e
Change `keyword` -> `name` and `section` -> `name`
robertodr Mar 14, 2019
35f8bef
Add tools for views
robertodr Mar 15, 2019
2fb9505
Add tests for views
robertodr Mar 18, 2019
59652ed
Merge template and input dictionary
robertodr Mar 18, 2019
8175000
Add callable defaults and global error reporting
robertodr Mar 18, 2019
ba379b9
Separate and refactor type matching machinery
robertodr Mar 18, 2019
268aac8
Implement type checking
robertodr Mar 19, 2019
ace19cc
Add more tests for defaulting
robertodr Mar 20, 2019
c4bf127
Implement global checks on templates
robertodr Mar 20, 2019
fff1a73
Update HISTORY.rst
robertodr Mar 21, 2019
b1b3cd7
Implement predicate checking
robertodr Mar 21, 2019
c673373
Make tests on type checking unit tests
robertodr Mar 22, 2019
dc3c686
Expand test suite for check_template
robertodr Mar 22, 2019
ea08140
Fix bugs in template and predicate checking
robertodr Mar 22, 2019
98fc8d1
Refactor check_template to not modify the template
robertodr Mar 22, 2019
79a4565
Refactor check_predicates function
robertodr Mar 22, 2019
7f688ad
Add tests for template errors
robertodr Mar 22, 2019
239a892
Report messages of failing predicates
robertodr Mar 22, 2019
9af27d8
Add integration testing
robertodr Mar 23, 2019
25130db
Drop support for Python 3.4
robertodr Mar 23, 2019
3b02c3c
Test 3.8-dev last
robertodr Mar 23, 2019
c01b439
Rework API and CLI
robertodr Mar 23, 2019
12e3519
Fix flake8 and mypy warnings
robertodr Mar 23, 2019
19dd3d8
Create file if not exists
robertodr Mar 24, 2019
4a2c7e5
Fix bug introduced when casting string to Path
robertodr Mar 24, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 12 additions & 16 deletions .azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -1,35 +1,31 @@
jobs:

- job: 'Windows'
- job: 'macOS'
pool:
vmImage: 'vs2017-win2016'
vmImage: 'macOS-10.13'
strategy:
matrix:
Python37:
python.version: '3.7'
Python36:
python.version: '3.6'
Python35:
python.version: '3.5'
Python34:
python.version: '3.4'
Python36:
python.version: '3.6'
Python37:
python.version: '3.7'
maxParallel: 2
steps:
- template: .ci/azure-steps.yml

- job: 'macOS'
- job: 'Windows'
pool:
vmImage: 'macOS-10.13'
vmImage: 'vs2017-win2016'
strategy:
matrix:
Python37:
python.version: '3.7'
Python36:
python.version: '3.6'
Python35:
python.version: '3.5'
Python34:
python.version: '3.4'
Python36:
python.version: '3.6'
Python37:
python.version: '3.7'
maxParallel: 2
steps:
- template: .ci/azure-steps.yml
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ instance/

# Sphinx documentation
docs/_build/
# These are autogenerated
docs/modules.rst
docs/parselglossy*.rst

# PyBuilder
target/
Expand Down Expand Up @@ -114,3 +117,5 @@ GPATH
GRTAGS
GTAGS

# Validated input files produced during testing
validated*.json
4 changes: 0 additions & 4 deletions .style.yapf

This file was deleted.

6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ dist: xenial
language: python

python:
- 3.7
- 3.6
- 3.5
- 3.4
- 3.6
- 3.7
- 3.8-dev

env:
- PIPENV_VERBOSITY=-1
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Before you submit a pull request, check that it meets these guidelines:
3. If the pull request adds functionality, the docs should be updated. Put
your new functionality into a function with a docstring, and add the
feature to the list in ``README.rst``.
4. The pull request should work for Python 3.4, 3.5, 3.6, and 3.7. Check
4. The pull request should work for Python 3.5, 3.6, and 3.7. Check
https://travis-ci.org/dev-cafe/parselglossy/pull_requests
and make sure that the tests pass for all supported Python versions.

Expand Down
29 changes: 25 additions & 4 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,36 @@
History
=======

0.2.0 (2019-03-11)
------------------
Unreleased
----------

* Dropped support for Python 3.4.
* Renamed the ``section`` and ``keyword`` fields in the template to ``name``.
* Renamed the ``documentation`` field in the template to ``docstring``.
* Arbitrary callables of the input dictionary are now allowed in the ``default``
field. Fix `#31 <https://github.com/dev-cafe/parselglossy/issues/31>`_.
* Complex numbers have the proper type after reading in. Fix `#26 <https://github.com/dev-cafe/parselglossy/issues/26>`_.
* Better error reporting. Exceptions are raised after each validation stage and
offer detail error messages that give a comprehensive overview of what went
wrong. Fix `#24 <https://github.com/dev-cafe/parselglossy/issues/24>`_.
* Fully defaulted sections are now properly taken into account. Fix `#33
<https://github.com/dev-cafe/parselglossy/issues/33>`_.
* Nesting of sections under keywords will throw an exception. Fix `#34
<https://github.com/dev-cafe/parselglossy/issues/34>`_.

0.2.0_ (2019-03-11)
-------------------

* Implementation of the Getkw_ input grammar.
* Implementation of the validation infrastructure.

0.1.0 (2018-12-03)
------------------
0.1.0_ (2018-12-03)
-------------------

* First release on PyPI.


.. _Unreleased: https://github.com/dev-cafe/parselglossy/compare/v0.2.0...HEAD
.. _0.2.0: https://github.com/dev-cafe/parselglossy/releases/tag/v0.2.0
.. _0.1.0: https://pypi.org/project/parselglossy/0.1.0/
.. _Getkw: https://github.com/dev-cafe/libgetkw
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Generic input parsing library, speaking in tongues
Requirements
------------

* Python 3.4 or later.
* Python 3.5 or later.


Features
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import sys

import guzzle_sphinx_theme
from sphinx.apidoc import main
from sphinx.ext.apidoc import main

sys.path.insert(0, str(pathlib.Path(__file__).parents[1]))

Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Generic input parsing library, speaking in tongues.
contributing
authors
history
modules

Indices and tables
==================
Expand Down
35 changes: 35 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,38 @@ Usage
To use parselglossy in a project::

import parselglossy


Validation specifications
---------------------------

Possible fields for keywords are:

:`name`:
The name of the keyword. Mandatory.
:`type`:
Type of the corresponding value. Mandatory.
:`docstring`:
A documentation string, possibly multiline. Mandatory.
:`default`:
The default value. It can be a value of the declared type or a callable.
The callable can be used to compute default values based on the default
value of any other keyword in the input. If absent, the keyword is
required, meaning that the user will have to supply it in their own
input.
:`predicates`:
A list of callables to sanity-check the passed value. Note that type checking is
always performed automatically.

Possible fields for sections are:

:`name`:
The name of the section. Mandatory.
:`docstring`:
A documentation string, possibly multiline. Mandatory.
:`keywords`:
A list of keywords belonging to the section.
:`sections`:
A list of sections belonging to the section.

One or both of `keywords` and `sections` **must** be present.
150 changes: 150 additions & 0 deletions parselglossy/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#
# parselglossy -- Generic input parsing library, speaking in tongues
# Copyright (C) 2019 Roberto Di Remigio, Radovan Bast, and contributors.
#
# This file is part of parselglossy.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# For information on the complete list of contributors to the
# parselglossy library, see: <http://parselglossy.readthedocs.io/>
#

# -*- coding: utf-8 -*-
"""Top-level functions for parselglossy."""

import json
from pathlib import Path
from typing import Optional, Union

from .grammars import lexer
from .utils import JSONDict, as_complex, path_resolver, read_yaml_file
from .validation import validate_from_dicts


def lex(
*,
infile: Union[str, Path],
grammar: str,
ir_file: Optional[Union[str, Path]] = None
) -> JSONDict:
"""Run grammar of choice on input string.

Parameters
----------
in_str : IO[Any]
The string to be parsed.
grammar : str
Grammar to be used.
ir_file : Union[str, Path]
File to write intermediate representation to (JSON format).
None by default, which means file is not written out.

Returns
-------
The contents of the input string as a dictionary.
"""

infile = path_resolver(infile)
if ir_file is not None:
ir_file = path_resolver(ir_file)

with infile.open("r") as f:
ir = lexer.lex_from_str(in_str=f, grammar=grammar, ir_file=ir_file)

return ir


def validate(
*,
infile: Union[str, Path],
fr_file: Optional[Union[str, Path]] = None,
template: Union[str, Path]
) -> JSONDict:
"""Validate intermediate representation into final representation.

Parameters
----------
infile : Union[str, Path]
The file with the intermediate representation (JSON format).
fr_file : Union[str, Path]
File to write final representation to (JSON format).
None by default, which means file is not written out.
template : Union[str, Path]
Which validation template to use.
"""

infile = path_resolver(infile)
with infile.open("r") as f:
ir = json.load(f, object_hook=as_complex)

template = path_resolver(template)
stencil = read_yaml_file(Path(template))

if fr_file is not None:
fr_file = path_resolver(fr_file)

fr = validate_from_dicts(ir=ir, template=stencil, fr_file=fr_file)

return fr


def parse(
*,
infile: Union[str, Path],
outfile: Optional[Union[str, Path]] = None,
grammar: str,
template: Union[str, Path],
dump_ir: bool = False
) -> None:
"""Parse input file.

Parameters
----------
infile : Union[str, Path]
The input file to be parsed.
outfile : Union[str, Path]
The output file.
None by default, which means file name default to <infile>_fr.json
grammar : str
Which grammar to use.
template : Union[str, Path]
Which validation template to use.
write_ir_out : bool
Whether to write out the intermediate representation to file (JSON format).
False by default. If true the filename if <infile>_ir.json
"""

stem = infile.rsplit(".", 1)[0] if isinstance(infile, str) else infile.stem

infile = path_resolver(infile)
if dump_ir:
ir_file = path_resolver(stem + "_ir.json")
else:
ir_file = None

ir = lex(infile=infile, ir_file=ir_file, grammar=grammar)

template = path_resolver(template)
stencil = read_yaml_file(Path(template))

if outfile is not None:
outfile = path_resolver(outfile)

fr = validate_from_dicts(ir=ir, template=stencil, fr_file=outfile) # noqa: F841
Loading