1# -*- coding: utf-8 -*- 2""" 3Provides an extended :class:`parse.Parser` class that supports the 4cardinality fields in (user-defined) types. 5""" 6 7from __future__ import absolute_import 8import logging 9import parse 10from .cardinality_field import CardinalityField, CardinalityFieldTypeBuilder 11from .parse_util import FieldParser 12 13 14log = logging.getLogger(__name__) # pylint: disable=invalid-name 15 16 17class Parser(parse.Parser): 18 """Provides an extended :class:`parse.Parser` with cardinality field support. 19 A cardinality field is a type suffix for parse format expression, ala: 20 21 "... {person:Person?} ..." -- OPTIONAL: Cardinality zero or one, 0..1 22 "... {persons:Person*} ..." -- MANY0: Cardinality zero or more, 0.. 23 "... {persons:Person+} ..." -- MANY: Cardinality one or more, 1.. 24 25 When the primary type converter for cardinality=1 is provided, 26 the type variants for the other cardinality cases can be derived from it. 27 28 This parser class automatically creates missing type variants for types 29 with a cardinality field and passes the extended type dictionary 30 to its base class. 31 """ 32 # -- TYPE-BUILDER: For missing types in Fields with CardinalityField part. 33 type_builder = CardinalityFieldTypeBuilder 34 35 def __init__(self, schema, extra_types=None, case_sensitive=False, 36 type_builder=None): 37 """Creates a parser with CardinalityField part support. 38 39 :param schema: Parse schema (or format) for parser (as string). 40 :param extra_types: Type dictionary with type converters (or None). 41 :param case_sensitive: Indicates if case-sensitive regexp are used. 42 :param type_builder: Type builder to use for missing types. 43 """ 44 if extra_types is None: 45 extra_types = {} 46 missing = self.create_missing_types(schema, extra_types, type_builder) 47 if missing: 48 # pylint: disable=logging-not-lazy 49 log.debug("MISSING TYPES: %s" % ",".join(missing.keys())) 50 extra_types.update(missing) 51 52 # -- FINALLY: Delegate to base class. 53 super(Parser, self).__init__(schema, extra_types, 54 case_sensitive=case_sensitive) 55 56 @classmethod 57 def create_missing_types(cls, schema, type_dict, type_builder=None): 58 """Creates missing types for fields with a CardinalityField part. 59 It is assumed that the primary type converter for cardinality=1 60 is registered in the type dictionary. 61 62 :param schema: Parse schema (or format) for parser (as string). 63 :param type_dict: Type dictionary with type converters. 64 :param type_builder: Type builder to use for missing types. 65 :return: Type dictionary with missing types. Empty, if none. 66 :raises: MissingTypeError, 67 if a primary type converter with cardinality=1 is missing. 68 """ 69 if not type_builder: 70 type_builder = cls.type_builder 71 72 missing = cls.extract_missing_special_type_names(schema, type_dict) 73 return type_builder.create_type_variants(missing, type_dict) 74 75 @staticmethod 76 def extract_missing_special_type_names(schema, type_dict): 77 # pylint: disable=invalid-name 78 """Extract the type names for fields with CardinalityField part. 79 Selects only the missing type names that are not in the type dictionary. 80 81 :param schema: Parse schema to use (as string). 82 :param type_dict: Type dictionary with type converters. 83 :return: Generator with missing type names (as string). 84 """ 85 for name in FieldParser.extract_types(schema): 86 if CardinalityField.matches_type(name) and (name not in type_dict): 87 yield name 88