• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1===============================================================================
2parse_type
3===============================================================================
4
5.. image:: https://img.shields.io/travis/jenisys/parse_type/master.svg
6    :target: https://travis-ci.org/jenisys/parse_type
7    :alt: Travis CI Build Status
8
9.. image:: https://img.shields.io/pypi/v/parse_type.svg
10    :target: https://pypi.python.org/pypi/parse_type
11    :alt: Latest Version
12
13.. image:: https://img.shields.io/pypi/dm/parse_type.svg
14    :target: https://pypi.python.org/pypi/parse_type
15    :alt: Downloads
16
17.. image:: https://img.shields.io/pypi/l/parse_type.svg
18    :target: https://pypi.python.org/pypi/parse_type/
19    :alt: License
20
21
22`parse_type`_ extends the `parse`_ module (opposite of `string.format()`_)
23with the following features:
24
25* build type converters for common use cases (enum/mapping, choice)
26* build a type converter with a cardinality constraint (0..1, 0..*, 1..*)
27    from the type converter with cardinality=1.
28* compose a type converter from other type converters
29* an extended parser that supports the CardinalityField naming schema
30    and creates missing type variants (0..1, 0..*, 1..*) from the
31    primary type converter
32
33.. _parse_type: http://pypi.python.org/pypi/parse_type
34.. _parse:      http://pypi.python.org/pypi/parse
35.. _`string.format()`: http://docs.python.org/library/string.html#format-string-syntax
36
37
38Definitions
39-------------------------------------------------------------------------------
40
41*type converter*
42    A type converter function that converts a textual representation
43    of a value type into instance of this value type.
44    In addition, a type converter function is often annotated with attributes
45    that allows the `parse`_ module to use it in a generic way.
46    A type converter is also called a *parse_type* (a definition used here).
47
48*cardinality field*
49    A naming convention for related types that differ in cardinality.
50    A cardinality field is a type name suffix in the format of a field.
51    It allows parse format expression, ala::
52
53        "{person:Person}"     #< Cardinality: 1    (one; the normal case)
54        "{person:Person?}"    #< Cardinality: 0..1 (zero or one  = optional)
55        "{persons:Person*}"   #< Cardinality: 0..* (zero or more = many0)
56        "{persons:Person+}"   #< Cardinality: 1..* (one  or more = many)
57
58    This naming convention mimics the relationship descriptions in UML diagrams.
59
60
61Basic Example
62-------------------------------------------------------------------------------
63
64Define an own type converter for numbers (integers):
65
66.. code-block:: python
67
68    # -- USE CASE:
69    def parse_number(text):
70        return int(text)
71    parse_number.pattern = r"\d+"  # -- REGULAR EXPRESSION pattern for type.
72
73This is equivalent to:
74
75.. code-block:: python
76
77    import parse
78
79    @parse.with_pattern(r"\d+")
80    def parse_number(text):
81         return int(text)
82    assert hasattr(parse_number, "pattern")
83    assert parse_number.pattern == r"\d+"
84
85
86.. code-block:: python
87
88    # -- USE CASE: Use the type converter with the parse module.
89    schema = "Hello {number:Number}"
90    parser = parse.Parser(schema, dict(Number=parse_number))
91    result = parser.parse("Hello 42")
92    assert result is not None, "REQUIRE: text matches the schema."
93    assert result["number"] == 42
94
95    result = parser.parse("Hello XXX")
96    assert result is None, "MISMATCH: text does not match the schema."
97
98.. hint::
99
100    The described functionality above is standard functionality
101    of the `parse`_ module. It serves as introduction for the remaining cases.
102
103
104Cardinality
105-------------------------------------------------------------------------------
106
107Create an type converter for "ManyNumbers" (List, separated with commas)
108with cardinality "1..* = 1+" (many) from the type converter for a "Number".
109
110.. code-block:: python
111
112    # -- USE CASE: Create new type converter with a cardinality constraint.
113    # CARDINALITY: many := one or more (1..*)
114    from parse import Parser
115    from parse_type import TypeBuilder
116    parse_numbers = TypeBuilder.with_many(parse_number, listsep=",")
117
118    schema = "List: {numbers:ManyNumbers}"
119    parser = Parser(schema, dict(ManyNumbers=parse_numbers))
120    result = parser.parse("List: 1, 2, 3")
121    assert result["numbers"] == [1, 2, 3]
122
123
124Create an type converter for an "OptionalNumbers" with cardinality "0..1 = ?"
125(optional) from the type converter for a "Number".
126
127.. code-block:: python
128
129    # -- USE CASE: Create new type converter with cardinality constraint.
130    # CARDINALITY: optional := zero or one (0..1)
131    from parse import Parser
132    from parse_type import TypeBuilder
133
134    parse_optional_number = TypeBuilder.with_optional(parse_number)
135    schema = "Optional: {number:OptionalNumber}"
136    parser = Parser(schema, dict(OptionalNumber=parse_optional_number))
137    result = parser.parse("Optional: 42")
138    assert result["number"] == 42
139    result = parser.parse("Optional: ")
140    assert result["number"] == None
141
142
143Enumeration (Name-to-Value Mapping)
144-------------------------------------------------------------------------------
145
146Create an type converter for an "Enumeration" from the description of
147the mapping as dictionary.
148
149.. code-block:: python
150
151    # -- USE CASE: Create a type converter for an enumeration.
152    from parse import Parser
153    from parse_type import TypeBuilder
154
155    parse_enum_yesno = TypeBuilder.make_enum({"yes": True, "no": False})
156    parser = Parser("Answer: {answer:YesNo}", dict(YesNo=parse_enum_yesno))
157    result = parser.parse("Answer: yes")
158    assert result["answer"] == True
159
160
161Create an type converter for an "Enumeration" from the description of
162the mapping as an enumeration class (`Python 3.4 enum`_ or the `enum34`_
163backport; see also: `PEP-0435`_).
164
165.. code-block:: python
166
167    # -- USE CASE: Create a type converter for enum34 enumeration class.
168    # NOTE: Use Python 3.4 or enum34 backport.
169    from parse import Parser
170    from parse_type import TypeBuilder
171    from enum import Enum
172
173    class Color(Enum):
174        red   = 1
175        green = 2
176        blue  = 3
177
178    parse_enum_color = TypeBuilder.make_enum(Color)
179    parser = Parser("Select: {color:Color}", dict(Color=parse_enum_color))
180    result = parser.parse("Select: red")
181    assert result["color"] is Color.red
182
183.. _`Python 3.4 enum`: http://docs.python.org/3.4/library/enum.html#module-enum
184.. _enum34:   http://pypi.python.org/pypi/enum34
185.. _PEP-0435: http://www.python.org/dev/peps/pep-0435
186
187
188Choice (Name Enumeration)
189-------------------------------------------------------------------------------
190
191A Choice data type allows to select one of several strings.
192
193Create an type converter for an "Choice" list, a list of unique names
194(as string).
195
196.. code-block:: python
197
198    from parse import Parser
199    from parse_type import TypeBuilder
200
201    parse_choice_yesno = TypeBuilder.make_choice(["yes", "no"])
202    schema = "Answer: {answer:ChoiceYesNo}"
203    parser = Parser(schema, dict(ChoiceYesNo=parse_choice_yesno))
204    result = parser.parse("Answer: yes")
205    assert result["answer"] == "yes"
206
207
208Variant (Type Alternatives)
209-------------------------------------------------------------------------------
210
211Sometimes you need a type converter that can accept text for multiple
212type converter alternatives. This is normally called a "variant" (or: union).
213
214Create an type converter for an "Variant" type that accepts:
215
216* Numbers (positive numbers, as integer)
217* Color enum values (by name)
218
219.. code-block:: python
220
221    from parse import Parser, with_pattern
222    from parse_type import TypeBuilder
223    from enum import Enum
224
225    class Color(Enum):
226        red   = 1
227        green = 2
228        blue  = 3
229
230    @with_pattern(r"\d+")
231    def parse_number(text):
232        return int(text)
233
234    # -- MAKE VARIANT: Alternatives of different type converters.
235    parse_color = TypeBuilder.make_enum(Color)
236    parse_variant = TypeBuilder.make_variant([parse_number, parse_color])
237    schema = "Variant: {variant:Number_or_Color}"
238    parser = Parser(schema, dict(Number_or_Color=parse_variant))
239
240    # -- TEST VARIANT: With number, color and mismatch.
241    result = parser.parse("Variant: 42")
242    assert result["variant"] == 42
243    result = parser.parse("Variant: blue")
244    assert result["variant"] is Color.blue
245    result = parser.parse("Variant: __MISMATCH__")
246    assert not result
247
248
249
250Extended Parser with CardinalityField support
251-------------------------------------------------------------------------------
252
253The parser extends the ``parse.Parser`` and adds the following functionality:
254
255* supports the CardinalityField naming scheme
256* automatically creates missing type variants for types with
257  a CardinalityField by using the primary type converter for cardinality=1
258* extends the provide type converter dictionary with new type variants.
259
260Example:
261
262.. code-block:: python
263
264    # -- USE CASE: Parser with CardinalityField support.
265    # NOTE: Automatically adds missing type variants with CardinalityField part.
266    # USE:  parse_number() type converter from above.
267    from parse_type.cfparse import Parser
268
269    # -- PREPARE: parser, adds missing type variant for cardinality 1..* (many)
270    type_dict = dict(Number=parse_number)
271    schema = "List: {numbers:Number+}"
272    parser = Parser(schema, type_dict)
273    assert "Number+" in type_dict, "Created missing type variant based on: Number"
274
275    # -- USE: parser.
276    result = parser.parse("List: 1, 2, 3")
277    assert result["numbers"] == [1, 2, 3]
278