| # -*- coding: utf-8 -*- |
| """ |
| Provides an extended :class:`parse.Parser` class that supports the |
| cardinality fields in (user-defined) types. |
| """ |
| |
| from __future__ import absolute_import |
| import logging |
| import parse |
| from .cardinality_field import CardinalityField, CardinalityFieldTypeBuilder |
| from .parse_util import FieldParser |
| |
| |
| log = logging.getLogger(__name__) # pylint: disable=invalid-name |
| |
| |
| class Parser(parse.Parser): |
| """Provides an extended :class:`parse.Parser` with cardinality field support. |
| A cardinality field is a type suffix for parse format expression, ala: |
| |
| "... {person:Person?} ..." -- OPTIONAL: Cardinality zero or one, 0..1 |
| "... {persons:Person*} ..." -- MANY0: Cardinality zero or more, 0.. |
| "... {persons:Person+} ..." -- MANY: Cardinality one or more, 1.. |
| |
| When the primary type converter for cardinality=1 is provided, |
| the type variants for the other cardinality cases can be derived from it. |
| |
| This parser class automatically creates missing type variants for types |
| with a cardinality field and passes the extended type dictionary |
| to its base class. |
| """ |
| # -- TYPE-BUILDER: For missing types in Fields with CardinalityField part. |
| type_builder = CardinalityFieldTypeBuilder |
| |
| def __init__(self, schema, extra_types=None, case_sensitive=False, |
| type_builder=None): |
| """Creates a parser with CardinalityField part support. |
| |
| :param schema: Parse schema (or format) for parser (as string). |
| :param extra_types: Type dictionary with type converters (or None). |
| :param case_sensitive: Indicates if case-sensitive regexp are used. |
| :param type_builder: Type builder to use for missing types. |
| """ |
| if extra_types is None: |
| extra_types = {} |
| missing = self.create_missing_types(schema, extra_types, type_builder) |
| if missing: |
| # pylint: disable=logging-not-lazy |
| log.debug("MISSING TYPES: %s" % ",".join(missing.keys())) |
| extra_types.update(missing) |
| |
| # -- FINALLY: Delegate to base class. |
| super(Parser, self).__init__(schema, extra_types, |
| case_sensitive=case_sensitive) |
| |
| @classmethod |
| def create_missing_types(cls, schema, type_dict, type_builder=None): |
| """Creates missing types for fields with a CardinalityField part. |
| It is assumed that the primary type converter for cardinality=1 |
| is registered in the type dictionary. |
| |
| :param schema: Parse schema (or format) for parser (as string). |
| :param type_dict: Type dictionary with type converters. |
| :param type_builder: Type builder to use for missing types. |
| :return: Type dictionary with missing types. Empty, if none. |
| :raises: MissingTypeError, |
| if a primary type converter with cardinality=1 is missing. |
| """ |
| if not type_builder: |
| type_builder = cls.type_builder |
| |
| missing = cls.extract_missing_special_type_names(schema, type_dict) |
| return type_builder.create_type_variants(missing, type_dict) |
| |
| @staticmethod |
| def extract_missing_special_type_names(schema, type_dict): |
| # pylint: disable=invalid-name |
| """Extract the type names for fields with CardinalityField part. |
| Selects only the missing type names that are not in the type dictionary. |
| |
| :param schema: Parse schema to use (as string). |
| :param type_dict: Type dictionary with type converters. |
| :return: Generator with missing type names (as string). |
| """ |
| for name in FieldParser.extract_types(schema): |
| if CardinalityField.matches_type(name) and (name not in type_dict): |
| yield name |