def __check_value_types(updater: DocumentUpdater, allowed_types: List[str]): """ Check if given field contains only given types of value. Raise InconsistencyError if other value types was found :param updater: :param allowed_types: :return: """ def by_path(ctx: ByPathContext): # Check for data types other than objects or arrays fltr = { "$and": [ { ctx.filter_dotpath: { "$ne": None } }, *[{ k: v } for k, v in ctx.extra_filter.items()], # $expr >= 3.6, $type >= 3.4 { "$expr": { "$not": [{ "$in": [{ "$type": f'${ctx.filter_dotpath}' }, allowed_types] }] } } ] } check_empty_result(ctx.collection, ctx.filter_dotpath, fltr) def by_doc(ctx: ByDocContext): # https://docs.mongodb.com/manual/reference/operator/aggregation/convert/ type_map = { 'double': float, 'string': str, 'objectId': bson.ObjectId, 'bool': bool, 'date': datetime, 'int': int, 'long': int, 'decimal': float } assert set(allowed_types) < type_map.keys() doc = ctx.document if updater.field_name in doc: f = doc[updater.field_name] valid_types = tuple(type_map[t] for t in allowed_types) valid = f is None or isinstance(f, valid_types) if not valid: raise InconsistencyError( f"Field {updater.field_name} has wrong type of value " f"{f!r} (should be any of {valid_types}) in record {doc}") updater.update_combined(by_path, by_doc, False, False)
def __check_legacy_point_coordinates(updater: DocumentUpdater): """ Check if all array values in field has legacy geo point coordinates type. Raise InconsistencyError if other arrays was found :param updater: :return: """ def by_path(ctx: ByPathContext): fltr = { "$and": [ { ctx.filter_dotpath: { "$ne": None } }, *[{ k: v } for k, v in ctx.extra_filter.items()], # $expr >= 3.6, $isArray >= 3.2 { "$expr": { "$eq": [{ "$isArray": f"${ctx.filter_dotpath}" }, True] } }, { "$expr": { "$ne": [{ "$size": f"${ctx.filter_dotpath}" }, 2] } }, # $expr >= 3.6 # TODO: add element type check ] } check_empty_result(ctx.collection, ctx.filter_dotpath, fltr) def by_doc(ctx: ByDocContext): doc = ctx.document if updater.field_name in doc: f = doc[updater.field_name] valid = f is None or (isinstance(f, (list, tuple)) and len(f) == 2) if not valid: raise InconsistencyError( f"Field {updater.field_name} has wrong value {f!r} " f"(should be legacy geo point) in record {doc}") updater.update_combined(by_path, by_doc, False, False)
def __check_geojson_objects(updater: DocumentUpdater, geojson_types: List[str]): """ Check if all object values in field are GeoJSON objects of given types. Raise InconsistencyError if other objects found :param updater: :param geojson_types: :return: """ def by_path(ctx: ByPathContext): fltr = { "$and": [ { ctx.filter_dotpath: { "$ne": None } }, *[{ k: v } for k, v in ctx.extra_filter.items()], { f'{ctx.filter_dotpath}.type': { '$nin': geojson_types } }, # $expr >= 3.6 { "$expr": { "$eq": [{ "$type": f'${ctx.filter_dotpath}' }, 'object'] } } ] } check_empty_result(ctx.collection, ctx.filter_dotpath, fltr) def by_doc(ctx: ByDocContext): doc = ctx.document if updater.field_name in doc: f = doc[updater.field_name] valid = f is None or (isinstance(f, dict) and f.get('type') in geojson_types) if not valid: raise InconsistencyError( f"Field {updater.field_name} has wrong value {f!r} " f"(should be GeoJSON) in record {doc}") updater.update_combined(by_path, by_doc, False, False)