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