Skip to content

Commit

Permalink
Merge pull request #73 from goodmami/v0.12.0
Browse files Browse the repository at this point in the history
V0.12.0
  • Loading branch information
goodmami authored Feb 14, 2020
2 parents bc0b820 + e1e958b commit 40f534c
Show file tree
Hide file tree
Showing 24 changed files with 569 additions and 128 deletions.
49 changes: 49 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,45 @@

(no unreleased changes yet)

## [v0.12.0][]

**Release date: 2020-02-14**

This release improves the amount and behavior of functions for
processing inputs from the `penman` command. It also replaces the
previous release's `Model.check()` with `Model.errors()`, which is
generally more useful. Finally, the new `penman.constant` module is
introduced for dealing with constant values in their interpreted
datatypes.

### Added

* `penman.model.Model.errors()` replaces `Model.check()` ([#65][])
* `penman.constant` module for constant values ([#69][])
* `penman.graph.Instance` subtype of `Triple` ([#66][])
* `penman.model.Model.alphanumeric_order()` ([#72][])
* `penman.layout.rearrange()`: `attributes_first` parameter ([#62][])

### Removed

* `penman.model.Model.check()` replaced by `Model.errors()` ([#65][])

### Fixed

* Don't flush standard streams in `penman` command ([#60][])

### Changed

* Return exit code of 1 when `penman --check` finds errors ([#63][])
* Moved the `Branch` and `Node` types from `penman.tree` to
`penman.types`
* `penman.model.Model.canonical_order()` is now just a shortcut for
calling `is_role_inverted()` and `alphanumeric_order()` ([#72][])
* `--rearrange` and `--reconfigure` can now take multiple ordered
sorting criteria ([#70][])
* The order of operations for processing inputs has changed so users
can do more with shorter pipelines ([#71][])


## [v0.11.1][]

Expand Down Expand Up @@ -585,6 +624,7 @@ First release with very basic functionality.
[v0.10.0]: ../../releases/tag/v0.10.0
[v0.11.0]: ../../releases/tag/v0.11.0
[v0.11.1]: ../../releases/tag/v0.11.1
[v0.12.0]: ../../releases/tag/v0.12.0
[README]: README.md

[#4]: https://github.com/goodmami/penman/issues/4
Expand Down Expand Up @@ -622,5 +662,14 @@ First release with very basic functionality.
[#52]: https://github.com/goodmami/penman/issues/52
[#53]: https://github.com/goodmami/penman/issues/53
[#55]: https://github.com/goodmami/penman/issues/55
[#60]: https://github.com/goodmami/penman/issues/60
[#61]: https://github.com/goodmami/penman/issues/61
[#62]: https://github.com/goodmami/penman/issues/62
[#63]: https://github.com/goodmami/penman/issues/63
[#65]: https://github.com/goodmami/penman/issues/65
[#66]: https://github.com/goodmami/penman/issues/66
[#67]: https://github.com/goodmami/penman/issues/67
[#69]: https://github.com/goodmami/penman/issues/69
[#70]: https://github.com/goodmami/penman/issues/70
[#71]: https://github.com/goodmami/penman/issues/71
[#72]: https://github.com/goodmami/penman/issues/72
6 changes: 3 additions & 3 deletions docs/api-demo.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,8 @@
{
"data": {
"text/plain": [
"[Attribute(source='b', role=':instance', target='bark-01'),\n",
" Attribute(source='d', role=':instance', target='dog')]"
"[Instance(source='b', role=':instance', target='bark-01'),\n",
" Instance(source='d', role=':instance', target='dog')]"
]
},
"execution_count": 6,
Expand Down Expand Up @@ -563,7 +563,7 @@
" # ::snt The dog that barked slept.\n",
" (s / sleep-01\n",
" :ARG0 (d / dog\n",
" :ARG0-of (b / bark)))''')\n",
" :ARG0-of (b / bark-01)))''')\n",
"g.edges() # note that edge directions are normalized"
]
},
Expand Down
30 changes: 30 additions & 0 deletions docs/api/penman.constant.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

penman.constant
===============

.. automodule:: penman.constant

When a PENMAN string is parsed to a tree or a graph, constant values
are left as strings or, if the value is missing, as ``None``. Penman
nevertheless recognizes four datatypes commonly used in PENMAN data:
integers, floats, strings, and symbols. A fifth type, called a "null"
value, is used when an attribute is missing its target, but aside from
robustness measures it is not a supported datatype.


Enumerated Datatypes
--------------------

.. autodata:: SYMBOL
.. autodata:: STRING
.. autodata:: INTEGER
.. autodata:: FLOAT
.. autodata:: NULL


Module Functions
----------------

.. autofunction:: type
.. autofunction:: evaluate
.. autofunction:: quote
4 changes: 4 additions & 0 deletions docs/api/penman.exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ penman.exceptions
.. autoexception:: PenmanError
:members:

.. autoexception:: ConstantError
:show-inheritance:
:members:

.. autoexception:: GraphError
:show-inheritance:
:members:
Expand Down
3 changes: 3 additions & 0 deletions docs/api/penman.graph.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ penman.graph
.. autoattribute:: role
.. autoattribute:: target

.. autoclass:: Instance
:show-inheritance:

.. autoclass:: Edge
:show-inheritance:

Expand Down
3 changes: 2 additions & 1 deletion docs/api/penman.model.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ penman.model
.. automethod:: from_dict

.. automethod:: has_role
.. automethod:: check
.. automethod:: errors

.. automethod:: is_role_inverted
.. automethod:: invert_role
Expand All @@ -26,5 +26,6 @@ penman.model
.. automethod:: dereify

.. automethod:: original_order
.. automethod:: alphanumeric_order
.. automethod:: canonical_order
.. automethod:: random_order
1 change: 1 addition & 0 deletions docs/api/penman.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Data Structures
Serialization
'''''''''''''

- :doc:`penman.constant` -- For working with constant values
- :doc:`penman.codec` -- Codec class for reading and writing PENMAN data
- :doc:`penman.layout` -- Conversion between trees and graphs
- :doc:`penman.lexer` -- Low-level parsing of PENMAN data
Expand Down
2 changes: 1 addition & 1 deletion docs/basic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ For example:
>>> codec = PENMANCodec()
>>> g = codec.decode('(b / bark-01 :ARG0 (d / dog))')
>>> g.instances()
[Attribute(source='b', role=':instance', target='bark-01'), Attribute(source='d', role=':instance', target='dog')]
[Instance(source='b', role=':instance', target='bark-01'), Instance(source='d', role=':instance', target='dog')]
>>> g.edges()
[Edge(source='b', role=':ARG0', target='d')]
>>> g.variables()
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ models if they need more control.

api/penman
api/penman.codec
api/penman.constant
api/penman.epigraph
api/penman.exceptions
api/penman.graph
Expand Down
2 changes: 1 addition & 1 deletion penman/__about__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-

__version__ = '0.11.1'
__version__ = '0.12.0'
__version_info__ = tuple(
int(x) if x.isdigit() else x
for x in __version__.replace('.', ' ').replace('-', ' ').split()
Expand Down
10 changes: 7 additions & 3 deletions penman/codec.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def decode(self, s: str) -> Graph:
Returns:
The :class:`~penman.graph.Graph` object described by *s*.
Example:
>>> from penman.codec import PENMANCodec
>>> codec = PENMANCodec()
>>> codec.decode('(b / bark-01 :ARG0 (d / dog))')
<Graph object (top=b) at ...>
Expand Down Expand Up @@ -90,9 +91,10 @@ def parse(self, s: str) -> Tree:
Returns:
The tree structure described by *s*.
Example:
>>> from penman.codec import PENMANCodec
>>> codec = PENMANCodec()
>>> codec.parse('(b / bark-01 :ARG0 (d / dog))') # noqa
Tree(('b', [('/', 'bark-01'), ('ARG0', ('d', [('/', 'dog')]))]))
Tree(('b', [('/', 'bark-01'), (':ARG0', ('d', [('/', 'dog')]))]))
"""
tokens = lex(s, pattern=PENMAN_RE)
return self._parse(tokens)
Expand Down Expand Up @@ -261,10 +263,11 @@ def encode(self,
Returns:
the PENMAN-serialized string of the Graph *g*
Example:
>>> from penman.graph import Graph
>>> from penman.codec import PENMANCodec
>>> codec = PENMANCodec()
>>> codec.encode(Graph([('h', 'instance', 'hi')]))
(h / hi)
'(h / hi)'
"""
tree = layout.configure(g, top=top, model=self.model)
Expand Down Expand Up @@ -359,6 +362,7 @@ def format_triples(self,
Returns:
the serialized triple conjunction of *triples*
Example:
>>> from penman.codec import PENMANCodec
>>> codec = PENMANCodec()
>>> codec.format_triples([('a', ':instance', 'alpha'),
... ('a', ':ARG0', 'b'),
Expand Down
146 changes: 146 additions & 0 deletions penman/constant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@

"""
Functions for working with constant values.
"""

from typing import Union
from enum import Enum
import json

from penman.types import Constant
from penman.exceptions import ConstantError


pytype = type # store because type() is redefined below


class Type(Enum):
"""
An enumeration of constant value types.
"""

SYMBOL = 'Symbol'
STRING = 'String'
INTEGER = 'Integer'
FLOAT = 'Float'
NULL = 'Null'


# Make them available at the module level
SYMBOL = Type.SYMBOL #: Symbol constants (e.g., :code:`(... :polarity -)`)
STRING = Type.STRING #: String constants (e.g., :code:`(... :op1 "Kim")`)
INTEGER = Type.INTEGER #: Integer constants (e.g., :code:`(... :value 12)`)
FLOAT = Type.FLOAT #: Float constants (e.g., :code:`(... :value 1.2)`)
NULL = Type.NULL #: Empty values (e.g., :code:`(... :ARG1 )`)

_typemap = {
str: SYMBOL, # needs further checking
int: INTEGER,
float: FLOAT,
type(None): NULL,
}


def type(constant_string: Union[str, None]) -> Type:
"""
Return the type of constant encoded by *constant_string*.
Examples:
>>> from penman import constant
>>> constant.type('-')
<Type.SYMBOL: 'Symbol'>
>>> constant.type('"foo"')
<Type.STRING: 'String'>
>>> constant.type('1')
<Type.INTEGER: 'Integer'>
>>> constant.type('1.2')
<Type.FLOAT: 'Float'>
>>> constant.type('')
<Type.NULL: 'Null'>
"""
if constant_string is None:
typ = NULL
else:
assert isinstance(constant_string, str)
value = evaluate(constant_string)
typ = _typemap[pytype(value)]
if (typ == Type.SYMBOL
and constant_string.startswith('"')
and constant_string.endswith('"')):
typ = Type.STRING
return typ


def evaluate(constant_string: Union[str, None]) -> Constant:
"""
Evaluate and return *constant_string*.
If *constant_string* is ``None`` or an empty symbol (``''``), this
function returns ``None``, while an empty string constant
(``'""'``) returns an empty :py:class:`str` object
(``''``). Otherwise, symbols are returned unchanged while strings
get quotes removed and escape sequences are unescaped. Note that
this means it is impossible to recover the original type of
strings and symbols once they have been evaluated. For integer and
float constants, this function returns the equivalent Python
:py:class:`int` or :py:class:`float` objects.
Examples:
>>> from penman import constant
>>> constant.evaluate('-')
'-'
>>> constant.evaluate('"foo"')
'foo'
>>> constant.evaluate('1')
1
>>> constant.evaluate('1.2')
1.2
>>> constant.evaluate('') is None
True
"""
value: Constant = constant_string
if value is None or value == '':
value = None
else:
assert isinstance(constant_string, str)
if constant_string.startswith('"') ^ constant_string.endswith('"'):
raise ConstantError(f'unbalanced quotes: {constant_string}')
if constant_string not in ('true', 'false', 'null'):
try:
value = json.loads(constant_string, parse_constant=str)
except json.JSONDecodeError:
value = constant_string

if not (value is None or isinstance(value, (str, int, float))):
raise ConstantError(f'invalid constant: {value!r}')

return value


def quote(constant: Constant) -> str:
"""
Return *constant* as a quoted string.
If *constant* is ``None``, this function returns an empty string
constant (``'""'``). All other types are cast to a string and
quoted.
Examples:
>>> from penman import constant
>>> constant.quote(None)
'""'
>>> constant.quote('')
'""'
>>> constant.quote('foo')
'"foo"'
>>> constant.quote('"foo"')
'"\\\\"foo\\\\""'
>>> constant.quote(1)
'"1"'
>>> constant.quote(1.5)
'"1.5"'
"""
if constant is None:
return '""'
else:
return json.dumps(str(constant))
6 changes: 5 additions & 1 deletion penman/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ class PenmanError(Exception):
"""Base class for errors in the Penman package."""


class ConstantError(PenmanError):
"""Raised when working with invalid constant values."""


class GraphError(PenmanError):
"""Raises on invalid graph structures or operations."""
"""Raised on invalid graph structures or operations."""


class LayoutError(PenmanError):
Expand Down
Loading

0 comments on commit 40f534c

Please sign in to comment.