From ddd0f79f98b3476adab2719356a13c6ebab02507 Mon Sep 17 00:00:00 2001 From: M Aswin Kishore <60577077+mak626@users.noreply.github.com> Date: Fri, 21 Jun 2024 09:47:00 +0530 Subject: [PATCH 01/14] fix: fields having required=true and null=true were being set as set as required in graphql layer --- graphene_mongo/converter.py | 60 +++++++++++++++++++++++-------------- graphene_mongo/utils.py | 8 +++++ 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index 5a8d519..80c3108 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -11,6 +11,7 @@ from .utils import ( get_field_description, get_query_fields, + get_field_is_required, ExecutorEnum, sync_to_async, ) @@ -34,34 +35,43 @@ def convert_mongoengine_field(field, registry=None, executor: ExecutorEnum = Exe @convert_mongoengine_field.register(mongoengine.URLField) def convert_field_to_string(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.String( - description=get_field_description(field, registry), required=field.required + description=get_field_description(field, registry), + required=get_field_is_required(field, registry), ) @convert_mongoengine_field.register(mongoengine.UUIDField) @convert_mongoengine_field.register(mongoengine.ObjectIdField) def convert_field_to_id(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): - return graphene.ID(description=get_field_description(field, registry), required=field.required) + return graphene.ID( + description=get_field_description(field, registry), + required=get_field_is_required(field, registry), + ) @convert_mongoengine_field.register(mongoengine.IntField) @convert_mongoengine_field.register(mongoengine.LongField) @convert_mongoengine_field.register(mongoengine.SequenceField) def convert_field_to_int(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): - return graphene.Int(description=get_field_description(field, registry), required=field.required) + return graphene.Int( + description=get_field_description(field, registry), + required=get_field_is_required(field, registry), + ) @convert_mongoengine_field.register(mongoengine.BooleanField) def convert_field_to_boolean(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.Boolean( - description=get_field_description(field, registry), required=field.required + description=get_field_description(field, registry), + required=get_field_is_required(field, registry), ) @convert_mongoengine_field.register(mongoengine.FloatField) def convert_field_to_float(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.Float( - description=get_field_description(field, registry), required=field.required + description=get_field_description(field, registry), + required=get_field_is_required(field, registry), ) @@ -69,28 +79,34 @@ def convert_field_to_float(field, registry=None, executor: ExecutorEnum = Execut @convert_mongoengine_field.register(mongoengine.DecimalField) def convert_field_to_decimal(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.Decimal( - description=get_field_description(field, registry), required=field.required + description=get_field_description(field, registry), + required=get_field_is_required(field, registry), ) @convert_mongoengine_field.register(mongoengine.DateTimeField) def convert_field_to_datetime(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.DateTime( - description=get_field_description(field, registry), required=field.required + description=get_field_description(field, registry), + required=get_field_is_required(field, registry), ) @convert_mongoengine_field.register(mongoengine.DateField) def convert_field_to_date(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): return graphene.Date( - description=get_field_description(field, registry), required=field.required + description=get_field_description(field, registry), + required=get_field_is_required(field, registry), ) @convert_mongoengine_field.register(mongoengine.DictField) @convert_mongoengine_field.register(mongoengine.MapField) def convert_field_to_jsonstring(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): - return JSONString(description=get_field_description(field, registry), required=field.required) + return JSONString( + description=get_field_description(field, registry), + required=get_field_is_required(field, registry), + ) @convert_mongoengine_field.register(mongoengine.PointField) @@ -98,7 +114,7 @@ def convert_point_to_field(field, registry=None, executor: ExecutorEnum = Execut return graphene.Field( advanced_types.PointFieldType, description=get_field_description(field, registry), - required=field.required, + required=get_field_is_required(field, registry), ) @@ -107,7 +123,7 @@ def convert_polygon_to_field(field, registry=None, executor: ExecutorEnum = Exec return graphene.Field( advanced_types.PolygonFieldType, description=get_field_description(field, registry), - required=field.required, + required=get_field_is_required(field, registry), ) @@ -116,7 +132,7 @@ def convert_multipolygon_to_field(field, registry=None, executor: ExecutorEnum = return graphene.Field( advanced_types.MultiPolygonFieldType, description=get_field_description(field, registry), - required=field.required, + required=get_field_is_required(field, registry), ) @@ -125,7 +141,7 @@ def convert_file_to_field(field, registry=None, executor: ExecutorEnum = Executo return graphene.Field( advanced_types.FileFieldType, description=get_field_description(field, registry), - required=field.required, + required=get_field_is_required(field, registry), ) @@ -298,7 +314,7 @@ async def reference_resolver_async(root, *args, **kwargs): return graphene.List( base_type._type, description=get_field_description(field, registry), - required=field.required, + required=get_field_is_required(field, registry), resolver=reference_resolver if executor == ExecutorEnum.SYNC else reference_resolver_async, @@ -306,7 +322,7 @@ async def reference_resolver_async(root, *args, **kwargs): return graphene.List( base_type._type, description=get_field_description(field, registry), - required=field.required, + required=get_field_is_required(field, registry), ) if isinstance(base_type, (graphene.Dynamic)): base_type = base_type.get_type() @@ -327,7 +343,7 @@ async def reference_resolver_async(root, *args, **kwargs): return graphene.List( base_type, description=get_field_description(field, registry), - required=field.required, + required=get_field_is_required(field, registry), ) @@ -491,7 +507,7 @@ async def lazy_reference_resolver_async(root, *args, **kwargs): field_resolver = None required = False if field.db_field is not None: - required = field.required + required = get_field_is_required(field, registry) resolver_function = getattr( registry.get_type_for_model(field.owner_document, executor=executor), "resolve_" + field.db_field, @@ -516,7 +532,7 @@ async def lazy_reference_resolver_async(root, *args, **kwargs): field_resolver = None required = False if field.db_field is not None: - required = field.required + required = get_field_is_required(field, registry) resolver_function = getattr( registry.get_type_for_model(field.owner_document, executor=executor), "resolve_" + field.db_field, @@ -658,12 +674,12 @@ def dynamic_type(): return graphene.Field( _type, description=get_field_description(field, registry), - required=field.required, + required=get_field_is_required(field, registry), ) field_resolver = None required = False if field.db_field is not None: - required = field.required + required = get_field_is_required(field, registry) resolver_function = getattr( registry.get_type_for_model(field.owner_document, executor=executor), "resolve_" + field.db_field, @@ -760,7 +776,7 @@ def dynamic_type(): field_resolver = None required = False if field.db_field is not None: - required = field.required + required = get_field_is_required(field, registry) resolver_function = getattr( registry.get_type_for_model(field.owner_document, executor=executor), "resolve_" + field.db_field, @@ -790,5 +806,5 @@ def convert_field_to_enum(field, registry=None, executor: ExecutorEnum = Executo return graphene.Field( _type, description=get_field_description(field, registry), - required=field.required, + required=get_field_is_required(field, registry), ) diff --git a/graphene_mongo/utils.py b/graphene_mongo/utils.py index 165d02a..463a3ea 100644 --- a/graphene_mongo/utils.py +++ b/graphene_mongo/utils.py @@ -86,6 +86,14 @@ def get_field_description(field, registry=None): return "\n".join(parts) +def get_field_is_required(field, registry=None): + """ + A field is said to be required in gql only if + field.required = True and field.null = False + """ + return field.required and not field.null + + def get_node_from_global_id(node, info, global_id): try: for interface in node._meta.interfaces: From 7b3bd08cdfa7bd7aa5d2a5dfbfa8ab089a09a491 Mon Sep 17 00:00:00 2001 From: M Aswin Kishore <60577077+mak626@users.noreply.github.com> Date: Fri, 21 Jun 2024 09:48:13 +0530 Subject: [PATCH 02/14] fix: package version in __init__.py --- graphene_mongo/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_mongo/__init__.py b/graphene_mongo/__init__.py index 27e2594..6d37c53 100644 --- a/graphene_mongo/__init__.py +++ b/graphene_mongo/__init__.py @@ -3,7 +3,7 @@ from .types import MongoengineInputType, MongoengineInterfaceType, MongoengineObjectType from .types_async import AsyncMongoengineObjectType -__version__ = "0.4.2" +__version__ = "0.4.4" __all__ = [ "__version__", From fb7a4bd8a3609de46206b70aa19230aaf25386a7 Mon Sep 17 00:00:00 2001 From: M Aswin Kishore <60577077+mak626@users.noreply.github.com> Date: Fri, 21 Jun 2024 09:54:48 +0530 Subject: [PATCH 03/14] feat: field's description can be set by adding description="..." argument in field similar to graphql --- graphene_mongo/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graphene_mongo/utils.py b/graphene_mongo/utils.py index 463a3ea..0a6053a 100644 --- a/graphene_mongo/utils.py +++ b/graphene_mongo/utils.py @@ -79,6 +79,8 @@ def get_field_description(field, registry=None): parts.append(field.verbose_name.title()) if hasattr(field, "help_text"): parts.append(field.help_text) + if hasattr(field, "description"): + parts.append(field.description) if field.db_field != field.name: name_format = "(%s)" if parts else "%s" parts.append(name_format % field.db_field) From 9a594feaecbba6127885792fe8149d408bce9d4a Mon Sep 17 00:00:00 2001 From: M Aswin Kishore <60577077+mak626@users.noreply.github.com> Date: Mon, 13 Jan 2025 14:44:51 +0530 Subject: [PATCH 04/14] fix[converter]: convert_field_to_list resolver error new get_query_fields cannot find union types called. Introduced get_queried_union_types to find it --- graphene_mongo/converter.py | 198 +++++++++++++++++------------------- graphene_mongo/utils.py | 34 +++++++ 2 files changed, 129 insertions(+), 103 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index 80c3108..b07bc71 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -11,6 +11,7 @@ from .utils import ( get_field_description, get_query_fields, + get_queried_union_types, get_field_is_required, ExecutorEnum, sync_to_async, @@ -154,7 +155,7 @@ def convert_field_to_list(field, registry=None, executor: ExecutorEnum = Executo if isinstance(field.field, mongoengine.GenericReferenceField): def get_reference_objects(*args, **kwargs): - document = get_document(args[0][0]) + document = get_document(args[0]) document_field = mongoengine.ReferenceField(document) document_field = convert_mongoengine_field(document_field, registry) document_field_type = document_field.get_type().type @@ -164,7 +165,7 @@ def get_reference_objects(*args, **kwargs): for key, values in document_field_type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) - for each in get_query_fields(args[0][3][0])[document_field_type._meta.name].keys(): + for each in args[4]: item = to_snake_case(each) if item in document._fields_ordered + tuple(filter_args): queried_fields.append(item) @@ -172,67 +173,62 @@ def get_reference_objects(*args, **kwargs): document.objects() .no_dereference() .only(*set(list(document_field_type._meta.required_fields) + queried_fields)) - .filter(pk__in=args[0][1]) + .filter(pk__in=args[1]) ) def get_non_querying_object(*args, **kwargs): - model = get_document(args[0][0]) - return [model(pk=each) for each in args[0][1]] + model = get_document(args[0]) + return [model(pk=each) for each in args[1]] def reference_resolver(root, *args, **kwargs): to_resolve = getattr(root, field.name or field.db_name) - if to_resolve: - choice_to_resolve = dict() - querying_union_types = list(get_query_fields(args[0]).keys()) - if "__typename" in querying_union_types: - querying_union_types.remove("__typename") - to_resolve_models = list() - for each in querying_union_types: - if executor == ExecutorEnum.SYNC: - to_resolve_models.append(registry._registry_string_map[each]) - else: - to_resolve_models.append(registry._registry_async_string_map[each]) - to_resolve_object_ids = list() - for each in to_resolve: - if isinstance(each, LazyReference): - to_resolve_object_ids.append(each.pk) - model = each.document_type._class_name - if model not in choice_to_resolve: - choice_to_resolve[model] = list() - choice_to_resolve[model].append(each.pk) - else: - to_resolve_object_ids.append(each["_ref"].id) - if each["_cls"] not in choice_to_resolve: - choice_to_resolve[each["_cls"]] = list() - choice_to_resolve[each["_cls"]].append(each["_ref"].id) - pool = ThreadPoolExecutor(5) - futures = list() - for model, object_id_list in choice_to_resolve.items(): - if model in to_resolve_models: - futures.append( - pool.submit( - get_reference_objects, - (model, object_id_list, registry, args), - ) + if not to_resolve: + return None + + choice_to_resolve = dict() + querying_union_types = get_queried_union_types(args[0]) + to_resolve_models = dict() + for each, queried_fields in querying_union_types.items(): + to_resolve_models[registry._registry_string_map[each]] = queried_fields + to_resolve_object_ids = list() + for each in to_resolve: + if isinstance(each, LazyReference): + to_resolve_object_ids.append(each.pk) + model = each.document_type._class_name + if model not in choice_to_resolve: + choice_to_resolve[model] = list() + choice_to_resolve[model].append(each.pk) + else: + to_resolve_object_ids.append(each["_ref"].id) + if each["_cls"] not in choice_to_resolve: + choice_to_resolve[each["_cls"]] = list() + choice_to_resolve[each["_cls"]].append(each["_ref"].id) + pool = ThreadPoolExecutor(5) + futures = list() + for model, object_id_list in choice_to_resolve.items(): + if model in to_resolve_models: + queried_fields = to_resolve_models[model] + futures.append( + pool.submit( + get_reference_objects, + *(model, object_id_list, registry, args, queried_fields), ) - else: - futures.append( - pool.submit( - get_non_querying_object, - (model, object_id_list, registry, args), - ) + ) + else: + futures.append( + pool.submit( + get_non_querying_object, + *(model, object_id_list, registry, args), ) - result = list() - for x in as_completed(futures): - result += x.result() - result_object_ids = list() - for each in result: - result_object_ids.append(each.id) - ordered_result = list() - for each in to_resolve_object_ids: - ordered_result.append(result[result_object_ids.index(each)]) - return ordered_result - return None + ) + result = list() + for x in as_completed(futures): + result += x.result() + result_object_ids = [each.id for each in result] + ordered_result = [ + result[result_object_ids.index(each)] for each in to_resolve_object_ids + ] + return ordered_result async def get_reference_objects_async(*args, **kwargs): document = get_document(args[0]) @@ -247,7 +243,7 @@ async def get_reference_objects_async(*args, **kwargs): for key, values in document_field_type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) - for each in get_query_fields(args[3][0])[document_field_type._meta.name].keys(): + for each in args[4]: item = to_snake_case(each) if item in document._fields_ordered + tuple(filter_args): queried_fields.append(item) @@ -259,57 +255,53 @@ async def get_reference_objects_async(*args, **kwargs): ) async def get_non_querying_object_async(*args, **kwargs): - model = get_document(args[0]) - return [model(pk=each) for each in args[1]] + return get_non_querying_object(*args, **kwargs) async def reference_resolver_async(root, *args, **kwargs): to_resolve = getattr(root, field.name or field.db_name) - if to_resolve: - choice_to_resolve = dict() - querying_union_types = list(get_query_fields(args[0]).keys()) - if "__typename" in querying_union_types: - querying_union_types.remove("__typename") - to_resolve_models = list() - for each in querying_union_types: - if executor == ExecutorEnum.SYNC: - to_resolve_models.append(registry._registry_string_map[each]) - else: - to_resolve_models.append(registry._registry_async_string_map[each]) - to_resolve_object_ids = list() - for each in to_resolve: - if isinstance(each, LazyReference): - to_resolve_object_ids.append(each.pk) - model = each.document_type._class_name - if model not in choice_to_resolve: - choice_to_resolve[model] = list() - choice_to_resolve[model].append(each.pk) - else: - to_resolve_object_ids.append(each["_ref"].id) - if each["_cls"] not in choice_to_resolve: - choice_to_resolve[each["_cls"]] = list() - choice_to_resolve[each["_cls"]].append(each["_ref"].id) - loop = asyncio.get_event_loop() - tasks = [] - for model, object_id_list in choice_to_resolve.items(): - if model in to_resolve_models: - task = loop.create_task( - get_reference_objects_async(model, object_id_list, registry, args) - ) - else: - task = loop.create_task( - get_non_querying_object_async(model, object_id_list, registry, args) + if not to_resolve: + return None + + choice_to_resolve = dict() + querying_union_types = get_queried_union_types(args[0]) + to_resolve_models = dict() + for each, queried_fields in querying_union_types.items(): + to_resolve_models[registry._registry_async_string_map[each]] = queried_fields + to_resolve_object_ids = list() + for each in to_resolve: + if isinstance(each, LazyReference): + to_resolve_object_ids.append(each.pk) + model = each.document_type._class_name + if model not in choice_to_resolve: + choice_to_resolve[model] = list() + choice_to_resolve[model].append(each.pk) + else: + to_resolve_object_ids.append(each["_ref"].id) + if each["_cls"] not in choice_to_resolve: + choice_to_resolve[each["_cls"]] = list() + choice_to_resolve[each["_cls"]].append(each["_ref"].id) + loop = asyncio.get_event_loop() + tasks = [] + for model, object_id_list in choice_to_resolve.items(): + if model in to_resolve_models: + queried_fields = to_resolve_models[model] + task = loop.create_task( + get_reference_objects_async( + model, object_id_list, registry, args, queried_fields ) - tasks.append(task) - result = await asyncio.gather(*tasks) - result_object = {} - for items in result: - for item in items: - result_object[item.id] = item - ordered_result = list() - for each in to_resolve_object_ids: - ordered_result.append(result_object[each]) - return ordered_result - return None + ) + else: + task = loop.create_task( + get_non_querying_object_async(model, object_id_list, registry, args) + ) + tasks.append(task) + result = await asyncio.gather(*tasks) + result_object = {} + for items in result: + for item in items: + result_object[item.id] = item + ordered_result = [result_object[each] for each in to_resolve_object_ids] + return ordered_result return graphene.List( base_type._type, diff --git a/graphene_mongo/utils.py b/graphene_mongo/utils.py index 0a6053a..e5fd457 100644 --- a/graphene_mongo/utils.py +++ b/graphene_mongo/utils.py @@ -203,6 +203,40 @@ def get_query_fields(info): return query +def get_queried_union_types(info): + """A convenience function to get queried union types with its fields + + Args: + info (ResolveInfo) + + Returns: + dict[union_type_name, queried_fields(dict)] + """ + + fragments = {} + node = ast_to_dict(info.field_nodes[0]) + variables = info.variable_values + + for name, value in info.fragments.items(): + fragments[name] = ast_to_dict(value) + + fragments_queries: dict[str, dict] = {} + + selection_set = node.get("selection_set") if isinstance(node, dict) else node.selection_set + if selection_set: + for leaf in selection_set.selections: + if leaf.kind == "fragment_spread": + fragment_name = fragments[leaf.name.value].type_condition.name.value + fragments_queries[fragment_name] = collect_query_fields( + fragments[leaf.name.value], fragments, variables + ) + elif leaf.kind == "inline_fragment": + fragment_name = leaf.type_condition.name.value + fragments_queries[fragment_name] = collect_query_fields(leaf, fragments, variables) + + return fragments_queries + + def has_page_info(info): """A convenience function to call collect_query_fields with info for retrieving if page_info details are required From 08295c6cd46cbae557ac8d08b9844898ecf3fb87 Mon Sep 17 00:00:00 2001 From: M Aswin Kishore <60577077+mak626@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:10:01 +0530 Subject: [PATCH 05/14] bump: dependencies graphene: 3.4.3 graphql-core: 3.2.5 pymongo: 4.10.1 mongoengine: 0.29.1 --- graphene_mongo/fields.py | 12 ++++++------ graphene_mongo/fields_async.py | 6 +++--- graphene_mongo/registry.py | 17 +++++++++-------- graphene_mongo/types.py | 3 +-- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index 5ba8317..c76feb9 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -49,9 +49,9 @@ class MongoengineConnectionField(ConnectionField): def __init__(self, type, *args, **kwargs): get_queryset = kwargs.pop("get_queryset", None) if get_queryset: - assert callable( - get_queryset - ), "Attribute `get_queryset` on {} must be callable.".format(self) + assert callable(get_queryset), ( + "Attribute `get_queryset` on {} must be callable.".format(self) + ) self._get_queryset = get_queryset super(MongoengineConnectionField, self).__init__(type, *args, **kwargs) @@ -64,9 +64,9 @@ def type(self): from .types import MongoengineObjectType _type = super(ConnectionField, self).type - assert issubclass( - _type, MongoengineObjectType - ), "MongoengineConnectionField only accepts MongoengineObjectType types" + assert issubclass(_type, MongoengineObjectType), ( + "MongoengineConnectionField only accepts MongoengineObjectType types" + ) assert _type._meta.connection, "The type {} doesn't have a connection".format( _type.__name__ ) diff --git a/graphene_mongo/fields_async.py b/graphene_mongo/fields_async.py index bbbd96a..5654b68 100644 --- a/graphene_mongo/fields_async.py +++ b/graphene_mongo/fields_async.py @@ -45,9 +45,9 @@ def type(self): from .types_async import AsyncMongoengineObjectType _type = super(ConnectionField, self).type - assert issubclass( - _type, AsyncMongoengineObjectType - ), "AsyncMongoengineConnectionField only accepts AsyncMongoengineObjectType types" + assert issubclass(_type, AsyncMongoengineObjectType), ( + "AsyncMongoengineConnectionField only accepts AsyncMongoengineObjectType types" + ) assert _type._meta.connection, "The type {} doesn't have a connection".format( _type.__name__ ) diff --git a/graphene_mongo/registry.py b/graphene_mongo/registry.py index 70e8848..7ec3e41 100644 --- a/graphene_mongo/registry.py +++ b/graphene_mongo/registry.py @@ -15,11 +15,12 @@ def register(self, cls): from .types import GrapheneMongoengineObjectTypes from .types_async import AsyncGrapheneMongoengineObjectTypes - assert ( - issubclass(cls, GrapheneMongoengineObjectTypes) - or issubclass(cls, AsyncGrapheneMongoengineObjectTypes) - ), 'Only Mongoengine/Async Mongoengine object types can be registered, received "{}"'.format( - cls.__name__ + assert issubclass(cls, GrapheneMongoengineObjectTypes) or issubclass( + cls, AsyncGrapheneMongoengineObjectTypes + ), ( + 'Only Mongoengine/Async Mongoengine object types can be registered, received "{}"'.format( + cls.__name__ + ) ) assert cls._meta.registry == self, "Registry for a Model have to match." if issubclass(cls, GrapheneMongoengineObjectTypes): @@ -36,9 +37,9 @@ def register(self, cls): def register_enum(self, cls): from enum import EnumMeta - assert isinstance( - cls, EnumMeta - ), f'Only EnumMeta can be registered, received "{cls.__name__}"' + assert isinstance(cls, EnumMeta), ( + f'Only EnumMeta can be registered, received "{cls.__name__}"' + ) if not cls.__name__.endswith("Enum"): name = cls.__name__ + "Enum" else: diff --git a/graphene_mongo/types.py b/graphene_mongo/types.py index 0194ea5..798096d 100644 --- a/graphene_mongo/types.py +++ b/graphene_mongo/types.py @@ -164,8 +164,7 @@ def __init_subclass_with_meta__( if _meta: assert isinstance(_meta, MongoengineGenericObjectTypeOptions), ( - "_meta must be an instance of MongoengineGenericObjectTypeOptions, " - "received {}" + "_meta must be an instance of MongoengineGenericObjectTypeOptions, received {}" ).format(_meta.__class__) else: _meta = MongoengineGenericObjectTypeOptions(option_type) From 123195209a38e09dd4b3d6b39a2ea86bf71660df Mon Sep 17 00:00:00 2001 From: M Aswin Kishore <60577077+mak626@users.noreply.github.com> Date: Fri, 21 Feb 2025 11:38:29 +0530 Subject: [PATCH 06/14] fix[union-converter]: get_queried_union_types can now handle union fragments --- graphene_mongo/converter.py | 8 ++++++-- graphene_mongo/utils.py | 12 ++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index b07bc71..c99a7ce 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -186,7 +186,9 @@ def reference_resolver(root, *args, **kwargs): return None choice_to_resolve = dict() - querying_union_types = get_queried_union_types(args[0]) + querying_union_types = get_queried_union_types( + info=args[0], valid_gql_types=registry._registry_string_map.keys() + ) to_resolve_models = dict() for each, queried_fields in querying_union_types.items(): to_resolve_models[registry._registry_string_map[each]] = queried_fields @@ -263,7 +265,9 @@ async def reference_resolver_async(root, *args, **kwargs): return None choice_to_resolve = dict() - querying_union_types = get_queried_union_types(args[0]) + querying_union_types = get_queried_union_types( + info=args[0], valid_gql_types=registry._registry_async_string_map.keys() + ) to_resolve_models = dict() for each, queried_fields in querying_union_types.items(): to_resolve_models[registry._registry_async_string_map[each]] = queried_fields diff --git a/graphene_mongo/utils.py b/graphene_mongo/utils.py index e5fd457..b883a4e 100644 --- a/graphene_mongo/utils.py +++ b/graphene_mongo/utils.py @@ -203,11 +203,12 @@ def get_query_fields(info): return query -def get_queried_union_types(info): +def get_queried_union_types(info, valid_gql_types): """A convenience function to get queried union types with its fields Args: info (ResolveInfo) + valid_gql_types (dict_keys) Returns: dict[union_type_name, queried_fields(dict)] @@ -227,9 +228,16 @@ def get_queried_union_types(info): for leaf in selection_set.selections: if leaf.kind == "fragment_spread": fragment_name = fragments[leaf.name.value].type_condition.name.value - fragments_queries[fragment_name] = collect_query_fields( + sub_query_fields = collect_query_fields( fragments[leaf.name.value], fragments, variables ) + if fragment_name not in valid_gql_types: + # This is done to avoid UnionFragments coming in fragments_queries as + # we actually need its children types and not the UnionFragments itself + fragments_queries.update(sub_query_fields) + fragments_queries.pop('__typename', None) # cannot resolve __typename for a union type + else: + fragments_queries[fragment_name] = sub_query_fields elif leaf.kind == "inline_fragment": fragment_name = leaf.type_condition.name.value fragments_queries[fragment_name] = collect_query_fields(leaf, fragments, variables) From ff700b3448b7bdbefd975c3c4bec98e221c52f8c Mon Sep 17 00:00:00 2001 From: M Aswin Kishore <60577077+mak626@users.noreply.github.com> Date: Fri, 28 Feb 2025 23:48:32 +0530 Subject: [PATCH 07/14] fix[connection-filter]: connection filter generation error for the following types is fixed. ListField(EmbeddedDocumentListField(...)) ListField(GenericEmbeddedDocumentField(...)) ListField(GenericLazyReferenceField(...)) --- graphene_mongo/fields.py | 20 ++++----- graphene_mongo/tests/models.py | 36 +++++++++++++++ graphene_mongo/tests/test_types.py | 71 +++++++++++++++++++++++++++--- graphene_mongo/utils.py | 4 +- 4 files changed, 114 insertions(+), 17 deletions(-) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index c76feb9..32b2a69 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -163,16 +163,16 @@ def is_filterable(k): ), ): return False - if ( - getattr(converted, "type", None) - and getattr(converted.type, "_of_type", None) - and issubclass((get_type(converted.type.of_type)), graphene.Union) - ): - return False - if isinstance(converted, (graphene.List)) and issubclass( - getattr(converted, "_of_type", None), graphene.Union - ): - return False + + if isinstance(converted, (graphene.List)): + sub_type = getattr(converted, "_of_type", None) + if hasattr(sub_type, "of_type"): # graphene.NonNull + sub_type = sub_type.of_type + if issubclass(sub_type, graphene.Union) or issubclass( + sub_type, graphene.ObjectType + ): + return False + # below if condition: workaround for DB filterable field redefined as custom graphene type if ( hasattr(field_, "type") diff --git a/graphene_mongo/tests/models.py b/graphene_mongo/tests/models.py index 871b5c9..f700105 100644 --- a/graphene_mongo/tests/models.py +++ b/graphene_mongo/tests/models.py @@ -1,4 +1,5 @@ from datetime import datetime +from enum import Enum import mongoengine import mongomock @@ -177,3 +178,38 @@ class Bar(mongoengine.EmbeddedDocument): class Foo(mongoengine.Document): bars = mongoengine.EmbeddedDocumentListField(Bar) + + +class GradeEnum(Enum): + A = "A" + B = "B" + + +class Student(mongoengine.EmbeddedDocument): + name = mongoengine.StringField() + + +class Teacher(mongoengine.EmbeddedDocument): + name = mongoengine.StringField() + + +class Bench(mongoengine.Document): + size = mongoengine.IntField() + + +class Exam(mongoengine.Document): + size = mongoengine.IntField() + + +class SchoolClass(mongoengine.Document): + allowed_grades = mongoengine.ListField(mongoengine.EnumField(GradeEnum)) + subjects = mongoengine.ListField(mongoengine.StringField()) + students = mongoengine.ListField(mongoengine.EmbeddedDocumentListField(Student)) + members = mongoengine.ListField( + mongoengine.GenericEmbeddedDocumentField(choices=[Student, Teacher]) + ) + records = mongoengine.ListField(mongoengine.GenericLazyReferenceField(choices=[Bench, Exam])) + + +class School(mongoengine.Document): + classes = mongoengine.ListField(mongoengine.ReferenceField(SchoolClass)) diff --git a/graphene_mongo/tests/test_types.py b/graphene_mongo/tests/test_types.py index c121772..af21ab2 100644 --- a/graphene_mongo/tests/test_types.py +++ b/graphene_mongo/tests/test_types.py @@ -1,13 +1,22 @@ -from pytest import raises - from graphene import Field, Int, Interface, ObjectType from graphene.relay import Node, is_node +from pytest import raises +from .models import ( + Article, + Bench, + Child, + EmbeddedArticle, + Exam, + Parent, + Reporter, + School, + SchoolClass, + Student, +) +from .utils import with_local_registry from .. import registry from ..types import MongoengineObjectType, MongoengineObjectTypeOptions -from .models import Article, EmbeddedArticle, Reporter -from .models import Parent, Child -from .utils import with_local_registry registry.reset_global_registry() @@ -83,7 +92,13 @@ def test_node_replacedfield(): def test_object_type(): assert issubclass(Human, ObjectType) assert set(Human._meta.fields.keys()) == set( - ["id", "headline", "pub_date", "editor", "reporter"] + [ + "id", + "headline", + "pub_date", + "editor", + "reporter", + ] ) assert is_node(Human) @@ -176,3 +191,47 @@ class Meta: assert hasattr(B._meta, "some_subclass_attr") assert B._meta.some_subclass_attr == "someval" + + +@with_local_registry +def test_filter_list_types(): + """ + Test to check filter args should not be generated the following type of fields + + ListField(EmbeddedDocumentListField(...)) + ListField(GenericEmbeddedDocumentField(...)) + ListField(GenericLazyReferenceField(...)) + """ + + class ExamType(MongoengineObjectType): + class Meta: + model = Exam + + class BenchType(MongoengineObjectType): + class Meta: + model = Bench + + class StudentType(MongoengineObjectType): + class Meta: + model = Student + + class SchoolClassType(MongoengineObjectType): + class Meta: + model = SchoolClass + interfaces = (Node,) + + class SchoolType(MongoengineObjectType): + class Meta: + model = School + interfaces = (Node,) + + class_type_filter_args = SchoolType._meta.fields["classes"].args + assert class_type_filter_args.keys() == { + "before", + "after", + "first", + "last", + "allowed_grades", + "id", + "subjects", + } diff --git a/graphene_mongo/utils.py b/graphene_mongo/utils.py index b883a4e..3a9c515 100644 --- a/graphene_mongo/utils.py +++ b/graphene_mongo/utils.py @@ -235,7 +235,9 @@ def get_queried_union_types(info, valid_gql_types): # This is done to avoid UnionFragments coming in fragments_queries as # we actually need its children types and not the UnionFragments itself fragments_queries.update(sub_query_fields) - fragments_queries.pop('__typename', None) # cannot resolve __typename for a union type + fragments_queries.pop( + "__typename", None + ) # cannot resolve __typename for a union type else: fragments_queries[fragment_name] = sub_query_fields elif leaf.kind == "inline_fragment": From b6b1ad6666dd0da001038e27a1d879354607bc7b Mon Sep 17 00:00:00 2001 From: M Aswin Kishore <60577077+mak626@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:36:53 +0530 Subject: [PATCH 08/14] fix[union-type]: union type resolution caused errors when using fragment spread inside a UnionType --- graphene_mongo/utils.py | 42 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/graphene_mongo/utils.py b/graphene_mongo/utils.py index 3a9c515..e58e106 100644 --- a/graphene_mongo/utils.py +++ b/graphene_mongo/utils.py @@ -214,6 +214,42 @@ def get_queried_union_types(info, valid_gql_types): dict[union_type_name, queried_fields(dict)] """ + def collect_query_fields_for_union(node, fragments, variables): + """ + Similar to collect_query_fields(...) + + fragment_spread - logic is different for union + """ + + field = {} + selection_set = node.get("selection_set") if isinstance(node, dict) else node.selection_set + if selection_set: + for leaf in selection_set.selections: + if leaf.kind == "field": + if include_field_by_directives(leaf, variables): + field.update( + {leaf.name.value: collect_query_fields(leaf, fragments, variables)} + ) + elif leaf.kind == "fragment_spread": # This is different + fragment = fragments[leaf.name.value] + field.update( + { + fragment.type_condition.name.value: collect_query_fields( + fragment, fragments, variables + ) + } + ) + elif leaf.kind == "inline_fragment": + field.update( + { + leaf.type_condition.name.value: collect_query_fields( + leaf, fragments, variables + ) + } + ) + + return field + fragments = {} node = ast_to_dict(info.field_nodes[0]) variables = info.variable_values @@ -228,7 +264,7 @@ def get_queried_union_types(info, valid_gql_types): for leaf in selection_set.selections: if leaf.kind == "fragment_spread": fragment_name = fragments[leaf.name.value].type_condition.name.value - sub_query_fields = collect_query_fields( + sub_query_fields = collect_query_fields_for_union( fragments[leaf.name.value], fragments, variables ) if fragment_name not in valid_gql_types: @@ -242,7 +278,9 @@ def get_queried_union_types(info, valid_gql_types): fragments_queries[fragment_name] = sub_query_fields elif leaf.kind == "inline_fragment": fragment_name = leaf.type_condition.name.value - fragments_queries[fragment_name] = collect_query_fields(leaf, fragments, variables) + fragments_queries[fragment_name] = collect_query_fields_for_union( + leaf, fragments, variables + ) return fragments_queries From f29063d0d9834c66ca07d5390e30473dbe368af5 Mon Sep 17 00:00:00 2001 From: M Aswin Kishore <60577077+mak626@users.noreply.github.com> Date: Tue, 7 Oct 2025 21:54:08 +0530 Subject: [PATCH 09/14] fix[union-type]: union type resolution caused errors when using fragment spread syntax for GenericReferenceField --- graphene_mongo/converter.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index c99a7ce..8c09c24 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -393,10 +393,12 @@ def reference_resolver(root, *args, **kwargs): for key, values in _type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) - querying_types = list(get_query_fields(args[0]).keys()) - if _type.__name__ in querying_types: + querying_union_types = get_queried_union_types( + info=args[0], valid_gql_types=registry._registry_string_map.keys() + ) + if _type.__name__ in querying_union_types: queried_fields = list() - for each in get_query_fields(args[0])[_type._meta.name].keys(): + for each in querying_union_types[_type._meta.name].keys(): item = to_snake_case(each) if item in document._fields_ordered + tuple(filter_args): queried_fields.append(item) @@ -418,14 +420,16 @@ def lazy_reference_resolver(root, *args, **kwargs): document_field_type = registry.get_type_for_model( document.document_type, executor=executor ) - querying_types = list(get_query_fields(args[0]).keys()) + querying_union_types = get_queried_union_types( + info=args[0], valid_gql_types=registry._registry_string_map.keys() + ) filter_args = list() if document_field_type._meta.filter_fields: for key, values in document_field_type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) - if document_field_type._meta.name in querying_types: - for each in get_query_fields(args[0])[document_field_type._meta.name].keys(): + if document_field_type._meta.name in querying_union_types: + for each in querying_union_types[document_field_type._meta.name].keys(): item = to_snake_case(each) if item in document.document_type._fields_ordered + tuple(filter_args): queried_fields.append(item) @@ -453,10 +457,12 @@ async def reference_resolver_async(root, *args, **kwargs): for key, values in _type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) - querying_types = list(get_query_fields(args[0]).keys()) - if _type.__name__ in querying_types: + querying_union_types = get_queried_union_types( + info=args[0], valid_gql_types=registry._registry_string_map.keys() + ) + if _type.__name__ in querying_union_types: queried_fields = list() - for each in get_query_fields(args[0])[_type._meta.name].keys(): + for each in querying_union_types[_type._meta.name].keys(): item = to_snake_case(each) if item in document._fields_ordered + tuple(filter_args): queried_fields.append(item) @@ -478,14 +484,16 @@ async def lazy_reference_resolver_async(root, *args, **kwargs): document_field_type = registry.get_type_for_model( document.document_type, executor=executor ) - querying_types = list(get_query_fields(args[0]).keys()) + querying_union_types = get_queried_union_types( + info=args[0], valid_gql_types=registry._registry_string_map.keys() + ) filter_args = list() if document_field_type._meta.filter_fields: for key, values in document_field_type._meta.filter_fields.items(): for each in values: filter_args.append(key + "__" + each) - if document_field_type._meta.name in querying_types: - for each in get_query_fields(args[0])[document_field_type._meta.name].keys(): + if document_field_type._meta.name in querying_union_types: + for each in querying_union_types[document_field_type._meta.name].keys(): item = to_snake_case(each) if item in document.document_type._fields_ordered + tuple(filter_args): queried_fields.append(item) From 07101f69c899491ae6e3a07e96ebf02edd34f413 Mon Sep 17 00:00:00 2001 From: M Aswin Kishore <60577077+mak626@users.noreply.github.com> Date: Wed, 22 Oct 2025 20:21:29 +0530 Subject: [PATCH 10/14] refact: converter.py resolver code using get_field_resolver --- graphene_mongo/converter.py | 52 ++++++++++++------------ graphene_mongo/utils.py | 79 +++++++++++++++++++++++-------------- 2 files changed, 76 insertions(+), 55 deletions(-) diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index 8c09c24..4629a2c 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -13,6 +13,7 @@ get_query_fields, get_queried_union_types, get_field_is_required, + get_field_resolver, ExecutorEnum, sync_to_async, ) @@ -521,12 +522,11 @@ async def lazy_reference_resolver_async(root, *args, **kwargs): field_resolver = resolver_function return graphene.Field( _union, - resolver=field_resolver - if field_resolver - else ( - lazy_reference_resolver - if executor == ExecutorEnum.SYNC - else lazy_reference_resolver_async + resolver=get_field_resolver( + field_resolver=field_resolver, + default_sync_resolver=lazy_reference_resolver, + default_async_resolver=lazy_reference_resolver_async, + executor=executor, ), description=get_field_description(field, registry), required=required, @@ -546,10 +546,11 @@ async def lazy_reference_resolver_async(root, *args, **kwargs): field_resolver = resolver_function return graphene.Field( _union, - resolver=field_resolver - if field_resolver - else ( - reference_resolver if executor == ExecutorEnum.SYNC else reference_resolver_async + resolver=get_field_resolver( + field_resolver=field_resolver, + default_sync_resolver=reference_resolver, + default_async_resolver=reference_resolver_async, + executor=executor, ), description=get_field_description(field, registry), required=required, @@ -694,12 +695,11 @@ def dynamic_type(): if isinstance(field, mongoengine.ReferenceField): return graphene.Field( _type, - resolver=field_resolver - if field_resolver - else ( - reference_resolver - if executor == ExecutorEnum.SYNC - else reference_resolver_async + resolver=get_field_resolver( + field_resolver=field_resolver, + default_sync_resolver=reference_resolver, + default_async_resolver=reference_resolver_async, + executor=executor, ), description=get_field_description(field, registry), required=required, @@ -707,12 +707,11 @@ def dynamic_type(): else: return graphene.Field( _type, - resolver=field_resolver - if field_resolver - else ( - cached_reference_resolver - if executor == ExecutorEnum.SYNC - else cached_reference_resolver_async + resolver=get_field_resolver( + field_resolver=field_resolver, + default_sync_resolver=cached_reference_resolver, + default_async_resolver=cached_reference_resolver_async, + executor=executor, ), description=get_field_description(field, registry), required=required, @@ -790,9 +789,12 @@ def dynamic_type(): field_resolver = resolver_function return graphene.Field( _type, - resolver=field_resolver - if field_resolver - else (lazy_resolver if executor == ExecutorEnum.SYNC else lazy_resolver_async), + resolver=get_field_resolver( + field_resolver=field_resolver, + default_sync_resolver=lazy_resolver, + default_async_resolver=lazy_resolver_async, + executor=executor, + ), description=get_field_description(field, registry), required=required, ) diff --git a/graphene_mongo/utils.py b/graphene_mongo/utils.py index e58e106..8b39b79 100644 --- a/graphene_mongo/utils.py +++ b/graphene_mongo/utils.py @@ -1,12 +1,11 @@ from __future__ import unicode_literals -import enum -import inspect from collections import OrderedDict from concurrent.futures import ThreadPoolExecutor +import enum +import inspect from typing import Any, Callable, Union -import mongoengine from asgiref.sync import SyncToAsync from asgiref.sync import sync_to_async as asgiref_sync_to_async from graphene import Node @@ -19,6 +18,7 @@ VariableNode, ) from graphql_relay.connection.array_connection import offset_to_cursor +import mongoengine class ExecutorEnum(enum.Enum): @@ -163,19 +163,15 @@ def collect_query_fields(node, fragments, variables): for leaf in selection_set.selections: if leaf.kind == "field": if include_field_by_directives(leaf, variables): - field.update( - {leaf.name.value: collect_query_fields(leaf, fragments, variables)} - ) + field.update({ + leaf.name.value: collect_query_fields(leaf, fragments, variables) + }) elif leaf.kind == "fragment_spread": field.update(collect_query_fields(fragments[leaf.name.value], fragments, variables)) elif leaf.kind == "inline_fragment": - field.update( - { - leaf.type_condition.name.value: collect_query_fields( - leaf, fragments, variables - ) - } - ) + field.update({ + leaf.type_condition.name.value: collect_query_fields(leaf, fragments, variables) + }) return field @@ -227,26 +223,22 @@ def collect_query_fields_for_union(node, fragments, variables): for leaf in selection_set.selections: if leaf.kind == "field": if include_field_by_directives(leaf, variables): - field.update( - {leaf.name.value: collect_query_fields(leaf, fragments, variables)} - ) + field.update({ + leaf.name.value: collect_query_fields(leaf, fragments, variables) + }) elif leaf.kind == "fragment_spread": # This is different fragment = fragments[leaf.name.value] - field.update( - { - fragment.type_condition.name.value: collect_query_fields( - fragment, fragments, variables - ) - } - ) + field.update({ + fragment.type_condition.name.value: collect_query_fields( + fragment, fragments, variables + ) + }) elif leaf.kind == "inline_fragment": - field.update( - { - leaf.type_condition.name.value: collect_query_fields( - leaf, fragments, variables - ) - } - ) + field.update({ + leaf.type_condition.name.value: collect_query_fields( + leaf, fragments, variables + ) + }) return field @@ -421,3 +413,30 @@ def sync_to_async( if executor is None: executor = ThreadPoolExecutor() return asgiref_sync_to_async(func=func, thread_sensitive=thread_sensitive, executor=executor) + + +def get_field_resolver( + field_resolver: Callable | None, + default_async_resolver: Callable, + default_sync_resolver: Callable, + executor: ExecutorEnum, +) -> Callable: + """ + Helpr function to get the resolver for a field + + Args: + field_resolver: user defined resolver (optional) + default_async_resolver: default library async resolver + default_sync_resolver: default library sync resolver + executor: ExecutorEnum + + Returns: + resolver: Callable + """ + if field_resolver is not None: + return field_resolver + + if executor == ExecutorEnum.ASYNC: + return default_async_resolver + + return default_sync_resolver From cc4bbac487ed176625c11491b9274da9c6dc1248 Mon Sep 17 00:00:00 2001 From: M Aswin Kishore <60577077+mak626@users.noreply.github.com> Date: Wed, 22 Oct 2025 23:08:50 +0530 Subject: [PATCH 11/14] refact: converter.py resolver code by separating resolvers to separate classes --- graphene_mongo/converter.py | 541 ++---------------- graphene_mongo/field_resolvers/__init__.py | 11 + .../dynamic_lazy_field_resolver.py | 67 +++ .../dynamic_reference_field_resolver.py | 74 +++ .../field_resolvers/list_field_resolver.py | 200 +++++++ .../field_resolvers/union_resolver.py | 127 ++++ graphene_mongo/utils.py | 50 +- 7 files changed, 554 insertions(+), 516 deletions(-) create mode 100644 graphene_mongo/field_resolvers/__init__.py create mode 100644 graphene_mongo/field_resolvers/dynamic_lazy_field_resolver.py create mode 100644 graphene_mongo/field_resolvers/dynamic_reference_field_resolver.py create mode 100644 graphene_mongo/field_resolvers/list_field_resolver.py create mode 100644 graphene_mongo/field_resolvers/union_resolver.py diff --git a/graphene_mongo/converter.py b/graphene_mongo/converter.py index 4629a2c..20f6cd0 100644 --- a/graphene_mongo/converter.py +++ b/graphene_mongo/converter.py @@ -1,23 +1,24 @@ -import asyncio import sys import graphene import mongoengine from graphene.types.json import JSONString -from graphene.utils.str_converters import to_snake_case, to_camel_case -from mongoengine.base import get_document, LazyReference +from graphene.utils.str_converters import to_camel_case +from mongoengine.base import get_document from . import advanced_types from .utils import ( get_field_description, - get_query_fields, - get_queried_union_types, get_field_is_required, get_field_resolver, ExecutorEnum, - sync_to_async, ) -from concurrent.futures import ThreadPoolExecutor, as_completed +from .field_resolvers import ( + DynamicLazyFieldResolver, + DynamicReferenceFieldResolver, + ListFieldResolver, + UnionFieldResolver, +) from functools import singledispatch @@ -154,167 +155,19 @@ def convert_field_to_list(field, registry=None, executor: ExecutorEnum = Executo base_type = convert_mongoengine_field(field.field, registry=registry, executor=executor) if isinstance(base_type, graphene.Field): if isinstance(field.field, mongoengine.GenericReferenceField): - - def get_reference_objects(*args, **kwargs): - document = get_document(args[0]) - document_field = mongoengine.ReferenceField(document) - document_field = convert_mongoengine_field(document_field, registry) - document_field_type = document_field.get_type().type - queried_fields = list() - filter_args = list() - if document_field_type._meta.filter_fields: - for key, values in document_field_type._meta.filter_fields.items(): - for each in values: - filter_args.append(key + "__" + each) - for each in args[4]: - item = to_snake_case(each) - if item in document._fields_ordered + tuple(filter_args): - queried_fields.append(item) - return ( - document.objects() - .no_dereference() - .only(*set(list(document_field_type._meta.required_fields) + queried_fields)) - .filter(pk__in=args[1]) - ) - - def get_non_querying_object(*args, **kwargs): - model = get_document(args[0]) - return [model(pk=each) for each in args[1]] - - def reference_resolver(root, *args, **kwargs): - to_resolve = getattr(root, field.name or field.db_name) - if not to_resolve: - return None - - choice_to_resolve = dict() - querying_union_types = get_queried_union_types( - info=args[0], valid_gql_types=registry._registry_string_map.keys() - ) - to_resolve_models = dict() - for each, queried_fields in querying_union_types.items(): - to_resolve_models[registry._registry_string_map[each]] = queried_fields - to_resolve_object_ids = list() - for each in to_resolve: - if isinstance(each, LazyReference): - to_resolve_object_ids.append(each.pk) - model = each.document_type._class_name - if model not in choice_to_resolve: - choice_to_resolve[model] = list() - choice_to_resolve[model].append(each.pk) - else: - to_resolve_object_ids.append(each["_ref"].id) - if each["_cls"] not in choice_to_resolve: - choice_to_resolve[each["_cls"]] = list() - choice_to_resolve[each["_cls"]].append(each["_ref"].id) - pool = ThreadPoolExecutor(5) - futures = list() - for model, object_id_list in choice_to_resolve.items(): - if model in to_resolve_models: - queried_fields = to_resolve_models[model] - futures.append( - pool.submit( - get_reference_objects, - *(model, object_id_list, registry, args, queried_fields), - ) - ) - else: - futures.append( - pool.submit( - get_non_querying_object, - *(model, object_id_list, registry, args), - ) - ) - result = list() - for x in as_completed(futures): - result += x.result() - result_object_ids = [each.id for each in result] - ordered_result = [ - result[result_object_ids.index(each)] for each in to_resolve_object_ids - ] - return ordered_result - - async def get_reference_objects_async(*args, **kwargs): - document = get_document(args[0]) - document_field = mongoengine.ReferenceField(document) - document_field = convert_mongoengine_field( - document_field, registry, executor=ExecutorEnum.ASYNC - ) - document_field_type = document_field.get_type().type - queried_fields = list() - filter_args = list() - if document_field_type._meta.filter_fields: - for key, values in document_field_type._meta.filter_fields.items(): - for each in values: - filter_args.append(key + "__" + each) - for each in args[4]: - item = to_snake_case(each) - if item in document._fields_ordered + tuple(filter_args): - queried_fields.append(item) - return await sync_to_async(list)( - document.objects() - .no_dereference() - .only(*set(list(document_field_type._meta.required_fields) + queried_fields)) - .filter(pk__in=args[1]) - ) - - async def get_non_querying_object_async(*args, **kwargs): - return get_non_querying_object(*args, **kwargs) - - async def reference_resolver_async(root, *args, **kwargs): - to_resolve = getattr(root, field.name or field.db_name) - if not to_resolve: - return None - - choice_to_resolve = dict() - querying_union_types = get_queried_union_types( - info=args[0], valid_gql_types=registry._registry_async_string_map.keys() - ) - to_resolve_models = dict() - for each, queried_fields in querying_union_types.items(): - to_resolve_models[registry._registry_async_string_map[each]] = queried_fields - to_resolve_object_ids = list() - for each in to_resolve: - if isinstance(each, LazyReference): - to_resolve_object_ids.append(each.pk) - model = each.document_type._class_name - if model not in choice_to_resolve: - choice_to_resolve[model] = list() - choice_to_resolve[model].append(each.pk) - else: - to_resolve_object_ids.append(each["_ref"].id) - if each["_cls"] not in choice_to_resolve: - choice_to_resolve[each["_cls"]] = list() - choice_to_resolve[each["_cls"]].append(each["_ref"].id) - loop = asyncio.get_event_loop() - tasks = [] - for model, object_id_list in choice_to_resolve.items(): - if model in to_resolve_models: - queried_fields = to_resolve_models[model] - task = loop.create_task( - get_reference_objects_async( - model, object_id_list, registry, args, queried_fields - ) - ) - else: - task = loop.create_task( - get_non_querying_object_async(model, object_id_list, registry, args) - ) - tasks.append(task) - result = await asyncio.gather(*tasks) - result_object = {} - for items in result: - for item in items: - result_object[item.id] = item - ordered_result = [result_object[each] for each in to_resolve_object_ids] - return ordered_result - return graphene.List( base_type._type, description=get_field_description(field, registry), required=get_field_is_required(field, registry), - resolver=reference_resolver - if executor == ExecutorEnum.SYNC - else reference_resolver_async, + resolver=get_field_resolver( + default_sync_resolver=ListFieldResolver.reference_resolver( + field=field, registry=registry, executor=executor + ), + default_async_resolver=ListFieldResolver.reference_resolver_async( + field=field, registry=registry, executor=executor + ), + executor=executor, + ), ) return graphene.List( base_type._type, @@ -382,157 +235,9 @@ def convert_field_to_union(field, registry=None, executor: ExecutorEnum = Execut Meta = type("Meta", (object,), {"types": tuple(_types)}) _union = type(name, (graphene.Union,), {"Meta": Meta}) - def reference_resolver(root, *args, **kwargs): - de_referenced = getattr(root, field.name or field.db_name) - if de_referenced: - document = get_document(de_referenced["_cls"]) - document_field = mongoengine.ReferenceField(document) - document_field = convert_mongoengine_field(document_field, registry, executor=executor) - _type = document_field.get_type().type - filter_args = list() - if _type._meta.filter_fields: - for key, values in _type._meta.filter_fields.items(): - for each in values: - filter_args.append(key + "__" + each) - querying_union_types = get_queried_union_types( - info=args[0], valid_gql_types=registry._registry_string_map.keys() - ) - if _type.__name__ in querying_union_types: - queried_fields = list() - for each in querying_union_types[_type._meta.name].keys(): - item = to_snake_case(each) - if item in document._fields_ordered + tuple(filter_args): - queried_fields.append(item) - return ( - document.objects() - .no_dereference() - .only(*list(set(list(_type._meta.required_fields) + queried_fields))) - .get(pk=de_referenced["_ref"].id) - ) - return document() - return None - - def lazy_reference_resolver(root, *args, **kwargs): - document = getattr(root, field.name or field.db_name) - if document: - if document._cached_doc: - return document._cached_doc - queried_fields = list() - document_field_type = registry.get_type_for_model( - document.document_type, executor=executor - ) - querying_union_types = get_queried_union_types( - info=args[0], valid_gql_types=registry._registry_string_map.keys() - ) - filter_args = list() - if document_field_type._meta.filter_fields: - for key, values in document_field_type._meta.filter_fields.items(): - for each in values: - filter_args.append(key + "__" + each) - if document_field_type._meta.name in querying_union_types: - for each in querying_union_types[document_field_type._meta.name].keys(): - item = to_snake_case(each) - if item in document.document_type._fields_ordered + tuple(filter_args): - queried_fields.append(item) - _type = registry.get_type_for_model(document.document_type, executor=executor) - return ( - document.document_type.objects() - .no_dereference() - .only(*(set((list(_type._meta.required_fields) + queried_fields)))) - .get(pk=document.pk) - ) - return document.document_type() - return None - - async def reference_resolver_async(root, *args, **kwargs): - de_referenced = getattr(root, field.name or field.db_name) - if de_referenced: - document = get_document(de_referenced["_cls"]) - document_field = mongoengine.ReferenceField(document) - document_field = convert_mongoengine_field( - document_field, registry, executor=ExecutorEnum.ASYNC - ) - _type = document_field.get_type().type - filter_args = list() - if _type._meta.filter_fields: - for key, values in _type._meta.filter_fields.items(): - for each in values: - filter_args.append(key + "__" + each) - querying_union_types = get_queried_union_types( - info=args[0], valid_gql_types=registry._registry_string_map.keys() - ) - if _type.__name__ in querying_union_types: - queried_fields = list() - for each in querying_union_types[_type._meta.name].keys(): - item = to_snake_case(each) - if item in document._fields_ordered + tuple(filter_args): - queried_fields.append(item) - return await sync_to_async( - document.objects() - .no_dereference() - .only(*list(set(list(_type._meta.required_fields) + queried_fields))) - .get - )(pk=de_referenced["_ref"].id) - return await sync_to_async(document)() - return None - - async def lazy_reference_resolver_async(root, *args, **kwargs): - document = getattr(root, field.name or field.db_name) - if document: - if document._cached_doc: - return document._cached_doc - queried_fields = list() - document_field_type = registry.get_type_for_model( - document.document_type, executor=executor - ) - querying_union_types = get_queried_union_types( - info=args[0], valid_gql_types=registry._registry_string_map.keys() - ) - filter_args = list() - if document_field_type._meta.filter_fields: - for key, values in document_field_type._meta.filter_fields.items(): - for each in values: - filter_args.append(key + "__" + each) - if document_field_type._meta.name in querying_union_types: - for each in querying_union_types[document_field_type._meta.name].keys(): - item = to_snake_case(each) - if item in document.document_type._fields_ordered + tuple(filter_args): - queried_fields.append(item) - _type = registry.get_type_for_model(document.document_type, executor=executor) - return await sync_to_async( - document.document_type.objects() - .no_dereference() - .only(*(set((list(_type._meta.required_fields) + queried_fields)))) - .get - )(pk=document.pk) - return await sync_to_async(document.document_type)() - return None - - if isinstance(field, mongoengine.GenericLazyReferenceField): - field_resolver = None - required = False - if field.db_field is not None: - required = get_field_is_required(field, registry) - resolver_function = getattr( - registry.get_type_for_model(field.owner_document, executor=executor), - "resolve_" + field.db_field, - None, - ) - if resolver_function and callable(resolver_function): - field_resolver = resolver_function - return graphene.Field( - _union, - resolver=get_field_resolver( - field_resolver=field_resolver, - default_sync_resolver=lazy_reference_resolver, - default_async_resolver=lazy_reference_resolver_async, - executor=executor, - ), - description=get_field_description(field, registry), - required=required, - ) - - elif isinstance(field, mongoengine.GenericReferenceField): + if isinstance(field, mongoengine.GenericReferenceField) or isinstance( + field, mongoengine.GenericLazyReferenceField + ): field_resolver = None required = False if field.db_field is not None: @@ -548,8 +253,12 @@ async def lazy_reference_resolver_async(root, *args, **kwargs): _union, resolver=get_field_resolver( field_resolver=field_resolver, - default_sync_resolver=reference_resolver, - default_async_resolver=reference_resolver_async, + default_sync_resolver=UnionFieldResolver.resolver( + field=field, registry=registry, executor=executor + ), + default_async_resolver=UnionFieldResolver.resolver_async( + field=field, registry=registry, executor=executor + ), executor=executor, ), description=get_field_description(field, registry), @@ -565,112 +274,6 @@ async def lazy_reference_resolver_async(root, *args, **kwargs): def convert_field_to_dynamic(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): model = field.document_type - def reference_resolver(root, *args, **kwargs): - document = root._data.get(field.name or field.db_name, None) - if document: - queried_fields = list() - _type = registry.get_type_for_model(field.document_type, executor=executor) - filter_args = list() - if _type._meta.filter_fields: - for key, values in _type._meta.filter_fields.items(): - for each in values: - filter_args.append(key + "__" + each) - for each in get_query_fields(args[0]).keys(): - item = to_snake_case(each) - if item in field.document_type._fields_ordered + tuple(filter_args): - queried_fields.append(item) - - fields_to_fetch = set(list(_type._meta.required_fields) + queried_fields) - if isinstance(document, field.document_type) and all( - document._data[_field] is not None for _field in fields_to_fetch - ): - return document # Data is already fetched - return ( - field.document_type.objects() - .no_dereference() - .only(*fields_to_fetch) - .get(pk=document.id) - ) - return None - - def cached_reference_resolver(root, *args, **kwargs): - document = root._data.get(field.name or field.db_name, None) - if document: - queried_fields = list() - _type = registry.get_type_for_model(field.document_type, executor=executor) - filter_args = list() - if _type._meta.filter_fields: - for key, values in _type._meta.filter_fields.items(): - for each in values: - filter_args.append(key + "__" + each) - for each in get_query_fields(args[0]).keys(): - item = to_snake_case(each) - if item in field.document_type._fields_ordered + tuple(filter_args): - queried_fields.append(item) - - fields_to_fetch = set(list(_type._meta.required_fields) + queried_fields) - if isinstance(document, field.document_type) and all( - document._data[_field] is not None for _field in fields_to_fetch - ): - return document # Data is already fetched - return ( - field.document_type.objects() - .no_dereference() - .only(*fields_to_fetch) - .get(pk=getattr(root, field.name or field.db_name)) - ) - return None - - async def reference_resolver_async(root, *args, **kwargs): - document = root._data.get(field.name or field.db_name, None) - if document: - queried_fields = list() - _type = registry.get_type_for_model(field.document_type, executor=executor) - filter_args = list() - if _type._meta.filter_fields: - for key, values in _type._meta.filter_fields.items(): - for each in values: - filter_args.append(key + "__" + each) - for each in get_query_fields(args[0]).keys(): - item = to_snake_case(each) - if item in field.document_type._fields_ordered + tuple(filter_args): - queried_fields.append(item) - - fields_to_fetch = set(list(_type._meta.required_fields) + queried_fields) - if isinstance(document, field.document_type) and all( - document._data[_field] is not None for _field in fields_to_fetch - ): - return document # Data is already fetched - return await sync_to_async( - field.document_type.objects().no_dereference().only(*fields_to_fetch).get - )(pk=document.id) - return None - - async def cached_reference_resolver_async(root, *args, **kwargs): - document = root._data.get(field.name or field.db_name, None) - if document: - queried_fields = list() - _type = registry.get_type_for_model(field.document_type, executor=executor) - filter_args = list() - if _type._meta.filter_fields: - for key, values in _type._meta.filter_fields.items(): - for each in values: - filter_args.append(key + "__" + each) - for each in get_query_fields(args[0]).keys(): - item = to_snake_case(each) - if item in field.document_type._fields_ordered + tuple(filter_args): - queried_fields.append(item) - - fields_to_fetch = set(list(_type._meta.required_fields) + queried_fields) - if isinstance(document, field.document_type) and all( - document._data[_field] is not None for _field in fields_to_fetch - ): - return document # Data is already fetched - return await sync_to_async( - field.document_type.objects().no_dereference().only(*fields_to_fetch).get - )(pk=getattr(root, field.name or field.db_name)) - return None - def dynamic_type(): _type = registry.get_type_for_model(model, executor=executor) if not _type: @@ -692,30 +295,21 @@ def dynamic_type(): ) if resolver_function and callable(resolver_function): field_resolver = resolver_function - if isinstance(field, mongoengine.ReferenceField): - return graphene.Field( - _type, - resolver=get_field_resolver( - field_resolver=field_resolver, - default_sync_resolver=reference_resolver, - default_async_resolver=reference_resolver_async, - executor=executor, + return graphene.Field( + _type, + resolver=get_field_resolver( + field_resolver=field_resolver, + default_sync_resolver=DynamicReferenceFieldResolver.reference_resolver( + field=field, registry=registry, executor=executor ), - description=get_field_description(field, registry), - required=required, - ) - else: - return graphene.Field( - _type, - resolver=get_field_resolver( - field_resolver=field_resolver, - default_sync_resolver=cached_reference_resolver, - default_async_resolver=cached_reference_resolver_async, - executor=executor, + default_async_resolver=DynamicReferenceFieldResolver.reference_resolver_async( + field=field, registry=registry, executor=executor ), - description=get_field_description(field, registry), - required=required, - ) + executor=executor, + ), + description=get_field_description(field, registry), + required=required, + ) return graphene.Dynamic(dynamic_type) @@ -724,54 +318,6 @@ def dynamic_type(): def convert_lazy_field_to_dynamic(field, registry=None, executor: ExecutorEnum = ExecutorEnum.SYNC): model = field.document_type - def lazy_resolver(root, *args, **kwargs): - document = getattr(root, field.name or field.db_name) - if document: - if document._cached_doc: - return document._cached_doc - queried_fields = list() - _type = registry.get_type_for_model(document.document_type, executor=executor) - filter_args = list() - if _type._meta.filter_fields: - for key, values in _type._meta.filter_fields.items(): - for each in values: - filter_args.append(key + "__" + each) - for each in get_query_fields(args[0]).keys(): - item = to_snake_case(each) - if item in document.document_type._fields_ordered + tuple(filter_args): - queried_fields.append(item) - return ( - document.document_type.objects() - .no_dereference() - .only(*(set((list(_type._meta.required_fields) + queried_fields)))) - .get(pk=document.pk) - ) - return None - - async def lazy_resolver_async(root, *args, **kwargs): - document = getattr(root, field.name or field.db_name) - if document: - if document._cached_doc: - return document._cached_doc - queried_fields = list() - _type = registry.get_type_for_model(document.document_type, executor=executor) - filter_args = list() - if _type._meta.filter_fields: - for key, values in _type._meta.filter_fields.items(): - for each in values: - filter_args.append(key + "__" + each) - for each in get_query_fields(args[0]).keys(): - item = to_snake_case(each) - if item in document.document_type._fields_ordered + tuple(filter_args): - queried_fields.append(item) - return await sync_to_async( - document.document_type.objects() - .no_dereference() - .only(*(set((list(_type._meta.required_fields) + queried_fields)))) - .get - )(pk=document.pk) - return None - def dynamic_type(): _type = registry.get_type_for_model(model, executor=executor) if not _type: @@ -787,12 +333,17 @@ def dynamic_type(): ) if resolver_function and callable(resolver_function): field_resolver = resolver_function + return graphene.Field( _type, resolver=get_field_resolver( field_resolver=field_resolver, - default_sync_resolver=lazy_resolver, - default_async_resolver=lazy_resolver_async, + default_sync_resolver=DynamicLazyFieldResolver.lazy_resolver( + field=field, registry=registry, executor=executor + ), + default_async_resolver=DynamicLazyFieldResolver.lazy_resolver_async( + field=field, registry=registry, executor=executor + ), executor=executor, ), description=get_field_description(field, registry), diff --git a/graphene_mongo/field_resolvers/__init__.py b/graphene_mongo/field_resolvers/__init__.py new file mode 100644 index 0000000..a1630ca --- /dev/null +++ b/graphene_mongo/field_resolvers/__init__.py @@ -0,0 +1,11 @@ +from .dynamic_lazy_field_resolver import DynamicLazyFieldResolver +from .dynamic_reference_field_resolver import DynamicReferenceFieldResolver +from .list_field_resolver import ListFieldResolver +from .union_resolver import UnionFieldResolver + +__all__ = [ + "DynamicLazyFieldResolver", + "DynamicReferenceFieldResolver", + "ListFieldResolver", + "UnionFieldResolver", +] diff --git a/graphene_mongo/field_resolvers/dynamic_lazy_field_resolver.py b/graphene_mongo/field_resolvers/dynamic_lazy_field_resolver.py new file mode 100644 index 0000000..c58f551 --- /dev/null +++ b/graphene_mongo/field_resolvers/dynamic_lazy_field_resolver.py @@ -0,0 +1,67 @@ +from collections.abc import Callable +from typing import Optional, Union + +from bson import ObjectId +from graphene.utils.str_converters import to_snake_case +from graphene_mongo.utils import ( + ExecutorEnum, + get_query_fields, + sync_to_async, +) +from mongoengine import Document + + +class DynamicLazyFieldResolver: + @staticmethod + def __lazy_resolver_common( + field, registry, executor: ExecutorEnum, root, *args, **kwargs + ) -> Optional[Union[tuple[Document, set[str], ObjectId], Document]]: + document = getattr(root, field.name or field.db_name) + if not document: + return None + if document._cached_doc: + return document._cached_doc + + queried_fields = [] + _type = registry.get_type_for_model(document.document_type, executor=executor) + filter_args = [] + if _type._meta.filter_fields: + for key, values in _type._meta.filter_fields.items(): + for each in values: + filter_args.append(key + "__" + each) + for each in get_query_fields(args[0]).keys(): + item = to_snake_case(each) + if item in document.document_type._fields_ordered + tuple(filter_args): + queried_fields.append(item) + + only_fields = set((list(_type._meta.required_fields) + queried_fields)) + + return document.document_type, only_fields, document.id + + @staticmethod + def lazy_resolver(field, registry, executor) -> Callable: + def resolver(root, *args, **kwargs) -> Optional[Document]: + result = DynamicLazyFieldResolver.__lazy_resolver_common( + field, registry, executor, root, *args, **kwargs + ) + if not isinstance(result, tuple): + return result + document, only_fields, pk = result + return document.objects.no_dereference().only(*only_fields).get(pk=pk) + + return resolver + + @staticmethod + def lazy_resolver_async(field, registry, executor) -> Callable: + async def resolver(root, *args, **kwargs) -> Optional[Document]: + result = DynamicLazyFieldResolver.__lazy_resolver_common( + field, registry, executor, root, *args, **kwargs + ) + if not isinstance(result, tuple): + return result + document, only_fields, pk = result + return await sync_to_async(document.objects.no_dereference().only(*only_fields).get)( + pk=pk + ) + + return resolver diff --git a/graphene_mongo/field_resolvers/dynamic_reference_field_resolver.py b/graphene_mongo/field_resolvers/dynamic_reference_field_resolver.py new file mode 100644 index 0000000..07b8454 --- /dev/null +++ b/graphene_mongo/field_resolvers/dynamic_reference_field_resolver.py @@ -0,0 +1,74 @@ +from collections.abc import Callable +from typing import Optional, Union + +from bson import ObjectId +from graphene.utils.str_converters import to_snake_case +from graphene_mongo.utils import ( + ExecutorEnum, + get_query_fields, + sync_to_async, +) +from mongoengine import Document, ReferenceField + + +class DynamicReferenceFieldResolver: + @staticmethod + def __reference_resolver_common( + field, registry, executor: ExecutorEnum, root, *args, **kwargs + ) -> Optional[Union[tuple[Document, set[str], ObjectId], Document]]: + document = root._data.get(field.name or field.db_name, None) + if not document: + return None + + queried_fields = list() + _type = registry.get_type_for_model(field.document_type, executor=executor) + filter_args = list() + if _type._meta.filter_fields: + for key, values in _type._meta.filter_fields.items(): + for each in values: + filter_args.append(key + "__" + each) + for each in get_query_fields(args[0]).keys(): + item = to_snake_case(each) + if item in field.document_type._fields_ordered + tuple(filter_args): + queried_fields.append(item) + + fields_to_fetch = set(list(_type._meta.required_fields) + queried_fields) + if isinstance(document, field.document_type) and all( + document._data[_field] is not None for _field in fields_to_fetch + ): + return document # Data is already fetched + + document_id = ( + document.id + if isinstance(field, ReferenceField) + else getattr(root, field.name or field.db_name) + ) + return field.document_type, fields_to_fetch, document_id + + @staticmethod + def reference_resolver(field, registry, executor) -> Callable: + def resolver(root, *args, **kwargs) -> Optional[Document]: + result = DynamicReferenceFieldResolver.__reference_resolver_common( + field, registry, executor, root, *args, **kwargs + ) + if not isinstance(result, tuple): + return result + document, only_fields, pk = result + return document.objects.no_dereference().only(*only_fields).get(pk=pk) + + return resolver + + @staticmethod + def reference_resolver_async(field, registry, executor) -> Callable: + async def resolver(root, *args, **kwargs) -> Optional[Document]: + result = DynamicReferenceFieldResolver.__reference_resolver_common( + field, registry, executor, root, *args, **kwargs + ) + if not isinstance(result, tuple): + return result + document, only_fields, pk = result + return await sync_to_async(document.objects.no_dereference().only(*only_fields).get)( + pk=pk + ) + + return resolver diff --git a/graphene_mongo/field_resolvers/list_field_resolver.py b/graphene_mongo/field_resolvers/list_field_resolver.py new file mode 100644 index 0000000..57319e8 --- /dev/null +++ b/graphene_mongo/field_resolvers/list_field_resolver.py @@ -0,0 +1,200 @@ +import asyncio +from asyncio import Future, Task +from collections.abc import Callable +from concurrent.futures import ThreadPoolExecutor, as_completed +from typing import Optional, Union + +from bson import ObjectId +from graphene.utils.str_converters import to_snake_case +from graphene_mongo.utils import ( + ExecutorEnum, + get_queried_union_types, + sync_to_async, +) +import mongoengine +from mongoengine import Document +from mongoengine.base import LazyReference, get_document + + +class ListFieldResolver: + @staticmethod + def __get_reference_objects_common( + registry, + model, + executor: ExecutorEnum, + object_id_list: list[ObjectId], + queried_fields: dict, + ) -> tuple[Document, set[str], list[ObjectId]]: + from graphene_mongo.converter import convert_mongoengine_field + + document = get_document(model) + document_field = mongoengine.ReferenceField(document) + document_field = convert_mongoengine_field(document_field, registry, executor) + document_field_type = document_field.get_type().type + _queried_fields = list() + filter_args = list() + if document_field_type._meta.filter_fields: + for key, values in document_field_type._meta.filter_fields.items(): + for each in values: + filter_args.append(key + "__" + each) + for each in queried_fields: + item = to_snake_case(each) + if item in document._fields_ordered + tuple(filter_args): + _queried_fields.append(item) + + only_fields = set(list(document_field_type._meta.required_fields) + _queried_fields) + return document, only_fields, object_id_list + + # ======================= DB CALLS ======================= + @staticmethod + def __get_reference_objects( + registry, + model, + executor: ExecutorEnum, + object_id_list: list[ObjectId], + queried_fields: dict, + ): + document, only_fields, document_ids = ListFieldResolver.__get_reference_objects_common( + registry, model, executor, object_id_list, queried_fields + ) + return document.objects().no_dereference().only(*only_fields).filter(pk__in=document_ids) + + @staticmethod + async def __get_reference_objects_async( + registry, + model, + executor: ExecutorEnum, + object_id_list: list[ObjectId], + queried_fields: dict, + ): + document, only_fields, document_ids = ListFieldResolver.__get_reference_objects_common( + registry, model, executor, object_id_list, queried_fields + ) + return await sync_to_async(list)( + document.objects().no_dereference().only(*only_fields).filter(pk__in=document_ids) + ) + + # ======================= DB CALLS: END ======================= + + @staticmethod + def __get_non_querying_object(model, object_id_list) -> list[Document]: + model = get_document(model) + return [model(pk=each) for each in object_id_list] + + @staticmethod + async def __get_non_querying_object_async(model, object_id_list) -> list[Document]: + return ListFieldResolver.__get_non_querying_object(model, object_id_list) + + @staticmethod + def __build_results( + result: list[Document], to_resolve_object_ids: list[ObjectId] + ) -> list[Document]: + result_object: dict[ObjectId, Document] = {} + for items in result: + for item in items: + result_object[item.id] = item + return [result_object[each] for each in to_resolve_object_ids] + + # ======================= Main Logic ======================= + + @staticmethod + def __reference_resolver_common( + field, registry, executor: ExecutorEnum, root, *args, **kwargs + ) -> Optional[tuple[Union[list[Task], list[Document]], list[ObjectId]]]: + to_resolve = getattr(root, field.name or field.db_name) + if not to_resolve: + return None + + choice_to_resolve = dict() + registry_string_map = ( + registry._registry_string_map + if executor == ExecutorEnum.SYNC + else registry._registry_async_string_map + ) + querying_union_types = get_queried_union_types( + info=args[0], valid_gql_types=registry_string_map.keys() + ) + to_resolve_models = dict() + for each, queried_fields in querying_union_types.items(): + to_resolve_models[registry_string_map[each]] = queried_fields + to_resolve_object_ids: list[ObjectId] = list() + for each in to_resolve: + if isinstance(each, LazyReference): + to_resolve_object_ids.append(each.pk) + model = each.document_type._class_name + if model not in choice_to_resolve: + choice_to_resolve[model] = list() + choice_to_resolve[model].append(each.pk) + else: + to_resolve_object_ids.append(each["_ref"].id) + if each["_cls"] not in choice_to_resolve: + choice_to_resolve[each["_cls"]] = list() + choice_to_resolve[each["_cls"]].append(each["_ref"].id) + + if executor == ExecutorEnum.SYNC: + pool = ThreadPoolExecutor(5) + futures: list[Future] = list() + for model, object_id_list in choice_to_resolve.items(): + if model in to_resolve_models: + queried_fields = to_resolve_models[model] + futures.append( + pool.submit( + ListFieldResolver.__get_reference_objects, + *(registry, model, executor, object_id_list, queried_fields), + ) + ) + else: + futures.append( + pool.submit( + ListFieldResolver.__get_non_querying_object, + *(model, object_id_list), + ) + ) + result = [future.result() for future in as_completed(futures)] + return result, to_resolve_object_ids + else: + loop = asyncio.get_event_loop() + tasks: list[Task] = [] + for model, object_id_list in choice_to_resolve.items(): + if model in to_resolve_models: + queried_fields = to_resolve_models[model] + task = loop.create_task( + ListFieldResolver.__get_reference_objects_async( + registry, model, executor, object_id_list, queried_fields + ) + ) + else: + task = loop.create_task( + ListFieldResolver.__get_non_querying_object_async(model, object_id_list) + ) + tasks.append(task) + return tasks, to_resolve_object_ids + + @staticmethod + def reference_resolver(field, registry, executor) -> Callable: + def resolver(root, *args, **kwargs) -> Optional[list[Document]]: + resolver_result = ListFieldResolver.__reference_resolver_common( + field, registry, executor, root, *args, **kwargs + ) + if not isinstance(resolver_result, tuple): + return resolver_result + result, to_resolve_object_ids = resolver_result + return ListFieldResolver.__build_results(result, to_resolve_object_ids) + + return resolver + + @staticmethod + def reference_resolver_async(field, registry, executor) -> Callable: + async def resolver(root, *args, **kwargs) -> Optional[list[Document]]: + resolver_result = ListFieldResolver.__reference_resolver_common( + field, registry, executor, root, *args, **kwargs + ) + if not isinstance(resolver_result, tuple): + return resolver_result + tasks, to_resolve_object_ids = resolver_result + result: list[Document] = await asyncio.gather(*tasks) + return ListFieldResolver.__build_results(result, to_resolve_object_ids) + + return resolver + + # ======================= Main Logic: END ======================= diff --git a/graphene_mongo/field_resolvers/union_resolver.py b/graphene_mongo/field_resolvers/union_resolver.py new file mode 100644 index 0000000..bce71bd --- /dev/null +++ b/graphene_mongo/field_resolvers/union_resolver.py @@ -0,0 +1,127 @@ +from collections.abc import Callable +from typing import Optional, Union + +from bson import ObjectId +from graphene.utils.str_converters import to_snake_case +from graphene_mongo.utils import ( + ExecutorEnum, + get_queried_union_types, + sync_to_async, +) +import mongoengine +from mongoengine import Document +from mongoengine.base import get_document + + +class UnionFieldResolver: + @staticmethod + def __reference_resolver_common( + field, registry, executor: ExecutorEnum, root, *args, **kwargs + ) -> Optional[Union[tuple[Document, set[str], ObjectId], Document]]: + from graphene_mongo.converter import convert_mongoengine_field + + de_referenced = getattr(root, field.name or field.db_name) + if not de_referenced: + return None + + document = get_document(de_referenced["_cls"]) + document_id = de_referenced["_ref"].id + document_field = mongoengine.ReferenceField(document) + document_field = convert_mongoengine_field(document_field, registry, executor=executor) + _type = document_field.get_type().type + filter_args = list() + if _type._meta.filter_fields: + for key, values in _type._meta.filter_fields.items(): + for each in values: + filter_args.append(key + "__" + each) + + registry_string_map = ( + registry._registry_string_map + if executor == ExecutorEnum.SYNC + else registry._registry_async_string_map + ) + querying_union_types = get_queried_union_types( + info=args[0], valid_gql_types=registry_string_map.keys() + ) + + if _type.__name__ in querying_union_types: + queried_fields = list() + for each in querying_union_types[_type._meta.name].keys(): + item = to_snake_case(each) + if item in document._fields_ordered + tuple(filter_args): + queried_fields.append(item) + + only_fields = set(list(_type._meta.required_fields) + queried_fields) + + return document, only_fields, document_id + + return document(id=document_id) + + @staticmethod + def __lazy_reference_resolver_common( + field, registry, executor: ExecutorEnum, root, *args, **kwargs + ) -> Optional[Union[tuple[Document, set[str], ObjectId], Document]]: + document = getattr(root, field.name or field.db_name) + + if not document: + return None + + if document._cached_doc: + return document._cached_doc + + document_id = document.pk + queried_fields = list() + document_field_type = registry.get_type_for_model(document.document_type, executor=executor) + querying_union_types = get_queried_union_types( + info=args[0], valid_gql_types=registry._registry_string_map.keys() + ) + filter_args = list() + if document_field_type._meta.filter_fields: + for key, values in document_field_type._meta.filter_fields.items(): + for each in values: + filter_args.append(key + "__" + each) + if document_field_type._meta.name in querying_union_types: + for each in querying_union_types[document_field_type._meta.name].keys(): + item = to_snake_case(each) + if item in document.document_type._fields_ordered + tuple(filter_args): + queried_fields.append(item) + _type = registry.get_type_for_model(document.document_type, executor=executor) + only_fields = set(list(_type._meta.required_fields) + queried_fields) + + return document.document_type, only_fields, document_id + + return document.document_type(id=document.pk) + + @staticmethod + def resolver(field, registry, executor) -> Callable: + def resolver(root, *args, **kwargs) -> Optional[Document]: + resolver_fun = ( + UnionFieldResolver.__reference_resolver_common + if isinstance(field, mongoengine.GenericReferenceField) + else UnionFieldResolver.__lazy_reference_resolver_common + ) + result = resolver_fun(field, registry, executor, root, *args, **kwargs) + if not isinstance(result, tuple): + return result + document, only_fields, pk = result + return document.objects.no_dereference().only(*only_fields).get(pk=pk) + + return resolver + + @staticmethod + def resolver_async(field, registry, executor) -> Callable: + async def resolver(root, *args, **kwargs) -> Optional[Document]: + resolver_fun = ( + UnionFieldResolver.__reference_resolver_common + if isinstance(field, mongoengine.GenericReferenceField) + else UnionFieldResolver.__lazy_reference_resolver_common + ) + result = resolver_fun(field, registry, executor, root, *args, **kwargs) + if not isinstance(result, tuple): + return result + document, only_fields, pk = result + return await sync_to_async(document.objects.no_dereference().only(*only_fields).get)( + pk=pk + ) + + return resolver diff --git a/graphene_mongo/utils.py b/graphene_mongo/utils.py index 8b39b79..ee2b1c1 100644 --- a/graphene_mongo/utils.py +++ b/graphene_mongo/utils.py @@ -4,7 +4,7 @@ from concurrent.futures import ThreadPoolExecutor import enum import inspect -from typing import Any, Callable, Union +from typing import Any, Callable, Optional, Union from asgiref.sync import SyncToAsync from asgiref.sync import sync_to_async as asgiref_sync_to_async @@ -163,15 +163,19 @@ def collect_query_fields(node, fragments, variables): for leaf in selection_set.selections: if leaf.kind == "field": if include_field_by_directives(leaf, variables): - field.update({ - leaf.name.value: collect_query_fields(leaf, fragments, variables) - }) + field.update( + {leaf.name.value: collect_query_fields(leaf, fragments, variables)} + ) elif leaf.kind == "fragment_spread": field.update(collect_query_fields(fragments[leaf.name.value], fragments, variables)) elif leaf.kind == "inline_fragment": - field.update({ - leaf.type_condition.name.value: collect_query_fields(leaf, fragments, variables) - }) + field.update( + { + leaf.type_condition.name.value: collect_query_fields( + leaf, fragments, variables + ) + } + ) return field @@ -223,22 +227,26 @@ def collect_query_fields_for_union(node, fragments, variables): for leaf in selection_set.selections: if leaf.kind == "field": if include_field_by_directives(leaf, variables): - field.update({ - leaf.name.value: collect_query_fields(leaf, fragments, variables) - }) + field.update( + {leaf.name.value: collect_query_fields(leaf, fragments, variables)} + ) elif leaf.kind == "fragment_spread": # This is different fragment = fragments[leaf.name.value] - field.update({ - fragment.type_condition.name.value: collect_query_fields( - fragment, fragments, variables - ) - }) + field.update( + { + fragment.type_condition.name.value: collect_query_fields( + fragment, fragments, variables + ) + } + ) elif leaf.kind == "inline_fragment": - field.update({ - leaf.type_condition.name.value: collect_query_fields( - leaf, fragments, variables - ) - }) + field.update( + { + leaf.type_condition.name.value: collect_query_fields( + leaf, fragments, variables + ) + } + ) return field @@ -416,10 +424,10 @@ def sync_to_async( def get_field_resolver( - field_resolver: Callable | None, default_async_resolver: Callable, default_sync_resolver: Callable, executor: ExecutorEnum, + field_resolver: Optional[Callable] = None, ) -> Callable: """ Helpr function to get the resolver for a field From 75938dbb8559dc048fc6dd201d98edfc95109bf5 Mon Sep 17 00:00:00 2001 From: M Aswin Kishore <60577077+mak626@users.noreply.github.com> Date: Thu, 23 Oct 2025 00:02:01 +0530 Subject: [PATCH 12/14] fix: exclude tests from package build --- poetry.lock | 648 ++++++++++++++++++++++++++++++------------------- pyproject.toml | 1 + 2 files changed, 402 insertions(+), 247 deletions(-) diff --git a/poetry.lock b/poetry.lock index b4ac7a9..da0afcb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,37 +1,22 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. - -[[package]] -name = "aniso8601" -version = "9.0.1" -description = "A library for parsing ISO 8601 strings." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "aniso8601-9.0.1-py2.py3-none-any.whl", hash = "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f"}, - {file = "aniso8601-9.0.1.tar.gz", hash = "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973"}, -] - -[package.extras] -dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "asgiref" -version = "3.8.1" +version = "3.10.0" description = "ASGI specs, helper code, and adapters" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, - {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, + {file = "asgiref-3.10.0-py3-none-any.whl", hash = "sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734"}, + {file = "asgiref-3.10.0.tar.gz", hash = "sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e"}, ] [package.dependencies] -typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} +typing_extensions = {version = ">=4", markers = "python_version < \"3.11\""} [package.extras] -tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] +tests = ["mypy (>=1.14.0)", "pytest", "pytest-asyncio"] [[package]] name = "colorama" @@ -48,64 +33,116 @@ files = [ [[package]] name = "coverage" -version = "7.5.3" +version = "7.10.7" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, - {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, - {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, - {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, - {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, - {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, - {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, - {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, - {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, - {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, - {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, - {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, - {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, - {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, + {file = "coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a"}, + {file = "coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13"}, + {file = "coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b"}, + {file = "coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807"}, + {file = "coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59"}, + {file = "coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61"}, + {file = "coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14"}, + {file = "coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2"}, + {file = "coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a"}, + {file = "coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417"}, + {file = "coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1"}, + {file = "coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256"}, + {file = "coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba"}, + {file = "coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf"}, + {file = "coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d"}, + {file = "coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f"}, + {file = "coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698"}, + {file = "coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843"}, + {file = "coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546"}, + {file = "coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c"}, + {file = "coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2"}, + {file = "coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a"}, + {file = "coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb"}, + {file = "coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb"}, + {file = "coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520"}, + {file = "coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd"}, + {file = "coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2"}, + {file = "coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681"}, + {file = "coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880"}, + {file = "coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63"}, + {file = "coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399"}, + {file = "coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235"}, + {file = "coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d"}, + {file = "coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a"}, + {file = "coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3"}, + {file = "coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f"}, + {file = "coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431"}, + {file = "coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07"}, + {file = "coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260"}, + {file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"}, ] [package.dependencies] @@ -116,72 +153,76 @@ toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "dnspython" -version = "2.6.1" +version = "2.7.0" description = "DNS toolkit" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, ] [package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=41)"] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=43)"] doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=0.9.25)"] -idna = ["idna (>=3.6)"] +doq = ["aioquic (>=1.0.0)"] +idna = ["idna (>=3.7)"] trio = ["trio (>=0.23)"] wmi = ["wmi (>=1.5.1)"] [[package]] name = "exceptiongroup" -version = "1.2.1" +version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["dev"] markers = "python_version < \"3.11\"" files = [ - {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, - {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + [package.extras] test = ["pytest (>=6)"] [[package]] name = "graphene" -version = "3.3" +version = "3.4.3" description = "GraphQL Framework for Python" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "graphene-3.3-py2.py3-none-any.whl", hash = "sha256:bb3810be33b54cb3e6969506671eb72319e8d7ba0d5ca9c8066472f75bf35a38"}, - {file = "graphene-3.3.tar.gz", hash = "sha256:529bf40c2a698954217d3713c6041d69d3f719ad0080857d7ee31327112446b0"}, + {file = "graphene-3.4.3-py2.py3-none-any.whl", hash = "sha256:820db6289754c181007a150db1f7fff544b94142b556d12e3ebc777a7bf36c71"}, + {file = "graphene-3.4.3.tar.gz", hash = "sha256:2a3786948ce75fe7e078443d37f609cbe5bb36ad8d6b828740ad3b95ed1a0aaa"}, ] [package.dependencies] -aniso8601 = ">=8,<10" graphql-core = ">=3.1,<3.3" graphql-relay = ">=3.1,<3.3" +python-dateutil = ">=2.7.0,<3" +typing-extensions = ">=4.7.1,<5" [package.extras] -dev = ["black (==22.3.0)", "coveralls (>=3.3,<4)", "flake8 (>=4,<5)", "iso8601 (>=1,<2)", "mock (>=4,<5)", "pytest (>=6,<7)", "pytest-asyncio (>=0.16,<2)", "pytest-benchmark (>=3.4,<4)", "pytest-cov (>=3,<4)", "pytest-mock (>=3,<4)", "pytz (==2022.1)", "snapshottest (>=0.6,<1)"] -test = ["coveralls (>=3.3,<4)", "iso8601 (>=1,<2)", "mock (>=4,<5)", "pytest (>=6,<7)", "pytest-asyncio (>=0.16,<2)", "pytest-benchmark (>=3.4,<4)", "pytest-cov (>=3,<4)", "pytest-mock (>=3,<4)", "pytz (==2022.1)", "snapshottest (>=0.6,<1)"] +dev = ["coveralls (>=3.3,<5)", "mypy (>=1.10,<2)", "pytest (>=8,<9)", "pytest-asyncio (>=0.16,<2)", "pytest-benchmark (>=4,<5)", "pytest-cov (>=5,<6)", "pytest-mock (>=3,<4)", "ruff (==0.5.0)", "types-python-dateutil (>=2.8.1,<3)"] +test = ["coveralls (>=3.3,<5)", "pytest (>=8,<9)", "pytest-asyncio (>=0.16,<2)", "pytest-benchmark (>=4,<5)", "pytest-cov (>=5,<6)", "pytest-mock (>=3,<4)"] [[package]] name = "graphene-directives" -version = "0.4.6" +version = "0.5.0" description = "Schema Directives implementation for graphene" optional = false -python-versions = ">=3.9,<4" +python-versions = "<4,>=3.9" groups = ["main"] files = [ - {file = "graphene_directives-0.4.6-py3-none-any.whl", hash = "sha256:6caae14e675bfafe90ed54e6c2aa571b502b854f56b41fb6dedd3c9e98e3c08d"}, - {file = "graphene_directives-0.4.6.tar.gz", hash = "sha256:c37bd3223817433f3442794748721b7a72ad821110018975607652d4d9d33a16"}, + {file = "graphene_directives-0.5.0-py3-none-any.whl", hash = "sha256:655c636845c52facac3e352d95df28dabee86209b5c36a176afe6e5280734802"}, + {file = "graphene_directives-0.5.0.tar.gz", hash = "sha256:09afc098028c01f14f4891c6e15eee0c6f3c9190853155c30bc72f89681363a5"}, ] [package.dependencies] @@ -189,37 +230,40 @@ graphene = ">=3" [[package]] name = "graphene-federation" -version = "3.1.5" +version = "3.3.0" description = "Federation implementation for graphene" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "graphene-federation-3.1.5.tar.gz", hash = "sha256:703b59bfdb1cc7275aa6e79acc1348aac72da311dd0e9b88811298f84905b005"}, - {file = "graphene_federation-3.1.5-py3-none-any.whl", hash = "sha256:f4c11cf708dc5cba1f1b922cbc5ce19d8afbb9ac4f99bbf67588689ff85d0e4a"}, + {file = "graphene_federation-3.3.0-py3-none-any.whl", hash = "sha256:8f0ca52c7809ca333b8a7677c1366599644ae068bac4492273321593c8181b33"}, + {file = "graphene_federation-3.3.0.tar.gz", hash = "sha256:2a142115cd9b199ea8b9bdf8f456b78e4c7e2efd08b448e74ffd129b5d55a46d"}, ] [package.dependencies] graphene = ">=3.1" -graphene-directives = ">=0.4.6" +graphene-directives = ">=0.5.0" graphql-core = ">=3.1" [package.extras] -dev = ["black (==23.12.1)", "flake8 (==4.0.1)", "mypy (==0.961)", "pytest (==7.1.2)", "pytest-cov"] -test = ["pytest (==7.1.2)", "pytest-cov"] +dev = ["black (==23.12.1)", "flake8 (==7.3.0)", "mypy (==1.18.2)", "pytest (==8.4.2)", "pytest-cov"] +test = ["pytest (==8.4.2)", "pytest-cov"] [[package]] name = "graphql-core" -version = "3.2.3" +version = "3.2.6" description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." optional = false -python-versions = ">=3.6,<4" +python-versions = "<4,>=3.6" groups = ["main"] files = [ - {file = "graphql-core-3.2.3.tar.gz", hash = "sha256:06d2aad0ac723e35b1cb47885d3e5c45e956a53bc1b209a9fc5369007fe46676"}, - {file = "graphql_core-3.2.3-py3-none-any.whl", hash = "sha256:5766780452bd5ec8ba133f8bf287dc92713e3868ddd83aee4faab9fc3e303dc3"}, + {file = "graphql_core-3.2.6-py3-none-any.whl", hash = "sha256:78b016718c161a6fb20a7d97bbf107f331cd1afe53e45566c59f776ed7f0b45f"}, + {file = "graphql_core-3.2.6.tar.gz", hash = "sha256:c08eec22f9e40f0bd61d805907e3b3b1b9a320bc606e23dc145eebca07c8fbab"}, ] +[package.dependencies] +typing-extensions = {version = ">=4,<5", markers = "python_version < \"3.10\""} + [[package]] name = "graphql-relay" version = "3.2.0" @@ -237,26 +281,26 @@ graphql-core = ">=3.2,<3.3" [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] name = "mock" -version = "5.1.0" +version = "5.2.0" description = "Rolling backport of unittest.mock for all Pythons" optional = false python-versions = ">=3.6" groups = ["dev"] files = [ - {file = "mock-5.1.0-py3-none-any.whl", hash = "sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744"}, - {file = "mock-5.1.0.tar.gz", hash = "sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d"}, + {file = "mock-5.2.0-py3-none-any.whl", hash = "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f"}, + {file = "mock-5.2.0.tar.gz", hash = "sha256:4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0"}, ] [package.extras] @@ -266,62 +310,70 @@ test = ["pytest", "pytest-cov"] [[package]] name = "mongoengine" -version = "0.28.2" +version = "0.29.1" description = "MongoEngine is a Python Object-Document Mapper for working with MongoDB." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "mongoengine-0.28.2-py3-none-any.whl", hash = "sha256:8e0f84a5ad3d335e5da98261454d4ab546c866241ed064adc6433fe2077d43c9"}, - {file = "mongoengine-0.28.2.tar.gz", hash = "sha256:67c35a2ebe0ee7fd8eda3766dc251b9e0aada4489bb935f7a55b4c570d148ca7"}, + {file = "mongoengine-0.29.1-py3-none-any.whl", hash = "sha256:9302ec407dd60f47f62cc07684d9f6cac87f1e93283c54203851788104d33df4"}, + {file = "mongoengine-0.29.1.tar.gz", hash = "sha256:3b43abaf2d5f0b7d39efc2b7d9e78f4d4a5dc7ce92b9889ba81a5a9b8dee3cf3"}, ] [package.dependencies] pymongo = ">=3.4,<5.0" +[package.extras] +test = ["Pillow (>=7.0.0)", "blinker", "coverage", "pytest", "pytest-cov"] + [[package]] name = "mongomock" -version = "4.1.2" +version = "4.3.0" description = "Fake pymongo stub for testing simple MongoDB-dependent code" optional = false python-versions = "*" groups = ["dev"] files = [ - {file = "mongomock-4.1.2-py2.py3-none-any.whl", hash = "sha256:08a24938a05c80c69b6b8b19a09888d38d8c6e7328547f94d46cadb7f47209f2"}, - {file = "mongomock-4.1.2.tar.gz", hash = "sha256:f06cd62afb8ae3ef63ba31349abd220a657ef0dd4f0243a29587c5213f931b7d"}, + {file = "mongomock-4.3.0-py2.py3-none-any.whl", hash = "sha256:5ef86bd12fc8806c6e7af32f21266c61b6c4ba96096f85129852d1c4fec1327e"}, + {file = "mongomock-4.3.0.tar.gz", hash = "sha256:32667b79066fabc12d4f17f16a8fd7361b5f4435208b3ba32c226e52212a8c30"}, ] [package.dependencies] packaging = "*" +pytz = "*" sentinels = "*" +[package.extras] +pyexecjs = ["pyexecjs"] +pymongo = ["pymongo"] + [[package]] name = "packaging" -version = "24.0" +version = "25.0" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "promise" @@ -340,74 +392,100 @@ six = "*" [package.extras] test = ["coveralls", "futures", "mock", "pytest (>=2.7.3)", "pytest-benchmark", "pytest-cov"] +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pymongo" -version = "4.7.2" -description = "Python driver for MongoDB " +version = "4.15.3" +description = "PyMongo - the Official MongoDB Python driver" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pymongo-4.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:268d8578c0500012140c5460755ea405cbfe541ef47c81efa9d6744f0f99aeca"}, - {file = "pymongo-4.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:827611beb6c483260d520cfa6a49662d980dfa5368a04296f65fa39e78fccea7"}, - {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a754e366c404d19ff3f077ddeed64be31e0bb515e04f502bf11987f1baa55a16"}, - {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c44efab10d9a3db920530f7bcb26af8f408b7273d2f0214081d3891979726328"}, - {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35b3f0c7d49724859d4df5f0445818d525824a6cd55074c42573d9b50764df67"}, - {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e37faf298a37ffb3e0809e77fbbb0a32b6a2d18a83c59cfc2a7b794ea1136b0"}, - {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1bcd58669e56c08f1e72c5758868b5df169fe267501c949ee83c418e9df9155"}, - {file = "pymongo-4.7.2-cp310-cp310-win32.whl", hash = "sha256:c72d16fede22efe7cdd1f422e8da15760e9498024040429362886f946c10fe95"}, - {file = "pymongo-4.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:12d1fef77d25640cb78893d07ff7d2fac4c4461d8eec45bd3b9ad491a1115d6e"}, - {file = "pymongo-4.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fc5af24fcf5fc6f7f40d65446400d45dd12bea933d0299dc9e90c5b22197f1e9"}, - {file = "pymongo-4.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:730778b6f0964b164c187289f906bbc84cb0524df285b7a85aa355bbec43eb21"}, - {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47a1a4832ef2f4346dcd1a10a36ade7367ad6905929ddb476459abb4fd1b98cb"}, - {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6eab12c6385526d386543d6823b07187fefba028f0da216506e00f0e1855119"}, - {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37e9ea81fa59ee9274457ed7d59b6c27f6f2a5fe8e26f184ecf58ea52a019cb8"}, - {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e9d9d2c0aae73aa4369bd373ac2ac59f02c46d4e56c4b6d6e250cfe85f76802"}, - {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6e00a79dff22c9a72212ad82021b54bdb3b85f38a85f4fc466bde581d7d17a"}, - {file = "pymongo-4.7.2-cp311-cp311-win32.whl", hash = "sha256:02efd1bb3397e24ef2af45923888b41a378ce00cb3a4259c5f4fc3c70497a22f"}, - {file = "pymongo-4.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:87bb453ac3eb44db95cb6d5a616fbc906c1c00661eec7f55696253a6245beb8a"}, - {file = "pymongo-4.7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:12c466e02133b7f8f4ff1045c6b5916215c5f7923bc83fd6e28e290cba18f9f6"}, - {file = "pymongo-4.7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f91073049c43d14e66696970dd708d319b86ee57ef9af359294eee072abaac79"}, - {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87032f818bf5052ab742812c715eff896621385c43f8f97cdd37d15b5d394e95"}, - {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a87eef394039765679f75c6a47455a4030870341cb76eafc349c5944408c882"}, - {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d275596f840018858757561840767b39272ac96436fcb54f5cac6d245393fd97"}, - {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82102e353be13f1a6769660dd88115b1da382447672ba1c2662a0fbe3df1d861"}, - {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194065c9d445017b3c82fb85f89aa2055464a080bde604010dc8eb932a6b3c95"}, - {file = "pymongo-4.7.2-cp312-cp312-win32.whl", hash = "sha256:db4380d1e69fdad1044a4b8f3bb105200542c49a0dde93452d938ff9db1d6d29"}, - {file = "pymongo-4.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:fadc6e8db7707c861ebe25b13ad6aca19ea4d2c56bf04a26691f46c23dadf6e4"}, - {file = "pymongo-4.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2cb77d09bd012cb4b30636e7e38d00b5f9be5eb521c364bde66490c45ee6c4b4"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56bf8b706946952acdea0fe478f8e44f1ed101c4b87f046859e6c3abe6c0a9f4"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcf337d1b252405779d9c79978d6ca15eab3cdaa2f44c100a79221bddad97c8a"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ffd1519edbe311df73c74ec338de7d294af535b2748191c866ea3a7c484cd15"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d59776f435564159196d971aa89422ead878174aff8fe18e06d9a0bc6d648c"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:347c49cf7f0ba49ea87c1a5a1984187ecc5516b7c753f31938bf7b37462824fd"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:84bc00200c3cbb6c98a2bb964c9e8284b641e4a33cf10c802390552575ee21de"}, - {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fcaf8c911cb29316a02356f89dbc0e0dfcc6a712ace217b6b543805690d2aefd"}, - {file = "pymongo-4.7.2-cp37-cp37m-win32.whl", hash = "sha256:b48a5650ee5320d59f6d570bd99a8d5c58ac6f297a4e9090535f6561469ac32e"}, - {file = "pymongo-4.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:5239ef7e749f1326ea7564428bf861d5250aa39d7f26d612741b1b1273227062"}, - {file = "pymongo-4.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2dcf608d35644e8d276d61bf40a93339d8d66a0e5f3e3f75b2c155a421a1b71"}, - {file = "pymongo-4.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:25eeb2c18ede63891cbd617943dd9e6b9cbccc54f276e0b2e693a0cc40f243c5"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9349f0bb17a31371d4cacb64b306e4ca90413a3ad1fffe73ac7cd495570d94b5"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffd4d7cb2e6c6e100e2b39606d38a9ffc934e18593dc9bb326196afc7d93ce3d"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a8bd37f5dabc86efceb8d8cbff5969256523d42d08088f098753dba15f3b37a"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c78f156edc59b905c80c9003e022e1a764c54fd40ac4fea05b0764f829790e2"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d892fb91e81cccb83f507cdb2ea0aa026ec3ced7f12a1d60f6a5bf0f20f9c1f"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:87832d6076c2c82f42870157414fd876facbb6554d2faf271ffe7f8f30ce7bed"}, - {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ce1a374ea0e49808e0380ffc64284c0ce0f12bd21042b4bef1af3eb7bdf49054"}, - {file = "pymongo-4.7.2-cp38-cp38-win32.whl", hash = "sha256:eb0642e5f0dd7e86bb358749cc278e70b911e617f519989d346f742dc9520dfb"}, - {file = "pymongo-4.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:4bdb5ffe1cd3728c9479671a067ef44dacafc3743741d4dc700c377c4231356f"}, - {file = "pymongo-4.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:743552033c63f0afdb56b9189ab04b5c1dbffd7310cf7156ab98eebcecf24621"}, - {file = "pymongo-4.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5239776633f7578b81207e5646245415a5a95f6ae5ef5dff8e7c2357e6264bfc"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727ad07952c155cd20045f2ce91143c7dc4fb01a5b4e8012905a89a7da554b0c"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9385654f01a90f73827af4db90c290a1519f7d9102ba43286e187b373e9a78e9"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d833651f1ba938bb7501f13e326b96cfbb7d98867b2d545ca6d69c7664903e0"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf17ea9cea14d59b0527403dd7106362917ced7c4ec936c4ba22bd36c912c8e0"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cecd2df037249d1c74f0af86fb5b766104a5012becac6ff63d85d1de53ba8b98"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65b4c00dedbd333698b83cd2095a639a6f0d7c4e2a617988f6c65fb46711f028"}, - {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d9b6cbc037108ff1a0a867e7670d8513c37f9bcd9ee3d2464411bfabf70ca002"}, - {file = "pymongo-4.7.2-cp39-cp39-win32.whl", hash = "sha256:cf28430ec1924af1bffed37b69a812339084697fd3f3e781074a0148e6475803"}, - {file = "pymongo-4.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:e004527ea42a6b99a8b8d5b42b42762c3bdf80f88fbdb5c3a9d47f3808495b86"}, - {file = "pymongo-4.7.2.tar.gz", hash = "sha256:9024e1661c6e40acf468177bf90ce924d1bc681d2b244adda3ed7b2f4c4d17d7"}, + {file = "pymongo-4.15.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:482ca9b775747562ce1589df10c97a0e62a604ce5addf933e5819dd967c5e23c"}, + {file = "pymongo-4.15.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7eb497519f42ac89c30919a51f80e68a070cfc2f3b0543cac74833cd45a6b9c"}, + {file = "pymongo-4.15.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4a0a054e9937ec8fdb465835509b176f6b032851c8648f6a5d1b19932d0eacd6"}, + {file = "pymongo-4.15.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49fd6e158cf75771b2685a8a221a40ab96010ae34dd116abd06371dc6c38ab60"}, + {file = "pymongo-4.15.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82a490f1ade4ec6a72068e3676b04c126e3043e69b38ec474a87c6444cf79098"}, + {file = "pymongo-4.15.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:982107c667921e896292f4be09c057e2f1a40c645c9bfc724af5dd5fb8398094"}, + {file = "pymongo-4.15.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45aebbd369ca79b7c46eaea5b04d2e4afca4eda117b68965a07a9da05d774e4d"}, + {file = "pymongo-4.15.3-cp310-cp310-win32.whl", hash = "sha256:90ad56bd1d769d2f44af74f0fd0c276512361644a3c636350447994412cbc9a1"}, + {file = "pymongo-4.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:8bd6dd736f5d07a825caf52c38916d5452edc0fac7aee43ec67aba6f61c2dbb7"}, + {file = "pymongo-4.15.3-cp310-cp310-win_arm64.whl", hash = "sha256:300eaf83ad053e51966be1839324341b08eaf880d3dc63ada7942d5912e09c49"}, + {file = "pymongo-4.15.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a13d8f7141294404ce46dfbabb2f2d17e9b1192456651ae831fa351f86fbeb"}, + {file = "pymongo-4.15.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:17d13458baf4a6a9f2e787d95adf8ec50d412accb9926a044bd1c41029c323b2"}, + {file = "pymongo-4.15.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fe4bcb8acfb288e238190397d4a699aeb4adb70e8545a6f4e44f99d4e8096ab1"}, + {file = "pymongo-4.15.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d09d895c7f08bcbed4d2e96a00e52e9e545ae5a37b32d2dc10099b205a21fc6d"}, + {file = "pymongo-4.15.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:21c0a95a4db72562fd0805e2f76496bf432ba2e27a5651f4b9c670466260c258"}, + {file = "pymongo-4.15.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89e45d7fa987f4e246cdf43ff001e3f911f73eb19ba9dabc2a6d80df5c97883b"}, + {file = "pymongo-4.15.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1246a82fa6dd73ac2c63aa7e463752d5d1ca91e0c7a23396b78f21273befd3a7"}, + {file = "pymongo-4.15.3-cp311-cp311-win32.whl", hash = "sha256:9483521c03f6017336f54445652ead3145154e8d3ea06418e52cea57fee43292"}, + {file = "pymongo-4.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:c57dad9f289d72af1d7c47a444c4d9fa401f951cedbbcc54c7dd0c2107d6d786"}, + {file = "pymongo-4.15.3-cp311-cp311-win_arm64.whl", hash = "sha256:2fd3b99520f2bb013960ac29dece1b43f2f1b6d94351ca33ba1b1211ecf79a09"}, + {file = "pymongo-4.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bd0497c564b0ae34fb816464ffc09986dd9ca29e2772a0f7af989e472fecc2ad"}, + {file = "pymongo-4.15.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:292fd5a3f045751a823a54cdea75809b2216a62cc5f74a1a96b337db613d46a8"}, + {file = "pymongo-4.15.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:959ef69c5e687b6b749fbf2140c7062abdb4804df013ae0507caabf30cba6875"}, + {file = "pymongo-4.15.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de3bc878c3be54ae41c2cabc9e9407549ed4fec41f4e279c04e840dddd7c630c"}, + {file = "pymongo-4.15.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07bcc36d11252f24fe671e7e64044d39a13d997b0502c6401161f28cc144f584"}, + {file = "pymongo-4.15.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b63bac343b79bd209e830aac1f5d9d552ff415f23a924d3e51abbe3041265436"}, + {file = "pymongo-4.15.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b33d59bf6fa1ca1d7d96d4fccff51e41312358194190d53ef70a84c070f5287e"}, + {file = "pymongo-4.15.3-cp312-cp312-win32.whl", hash = "sha256:b3a0ec660d61efb91c16a5962ec937011fe3572c4338216831f102e53d294e5c"}, + {file = "pymongo-4.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:f6b0513e5765fdde39f36e6a29a36c67071122b5efa748940ae51075beb5e4bc"}, + {file = "pymongo-4.15.3-cp312-cp312-win_arm64.whl", hash = "sha256:c4fdd8e6eab8ff77c1c8041792b5f760d48508623cd10b50d5639e73f1eec049"}, + {file = "pymongo-4.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a47a3218f7900f65bf0f36fcd1f2485af4945757360e7e143525db9d715d2010"}, + {file = "pymongo-4.15.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:09440e78dff397b2f34a624f445ac8eb44c9756a2688b85b3bf344d351d198e1"}, + {file = "pymongo-4.15.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:97f9babdb98c31676f97d468f7fe2dc49b8a66fb6900effddc4904c1450196c8"}, + {file = "pymongo-4.15.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71413cd8f091ae25b1fec3af7c2e531cf9bdb88ce4079470e64835f6a664282a"}, + {file = "pymongo-4.15.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:76a8d4de8dceb69f6e06736198ff6f7e1149515ef946f192ff2594d2cc98fc53"}, + {file = "pymongo-4.15.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:77353978be9fc9e5fe56369682efed0aac5f92a2a1570704d62b62a3c9e1a24f"}, + {file = "pymongo-4.15.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9897a837677e3814873d0572f7e5d53c23ce18e274f3b5b87f05fb6eea22615b"}, + {file = "pymongo-4.15.3-cp313-cp313-win32.whl", hash = "sha256:d66da207ccb0d68c5792eaaac984a0d9c6c8ec609c6bcfa11193a35200dc5992"}, + {file = "pymongo-4.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:52f40c4b8c00bc53d4e357fe0de13d031c4cddb5d201e1a027db437e8d2887f8"}, + {file = "pymongo-4.15.3-cp313-cp313-win_arm64.whl", hash = "sha256:fb384623ece34db78d445dd578a52d28b74e8319f4d9535fbaff79d0eae82b3d"}, + {file = "pymongo-4.15.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dcff15b9157c16bc796765d4d3d151df669322acfb0357e4c3ccd056153f0ff4"}, + {file = "pymongo-4.15.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1f681722c9f27e86c49c2e8a838e61b6ecf2285945fd1798bd01458134257834"}, + {file = "pymongo-4.15.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2c96dde79bdccd167b930a709875b0cd4321ac32641a490aebfa10bdcd0aa99b"}, + {file = "pymongo-4.15.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d2d4ca446348d850ac4a5c3dc603485640ae2e7805dbb90765c3ba7d79129b37"}, + {file = "pymongo-4.15.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7c0fd3de3a12ff0a8113a3f64cedb01f87397ab8eaaffa88d7f18ca66cd39385"}, + {file = "pymongo-4.15.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e84dec392cf5f72d365e0aac73f627b0a3170193ebb038c3f7e7df11b7983ee7"}, + {file = "pymongo-4.15.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d4b01a48369ea6d5bc83fea535f56279f806aa3e4991189f0477696dd736289"}, + {file = "pymongo-4.15.3-cp314-cp314-win32.whl", hash = "sha256:3561fa96c3123275ec5ccf919e595547e100c412ec0894e954aa0da93ecfdb9e"}, + {file = "pymongo-4.15.3-cp314-cp314-win_amd64.whl", hash = "sha256:9df2db6bd91b07400879b6ec89827004c0c2b55fc606bb62db93cafb7677c340"}, + {file = "pymongo-4.15.3-cp314-cp314-win_arm64.whl", hash = "sha256:ff99864085d2c7f4bb672c7167680ceb7d273e9a93c1a8074c986a36dbb71cc6"}, + {file = "pymongo-4.15.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ffe217d2502f3fba4e2b0dc015ce3b34f157b66dfe96835aa64432e909dd0d95"}, + {file = "pymongo-4.15.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:390c4954c774eda280898e73aea36482bf20cba3ecb958dbb86d6a68b9ecdd68"}, + {file = "pymongo-4.15.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7dd2a49f088890ca08930bbf96121443b48e26b02b84ba0a3e1ae2bf2c5a9b48"}, + {file = "pymongo-4.15.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f6feb678f26171f2a6b2cbb340949889154c7067972bd4cc129b62161474f08"}, + {file = "pymongo-4.15.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:446417a34ff6c2411ce3809e17ce9a67269c9f1cb4966b01e49e0c590cc3c6b3"}, + {file = "pymongo-4.15.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cfa4a0a0f024a0336640e1201994e780a17bda5e6a7c0b4d23841eb9152e868b"}, + {file = "pymongo-4.15.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b03db2fe37c950aff94b29ded5c349b23729bccd90a0a5907bbf807d8c77298"}, + {file = "pymongo-4.15.3-cp314-cp314t-win32.whl", hash = "sha256:e7cde58ef6470c0da922b65e885fb1ffe04deef81e526bd5dea429290fa358ca"}, + {file = "pymongo-4.15.3-cp314-cp314t-win_amd64.whl", hash = "sha256:fae552767d8e5153ed498f1bca92d905d0d46311d831eefb0f06de38f7695c95"}, + {file = "pymongo-4.15.3-cp314-cp314t-win_arm64.whl", hash = "sha256:47ffb068e16ae5e43580d5c4e3b9437f05414ea80c32a1e5cac44a835859c259"}, + {file = "pymongo-4.15.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:58d0f4123855f05c0649f9b8ee083acc5b26e7f4afde137cd7b8dc03e9107ff3"}, + {file = "pymongo-4.15.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9bc9f99e7702fdb0dcc3ff1dd490adc5d20b3941ad41e58f887d4998b9922a14"}, + {file = "pymongo-4.15.3-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:86b1b5b63f4355adffc329733733a9b71fdad88f37a9dc41e163aed2130f9abc"}, + {file = "pymongo-4.15.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a054d282dd922ac400b6f47ea3ef58d8b940968d76d855da831dc739b7a04de"}, + {file = "pymongo-4.15.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc583a1130e2516440b93bb2ecb55cfdac6d5373615ae472a9d1f26801f58749"}, + {file = "pymongo-4.15.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c78237e878e0296130e398151b0d4aa6c9eaf82e38fb6e0aaae2029bc7ef0ce"}, + {file = "pymongo-4.15.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c85a4c72b7965033f95c94c42dac27d886c01dbc23fe337ccb14f052a0ccc29"}, + {file = "pymongo-4.15.3-cp39-cp39-win32.whl", hash = "sha256:17fc94d1e067556b122eeb09e25c003268e8c0ea1f2f78e745b33bb59a1209c4"}, + {file = "pymongo-4.15.3-cp39-cp39-win_amd64.whl", hash = "sha256:5bf879a6ed70264574d4d8fb5a467c2a64dc76ecd72c0cb467c4464f849c8c77"}, + {file = "pymongo-4.15.3-cp39-cp39-win_arm64.whl", hash = "sha256:2f3d66f7c495efc3cfffa611b36075efe86da1860a7df75522a6fe499ee10383"}, + {file = "pymongo-4.15.3.tar.gz", hash = "sha256:7a981271347623b5319932796690c2d301668ac3a1965974ac9f5c3b8a22cea5"}, ] [package.dependencies] @@ -415,35 +493,37 @@ dnspython = ">=1.16.0,<3.0.0" [package.extras] aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] -encryption = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.6.0,<2.0.0)"] +docs = ["furo (==2025.7.19)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<9)", "sphinx-autobuild (>=2020.9.1)", "sphinx-rtd-theme (>=2,<4)", "sphinxcontrib-shellcheck (>=1,<2)"] +encryption = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.13.0,<2.0.0)"] gssapi = ["pykerberos ; os_name != \"nt\"", "winkerberos (>=0.5.0) ; os_name == \"nt\""] ocsp = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] snappy = ["python-snappy"] -test = ["pytest (>=7)"] +test = ["pytest (>=8.2)", "pytest-asyncio (>=0.24.0)"] zstd = ["zstandard"] [[package]] name = "pytest" -version = "8.2.1" +version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"}, - {file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"}, + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2.0" +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" @@ -466,61 +546,95 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy [[package]] name = "pytest-cov" -version = "5.0.0" +version = "7.0.0" description = "Pytest plugin for measuring coverage." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, - {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, + {file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"}, + {file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"}, ] [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" +coverage = {version = ">=7.10.6", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=7" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +testing = ["process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2025.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, + {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, +] [[package]] name = "ruff" -version = "0.4.7" +version = "0.14.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.4.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e089371c67892a73b6bb1525608e89a2aca1b77b5440acf7a71dda5dac958f9e"}, - {file = "ruff-0.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:10f973d521d910e5f9c72ab27e409e839089f955be8a4c8826601a6323a89753"}, - {file = "ruff-0.4.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59c3d110970001dfa494bcd95478e62286c751126dfb15c3c46e7915fc49694f"}, - {file = "ruff-0.4.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa9773c6c00f4958f73b317bc0fd125295110c3776089f6ef318f4b775f0abe4"}, - {file = "ruff-0.4.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07fc80bbb61e42b3b23b10fda6a2a0f5a067f810180a3760c5ef1b456c21b9db"}, - {file = "ruff-0.4.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fa4dafe3fe66d90e2e2b63fa1591dd6e3f090ca2128daa0be33db894e6c18648"}, - {file = "ruff-0.4.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7c0083febdec17571455903b184a10026603a1de078428ba155e7ce9358c5f6"}, - {file = "ruff-0.4.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad1b20e66a44057c326168437d680a2166c177c939346b19c0d6b08a62a37589"}, - {file = "ruff-0.4.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbf5d818553add7511c38b05532d94a407f499d1a76ebb0cad0374e32bc67202"}, - {file = "ruff-0.4.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:50e9651578b629baec3d1513b2534de0ac7ed7753e1382272b8d609997e27e83"}, - {file = "ruff-0.4.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8874a9df7766cb956b218a0a239e0a5d23d9e843e4da1e113ae1d27ee420877a"}, - {file = "ruff-0.4.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b9de9a6e49f7d529decd09381c0860c3f82fa0b0ea00ea78409b785d2308a567"}, - {file = "ruff-0.4.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:13a1768b0691619822ae6d446132dbdfd568b700ecd3652b20d4e8bc1e498f78"}, - {file = "ruff-0.4.7-py3-none-win32.whl", hash = "sha256:769e5a51df61e07e887b81e6f039e7ed3573316ab7dd9f635c5afaa310e4030e"}, - {file = "ruff-0.4.7-py3-none-win_amd64.whl", hash = "sha256:9e3ab684ad403a9ed1226894c32c3ab9c2e0718440f6f50c7c5829932bc9e054"}, - {file = "ruff-0.4.7-py3-none-win_arm64.whl", hash = "sha256:10f2204b9a613988e3484194c2c9e96a22079206b22b787605c255f130db5ed7"}, - {file = "ruff-0.4.7.tar.gz", hash = "sha256:2331d2b051dc77a289a653fcc6a42cce357087c5975738157cd966590b18b5e1"}, + {file = "ruff-0.14.1-py3-none-linux_armv6l.whl", hash = "sha256:083bfc1f30f4a391ae09c6f4f99d83074416b471775b59288956f5bc18e82f8b"}, + {file = "ruff-0.14.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f6fa757cd717f791009f7669fefb09121cc5f7d9bd0ef211371fad68c2b8b224"}, + {file = "ruff-0.14.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6191903d39ac156921398e9c86b7354d15e3c93772e7dbf26c9fcae59ceccd5"}, + {file = "ruff-0.14.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed04f0e04f7a4587244e5c9d7df50e6b5bf2705d75059f409a6421c593a35896"}, + {file = "ruff-0.14.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c9e6cf6cd4acae0febbce29497accd3632fe2025c0c583c8b87e8dbdeae5f61"}, + {file = "ruff-0.14.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fa2458527794ecdfbe45f654e42c61f2503a230545a91af839653a0a93dbc6"}, + {file = "ruff-0.14.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:39f1c392244e338b21d42ab29b8a6392a722c5090032eb49bb4d6defcdb34345"}, + {file = "ruff-0.14.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7382fa12a26cce1f95070ce450946bec357727aaa428983036362579eadcc5cf"}, + {file = "ruff-0.14.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0bf2be3ae8521e1093a487c4aa3b455882f139787770698530d28ed3fbb37c"}, + {file = "ruff-0.14.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabcaa9ccf8089fb4fdb78d17cc0e28241520f50f4c2e88cb6261ed083d85151"}, + {file = "ruff-0.14.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:747d583400f6125ec11a4c14d1c8474bf75d8b419ad22a111a537ec1a952d192"}, + {file = "ruff-0.14.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5a6e74c0efd78515a1d13acbfe6c90f0f5bd822aa56b4a6d43a9ffb2ae6e56cd"}, + {file = "ruff-0.14.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0ea6a864d2fb41a4b6d5b456ed164302a0d96f4daac630aeba829abfb059d020"}, + {file = "ruff-0.14.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0826b8764f94229604fa255918d1cc45e583e38c21c203248b0bfc9a0e930be5"}, + {file = "ruff-0.14.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cbc52160465913a1a3f424c81c62ac8096b6a491468e7d872cb9444a860bc33d"}, + {file = "ruff-0.14.1-py3-none-win32.whl", hash = "sha256:e037ea374aaaff4103240ae79168c0945ae3d5ae8db190603de3b4012bd1def6"}, + {file = "ruff-0.14.1-py3-none-win_amd64.whl", hash = "sha256:59d599cdff9c7f925a017f6f2c256c908b094e55967f93f2821b1439928746a1"}, + {file = "ruff-0.14.1-py3-none-win_arm64.whl", hash = "sha256:e3b443c4c9f16ae850906b8d0a707b2a4c16f8d2f0a7fe65c475c5886665ce44"}, + {file = "ruff-0.14.1.tar.gz", hash = "sha256:1dd86253060c4772867c61791588627320abcb6ed1577a90ef432ee319729b69"}, ] [[package]] name = "sentinels" -version = "1.0.0" +version = "1.1.1" description = "Various objects to denote special meanings in python" optional = false -python-versions = "*" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "sentinels-1.0.0.tar.gz", hash = "sha256:7be0704d7fe1925e397e92d18669ace2f619c92b5d4eb21a89f31e026f9ff4b1"}, + {file = "sentinels-1.1.1-py3-none-any.whl", hash = "sha256:835d3b28f3b47f5284afa4bf2db6e00f2dc5f80f9923d4b7e7aeeeccf6146a11"}, + {file = "sentinels-1.1.1.tar.gz", hash = "sha256:3c2f64f754187c19e0a1a029b148b74cf58dd12ec27b4e19c0e5d6e22b5a9a86"}, ] +[package.extras] +testing = ["pylint", "pytest"] + [[package]] name = "setuptools" version = "78.1.1" @@ -544,41 +658,81 @@ type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.deve [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] name = "tomli" -version = "2.0.1" +version = "2.3.0" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] markers = "python_full_version <= \"3.11.0a6\"" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, + {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, + {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, + {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, + {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, + {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, + {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, + {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, + {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, + {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, + {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, + {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, + {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, + {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, ] [[package]] name = "typing-extensions" -version = "4.12.1" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version < \"3.11\"" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, - {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] +markers = {dev = "python_version < \"3.11\""} [metadata] lock-version = "2.1" diff --git a/pyproject.toml b/pyproject.toml index 7ced398..923747e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ keywords = [ "graphene-mongo", "graphql", "api", "graphql", "protocol", "relay", "graphene", "mongo", "mongoengine" ] +exclude = ["graphene_mongo/tests"] [tool.poetry.dependencies] python = ">=3.9,<4" From a93c58e27139d6f99dd31c65053516aed8f96658 Mon Sep 17 00:00:00 2001 From: Abhinand C Date: Fri, 24 Oct 2025 20:09:34 +0530 Subject: [PATCH 13/14] fix(ConnectionField): Priorities pk__in filter for finding count of nested connection field --- graphene_mongo/fields.py | 37 ++++++++++++++++---------------- graphene_mongo/fields_async.py | 39 +++++++++++++++++----------------- 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/graphene_mongo/fields.py b/graphene_mongo/fields.py index 32b2a69..8931d14 100644 --- a/graphene_mongo/fields.py +++ b/graphene_mongo/fields.py @@ -424,7 +424,24 @@ def default_resolver(self, _root, info, required_fields=None, resolved=None, **a list_length = len(iterables) elif callable(getattr(self.model, "objects", None)): - if ( + if "pk__in" in args and args["pk__in"]: + count = len(args["pk__in"]) + skip, limit = find_skip_and_limit( + first=first, last=last, after=after, before=before, count=count + ) + if limit: + args["pk__in"] = args["pk__in"][skip : skip + limit] + elif skip: + args["pk__in"] = args["pk__in"][skip:] + iterables = self.get_queryset(self.model, info, required_fields, **args) + list_length = len(iterables) + if isinstance(info, GraphQLResolveInfo): + if not info.context: + info = info._replace(context=Context()) + info.context.queryset = self.get_queryset( + self.model, info, required_fields, **args + ) + elif ( _root is None or args or isinstance(getattr(_root, field_name, []), MongoengineConnectionField) @@ -486,24 +503,6 @@ def default_resolver(self, _root, info, required_fields=None, resolved=None, **a self.model, info, required_fields, **args ) - elif "pk__in" in args and args["pk__in"]: - count = len(args["pk__in"]) - skip, limit = find_skip_and_limit( - first=first, last=last, after=after, before=before, count=count - ) - if limit: - args["pk__in"] = args["pk__in"][skip : skip + limit] - elif skip: - args["pk__in"] = args["pk__in"][skip:] - iterables = self.get_queryset(self.model, info, required_fields, **args) - list_length = len(iterables) - if isinstance(info, GraphQLResolveInfo): - if not info.context: - info = info._replace(context=Context()) - info.context.queryset = self.get_queryset( - self.model, info, required_fields, **args - ) - elif _root is not None: field_name = to_snake_case(info.field_name) items = getattr(_root, field_name, []) diff --git a/graphene_mongo/fields_async.py b/graphene_mongo/fields_async.py index 5654b68..edf4ba7 100644 --- a/graphene_mongo/fields_async.py +++ b/graphene_mongo/fields_async.py @@ -150,7 +150,25 @@ async def default_resolver(self, _root, info, required_fields=None, resolved=Non list_length = len(iterables) elif callable(getattr(self.model, "objects", None)): - if ( + if "pk__in" in args and args["pk__in"]: + count = len(args["pk__in"]) + skip, limit = find_skip_and_limit( + first=first, last=last, after=after, before=before, count=count + ) + if limit: + args["pk__in"] = args["pk__in"][skip : skip + limit] + elif skip: + args["pk__in"] = args["pk__in"][skip:] + iterables = self.get_queryset(self.model, info, required_fields, **args) + iterables = await sync_to_async(list)(iterables) + list_length = len(iterables) + if isinstance(info, GraphQLResolveInfo): + if not info.context: + info = info._replace(context=Context()) + info.context.queryset = self.get_queryset( + self.model, info, required_fields, **args + ) + elif ( _root is None or args or isinstance(getattr(_root, field_name, []), AsyncMongoengineConnectionField) @@ -206,25 +224,6 @@ async def default_resolver(self, _root, info, required_fields=None, resolved=Non self.model, info, required_fields, **args ) - elif "pk__in" in args and args["pk__in"]: - count = len(args["pk__in"]) - skip, limit = find_skip_and_limit( - first=first, last=last, after=after, before=before, count=count - ) - if limit: - args["pk__in"] = args["pk__in"][skip : skip + limit] - elif skip: - args["pk__in"] = args["pk__in"][skip:] - iterables = self.get_queryset(self.model, info, required_fields, **args) - iterables = await sync_to_async(list)(iterables) - list_length = len(iterables) - if isinstance(info, GraphQLResolveInfo): - if not info.context: - info = info._replace(context=Context()) - info.context.queryset = self.get_queryset( - self.model, info, required_fields, **args - ) - elif _root is not None: field_name = to_snake_case(info.field_name) items = getattr(_root, field_name, []) From 33f5afa61cca120c5242c5aa3ef59f6ad4a5727f Mon Sep 17 00:00:00 2001 From: Abhinand C Date: Mon, 27 Oct 2025 13:07:14 +0530 Subject: [PATCH 14/14] fix(tests): Run tests on PR, and add support for 3.13, 3.14 --- .github/workflows/ci.yml | 3 +-- .github/workflows/publish.yml | 2 +- pyproject.toml | 2 ++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30f424f..bd98aac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,6 @@ on: push: branches: [ "master" ] pull_request: - branches: [ "master" ] permissions: contents: read @@ -17,7 +16,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python: ["3.9", "3.10", "3.11","3.12"] + python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 332031a..2fd7842 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python: ["3.9", "3.10", "3.11", "3.12"] + python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 diff --git a/pyproject.toml b/pyproject.toml index 923747e..2af084f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,8 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: PyPy", "License :: OSI Approved :: MIT License", ]