MindsDB 25.8.3.0__py3-none-any.whl → 25.9.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of MindsDB might be problematic. Click here for more details.

Files changed (109) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +3 -45
  3. mindsdb/api/a2a/__init__.py +52 -0
  4. mindsdb/api/a2a/agent.py +11 -12
  5. mindsdb/api/a2a/common/server/server.py +17 -36
  6. mindsdb/api/a2a/common/server/task_manager.py +14 -28
  7. mindsdb/api/a2a/task_manager.py +20 -21
  8. mindsdb/api/a2a/utils.py +1 -1
  9. mindsdb/api/common/middleware.py +106 -0
  10. mindsdb/api/executor/utilities/mysql_to_duckdb_functions.py +466 -18
  11. mindsdb/api/executor/utilities/sql.py +9 -31
  12. mindsdb/api/http/initialize.py +34 -43
  13. mindsdb/api/http/namespaces/auth.py +6 -14
  14. mindsdb/api/http/namespaces/config.py +0 -2
  15. mindsdb/api/http/namespaces/default.py +74 -106
  16. mindsdb/api/http/namespaces/file.py +9 -3
  17. mindsdb/api/http/namespaces/handlers.py +77 -87
  18. mindsdb/api/http/start.py +29 -47
  19. mindsdb/api/litellm/start.py +11 -10
  20. mindsdb/api/mcp/__init__.py +165 -0
  21. mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +33 -64
  22. mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +86 -85
  23. mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
  24. mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
  25. mindsdb/integrations/handlers/crate_handler/crate_handler.py +3 -7
  26. mindsdb/integrations/handlers/derby_handler/derby_handler.py +32 -34
  27. mindsdb/integrations/handlers/documentdb_handler/requirements.txt +1 -0
  28. mindsdb/integrations/handlers/dummy_data_handler/dummy_data_handler.py +12 -13
  29. mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
  30. mindsdb/integrations/handlers/google_books_handler/google_books_handler.py +45 -44
  31. mindsdb/integrations/handlers/google_calendar_handler/google_calendar_handler.py +101 -95
  32. mindsdb/integrations/handlers/google_content_shopping_handler/google_content_shopping_handler.py +129 -129
  33. mindsdb/integrations/handlers/google_fit_handler/google_fit_handler.py +59 -43
  34. mindsdb/integrations/handlers/google_search_handler/google_search_handler.py +38 -39
  35. mindsdb/integrations/handlers/informix_handler/informix_handler.py +5 -18
  36. mindsdb/integrations/handlers/lightfm_handler/requirements.txt +1 -1
  37. mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
  38. mindsdb/integrations/handlers/maxdb_handler/maxdb_handler.py +22 -28
  39. mindsdb/integrations/handlers/monetdb_handler/monetdb_handler.py +3 -7
  40. mindsdb/integrations/handlers/mongodb_handler/mongodb_handler.py +53 -67
  41. mindsdb/integrations/handlers/mongodb_handler/requirements.txt +1 -0
  42. mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_ast.py +43 -68
  43. mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_parser.py +17 -25
  44. mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_query.py +10 -16
  45. mindsdb/integrations/handlers/mongodb_handler/utils/mongodb_render.py +43 -69
  46. mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
  47. mindsdb/integrations/libs/base.py +1 -1
  48. mindsdb/integrations/libs/llm/config.py +15 -0
  49. mindsdb/integrations/libs/llm/utils.py +15 -0
  50. mindsdb/interfaces/agents/constants.py +1 -0
  51. mindsdb/interfaces/agents/langchain_agent.py +4 -0
  52. mindsdb/interfaces/agents/providers.py +20 -0
  53. mindsdb/interfaces/knowledge_base/controller.py +25 -7
  54. mindsdb/utilities/config.py +15 -158
  55. mindsdb/utilities/log.py +0 -25
  56. mindsdb/utilities/render/sqlalchemy_render.py +7 -1
  57. mindsdb/utilities/starters.py +0 -39
  58. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/METADATA +269 -267
  59. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/RECORD +62 -105
  60. mindsdb/api/a2a/__main__.py +0 -144
  61. mindsdb/api/a2a/run_a2a.py +0 -86
  62. mindsdb/api/common/check_auth.py +0 -42
  63. mindsdb/api/http/gunicorn_wrapper.py +0 -17
  64. mindsdb/api/mcp/start.py +0 -205
  65. mindsdb/api/mongo/__init__.py +0 -0
  66. mindsdb/api/mongo/classes/__init__.py +0 -5
  67. mindsdb/api/mongo/classes/query_sql.py +0 -19
  68. mindsdb/api/mongo/classes/responder.py +0 -45
  69. mindsdb/api/mongo/classes/responder_collection.py +0 -34
  70. mindsdb/api/mongo/classes/scram.py +0 -86
  71. mindsdb/api/mongo/classes/session.py +0 -23
  72. mindsdb/api/mongo/functions/__init__.py +0 -19
  73. mindsdb/api/mongo/responders/__init__.py +0 -73
  74. mindsdb/api/mongo/responders/add_shard.py +0 -13
  75. mindsdb/api/mongo/responders/aggregate.py +0 -90
  76. mindsdb/api/mongo/responders/buildinfo.py +0 -17
  77. mindsdb/api/mongo/responders/coll_stats.py +0 -63
  78. mindsdb/api/mongo/responders/company_id.py +0 -25
  79. mindsdb/api/mongo/responders/connection_status.py +0 -22
  80. mindsdb/api/mongo/responders/count.py +0 -21
  81. mindsdb/api/mongo/responders/db_stats.py +0 -32
  82. mindsdb/api/mongo/responders/delete.py +0 -105
  83. mindsdb/api/mongo/responders/describe.py +0 -23
  84. mindsdb/api/mongo/responders/end_sessions.py +0 -13
  85. mindsdb/api/mongo/responders/find.py +0 -175
  86. mindsdb/api/mongo/responders/get_cmd_line_opts.py +0 -18
  87. mindsdb/api/mongo/responders/get_free_monitoring_status.py +0 -14
  88. mindsdb/api/mongo/responders/get_parameter.py +0 -23
  89. mindsdb/api/mongo/responders/getlog.py +0 -14
  90. mindsdb/api/mongo/responders/host_info.py +0 -28
  91. mindsdb/api/mongo/responders/insert.py +0 -270
  92. mindsdb/api/mongo/responders/is_master.py +0 -20
  93. mindsdb/api/mongo/responders/is_master_lower.py +0 -13
  94. mindsdb/api/mongo/responders/list_collections.py +0 -55
  95. mindsdb/api/mongo/responders/list_databases.py +0 -37
  96. mindsdb/api/mongo/responders/list_indexes.py +0 -22
  97. mindsdb/api/mongo/responders/ping.py +0 -13
  98. mindsdb/api/mongo/responders/recv_chunk_start.py +0 -13
  99. mindsdb/api/mongo/responders/replsetgetstatus.py +0 -13
  100. mindsdb/api/mongo/responders/sasl_continue.py +0 -34
  101. mindsdb/api/mongo/responders/sasl_start.py +0 -33
  102. mindsdb/api/mongo/responders/update_range_deletions.py +0 -12
  103. mindsdb/api/mongo/responders/whatsmyuri.py +0 -18
  104. mindsdb/api/mongo/server.py +0 -388
  105. mindsdb/api/mongo/start.py +0 -15
  106. mindsdb/api/mongo/utilities/__init__.py +0 -0
  107. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/WHEEL +0 -0
  108. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/licenses/LICENSE +0 -0
  109. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/top_level.txt +0 -0
@@ -10,13 +10,13 @@ from pymongo import MongoClient
10
10
  from pymongo.errors import ServerSelectionTimeoutError, OperationFailure, ConfigurationError, InvalidURI
11
11
  from typing import Text, List, Dict, Any, Union
12
12
 
13
- from mindsdb.api.mongo.utilities.mongodb_query import MongoQuery
14
- from mindsdb.api.mongo.utilities.mongodb_parser import MongodbParser
13
+ from mindsdb.integrations.handlers.mongodb_handler.utils.mongodb_query import MongoQuery
14
+ from mindsdb.integrations.handlers.mongodb_handler.utils.mongodb_parser import MongodbParser
15
15
  from mindsdb.integrations.libs.base import DatabaseHandler
16
16
  from mindsdb.integrations.libs.response import (
17
17
  HandlerStatusResponse as StatusResponse,
18
18
  HandlerResponse as Response,
19
- RESPONSE_TYPE
19
+ RESPONSE_TYPE,
20
20
  )
21
21
  from mindsdb.utilities import log
22
22
  from .utils.mongodb_render import MongodbRender
@@ -31,7 +31,7 @@ class MongoDBHandler(DatabaseHandler):
31
31
  """
32
32
 
33
33
  _SUBSCRIBE_SLEEP_INTERVAL = 0.5
34
- name = 'mongodb'
34
+ name = "mongodb"
35
35
 
36
36
  def __init__(self, name: Text, **kwargs: Any) -> None:
37
37
  """
@@ -42,13 +42,13 @@ class MongoDBHandler(DatabaseHandler):
42
42
  kwargs: Arbitrary keyword arguments including the connection data.
43
43
  """
44
44
  super().__init__(name)
45
- connection_data = kwargs['connection_data']
45
+ connection_data = kwargs["connection_data"]
46
46
  self.host = connection_data.get("host")
47
47
  self.port = int(connection_data.get("port") or 27017)
48
48
  self.user = connection_data.get("username")
49
49
  self.password = connection_data.get("password")
50
- self.database = connection_data.get('database')
51
- self.flatten_level = connection_data.get('flatten_level', 0)
50
+ self.database = connection_data.get("database")
51
+ self.flatten_level = connection_data.get("flatten_level", 0)
52
52
 
53
53
  self.connection = None
54
54
  self.is_connected = False
@@ -72,31 +72,27 @@ class MongoDBHandler(DatabaseHandler):
72
72
  """
73
73
  kwargs = {}
74
74
  if isinstance(self.user, str) and len(self.user) > 0:
75
- kwargs['username'] = self.user
75
+ kwargs["username"] = self.user
76
76
 
77
77
  if isinstance(self.password, str) and len(self.password) > 0:
78
- kwargs['password'] = self.password
78
+ kwargs["password"] = self.password
79
79
 
80
- if re.match(r'/?.*tls=true', self.host.lower()):
81
- kwargs['tls'] = True
80
+ if re.match(r"/?.*tls=true", self.host.lower()):
81
+ kwargs["tls"] = True
82
82
 
83
- if re.match(r'/?.*tls=false', self.host.lower()):
84
- kwargs['tls'] = False
83
+ if re.match(r"/?.*tls=false", self.host.lower()):
84
+ kwargs["tls"] = False
85
85
 
86
86
  try:
87
- connection = MongoClient(
88
- self.host,
89
- port=self.port,
90
- **kwargs
91
- )
87
+ connection = MongoClient(self.host, port=self.port, **kwargs)
92
88
  except InvalidURI as invalid_uri_error:
93
- logger.error(f'Invalid URI provided for MongoDB connection: {invalid_uri_error}!')
89
+ logger.error(f"Invalid URI provided for MongoDB connection: {invalid_uri_error}!")
94
90
  raise
95
91
  except ConfigurationError as config_error:
96
- logger.error(f'Configuration error connecting to MongoDB: {config_error}!')
92
+ logger.error(f"Configuration error connecting to MongoDB: {config_error}!")
97
93
  raise
98
94
  except Exception as unknown_error:
99
- logger.error(f'Unknown error connecting to MongoDB: {unknown_error}!')
95
+ logger.error(f"Unknown error connecting to MongoDB: {unknown_error}!")
100
96
  raise
101
97
 
102
98
  # Get the database name from the connection if it's not provided.
@@ -107,7 +103,9 @@ class MongoDBHandler(DatabaseHandler):
107
103
  self.connection = connection
108
104
  return self.connection
109
105
 
110
- def subscribe(self, stop_event: threading.Event, callback: callable, table_name: Text, columns: List = None, **kwargs: Any) -> None:
106
+ def subscribe(
107
+ self, stop_event: threading.Event, callback: callable, table_name: Text, columns: List = None, **kwargs: Any
108
+ ) -> None:
111
109
  """
112
110
  Subscribes to changes in a MongoDB collection and calls the provided callback function when changes occur.
113
111
 
@@ -131,26 +129,26 @@ class MongoDBHandler(DatabaseHandler):
131
129
  time.sleep(self._SUBSCRIBE_SLEEP_INTERVAL)
132
130
  continue
133
131
 
134
- _id = res['documentKey']['_id']
135
- if res['operationType'] == 'insert':
132
+ _id = res["documentKey"]["_id"]
133
+ if res["operationType"] == "insert":
136
134
  if columns is not None:
137
- updated_columns = set(res['fullDocument'].keys())
135
+ updated_columns = set(res["fullDocument"].keys())
138
136
  if not set(columns) & set(updated_columns):
139
137
  # Do nothing.
140
138
  continue
141
139
 
142
- callback(row=res['fullDocument'], key={'_id': _id})
140
+ callback(row=res["fullDocument"], key={"_id": _id})
143
141
 
144
- if res['operationType'] == 'update':
142
+ if res["operationType"] == "update":
145
143
  if columns is not None:
146
- updated_columns = set(res['updateDescription']['updatedFields'].keys())
144
+ updated_columns = set(res["updateDescription"]["updatedFields"].keys())
147
145
  if not set(columns) & set(updated_columns):
148
146
  # Do nothing.
149
147
  continue
150
148
 
151
149
  # Get the full document.
152
- full_doc = con[self.database][table_name].find_one(res['documentKey'])
153
- callback(row=full_doc, key={'_id': _id})
150
+ full_doc = con[self.database][table_name].find_one(res["documentKey"])
151
+ callback(row=full_doc, key={"_id": _id})
154
152
 
155
153
  def disconnect(self) -> None:
156
154
  """
@@ -178,14 +176,20 @@ class MongoDBHandler(DatabaseHandler):
178
176
 
179
177
  # Check if the database exists.
180
178
  if self.database not in con.list_database_names():
181
- raise ValueError(f'Database {self.database} not found!')
179
+ raise ValueError(f"Database {self.database} not found!")
182
180
 
183
181
  response.success = True
184
- except (InvalidURI, ServerSelectionTimeoutError, OperationFailure, ConfigurationError, ValueError) as known_error:
185
- logger.error(f'Error connecting to MongoDB {self.database}, {known_error}!')
182
+ except (
183
+ InvalidURI,
184
+ ServerSelectionTimeoutError,
185
+ OperationFailure,
186
+ ConfigurationError,
187
+ ValueError,
188
+ ) as known_error:
189
+ logger.error(f"Error connecting to MongoDB {self.database}, {known_error}!")
186
190
  response.error_message = str(known_error)
187
191
  except Exception as unknown_error:
188
- logger.error(f'Unknown error connecting to MongoDB {self.database}, {unknown_error}!')
192
+ logger.error(f"Unknown error connecting to MongoDB {self.database}, {unknown_error}!")
189
193
  response.error_message = str(unknown_error)
190
194
 
191
195
  if response.success and need_to_close:
@@ -211,13 +215,10 @@ class MongoDBHandler(DatabaseHandler):
211
215
 
212
216
  if isinstance(query, dict):
213
217
  # Fallback for the previous API.
214
- mquery = MongoQuery(query['collection'])
218
+ mquery = MongoQuery(query["collection"])
215
219
 
216
- for c in query['call']:
217
- mquery.add_step({
218
- 'method': c['method'],
219
- 'args': c['args']
220
- })
220
+ for c in query["call"]:
221
+ mquery.add_step({"method": c["method"], "args": c["args"]})
221
222
 
222
223
  query = mquery
223
224
 
@@ -229,16 +230,15 @@ class MongoDBHandler(DatabaseHandler):
229
230
  # Check if the collection exists.
230
231
  if collection not in con[database].list_collection_names():
231
232
  return Response(
232
- RESPONSE_TYPE.ERROR,
233
- error_message=f'Collection {collection} not found in database {database}!'
233
+ RESPONSE_TYPE.ERROR, error_message=f"Collection {collection} not found in database {database}!"
234
234
  )
235
235
 
236
236
  try:
237
237
  cursor = con[database][collection]
238
238
 
239
239
  for step in query.pipeline:
240
- fnc = getattr(cursor, step['method'])
241
- cursor = fnc(*step['args'])
240
+ fnc = getattr(cursor, step["method"])
241
+ cursor = fnc(*step["args"])
242
242
 
243
243
  result = []
244
244
  if not isinstance(cursor, pymongo.results.UpdateResult):
@@ -254,16 +254,10 @@ class MongoDBHandler(DatabaseHandler):
254
254
  columns = list(self.get_columns(collection).data_frame.Field)
255
255
  df = pd.DataFrame([], columns=columns)
256
256
 
257
- response = Response(
258
- RESPONSE_TYPE.TABLE,
259
- df
260
- )
257
+ response = Response(RESPONSE_TYPE.TABLE, df)
261
258
  except Exception as e:
262
- logger.error(f'Error running query: {query} on {self.database}.{collection}!')
263
- response = Response(
264
- RESPONSE_TYPE.ERROR,
265
- error_message=str(e)
266
- )
259
+ logger.error(f"Error running query: {query} on {self.database}.{collection}!")
260
+ response = Response(RESPONSE_TYPE.ERROR, error_message=str(e))
267
261
 
268
262
  return response
269
263
 
@@ -289,7 +283,7 @@ class MongoDBHandler(DatabaseHandler):
289
283
  if level > 0:
290
284
  if isinstance(v, dict):
291
285
  for k2, v2 in self.flatten(v, level=level - 1).items():
292
- add[f'{k}.{k2}'] = v2
286
+ add[f"{k}.{k2}"] = v2
293
287
  del_keys.append(k)
294
288
 
295
289
  if add:
@@ -324,15 +318,10 @@ class MongoDBHandler(DatabaseHandler):
324
318
  """
325
319
  con = self.connect()
326
320
  collections = con[self.database].list_collection_names()
327
- collections_ar = [
328
- [i] for i in collections
329
- ]
330
- df = pd.DataFrame(collections_ar, columns=['table_name'])
321
+ collections_ar = [[i] for i in collections]
322
+ df = pd.DataFrame(collections_ar, columns=["table_name"])
331
323
 
332
- response = Response(
333
- RESPONSE_TYPE.TABLE,
334
- df
335
- )
324
+ response = Response(RESPONSE_TYPE.TABLE, df)
336
325
 
337
326
  return response
338
327
 
@@ -363,10 +352,7 @@ class MongoDBHandler(DatabaseHandler):
363
352
  for k, v in record.items():
364
353
  data.append([k, type(v).__name__])
365
354
 
366
- df = pd.DataFrame(data, columns=['Field', 'Type'])
355
+ df = pd.DataFrame(data, columns=["Field", "Type"])
367
356
 
368
- response = Response(
369
- RESPONSE_TYPE.TABLE,
370
- df
371
- )
357
+ response = Response(RESPONSE_TYPE.TABLE, df)
372
358
  return response
@@ -0,0 +1 @@
1
+ pymongo == 4.8.0
@@ -6,9 +6,8 @@ from mindsdb_sql_parser.ast import OrderBy, Identifier, Star, Select, Constant,
6
6
 
7
7
 
8
8
  class MongoToAst:
9
-
10
9
  """
11
- Converts query mongo to AST format
10
+ Converts query mongo to AST format
12
11
  """
13
12
 
14
13
  def from_mongoqeury(self, query):
@@ -19,24 +18,22 @@ class MongoToAst:
19
18
  filter, projection = None, None
20
19
  sort, limit, skip = None, None, None
21
20
  for step in query.pipeline:
22
- if step['method'] == 'find':
23
- filter = step['args'][0]
21
+ if step["method"] == "find":
22
+ filter = step["args"][0]
24
23
  if len(step) > 1:
25
- projection = step['args'][1]
26
- elif step['method'] == 'sort':
27
- sort = step['args'][0]
28
- elif step['method'] == 'limit':
29
- limit = step['args'][0]
30
- elif step['method'] == 'skip':
31
- skip = step['args'][0]
32
-
33
- return self.find(collection, filter=filter,
34
- sort=sort, projection=projection,
35
- limit=limit, skip=skip)
36
-
37
- def find(self, collection: t.Union[list, str],
38
- filter=None, sort=None, projection=None,
39
- limit=None, skip=None, **kwargs):
24
+ projection = step["args"][1]
25
+ elif step["method"] == "sort":
26
+ sort = step["args"][0]
27
+ elif step["method"] == "limit":
28
+ limit = step["args"][0]
29
+ elif step["method"] == "skip":
30
+ skip = step["args"][0]
31
+
32
+ return self.find(collection, filter=filter, sort=sort, projection=projection, limit=limit, skip=skip)
33
+
34
+ def find(
35
+ self, collection: t.Union[list, str], filter=None, sort=None, projection=None, limit=None, skip=None, **kwargs
36
+ ):
40
37
  # https://www.mongodb.com/docs/v4.2/reference/method/db.collection.find/
41
38
 
42
39
  order_by = None
@@ -44,12 +41,7 @@ class MongoToAst:
44
41
  # sort is dict
45
42
  order_by = []
46
43
  for col, direction in sort.items():
47
- order_by.append(
48
- OrderBy(
49
- field=Identifier(parts=[col]),
50
- direction='DESC' if direction == -1 else 'ASC'
51
- )
52
- )
44
+ order_by.append(OrderBy(field=Identifier(parts=[col]), direction="DESC" if direction == -1 else "ASC"))
53
45
 
54
46
  if projection is not None:
55
47
  targets = []
@@ -59,9 +51,7 @@ class MongoToAst:
59
51
  alias = Identifier(parts=[alias])
60
52
  else:
61
53
  alias = None
62
- targets.append(
63
- Identifier(path_str=col, alias=alias)
64
- )
54
+ targets.append(Identifier(path_str=col, alias=alias))
65
55
  else:
66
56
  targets = [Star()]
67
57
 
@@ -92,13 +82,13 @@ class MongoToAst:
92
82
 
93
83
  def convert_filter(self, filter):
94
84
  cond_ops = {
95
- '$and': 'and',
96
- '$or': 'or',
85
+ "$and": "and",
86
+ "$or": "or",
97
87
  }
98
88
 
99
89
  ast_filter = None
100
90
  for k, v in filter.items():
101
- if k in ('$or', '$and'):
91
+ if k in ("$or", "$and"):
102
92
  # suppose it is one key in dict
103
93
 
104
94
  op = cond_ops[k]
@@ -116,7 +106,7 @@ class MongoToAst:
116
106
  arg1 = BinaryOperation(op=op, args=[arg1, node])
117
107
 
118
108
  return arg1
119
- if k in ('$where', '$expr'):
109
+ if k in ("$where", "$expr"):
120
110
  # try to parse simple expression like 'this.saledate > this.latest'
121
111
  return MongoWhereParser(v).to_ast()
122
112
 
@@ -129,25 +119,12 @@ class MongoToAst:
129
119
  if ast_filter is None:
130
120
  ast_filter = ast_com
131
121
  else:
132
- ast_filter = BinaryOperation(op='and', args=[
133
- ast_filter,
134
- ast_com
135
- ])
122
+ ast_filter = BinaryOperation(op="and", args=[ast_filter, ast_com])
136
123
  return ast_filter
137
124
 
138
125
  def handle_filter(self, value):
139
- ops = {
140
- '$ge': '>=',
141
- '$gt': '>',
142
- '$lt': '<',
143
- '$le': '<=',
144
- '$ne': '!=',
145
- '$eq': '='
146
- }
147
- in_ops = {
148
- '$in': 'in',
149
- '$nin': 'not in'
150
- }
126
+ ops = {"$ge": ">=", "$gt": ">", "$lt": "<", "$le": "<=", "$ne": "!=", "$eq": "="}
127
+ in_ops = {"$in": "in", "$nin": "not in"}
151
128
 
152
129
  if isinstance(value, dict):
153
130
  key, value = list(value.items())[0]
@@ -158,18 +135,18 @@ class MongoToAst:
158
135
  if key in in_ops:
159
136
  op = in_ops[key]
160
137
  if not isinstance(value, list):
161
- raise NotImplementedError(f'Unknown type {key}, {value}')
138
+ raise NotImplementedError(f"Unknown type {key}, {value}")
162
139
  value = Tuple(value)
163
140
 
164
141
  return op, value
165
142
 
166
- raise NotImplementedError(f'Unknown type {key}')
143
+ raise NotImplementedError(f"Unknown type {key}")
167
144
 
168
145
  elif isinstance(value, list):
169
- raise NotImplementedError(f'Unknown filter {value}')
146
+ raise NotImplementedError(f"Unknown filter {value}")
170
147
  else:
171
148
  # is simple type
172
- op = '='
149
+ op = "="
173
150
  value = value
174
151
  return op, value
175
152
 
@@ -181,13 +158,12 @@ class MongoWhereParser:
181
158
  def to_ast(self):
182
159
  # parse as python string
183
160
  # replace '=' with '=='
184
- query = re.sub(r'([^=><])=([^=])', r'\1==\2', self.query)
161
+ query = re.sub(r"([^=><])=([^=])", r"\1==\2", self.query)
185
162
 
186
- tree = py_ast.parse(query, mode='eval')
163
+ tree = py_ast.parse(query, mode="eval")
187
164
  return self.process(tree.body)
188
165
 
189
166
  def process(self, node):
190
-
191
167
  if isinstance(node, py_ast.BoolOp):
192
168
  # is AND or OR
193
169
  op = node.op.__class__.__name__
@@ -202,7 +178,7 @@ class MongoWhereParser:
202
178
  if isinstance(node, py_ast.Compare):
203
179
  # it is
204
180
  if len(node.ops) != 1:
205
- raise NotImplementedError(f'Multiple ops {node.ops}')
181
+ raise NotImplementedError(f"Multiple ops {node.ops}")
206
182
  op = self.compare_op(node.ops[0])
207
183
  arg1 = self.process(node.left)
208
184
  arg2 = self.process(node.comparators[0])
@@ -210,7 +186,7 @@ class MongoWhereParser:
210
186
 
211
187
  if isinstance(node, py_ast.Name):
212
188
  # is special operator: latest, ...
213
- if node.id == 'latest':
189
+ if node.id == "latest":
214
190
  return Latest()
215
191
 
216
192
  if isinstance(node, py_ast.Constant):
@@ -228,28 +204,27 @@ class MongoWhereParser:
228
204
 
229
205
  if isinstance(node, py_ast.Attribute):
230
206
  # is 'this.field' - is attribute
231
- if node.value.id != 'this':
232
- raise NotImplementedError(f'Unknown variable {node.value.id}')
207
+ if node.value.id != "this":
208
+ raise NotImplementedError(f"Unknown variable {node.value.id}")
233
209
  return Identifier(parts=[node.attr])
234
210
 
235
- raise NotImplementedError(f'Unknown node {node}')
211
+ raise NotImplementedError(f"Unknown node {node}")
236
212
 
237
213
  def compare_op(self, op):
238
-
239
214
  opname = op.__class__.__name__
240
215
 
241
216
  # TODO: in, not
242
217
 
243
218
  ops = {
244
- 'Eq': '=',
245
- 'NotEq': '!=',
246
- 'Gt': '>',
247
- 'Lt': '<',
248
- 'GtE': '>=',
249
- 'LtE': '<=',
219
+ "Eq": "=",
220
+ "NotEq": "!=",
221
+ "Gt": ">",
222
+ "Lt": "<",
223
+ "GtE": ">=",
224
+ "LtE": "<=",
250
225
  }
251
226
  if opname not in ops:
252
- raise NotImplementedError(f'Unknown $where op: {opname}')
227
+ raise NotImplementedError(f"Unknown $where op: {opname}")
253
228
  return ops[opname]
254
229
 
255
230
  @staticmethod
@@ -1,4 +1,3 @@
1
-
2
1
  import ast as py_ast
3
2
 
4
3
  import dateutil.parser
@@ -8,30 +7,27 @@ from .mongodb_query import MongoQuery
8
7
 
9
8
 
10
9
  class MongodbParser:
11
- '''
12
- Converts string into MongoQuery
13
- '''
10
+ """
11
+ Converts string into MongoQuery
12
+ """
14
13
 
15
14
  def from_string(self, call_str):
16
- tree = py_ast.parse(call_str.strip(), mode='eval')
15
+ tree = py_ast.parse(call_str.strip(), mode="eval")
17
16
  calls = self.process(tree.body)
18
17
  # first call contents collection
19
- method1 = calls[0]['method']
18
+ method1 = calls[0]["method"]
20
19
  if len(method1) < 2:
21
- raise IndexError('Collection not found')
20
+ raise IndexError("Collection not found")
22
21
  collection = method1[-2]
23
22
 
24
23
  mquery = MongoQuery(collection)
25
24
 
26
25
  # keep only last name
27
- calls[0]['method'] = [method1[-1]]
26
+ calls[0]["method"] = [method1[-1]]
28
27
 
29
28
  # convert method names: get first item of list
30
29
  for c in calls:
31
- mquery.add_step({
32
- 'method': c['method'][0],
33
- 'args': c['args']
34
- })
30
+ mquery.add_step({"method": c["method"][0], "args": c["args"]})
35
31
 
36
32
  return mquery
37
33
 
@@ -49,20 +45,17 @@ class MongodbParser:
49
45
  func = node.func.id
50
46
 
51
47
  # special functions:
52
- if func == 'ISODate':
48
+ if func == "ISODate":
53
49
  return dateutil.parser.isoparse(args[0])
54
- if func == 'ObjectId':
50
+ if func == "ObjectId":
55
51
  return ObjectId(args[0])
56
52
  elif isinstance(node.func, py_ast.Attribute):
57
53
  # it can be an attribute or pipeline
58
54
  previous_call, func = self.process_func_name(node.func)
59
55
  else:
60
- raise NotImplementedError(f'Unknown function type: {node.func}')
56
+ raise NotImplementedError(f"Unknown function type: {node.func}")
61
57
 
62
- call = [{
63
- 'method': func,
64
- 'args': args
65
- }]
58
+ call = [{"method": func, "args": args}]
66
59
  if previous_call is not None:
67
60
  call = previous_call + call
68
61
 
@@ -75,7 +68,6 @@ class MongodbParser:
75
68
  return elements
76
69
 
77
70
  if isinstance(node, py_ast.Dict):
78
-
79
71
  keys = []
80
72
  for node2 in node.keys:
81
73
  if isinstance(node2, py_ast.Constant):
@@ -85,7 +77,7 @@ class MongodbParser:
85
77
  elif isinstance(node2, py_ast.Name):
86
78
  value = node2.id
87
79
  else:
88
- raise NotImplementedError(f'Unknown dict key {node2}')
80
+ raise NotImplementedError(f"Unknown dict key {node2}")
89
81
 
90
82
  keys.append(value)
91
83
 
@@ -98,11 +90,11 @@ class MongodbParser:
98
90
  if isinstance(node, py_ast.Name):
99
91
  # special attributes
100
92
  name = node.id
101
- if name == 'true':
93
+ if name == "true":
102
94
  return True
103
- elif name == 'false':
95
+ elif name == "false":
104
96
  return False
105
- elif name == 'null':
97
+ elif name == "null":
106
98
  return None
107
99
 
108
100
  if isinstance(node, py_ast.Constant):
@@ -122,7 +114,7 @@ class MongodbParser:
122
114
  value = self.process(node.operand)
123
115
  return -value
124
116
 
125
- raise NotImplementedError(f'Unknown node {node}')
117
+ raise NotImplementedError(f"Unknown node {node}")
126
118
 
127
119
  def process_func_name(self, node):
128
120
  previous_call = None
@@ -4,17 +4,15 @@ from bson import ObjectId
4
4
 
5
5
 
6
6
  class MongoJSONEncoder(json.JSONEncoder):
7
-
8
7
  def default(self, obj):
9
8
  if isinstance(obj, dt.datetime):
10
- return f'ISODate({obj.isoformat()})'
9
+ return f"ISODate({obj.isoformat()})"
11
10
  if isinstance(obj, ObjectId):
12
- return f'ObjectId({str(obj)})'
11
+ return f"ObjectId({str(obj)})"
13
12
  return super(MongoJSONEncoder, self).default(obj)
14
13
 
15
14
 
16
15
  class MongoQuery:
17
-
18
16
  def __init__(self, collection, pipline=None):
19
17
  self.collection = collection
20
18
  self.pipeline = []
@@ -30,10 +28,8 @@ class MongoQuery:
30
28
  # 'args': [{c: 3}]
31
29
  # }
32
30
 
33
- if 'method' not in step \
34
- or 'args' not in step \
35
- or not isinstance(step['args'], list):
36
- raise AttributeError(f'Wrong step {step}')
31
+ if "method" not in step or "args" not in step or not isinstance(step["args"], list):
32
+ raise AttributeError(f"Wrong step {step}")
37
33
 
38
34
  self.pipeline.append(step)
39
35
 
@@ -43,10 +39,8 @@ class MongoQuery:
43
39
  def __getattr__(self, item):
44
40
  # return callback to save step of pipeline
45
41
  def fnc(*args):
46
- self.pipeline.append({
47
- 'method': item,
48
- 'args': args
49
- })
42
+ self.pipeline.append({"method": item, "args": args})
43
+
50
44
  return fnc
51
45
 
52
46
  def __str__(self):
@@ -72,13 +66,13 @@ class MongoQuery:
72
66
  "db_test.fish.find({a:1}, {b:2}).sort({c:3})"
73
67
  """
74
68
 
75
- call_str = f'db.{self.collection}'
69
+ call_str = f"db.{self.collection}"
76
70
  for step in self.pipeline:
77
71
  args_str = []
78
- for arg in step['args']:
72
+ for arg in step["args"]:
79
73
  args_str.append(MongoJSONEncoder().encode(arg))
80
- call_str += f'.{step["method"]}({",".join(args_str)})'
74
+ call_str += f".{step['method']}({','.join(args_str)})"
81
75
  return call_str
82
76
 
83
77
  def __repr__(self):
84
- return f'MongoQuery({self.collection}, {str(self.pipeline)})'
78
+ return f"MongoQuery({self.collection}, {str(self.pipeline)})"