From 7e66b5c54e6eb097e53db62392bdf694f28ebd83 Mon Sep 17 00:00:00 2001 From: Dave Brondsema Date: Mon, 28 Sep 2026 12:11:55 -0400 Subject: [PATCH] support $type op in mim --- ming/mim.py | 21 +++++++++++++++++++++ ming/tests/test_mim.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/ming/mim.py b/ming/mim.py index b90c68c..a2b5651 100644 --- a/ming/mim.py +++ b/ming/mim.py @@ -1022,6 +1022,12 @@ def match(self, key, op, value): if value.lower() in self[field].lower(): return True return False + if op == '$type': + wanted = value if isinstance(value, list) else [value] + codes = {_BSON_TYPE_ALIASES.get(t, t) for t in wanted} + if 'number' in wanted: + codes |= {1, 16, 18, 19} + return _bson_type_code(val) in codes raise NotImplementedError(op) def _match_regex(self, regex, val): @@ -1332,6 +1338,21 @@ def setdefault(self, key, default): self._orig[key] = default +# mongo server's $type aliases; pymongo only has the numeric codes +_BSON_TYPE_ALIASES = { + 'double': 1, 'string': 2, 'object': 3, 'array': 4, 'binData': 5, 'objectId': 7, 'bool': 8, 'date': 9, + 'null': 10, 'regex': 11, 'javascript': 13, 'int': 16, 'timestamp': 17, 'long': 18, 'decimal': 19, + 'minKey': -1, 'maxKey': 127, +} + + +def _bson_type_code(val) -> int | None: + if val == (): + return None + code = bson_safe({'': val._orig if isinstance(val, Match) else val})[4] # type byte of the first element + return -1 if code == 0xFF else code # minKey + + def _parse_query(v): if isinstance(v, dict) and all(k.startswith('$') for k in v.keys()): return v.items() diff --git a/ming/tests/test_mim.py b/ming/tests/test_mim.py index 0be450e..03c70af 100644 --- a/ming/tests/test_mim.py +++ b/ming/tests/test_mim.py @@ -832,6 +832,40 @@ def test_exists(self): self.assertIsNone(mim.match({'e': { '$exists': 1 } }, doc)) self.assertIsNotNone(mim.match({'e': { '$exists': 0 } }, doc)) + def test_type(self): + doc = {'s': 'x', 'i': 1, 'l': 2**40, 'f': 1.5, 'b': True, 'n': None, 'o': {'x': 1}, 'a': ['x', 1], + 'id': bson.ObjectId(), 'dt': datetime(2020, 1, 1), 'bin': bson.Binary(b'x'), + 'u': uuid.uuid4(), 'js': bson.Code('x'), 'mk': bson.MinKey()} + self.assertIsNotNone(mim.match({'s': {'$type': 'string'}}, doc)) + self.assertIsNotNone(mim.match({'s': {'$type': 2}}, doc)) + self.assertIsNone(mim.match({'s': {'$type': 'int'}}, doc)) + self.assertIsNotNone(mim.match({'i': {'$type': 'int'}}, doc)) + self.assertIsNotNone(mim.match({'l': {'$type': 'long'}}, doc)) + self.assertIsNotNone(mim.match({'f': {'$type': 'double'}}, doc)) + self.assertIsNotNone(mim.match({'b': {'$type': 'bool'}}, doc)) + self.assertIsNone(mim.match({'b': {'$type': 'int'}}, doc)) + self.assertIsNotNone(mim.match({'n': {'$type': 'null'}}, doc)) + self.assertIsNotNone(mim.match({'o': {'$type': 'object'}}, doc)) + self.assertIsNotNone(mim.match({'id': {'$type': 'objectId'}}, doc)) + self.assertIsNotNone(mim.match({'dt': {'$type': 'date'}}, doc)) + self.assertIsNotNone(mim.match({'bin': {'$type': 'binData'}}, doc)) + self.assertIsNotNone(mim.match({'u': {'$type': 'binData'}}, doc)) + self.assertIsNotNone(mim.match({'js': {'$type': 'javascript'}}, doc)) + self.assertIsNone(mim.match({'js': {'$type': 'string'}}, doc)) + self.assertIsNotNone(mim.match({'mk': {'$type': 'minKey'}}, doc)) + self.assertIsNotNone(mim.match({'mk': {'$type': -1}}, doc)) + self.assertIsNone(mim.match({'missing': {'$type': 'null'}}, doc)) + for f in ('i', 'l', 'f'): + self.assertIsNotNone(mim.match({f: {'$type': 'number'}}, doc)) + self.assertIsNone(mim.match({'b': {'$type': 'number'}}, doc)) + self.assertIsNotNone(mim.match({'n': {'$type': ['string', 'null']}}, doc)) + self.assertIsNone(mim.match({'i': {'$type': ['string', 'null']}}, doc)) + # arrays match on the array itself or any element + self.assertIsNotNone(mim.match({'a': {'$type': 'array'}}, doc)) + self.assertIsNotNone(mim.match({'a': {'$type': 'string'}}, doc)) + self.assertIsNotNone(mim.match({'a': {'$type': 'int'}}, doc)) + self.assertIsNone(mim.match({'a': {'$type': 'double'}}, doc)) + def test_all(self): doc = { 'c': [ 1, 2 ] } self.assertIsNotNone(mim.match({'c': {'$all': [] } }, doc))