PK!G BBexcelschema/__init__.pyfrom .core import SchemaParser from .constraint import Constraint PK!.,,excelschema/constraint.pyfrom typing import NamedTuple, Any, Union class Constraint(NamedTuple): type_: Union[type, list, type(Any)] = Any unique: bool = False not_null: bool = False class ConstraintMapping: def __init__(self): self.type_ = dict() self.preexisting = dict() self.not_null = set() def update(self, schema_dict): if schema_dict: for k, c in schema_dict.items(): if isinstance(c, type): self._parse_type(k, c) else: assert isinstance(c, Constraint), repr(c) if c.type_: self._parse_type(k, c.type_) if c.unique: self.preexisting.setdefault(k, set()) if c.not_null: self.not_null.add(k) def _parse_type(self, k, type_): if k in self.type_.keys(): expected_type = self.type_[k] if expected_type is not Any: if type_ is not expected_type: raise TypeError else: self.type_[k] = type_ def _view(self): all_keys = set(self.type_.keys()) | set(self.preexisting.keys()) | self.not_null for k in all_keys: type_ = self.type_.get(k, Any) unique = k in self.preexisting.keys() not_null = k in self.not_null yield k, Constraint(type_, unique, not_null) def view(self): return dict(self._view()) def __repr__(self): return repr(self.view()) PK! oexcelschema/core.pyfrom collections import OrderedDict from copy import deepcopy from .constraint import ConstraintMapping from .exception import NotUniqueException, NotNullException, NonUniformTypeException from .util import parse_record class SchemaParser: constraint_mapping = ConstraintMapping() records = list() def __init__(self, records=None, array=None, as_datetime_str=False, schema=None): if records and array: raise ValueError('Please specify either records or array, not both.') if array: records = list() header = array[0] for row in array[1:]: records.append(OrderedDict(zip(header, row))) if records: pass if not array and not records: records = dict() if schema: self.constraint_mapping.update(schema) self.records = self.ensure_multiple(records) self.as_datetime_str = as_datetime_str @property def schema(self): """Get table's latest schema Returns: dict -- dictionary of constraints """ return self.constraint_mapping.view() @schema.setter def schema(self, schema_dict): """Reset and set a new schema Arguments: schema_dict {dict} -- dictionary of constraints or types """ self.constraint_mapping = ConstraintMapping() self.update_schema(schema_dict) def update_schema(self, schema_dict): """Update the schema Arguments: schema_dict {dict} -- dictionary of constraints or types """ self.constraint_mapping.update(schema_dict) self.ensure_multiple(self.records) def ensure_multiple(self, records, update_schema=True): """Sanitizes records, e.g. from Excel spreadsheet Arguments: records {iterable} -- Iterable of records Returns: list -- List of records """ def _records(): for record_ in records: record_schema = dict(parse_record(record_, yield_='type')) num_to_str = set() for k, v in record_schema.items(): expected_type = self.constraint_mapping.type_.get(k, None) if expected_type and v is not expected_type: if expected_type is str and v in (int, float): num_to_str.add(k) else: raise NonUniformTypeException('{} not in table schema {}' .format(v, self.schema)) self.constraint_mapping.update(schema_dict=record_schema) record_ = dict(parse_record(record_, yield_='record', as_datetime_str=self.as_datetime_str)) for k, v in record_.items(): if k in num_to_str: record_[k] = str(v) is_null = self.constraint_mapping.not_null - set(record_.keys()) if len(is_null) > 0: raise NotNullException('{} is null'.format(list(is_null))) yield record_ temp_mapping = None if not update_schema: temp_mapping = deepcopy(self.constraint_mapping) for c in self.schema.values(): assert not isinstance(c.type_, list) records = list(_records()) for record in records: self._update_uniqueness(record) if not update_schema: self.constraint_mapping = ConstraintMapping() self.constraint_mapping.update(temp_mapping) else: self.records.extend(records) return records def ensure_one(self, record, update_schema=True): return self.ensure_multiple([record], update_schema=update_schema)[0] def _update_uniqueness(self, record_dict): for k, v in parse_record(record_dict, yield_='type'): if k in self.constraint_mapping.preexisting.keys(): if v in self.constraint_mapping.preexisting[k]: raise NotUniqueException('Duplicate {} for {} exists'.format(v, k)) self.constraint_mapping.preexisting[k].add(v) PK!)'Uexcelschema/exception.pyclass NonUniformTypeException(TypeError): pass class NotUniqueException(ValueError): pass class NotNullException(ValueError): pass PK!B\excelschema/util.pyimport unicodedata from datetime import datetime, date import dateutil.parser def normalize_chars(s): return unicodedata.normalize("NFKD", s) def jsonify(d): for k, v in d.items(): if isinstance(v, datetime): yield k, v.isoformat() else: yield k, v def parse_record(record, yield_='type', as_datetime_str=False): def _yield_switch(x): if yield_ == 'type': return type(x) elif yield_ == 'record': if isinstance(x, (datetime, date)): x = x.isoformat() if not as_datetime_str: x = dateutil.parser.parse(x) return x else: raise ValueError for k, v in record.items(): if isinstance(v, str): v = normalize_chars(v.strip()) if v.isdigit(): v = int(v) elif '.' in v and v.replace('.', '', 1).isdigit(): v = float(v) elif v in {'', '-'}: continue else: try: v = dateutil.parser.parse(v) except ValueError: pass elif isinstance(v, date): v = datetime.combine(v, datetime.min.time()) yield k, _yield_switch(v) PK! ::#excelschema-0.1.0.dist-info/LICENSEMIT License Copyright (c) 2018 Pacharapol Withayasakpunt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!H WX!excelschema-0.1.0.dist-info/WHEEL A н#Z."jm)Afb~ڠO68oF04UhoAf f4=4h0k::wXPK!H$excelschema-0.1.0.dist-info/METADATAUN1}WD$RvCMZ5!Z m*qvĭ^|!ޱws AT^X3:^pǓh*tȎyUs,9[waN}YrSg.Ā\vƢ hRBY4]r)Y8:MG]bRu;Wl0 74"`WZ.X>oG_~#}nM֔P׳` ؁֊syb H?q52TFmӗ[f<6>}u3^l} / 䭰؏΄ 4hs#*GZKr\rVW4lWn@Sbl<KCo>#,`16LD :x{K@jѿ1wpnt T&7K1v f }ՠ ½sRW%j*'Q1-,P4Ҍ7wDYi4N37 ^,B#Cjn4H"(q(V nYwWMhsΆzu5z"=DBHlfg_PK!Hto"excelschema-0.1.0.dist-info/RECORD}=s@>e1,E 40:诿Lfҝ)΋~& MI{}PJҡ^t /ͧ~i': ȥDbi{ڥ#B^B\UYjfX*mpPfYBv}M1 mλB. ȋ-sS=d L4OzȺƛM9vNσbZg]( 8AzT#ڊ3m-ȂMhYX9t_Z47eb 7a'9)CC3c/[+aFCú#'ޣrYUIVu8ǖ-< ,zFՍ4EHFnU.j`ud5࿕ {_oV?PK!G BBexcelschema/__init__.pyPK!.,,wexcelschema/constraint.pyPK! oexcelschema/core.pyPK!)'Uexcelschema/exception.pyPK!B\excelschema/util.pyPK! ::#excelschema-0.1.0.dist-info/LICENSEPK!H WX!r"excelschema-0.1.0.dist-info/WHEELPK!H$#excelschema-0.1.0.dist-info/METADATAPK!Hto"_&excelschema-0.1.0.dist-info/RECORDPK T(