Skip to content

Commit

Permalink
Merge pull request #90 from rbeyer/new-tests-89
Browse files Browse the repository at this point in the history
Fixing new.py, lots of other minor fixes, and lots of tests.
  • Loading branch information
rbeyer authored Jun 7, 2021
2 parents 4c4f691 + fa6f196 commit d2c965c
Show file tree
Hide file tree
Showing 25 changed files with 780 additions and 55 deletions.
1 change: 1 addition & 0 deletions .github/workflows/code-cover.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
python -m pip install --upgrade pip
python -m pip install pytest
python -m pip install pytest-cov
python -m pip install multidict
- name: Install pvl
run: python -m pip install -e .
- name: Test with pytest and generate coverage report
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/python-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install pytest flake8
python -m pip install multidict
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
Expand Down
24 changes: 24 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,30 @@ and the release date, in year-month-day format (see examples below).
Unreleased
----------

1.2.1 (2021-05-31)
------------------

Added
+++++
* So many tests, increased coverage by about 10%.

Fixed
+++++
* Attempting to import `pvl.new` without *multidict* being available,
will now properly yield an ImportError.
* The `dump()` and `dumps()` functions now properly overwritten in `pvl.new`.
* All encoders that descended from PVLEncoder didn't properly have group_class and
object_class arguments to their constructors, now they do.
* The `char_allowed()` function in grammar objects now raises a more useful ValueError
than just a generic Exception.
* The new `collections.PVLMultiDict` wasn't correctly inserting Mapping objects with
the `insert_before()` and `insert_after()` methods.
* The `token.Token` class's `__index__()` function didn't always properly return an
index.
* The `token.Token` class's `__float__()` function would return int objects if the
token could be converted to int. Now always returns floats.


1.2.0 (2021-03-27)
------------------

Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ lint:
test:
python -m pytest --doctest-modules --doctest-glob='*.rst'

test-min:
python -m pytest --doctest-modules --ignore=pvl/new.py

test-all:
tox

Expand Down
5 changes: 3 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pvl

.. image:: https://codecov.io/gh/planetarypy/pvl/branch/master/graph/badge.svg?token=uWqotcPTGR
:target: https://codecov.io/gh/planetarypy/pvl
:alt: Codecov coverage


.. image:: https://img.shields.io/pypi/v/pvl.svg?style=flat-square
Expand All @@ -38,7 +39,7 @@ Python implementation of a PVL (Parameter Value Language) library.
* Support for Python 3.6 and higher (avaiable via pypi and conda).
* `PlanetaryPy`_ Affiliate Package.

PVL is a markup language, similar to XML, commonly employed for
PVL is a markup language, like JSON or YAML, commonly employed for
entries in the Planetary Data System used by NASA to archive
mission data, among other uses. This package supports both encoding
and decoding a variety of PVL 'flavors' including PVL itself, ODL,
Expand Down Expand Up @@ -214,7 +215,7 @@ Feedback, issues, and contributions are always gratefully welcomed. See the
environment.


.. _PlanetaryPy: https://github.com/planetarypy
.. _PlanetaryPy: https://planetarypy.org
.. _USGS ISIS Cube Labels: http://isis.astrogeology.usgs.gov/
.. _NASA PDS 3 Labels: https://pds.nasa.gov
.. _image: https://github.com/planetarypy/pvl/raw/master/tests/data/pattern.cub
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@

# General information about the project.
project = u'pvl'
copyright = u'2015, 2017, 2019-2020, pvl Developers'
copyright = u'2015, 2017, 2019-2021, pvl Developers'

# The version info for the project you're documenting, acts as replacement
# for |version| and |release|, also used in various other places throughout
Expand Down
2 changes: 1 addition & 1 deletion pvl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

__author__ = "The pvl Developers"
__email__ = "[email protected]"
__version__ = "1.2.0"
__version__ = "1.2.1"
__all__ = [
"load",
"loads",
Expand Down
21 changes: 2 additions & 19 deletions pvl/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
is no fundamental Python type for a quantity, so we define the Quantity
class (formerly the Units class).
"""
# Copyright 2015, 2017, 2019-2020, ``pvl`` library authors.
# Copyright 2015, 2017, 2019-2021, ``pvl`` library authors.
#
# Reuse is permitted under the terms of the license.
# The AUTHORS file and the LICENSE file are at the
Expand Down Expand Up @@ -268,9 +268,6 @@ def extend(self, *args, **kwargs):
if isinstance(iterable, abc.Mapping) or hasattr(iterable, "items"):
for key, value in iterable.items():
self.append(key, value)
elif hasattr(iterable, "keys"):
for key in iterable.keys():
self.append(key, iterable[key])
else:
for key, value in iterable:
self.append(key, value)
Expand Down Expand Up @@ -393,20 +390,6 @@ def insert(self, index: int, *args) -> None:

return

def __insert_wrapper(func):
"""Make sure the arguments given to the insert methods are correct."""

def check_func(self, key, new_item, instance=0):
if key not in self.keys():
raise KeyError(f"{key} not a key in label")
if not isinstance(new_item, (list, OrderedMultiDict)):
raise TypeError("The new item must be a list or PVLModule")
if isinstance(new_item, OrderedMultiDict):
new_item = list(new_item)
return func(self, key, new_item, instance)

return check_func

def key_index(self, key, instance: int = 0) -> int:
"""Get the index of the key to insert before or after."""
if key not in self:
Expand Down Expand Up @@ -586,7 +569,7 @@ def _insert_item(
index = index + 1 if is_after else index

if isinstance(new_item, abc.Mapping):
tuple_iter = new_item.items()
tuple_iter = tuple(new_item.items())
else:
tuple_iter = new_item
self.insert(index, tuple_iter)
Expand Down
12 changes: 12 additions & 0 deletions pvl/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,8 @@ def __init__(
aggregation_end=True,
end_delimiter=False,
newline="\r\n",
group_class=PVLGroup,
object_class=PVLObject
):

if grammar is None:
Expand All @@ -546,6 +548,8 @@ def __init__(
aggregation_end,
end_delimiter,
newline,
group_class=group_class,
object_class=object_class
)

def encode(self, module: abc.Mapping) -> str:
Expand Down Expand Up @@ -862,6 +866,8 @@ def __init__(
indent=2,
width=80,
aggregation_end=True,
group_class=PVLGroup,
object_class=PVLObject,
convert_group_to_object=True,
tab_replace=4,
symbol_single_quote=True,
Expand All @@ -882,6 +888,8 @@ def __init__(
aggregation_end,
end_delimiter=False,
newline="\r\n",
group_class=group_class,
object_class=object_class
)

self.convert_group_to_object = convert_group_to_object
Expand Down Expand Up @@ -1147,6 +1155,8 @@ def __init__(
aggregation_end=True,
end_delimiter=False,
newline="\n",
group_class=PVLGroup,
object_class=PVLObject
):

if grammar is None:
Expand All @@ -1163,4 +1173,6 @@ def __init__(
aggregation_end,
end_delimiter,
newline,
group_class=group_class,
object_class=object_class
)
8 changes: 5 additions & 3 deletions pvl/grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ def char_allowed(self, char):
set with some exclusions.
"""
if len(char) != 1:
raise Exception
raise ValueError(
f"This function only takes single characters and it was given "
f"{len(char)} ('{char}')."
)

o = ord(char)

Expand Down Expand Up @@ -207,8 +210,7 @@ def char_allowed(self, char):
characters than PVL, but appears to allow more control
characters to be in quoted strings than PVL does.
"""
if len(char) != 1:
raise Exception
super().char_allowed(char)

try:
char.encode(encoding="ascii")
Expand Down
77 changes: 76 additions & 1 deletion pvl/new.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,33 @@
be the new PVLMultiDict objects.
"""

# Copyright 2015, 2017, 2019-2020, ``pvl`` library authors.
# Copyright 2015, 2017, 2019-2021, ``pvl`` library authors.
#
# Reuse is permitted under the terms of the license.
# The AUTHORS file and the LICENSE file are at the
# top level of this library.

import inspect
import io
import urllib.request
from pathlib import Path

try: # noqa: C901
# In order to access super class attributes for our derived class, we must
# import the native Python version, instead of the default Cython version.
from multidict._multidict_py import MultiDict # noqa: F401
except ImportError as err:
raise ImportError(
"The multidict library is not present, so the new PVLMultiDict is not "
"available, and pvl.new can't be imported. In order to do so, install "
"the multidict package",
ImportWarning,
) from err

from pvl import * # noqa: F401,F403
from pvl import get_text_from, decode_by_char

from .encoder import PDSLabelEncoder, PVLEncoder
from .parser import PVLParser, OmniParser
from .collections import PVLModuleNew, PVLGroupNew, PVLObjectNew

Expand Down Expand Up @@ -132,3 +147,63 @@ def loads(s: str, parser=None, grammar=None, decoder=None, **kwargs):
raise TypeError("The parser must be an instance of pvl.PVLParser.")

return parser.parse(s)


def dump(module, path, **kwargs):
"""Serialize *module* as PVL text to the provided *path*.
:param module: a ``PVLModule`` or ``dict``-like object to serialize.
:param path: an :class:`os.PathLike`
:param ``**kwargs``: the keyword arguments to pass to :func:`dumps()`.
If *path* is an :class:`os.PathLike`, it will attempt to be opened
and the serialized module will be written into that file via
the :func:`pathlib.Path.write_text()` function, and will return
what that function returns.
If *path* is not an :class:`os.PathLike`, it will be assumed to be an
already-opened file object, and ``.write()`` will be applied
on that object to write the serialized module, and will return
what that function returns.
"""
try:
p = Path(path)
return p.write_text(dumps(module, **kwargs))

except TypeError:
# Not an os.PathLike, maybe it is an already-opened file object
try:
if isinstance(path, io.TextIOBase):
return path.write(dumps(module, **kwargs))
else:
return path.write(dumps(module, **kwargs).encode())
except AttributeError:
# Not a path, not an already-opened file.
raise TypeError(
"Expected an os.PathLike or an already-opened "
"file object for writing, but got neither."
)


def dumps(module, encoder=None, grammar=None, decoder=None, **kwargs) -> str:
"""Returns a string where the *module* object has been serialized
to PVL syntax.
:param module: a ``PVLModule`` or ``dict`` like object to serialize.
:param encoder: defaults to :class:`pvl.parser.PDSLabelEncoder()`.
:param grammar: defaults to :class:`pvl.grammar.ODLGrammar()`.
:param decoder: defaults to :class:`pvl.decoder.ODLDecoder()`.
:param ``**kwargs``: the keyword arguments to pass to the encoder
class if *encoder* is none.
"""
if encoder is None:
encoder = PDSLabelEncoder(
grammar=grammar,
decoder=decoder,
group_class=PVLGroupNew,
object_class=PVLObjectNew,
**kwargs)
elif not isinstance(encoder, PVLEncoder):
raise TypeError("The encoder must be an instance of pvl.PVLEncoder.")

return encoder.encode(module)
10 changes: 3 additions & 7 deletions pvl/pvl_translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
will raise errors.
"""

# Copyright 2020, ``pvl`` library authors.
# Copyright 2020-2021, ``pvl`` library authors.
#
# Reuse is permitted under the terms of the license.
# The AUTHORS file and the LICENSE file are at the
Expand Down Expand Up @@ -80,14 +80,10 @@ def arg_parser(formats):
return parser


def main():
args = arg_parser(formats).parse_args()
def main(argv=None):
args = arg_parser(formats).parse_args(argv)

some_pvl = pvl.load(args.infile)

formats[args.output_format].dump(some_pvl, args.outfile)
return


if __name__ == "__main__":
sys.exit(main())
9 changes: 2 additions & 7 deletions pvl/pvl_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import argparse
import logging
import sys
from collections import OrderedDict

import pvl
Expand Down Expand Up @@ -102,8 +101,8 @@ def arg_parser():
return p


def main():
args = arg_parser().parse_args()
def main(argv=None):
args = arg_parser().parse_args(argv)

logging.basicConfig(
format="%(levelname)s: %(message)s", level=(60 - 20 * args.verbose)
Expand Down Expand Up @@ -252,7 +251,3 @@ def build_line(elements: list, widths: list, sep=" | ") -> str:
cells.append("{0:^{width}}".format(e, width=w))

return sep.join(cells)


if __name__ == "__main__":
sys.exit(main())
Loading

0 comments on commit d2c965c

Please sign in to comment.