def suggest_criteria(self): # TODO figure out why this stops normal inner function completion. (appears to only stop join) if self.argument and self.argument[-1] == '.': # Looks like a column/relationship. argument = self.argument[:-1].split(',')[-1].lstrip() mapped_property = self.get_mapped_property(argument) if not mapped_property: raise NotQueryException() suggestions = self._mapped_property_functions( mapped_property, argument) elif self.argument and self.get_mapped_property( self.argument.split(',')[-1].lstrip()): # We want to remind the user how to use criteria. return self._get_working_operators() elif self._is_boolean_expression(): #return RedundantCriterionCompleter.suggest() raise NotQueryException() else: from_clause = self.get_query_analyzer().get_from_clause() if re.match('.*[a-zA-Z]+ ?= ?$', self.argument): raise NotQueryException() else: suggestions = self._get_normal_suggestions( from_clause, self.argument) return suggestions # + self.get_criterion_suggestion_variants(suggestions)
def get_base(self, string): ''' Receives a string, and returns the query object to be used for extracting information about possible suggestions. ''' base = None if re.match(r'%s\.[a-zA-Z]+' % self.module_name, string): if re.match(r'%s\.[a-zA-Z]+\.%s.*' % (self.module_name, self._get_main_query_func_name()), string): # Looks like a full query. base = self._get_query_from_query_string(string) else: # Looks like a class, called through the module. class_name = re.sub('%s\.' % self.module_name, '', string) if hasattr(self.module, class_name) and isinstance(getattr(self.module, class_name), self._get_base_meta_class()): base = self._get_query_from_class_name(class_name) else: raise NotQueryException() elif hasattr(self.module, string.split('.')[0]): # Looks like a class, separately imported. if re.match(r'[a-zA-Z]+\.%s.*' % self._get_main_query_func_name(), string): base = self._get_query_from_query_string(string) else: if not isinstance(getattr(self.module, string), self._get_base_meta_class()): raise NotQueryException() else: base = self._get_query_from_class_name(string) # TODO implement more cases. else: raise NotQueryException() return base
def get_property_from_property_string(self, property_string): try: relationship_property = eval(property_string, self.namespace) except Exception: raise NotQueryException() if hasattr(relationship_property, 'property') and isinstance( relationship_property.property, RelationshipProperty): return relationship_property.property.mapper.entity else: raise NotQueryException()
def get_last_table(self): if not self.open_calls: raise NotQueryException() last_call = self.open_calls[-1] return class_mapper( self.get_property_from_property_string( self.get_property_string_from_call(last_call)))
def get_base_string(self, line): # TODO support parsing of lines not starting with the query. regex = self.get_base_regex() base_string_parts = re.findall(regex, line) if not base_string_parts: raise NotQueryException() return base_string_parts[0]
def parse_arguments(self, arguments_string, query): split_regex = self._get_call_split_regex() calls = re.split(split_regex, arguments_string) if len(calls) == 1: return QuerySimpleCriterionCompleter(argument=arguments_string, query=query, module=self.module, namespace=self.namespace) open_calls = self.open_criterion_calls(calls) if not open_calls: if re.match('.*\) *,.*', calls[-1]): # Last argument is not the last call # ignore any calls, simply pass the last argument. return QuerySimpleCriterionCompleter( argument=arguments_string.split(',')[-1].strip(), query=query, module=self.module, namespace=self.namespace) else: # Last argument is the last call. raise NotQueryException() else: # There is an open criterion call, # remove all junk and pass everything after the first call to the actual completer. first_call = calls[0] arguments_to_remove = first_call.count(',') return ComplexCriterionCompleter( module=self.module, namespace=self.namespace, argument=arguments_string.split('(')[-1].strip(), query=query, open_calls=open_calls)
def get_completer(self, arguments, query): if not self.validate_argument(arguments): raise NotQueryException() if ',' in arguments: if arguments.count(',') > 1: raise NotQueryException() parts = arguments.split(',') cls_name = parts[0].strip() argument = parts[1].strip() cls = self._get_cls(cls_name) if cls: return CriterionJoinCompleter(argument=argument, query=query, cls=cls, module=self.module, namespace=self.namespace) else: raise NotQueryException() for cls in PuyolLikeQueryAnalyzer(query=query).get_from_clause(): if arguments.lower() in ( '%s.%s' % (get_module_name(self.module), cls.__name__)).lower(): return RelationshipJoinCompleter(argument=arguments, query=query, cls=cls, module=self.module) parts = arguments.split('.') if len(parts) > 1: cls_name = '.'.join(parts[:-1]) argument = parts[-1].strip() cls = self._get_cls(cls_name) if cls: return RelationshipJoinCompleter(argument=argument, query=query, cls=cls, module=self.module) return ClassJoinCompleter( argument=arguments, query=query, base_meta_class=self.module_analyzer.get_base_meta_class( self.module), module=self.module)
def _suggest_right_side(self, from_clause): parts = self.argument.split('==') left = parts[0].strip() right = parts[1].strip() try: left_attribute = eval(left, self.namespace) except Exception: raise NotQueryException() if left_attribute.class_ in from_clause: left_is_joinee = False elif left_attribute.class_ == self.cls: left_is_joinee = True else: raise NotQueryException() expression = left_attribute.property.expression if expression.primary_key: # Either (a, a.id == b.a_id) or (a, b.id == a.b_id) if left_is_joinee: # (a, a.id == b.a_id) return self._suggest_fk(argument=right, source_classes=from_clause, target_classes=[self.cls]) else: # (a, b.id == a.b_id) return self._suggest_fk(argument=right, source_classes=[self.cls], target_classes=from_clause) else: # Either (a, a.b_id == b.id) or (a, b.a_id == a.id) foreign_key = self._get_fk_for_expression(expression) if not foreign_key: raise NotQueryException() if left_is_joinee: # (a, a.b_id == b.id) cls = self._get_cls_for_fk(foreign_key, from_clause + [self.cls]) else: # (a, b.a_id == a.id) cls = self.cls if not cls: raise NotQueryException() return self._suggest_pk_for_cls(argument=right, cls=cls)
def suggest(self, line): parser = self.get_parser() query, function, arguments = parser.parse(line) completer_factory = self.get_factory_for_function(function) if not completer_factory: raise NotQueryException() completer = completer_factory.get_completer(arguments, query) return completer.suggest()
def _get_query_from_query_string(self, string): try: # TODO should maybe run in different thread? Should check how evaluation is done in IPython base = eval(string, self.namespace) except SyntaxError: # Not a full query. Probably a query inside a query. (And this works with union or minus and such) # TODO sometime in the future we may want to support this. Currently this is just annoying. raise NotQueryException() return base
def _suggest_pk_for_cls(self, argument, cls): pk_attr = self.primary_key(cls) if pk_attr: key = pk_attr.key else: raise NotQueryException() suggestion = '%s.%s.%s' % (get_module_name( self.module), cls.__name__, key) if argument.lower() in suggestion.lower(): return [suggestion] else: return []
def suggest_kwarg(self): try: attributes = self._get_attributes_for_kwarg(self.query) except AttributeError: raise NotQueryException() suggestions = [] column_properties = self._get_properties(attributes, ColumnProperty) column_suggestions = map(lambda x: x.key + '=', column_properties) suggestions += column_suggestions relationship_properties = self._get_properties(attributes, RelationshipProperty) relationship_suggestions = map( lambda x: x.key + '=', filter(lambda x: not x.uselist, relationship_properties)) suggestions += relationship_suggestions if self.argument: argument = self.argument.split(',')[-1] if '=' in argument: raise NotQueryException() suggestions = filter(lambda x: x.startswith(argument.lstrip()), suggestions) return suggestions
def _mapped_property_functions(mapped_property, string): suggestions = [] if isinstance(mapped_property, ColumnProperty): suggestions = ['in_', 'like', 'ilike' ] # TODO make a puyol function for getting these. elif isinstance(mapped_property, RelationshipProperty): if mapped_property.uselist: suggestions = ['any'] else: suggestions = ['has'] else: raise NotQueryException() return ['%s.%s(' % (string, suggestion) for suggestion in suggestions]
def get_property_string_from_call(self, call): if re.match( '.*%s\.[a-zA-Z]+\.[a-zA-Z]+$' % get_module_name(self.module), call): string = re.findall( '%s\.[a-zA-Z]+\.[a-zA-Z]+$' % get_module_name(self.module), call)[0] return string elif re.match('.*[a-zA-Z]+\.[a-zA-Z]+$', call): string = re.findall('[a-zA-Z]+\.[a-zA-Z]+$', call)[0] return string else: raise NotQueryException()
def suggest(self): mapper = class_mapper(self.cls) from_clause = self.query_analyzer.get_from_clause() if not self.cls in from_clause: raise NotQueryException() # All relationship properties. relationships = [ attr for attr in mapper.attrs if isinstance(attr, RelationshipProperty) ] # All relationship properties that are not already joined. allowed = [ rel for rel in relationships if rel.mapper.entity not in from_clause ] # And start with the argument. suggestions = [] for rel in allowed: suggestion = '%s.%s.%s' % (get_module_name( self.module), self.cls.__name__, rel.key) if self.argument in suggestion: suggestions.append(suggestion) return suggestions
def validate_func_and_args(self, func_and_args): if len(re.findall(self.get_funcs_regex(), func_and_args)) != 1: raise NotQueryException() if len(re.findall(self.get_funcs_wit_parenthesis_regex(), func_and_args)) != 1: raise NotQueryException()
def get_completer(self, arguments_string, query): if not self.validate_argument(arguments_string): raise NotQueryException() completer = self.parse_arguments(arguments_string, query) return completer