|
16 | 16 |
|
17 | 17 |
|
18 | 18 | import inspect |
| 19 | +import zlib |
19 | 20 |
|
20 | 21 | from google.cloud.ndb import exceptions |
21 | 22 | from google.cloud.ndb import key as key_module |
|
80 | 81 | ] |
81 | 82 |
|
82 | 83 |
|
| 84 | +_MAX_STRING_LENGTH = 1500 |
83 | 85 | Key = key_module.Key |
84 | 86 | BlobKey = NotImplemented # From `google.appengine.api.datastore_types` |
85 | 87 | GeoPt = NotImplemented # From `google.appengine.api.datastore_types` |
@@ -248,7 +250,7 @@ def __repr__(self): |
248 | 250 | ) |
249 | 251 |
|
250 | 252 | def __eq__(self, other): |
251 | | - """Compare two indexes.""" |
| 253 | + """Compare two index states.""" |
252 | 254 | if not isinstance(other, IndexState): |
253 | 255 | return NotImplemented |
254 | 256 |
|
@@ -611,11 +613,11 @@ def _verify_validator(validator): |
611 | 613 | ``value + "$"`` is not. |
612 | 614 |
|
613 | 615 | Args: |
614 | | - validator (Callable[[.Property, Any], bool]): A callable that can |
| 616 | + validator (Callable[[Property, Any], bool]): A callable that can |
615 | 617 | validate a property value. |
616 | 618 |
|
617 | 619 | Returns: |
618 | | - Callable[[.Property, Any], bool]: The ``validator``. |
| 620 | + Callable[[Property, Any], bool]: The ``validator``. |
619 | 621 |
|
620 | 622 | Raises: |
621 | 623 | TypeError: If ``validator`` is not callable. This is determined by |
@@ -1627,10 +1629,206 @@ def __init__(self, *args, **kwargs): |
1627 | 1629 | raise NotImplementedError |
1628 | 1630 |
|
1629 | 1631 |
|
| 1632 | +class _CompressedValue: |
| 1633 | + """A marker object wrapping compressed values. |
| 1634 | +
|
| 1635 | + Args: |
| 1636 | + z_val (bytes): A return value of ``zlib.compress``. |
| 1637 | + """ |
| 1638 | + |
| 1639 | + __slots__ = ("z_val",) |
| 1640 | + |
| 1641 | + def __init__(self, z_val): |
| 1642 | + self.z_val = z_val |
| 1643 | + |
| 1644 | + def __repr__(self): |
| 1645 | + return "_CompressedValue({!r})".format(self.z_val) |
| 1646 | + |
| 1647 | + def __eq__(self, other): |
| 1648 | + """Compare two compressed values.""" |
| 1649 | + if not isinstance(other, _CompressedValue): |
| 1650 | + return NotImplemented |
| 1651 | + |
| 1652 | + return self.z_val == other.z_val |
| 1653 | + |
| 1654 | + def __ne__(self, other): |
| 1655 | + """Inequality comparison operation.""" |
| 1656 | + return not self == other |
| 1657 | + |
| 1658 | + def __hash__(self): |
| 1659 | + raise TypeError("_CompressedValue is not immutable") |
| 1660 | + |
| 1661 | + |
1630 | 1662 | class BlobProperty(Property): |
1631 | | - __slots__ = () |
| 1663 | + """A property that contains values that are byte strings. |
1632 | 1664 |
|
1633 | | - def __init__(self, *args, **kwargs): |
| 1665 | + .. note:: |
| 1666 | +
|
| 1667 | + Unlike most property types, a :class:`BlobProperty` is **not** |
| 1668 | + indexed by default. |
| 1669 | +
|
| 1670 | + Args: |
| 1671 | + name (str): The name of the property. |
| 1672 | + compressed (bool): Indicates if the value should be compressed (via |
| 1673 | + ``zlib``). |
| 1674 | + indexed (bool): Indicates if the value should be indexed. |
| 1675 | + repeated (bool): Indicates if this property is repeated, i.e. contains |
| 1676 | + multiple values. |
| 1677 | + required (bool): Indicates if this property is required on the given |
| 1678 | + model type. |
| 1679 | + default (bytes): The default value for this property. |
| 1680 | + choices (Iterable[bytes]): A container of allowed values for this |
| 1681 | + property. |
| 1682 | + validator (Callable[[Property, Any], bool]): A validator to be used |
| 1683 | + to check values. |
| 1684 | + verbose_name (str): A longer, user-friendly name for this property. |
| 1685 | + write_empty_list (bool): Indicates if an empty list should be written |
| 1686 | + to the datastore. |
| 1687 | +
|
| 1688 | + Raises: |
| 1689 | + NotImplementedError: If the property is both compressed and indexed. |
| 1690 | + """ |
| 1691 | + |
| 1692 | + _indexed = False |
| 1693 | + _compressed = False |
| 1694 | + |
| 1695 | + def __init__( |
| 1696 | + self, |
| 1697 | + name=None, |
| 1698 | + compressed=None, |
| 1699 | + *, |
| 1700 | + indexed=None, |
| 1701 | + repeated=None, |
| 1702 | + required=None, |
| 1703 | + default=None, |
| 1704 | + choices=None, |
| 1705 | + validator=None, |
| 1706 | + verbose_name=None, |
| 1707 | + write_empty_list=None |
| 1708 | + ): |
| 1709 | + super(BlobProperty, self).__init__( |
| 1710 | + name=name, |
| 1711 | + indexed=indexed, |
| 1712 | + repeated=repeated, |
| 1713 | + required=required, |
| 1714 | + default=default, |
| 1715 | + choices=choices, |
| 1716 | + validator=validator, |
| 1717 | + verbose_name=verbose_name, |
| 1718 | + write_empty_list=write_empty_list, |
| 1719 | + ) |
| 1720 | + if compressed is not None: |
| 1721 | + self._compressed = compressed |
| 1722 | + if self._compressed and self._indexed: |
| 1723 | + raise NotImplementedError( |
| 1724 | + "BlobProperty {} cannot be compressed and " |
| 1725 | + "indexed at the same time.".format(self._name) |
| 1726 | + ) |
| 1727 | + |
| 1728 | + def _value_to_repr(self, value): |
| 1729 | + """Turn the value into a user friendly representation. |
| 1730 | +
|
| 1731 | + .. note:: |
| 1732 | +
|
| 1733 | + This will truncate the value based on the "visual" length, e.g. |
| 1734 | + if it contains many ``\\xXX`` or ``\\uUUUU`` sequences, those |
| 1735 | + will count against the length as more than one character. |
| 1736 | +
|
| 1737 | + Args: |
| 1738 | + value (Any): The value to convert to a pretty-print ``repr``. |
| 1739 | +
|
| 1740 | + Returns: |
| 1741 | + str: The ``repr`` of the "true" value. |
| 1742 | + """ |
| 1743 | + long_repr = super(BlobProperty, self)._value_to_repr(value) |
| 1744 | + if len(long_repr) > _MAX_STRING_LENGTH + 4: |
| 1745 | + # Truncate, assuming the final character is the closing quote. |
| 1746 | + long_repr = long_repr[:_MAX_STRING_LENGTH] + "..." + long_repr[-1] |
| 1747 | + return long_repr |
| 1748 | + |
| 1749 | + def _validate(self, value): |
| 1750 | + """Validate a ``value`` before setting it. |
| 1751 | +
|
| 1752 | + Args: |
| 1753 | + value (bytes): The value to check. |
| 1754 | +
|
| 1755 | + Raises: |
| 1756 | + .BadValueError: If ``value`` is not a :class:`bytes`. |
| 1757 | + .BadValueError: If the current property is indexed but the value |
| 1758 | + exceeds the maximum length (1500 bytes). |
| 1759 | + """ |
| 1760 | + if not isinstance(value, bytes): |
| 1761 | + raise exceptions.BadValueError( |
| 1762 | + "Expected bytes, got {!r}".format(value) |
| 1763 | + ) |
| 1764 | + |
| 1765 | + if self._indexed and len(value) > _MAX_STRING_LENGTH: |
| 1766 | + raise exceptions.BadValueError( |
| 1767 | + "Indexed value {} must be at most {:d} " |
| 1768 | + "bytes".format(self._name, _MAX_STRING_LENGTH) |
| 1769 | + ) |
| 1770 | + |
| 1771 | + def _to_base_type(self, value): |
| 1772 | + """Convert a value to the "base" value type for this property. |
| 1773 | +
|
| 1774 | + Args: |
| 1775 | + value (bytes): The value to be converted. |
| 1776 | +
|
| 1777 | + Returns: |
| 1778 | + Optional[bytes]: The converted value. If the current property is |
| 1779 | + compressed, this will return a wrapped version of the compressed |
| 1780 | + value. Otherwise, it will return :data:`None` to indicate that |
| 1781 | + the value didn't need to be converted. |
| 1782 | + """ |
| 1783 | + if self._compressed: |
| 1784 | + return _CompressedValue(zlib.compress(value)) |
| 1785 | + |
| 1786 | + def _from_base_type(self, value): |
| 1787 | + """Convert a value from the "base" value type for this property. |
| 1788 | +
|
| 1789 | + Args: |
| 1790 | + value (bytes): The value to be converted. |
| 1791 | +
|
| 1792 | + Returns: |
| 1793 | + Optional[bytes]: The converted value. If the current property is |
| 1794 | + a (wrapped) compressed value, this will unwrap the value and return |
| 1795 | + the decompressed form. Otherwise, it will return :data:`None` to |
| 1796 | + indicate that the value didn't need to be unwrapped and |
| 1797 | + decompressed. |
| 1798 | + """ |
| 1799 | + if isinstance(value, _CompressedValue): |
| 1800 | + return zlib.decompress(value.z_val) |
| 1801 | + |
| 1802 | + def _db_set_value(self, v, unused_p, value): |
| 1803 | + """Helper for :meth:`_serialize`. |
| 1804 | +
|
| 1805 | + Raises: |
| 1806 | + NotImplementedError: Always. This method is virtual. |
| 1807 | + """ |
| 1808 | + raise NotImplementedError |
| 1809 | + |
| 1810 | + def _db_set_compressed_meaning(self, p): |
| 1811 | + """Helper for :meth:`_db_set_value`. |
| 1812 | +
|
| 1813 | + Raises: |
| 1814 | + NotImplementedError: Always. This method is virtual. |
| 1815 | + """ |
| 1816 | + raise NotImplementedError |
| 1817 | + |
| 1818 | + def _db_set_uncompressed_meaning(self, p): |
| 1819 | + """Helper for :meth:`_db_set_value`. |
| 1820 | +
|
| 1821 | + Raises: |
| 1822 | + NotImplementedError: Always. This method is virtual. |
| 1823 | + """ |
| 1824 | + raise NotImplementedError |
| 1825 | + |
| 1826 | + def _db_get_value(self, v, unused_p): |
| 1827 | + """Helper for :meth:`_deserialize`. |
| 1828 | +
|
| 1829 | + Raises: |
| 1830 | + NotImplementedError: Always. This method is virtual. |
| 1831 | + """ |
1634 | 1832 | raise NotImplementedError |
1635 | 1833 |
|
1636 | 1834 |
|
|
0 commit comments