• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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