185 ]) |
188 ]) |
186 |
189 |
187 _ALLOWED_EXPANDO_PROPERTY_TYPES = set(_ALLOWED_PROPERTY_TYPES) |
190 _ALLOWED_EXPANDO_PROPERTY_TYPES = set(_ALLOWED_PROPERTY_TYPES) |
188 _ALLOWED_EXPANDO_PROPERTY_TYPES.update((list, tuple, type(None))) |
191 _ALLOWED_EXPANDO_PROPERTY_TYPES.update((list, tuple, type(None))) |
189 |
192 |
|
193 _OPERATORS = ['<', '<=', '>', '>=', '=', '==', '!=', 'in'] |
|
194 _FILTER_REGEX = re.compile( |
|
195 '^\s*([^\s]+)(\s+(%s)\s*)?$' % '|'.join(_OPERATORS), |
|
196 re.IGNORECASE | re.UNICODE) |
|
197 |
190 |
198 |
191 def class_for_kind(kind): |
199 def class_for_kind(kind): |
192 """Return base-class responsible for implementing kind. |
200 """Return base-class responsible for implementing kind. |
193 |
201 |
194 Necessary to recover the class responsible for implementing provided |
202 Necessary to recover the class responsible for implementing provided |
593 def key(self): |
601 def key(self): |
594 """Unique key for this entity. |
602 """Unique key for this entity. |
595 |
603 |
596 This property is only available if this entity is already stored in the |
604 This property is only available if this entity is already stored in the |
597 datastore, so it is available if this entity was fetched returned from a |
605 datastore, so it is available if this entity was fetched returned from a |
598 query, or after save() is called the first time for new entities. |
606 query, or after put() is called the first time for new entities. |
599 |
607 |
600 Returns: |
608 Returns: |
601 Datastore key of persisted entity. |
609 Datastore key of persisted entity. |
602 |
610 |
603 Raises: |
611 Raises: |
604 NotSavedError when entity is not persistent. |
612 NotSavedError when entity is not persistent. |
605 """ |
613 """ |
606 if self.is_saved(): |
614 if self.is_saved(): |
607 return self._entity.key() |
615 return self._entity.key() |
608 elif self._key_name: |
616 elif self._key_name: |
609 parent = self._parent and self._parent.key() |
617 if self._parent_key: |
|
618 parent_key = self._parent_key |
|
619 elif self._parent: |
|
620 parent_key = self._parent.key() |
|
621 parent = self._parent_key or (self._parent and self._parent.key()) |
610 return Key.from_path(self.kind(), self._key_name, parent=parent) |
622 return Key.from_path(self.kind(), self._key_name, parent=parent) |
611 else: |
623 else: |
612 raise NotSavedError() |
624 raise NotSavedError() |
613 |
625 |
614 def _to_entity(self, entity): |
626 def _to_entity(self, entity): |
1141 properties, such as methods and other values in __dict__ take precedence |
1153 properties, such as methods and other values in __dict__ take precedence |
1142 over values in the datastore. |
1154 over values in the datastore. |
1143 |
1155 |
1144 1 - Because it is not possible for the datastore to know what kind of |
1156 1 - Because it is not possible for the datastore to know what kind of |
1145 property to store on an undefined expando value, setting a property to |
1157 property to store on an undefined expando value, setting a property to |
1146 None is the same as deleting it form the expando. |
1158 None is the same as deleting it from the expando. |
1147 |
1159 |
1148 2 - Persistent variables on Expando must not begin with '_'. These |
1160 2 - Persistent variables on Expando must not begin with '_'. These |
1149 variables considered to be 'protected' in Python, and are used |
1161 variables considered to be 'protected' in Python, and are used |
1150 internally. |
1162 internally. |
1151 |
1163 |
1522 |
1534 |
1523 Args: |
1535 Args: |
1524 model_class: Model class to build query for. |
1536 model_class: Model class to build query for. |
1525 """ |
1537 """ |
1526 super(Query, self).__init__(model_class) |
1538 super(Query, self).__init__(model_class) |
1527 self.__query_set = {} |
1539 self.__query_sets = [{}] |
1528 self.__orderings = [] |
1540 self.__orderings = [] |
1529 self.__ancestor = None |
1541 self.__ancestor = None |
1530 |
1542 |
1531 def _get_query(self, _query_class=datastore.Query): |
1543 def _get_query(self, |
1532 query = _query_class(self._model_class.kind(), self.__query_set) |
1544 _query_class=datastore.Query, |
1533 if self.__ancestor is not None: |
1545 _multi_query_class=datastore.MultiQuery): |
1534 query.Ancestor(self.__ancestor) |
1546 queries = [] |
1535 query.Order(*self.__orderings) |
1547 for query_set in self.__query_sets: |
1536 return query |
1548 query = _query_class(self._model_class.kind(), query_set) |
|
1549 if self.__ancestor is not None: |
|
1550 query.Ancestor(self.__ancestor) |
|
1551 queries.append(query) |
|
1552 |
|
1553 if (_query_class != datastore.Query and |
|
1554 _multi_query_class == datastore.MultiQuery): |
|
1555 warnings.warn( |
|
1556 'Custom _query_class specified without corresponding custom' |
|
1557 ' _query_multi_class. Things will break if you use queries with' |
|
1558 ' the "IN" or "!=" operators.', RuntimeWarning) |
|
1559 if len(queries) > 1: |
|
1560 raise datastore_errors.BadArgumentError( |
|
1561 'Query requires multiple subqueries to satisfy. If _query_class' |
|
1562 ' is overridden, _multi_query_class must also be overridden.') |
|
1563 elif (_query_class == datastore.Query and |
|
1564 _multi_query_class != datastore.MultiQuery): |
|
1565 raise BadArgumentError('_query_class must also be overridden if' |
|
1566 ' _multi_query_class is overridden.') |
|
1567 |
|
1568 if len(queries) == 1: |
|
1569 queries[0].Order(*self.__orderings) |
|
1570 return queries[0] |
|
1571 else: |
|
1572 return _multi_query_class(queries, self.__orderings) |
|
1573 |
|
1574 def __filter_disjunction(self, operations, values): |
|
1575 """Add a disjunction of several filters and several values to the query. |
|
1576 |
|
1577 This is implemented by duplicating queries and combining the |
|
1578 results later. |
|
1579 |
|
1580 Args: |
|
1581 operations: a string or list of strings. Each string contains a |
|
1582 property name and an operator to filter by. The operators |
|
1583 themselves must not require multiple queries to evaluate |
|
1584 (currently, this means that 'in' and '!=' are invalid). |
|
1585 |
|
1586 values: a value or list of filter values, normalized by |
|
1587 _normalize_query_parameter. |
|
1588 """ |
|
1589 if not isinstance(operations, (list, tuple)): |
|
1590 operations = [operations] |
|
1591 if not isinstance(values, (list, tuple)): |
|
1592 values = [values] |
|
1593 |
|
1594 new_query_sets = [] |
|
1595 for operation in operations: |
|
1596 if operation.lower().endswith('in') or operation.endswith('!='): |
|
1597 raise BadQueryError('Cannot use "in" or "!=" in a disjunction.') |
|
1598 for query_set in self.__query_sets: |
|
1599 for value in values: |
|
1600 new_query_set = copy.copy(query_set) |
|
1601 datastore._AddOrAppend(new_query_set, operation, value) |
|
1602 new_query_sets.append(new_query_set) |
|
1603 self.__query_sets = new_query_sets |
1537 |
1604 |
1538 def filter(self, property_operator, value): |
1605 def filter(self, property_operator, value): |
1539 """Add filter to query. |
1606 """Add filter to query. |
1540 |
1607 |
1541 Args: |
1608 Args: |
1543 value: the filter value. |
1610 value: the filter value. |
1544 |
1611 |
1545 Returns: |
1612 Returns: |
1546 Self to support method chaining. |
1613 Self to support method chaining. |
1547 """ |
1614 """ |
1548 if isinstance(value, (list, tuple)): |
1615 match = _FILTER_REGEX.match(property_operator) |
1549 raise BadValueError('Filtering on lists is not supported') |
1616 prop = match.group(1) |
1550 |
1617 if match.group(3) is not None: |
1551 value = _normalize_query_parameter(value) |
1618 operator = match.group(3) |
1552 datastore._AddOrAppend(self.__query_set, property_operator, value) |
1619 else: |
|
1620 operator = '==' |
|
1621 |
|
1622 if operator.lower() == 'in': |
|
1623 if not isinstance(value, (list, tuple)): |
|
1624 raise BadValueError('Argument to the "in" operator must be a list') |
|
1625 values = [_normalize_query_parameter(v) for v in value] |
|
1626 self.__filter_disjunction(prop + ' =', values) |
|
1627 else: |
|
1628 if isinstance(value, (list, tuple)): |
|
1629 raise BadValueError('Filtering on lists is not supported') |
|
1630 if operator == '!=': |
|
1631 self.__filter_disjunction([prop + ' <', prop + ' >'], |
|
1632 _normalize_query_parameter(value)) |
|
1633 else: |
|
1634 value = _normalize_query_parameter(value) |
|
1635 for query_set in self.__query_sets: |
|
1636 datastore._AddOrAppend(query_set, property_operator, value) |
|
1637 |
1553 return self |
1638 return self |
1554 |
1639 |
1555 def order(self, property): |
1640 def order(self, property): |
1556 """Set order of query result. |
1641 """Set order of query result. |
1557 |
1642 |