query_generator.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. from __future__ import annotations
  2. class QueryGenerator:
  3. """QueryGenerator is a helper object to write filters for runs.
  4. <!-- lazydoc-ignore-class: internal -->
  5. """
  6. INDIVIDUAL_OP_TO_MONGO = {
  7. "!=": "$ne",
  8. ">": "$gt",
  9. ">=": "$gte",
  10. "<": "$lt",
  11. "<=": "$lte",
  12. "IN": "$in",
  13. "NIN": "$nin",
  14. "REGEX": "$regex",
  15. }
  16. MONGO_TO_INDIVIDUAL_OP = {v: k for k, v in INDIVIDUAL_OP_TO_MONGO.items()}
  17. GROUP_OP_TO_MONGO = {"AND": "$and", "OR": "$or"}
  18. MONGO_TO_GROUP_OP = {v: k for k, v in GROUP_OP_TO_MONGO.items()}
  19. def __init__(self):
  20. pass
  21. @classmethod
  22. def format_order_key(cls, key: str):
  23. """Format a key for sorting."""
  24. if key.startswith(("+", "-")):
  25. direction = key[0]
  26. key = key[1:]
  27. else:
  28. direction = "-"
  29. parts = key.split(".")
  30. if len(parts) == 1:
  31. # Assume the user meant summary_metrics if not a run column
  32. if parts[0] not in ["createdAt", "updatedAt", "name", "sweep"]:
  33. return direction + "summary_metrics." + parts[0]
  34. # Assume summary metrics if prefix isn't known
  35. elif parts[0] not in ["config", "summary_metrics", "tags"]:
  36. return direction + ".".join(["summary_metrics"] + parts)
  37. else:
  38. return direction + ".".join(parts)
  39. def _is_group(self, op):
  40. return op.get("filters") is not None
  41. def _is_individual(self, op):
  42. return op.get("key") is not None
  43. def _to_mongo_op_value(self, op, value):
  44. if op == "=":
  45. return value
  46. else:
  47. return {self.INDIVIDUAL_OP_TO_MONGO[op]: value}
  48. def key_to_server_path(self, key):
  49. """Convert a key dictionary to the corresponding server path string."""
  50. if key["section"] == "config":
  51. return "config." + key["name"]
  52. elif key["section"] == "summary":
  53. return "summary_metrics." + key["name"]
  54. elif key["section"] == "keys_info":
  55. return "keys_info.keys." + key["name"]
  56. elif key["section"] == "run":
  57. return key["name"]
  58. elif key["section"] == "tags":
  59. return "tags." + key["name"]
  60. raise ValueError("Invalid key: {}".format(key))
  61. def server_path_to_key(self, path):
  62. """Convert a server path string to the corresponding key dictionary."""
  63. if path.startswith("config."):
  64. return {"section": "config", "name": path.split("config.", 1)[1]}
  65. elif path.startswith("summary_metrics."):
  66. return {"section": "summary", "name": path.split("summary_metrics.", 1)[1]}
  67. elif path.startswith("keys_info.keys."):
  68. return {"section": "keys_info", "name": path.split("keys_info.keys.", 1)[1]}
  69. elif path.startswith("tags."):
  70. return {"section": "tags", "name": path.split("tags.", 1)[1]}
  71. else:
  72. return {"section": "run", "name": path}
  73. def keys_to_order(self, keys):
  74. """Convert a list of key dictionaries to an order string."""
  75. orders = []
  76. for key in keys["keys"]:
  77. order = self.key_to_server_path(key["key"])
  78. if key.get("ascending"):
  79. order = "+" + order
  80. else:
  81. order = "-" + order
  82. orders.append(order)
  83. # return ",".join(orders)
  84. return orders
  85. def order_to_keys(self, order):
  86. """Convert an order string to a list of key dictionaries."""
  87. keys = []
  88. for k in order: # orderstr.split(","):
  89. name = k[1:]
  90. if k[0] == "+":
  91. ascending = True
  92. elif k[0] == "-":
  93. ascending = False
  94. else:
  95. raise Exception("you must sort by ascending(+) or descending(-)")
  96. key = {"key": {"section": "run", "name": name}, "ascending": ascending}
  97. keys.append(key)
  98. return {"keys": keys}
  99. def _to_mongo_individual(self, filter):
  100. if filter["key"]["name"] == "":
  101. return None
  102. if filter.get("value") is None and filter["op"] != "=" and filter["op"] != "!=":
  103. return None
  104. if filter.get("disabled") is not None and filter["disabled"]:
  105. return None
  106. if filter["key"]["section"] == "tags":
  107. if filter["op"] == "IN":
  108. return {"tags": {"$in": filter["value"]}}
  109. if filter["value"] is False:
  110. return {
  111. "$or": [{"tags": None}, {"tags": {"$ne": filter["key"]["name"]}}]
  112. }
  113. else:
  114. return {"tags": filter["key"]["name"]}
  115. path = self.key_to_server_path(filter["key"])
  116. if path is None:
  117. return path
  118. return {path: self._to_mongo_op_value(filter["op"], filter["value"])}
  119. def filter_to_mongo(self, filter):
  120. """Returns dictionary with filter format converted to MongoDB filter."""
  121. if self._is_individual(filter):
  122. return self._to_mongo_individual(filter)
  123. elif self._is_group(filter):
  124. return {
  125. self.GROUP_OP_TO_MONGO[filter["op"]]: [
  126. self.filter_to_mongo(f) for f in filter["filters"]
  127. ]
  128. }
  129. def mongo_to_filter(self, filter):
  130. """Returns dictionary with MongoDB filter converted to filter format."""
  131. # Returns {"op": "OR", "filters": [{"op": "AND", "filters": []}]}
  132. if filter is None:
  133. return None # this covers the case where self.filter_to_mongo returns None.
  134. group_op = None
  135. for key in filter:
  136. # if self.MONGO_TO_GROUP_OP[key]:
  137. if key in self.MONGO_TO_GROUP_OP:
  138. group_op = key
  139. break
  140. if group_op is not None:
  141. return {
  142. "op": self.MONGO_TO_GROUP_OP[group_op],
  143. "filters": [self.mongo_to_filter(f) for f in filter[group_op]],
  144. }
  145. else:
  146. for k, v in filter.items():
  147. if isinstance(v, dict):
  148. # TODO: do we always have one key in this case?
  149. op = next(iter(v.keys()))
  150. return {
  151. "key": self.server_path_to_key(k),
  152. "op": self.MONGO_TO_INDIVIDUAL_OP[op],
  153. "value": v[op],
  154. }
  155. else:
  156. return {"key": self.server_path_to_key(k), "op": "=", "value": v}