|
15 | 15 | """Model classes for datastore objects and properties for models.""" |
16 | 16 |
|
17 | 17 |
|
| 18 | +import datetime |
18 | 19 | import inspect |
19 | 20 | import json |
20 | 21 | import pickle |
@@ -470,6 +471,11 @@ def _from_base_type(self, value): |
470 | 471 | original value is kept. (Returning a different value not equal to |
471 | 472 | :data:`None` will substitute the different value.) |
472 | 473 |
|
| 474 | + Additionally, :meth:`_prepare_for_put` can be used to integrate with |
| 475 | + datastore save hooks used by :class:`Model` instances. |
| 476 | +
|
| 477 | + .. automethod:: _prepare_for_put |
| 478 | +
|
473 | 479 | Args: |
474 | 480 | name (str): The name of the property. |
475 | 481 | indexed (bool): Indicates if the value should be indexed. |
@@ -1749,8 +1755,8 @@ class BlobProperty(Property): |
1749 | 1755 | def __init__( |
1750 | 1756 | self, |
1751 | 1757 | name=None, |
1752 | | - compressed=None, |
1753 | 1758 | *, |
| 1759 | + compressed=None, |
1754 | 1760 | indexed=None, |
1755 | 1761 | repeated=None, |
1756 | 1762 | required=None, |
@@ -2249,9 +2255,154 @@ def __init__(self, *args, **kwargs): |
2249 | 2255 |
|
2250 | 2256 |
|
2251 | 2257 | class DateTimeProperty(Property): |
2252 | | - __slots__ = () |
| 2258 | + """A property that contains :class:`~datetime.datetime` values. |
2253 | 2259 |
|
2254 | | - def __init__(self, *args, **kwargs): |
| 2260 | + This property expects "naive" datetime stamps, i.e. no timezone can |
| 2261 | + be set. Furthermore, the assumption is that naive datetime stamps |
| 2262 | + represent UTC. |
| 2263 | +
|
| 2264 | + .. note:: |
| 2265 | +
|
| 2266 | + Unlike Django, ``auto_now_add`` can be overridden by setting the |
| 2267 | + value before writing the entity. And unlike the legacy |
| 2268 | + ``google.appengine.ext.db``, ``auto_now`` does not supply a default |
| 2269 | + value. Also unlike legacy ``db``, when the entity is written, the |
| 2270 | + property values are updated to match what was written. Finally, beware |
| 2271 | + that this also updates the value in the in-process cache, **and** that |
| 2272 | + ``auto_now_add`` may interact weirdly with transaction retries (a retry |
| 2273 | + of a property with ``auto_now_add`` set will reuse the value that was |
| 2274 | + set on the first try). |
| 2275 | +
|
| 2276 | + .. automethod:: _validate |
| 2277 | + .. automethod:: _prepare_for_put |
| 2278 | +
|
| 2279 | + Args: |
| 2280 | + name (str): The name of the property. |
| 2281 | + auto_now (bool): Indicates that the property should be set to the |
| 2282 | + current datetime when an entity is created and whenever it is |
| 2283 | + updated. |
| 2284 | + auto_now_add (bool): Indicates that the property should be set to the |
| 2285 | + current datetime when an entity is created. |
| 2286 | + indexed (bool): Indicates if the value should be indexed. |
| 2287 | + repeated (bool): Indicates if this property is repeated, i.e. contains |
| 2288 | + multiple values. |
| 2289 | + required (bool): Indicates if this property is required on the given |
| 2290 | + model type. |
| 2291 | + default (bytes): The default value for this property. |
| 2292 | + choices (Iterable[bytes]): A container of allowed values for this |
| 2293 | + property. |
| 2294 | + validator (Callable[[~google.cloud.ndb.model.Property, Any], bool]): A |
| 2295 | + validator to be used to check values. |
| 2296 | + verbose_name (str): A longer, user-friendly name for this property. |
| 2297 | + write_empty_list (bool): Indicates if an empty list should be written |
| 2298 | + to the datastore. |
| 2299 | +
|
| 2300 | + Raises: |
| 2301 | + ValueError: If ``repeated=True`` and ``auto_now=True``. |
| 2302 | + ValueError: If ``repeated=True`` and ``auto_now_add=True``. |
| 2303 | + """ |
| 2304 | + |
| 2305 | + _auto_now = False |
| 2306 | + _auto_now_add = False |
| 2307 | + |
| 2308 | + def __init__( |
| 2309 | + self, |
| 2310 | + name=None, |
| 2311 | + *, |
| 2312 | + auto_now=None, |
| 2313 | + auto_now_add=None, |
| 2314 | + indexed=None, |
| 2315 | + repeated=None, |
| 2316 | + required=None, |
| 2317 | + default=None, |
| 2318 | + choices=None, |
| 2319 | + validator=None, |
| 2320 | + verbose_name=None, |
| 2321 | + write_empty_list=None |
| 2322 | + ): |
| 2323 | + super(DateTimeProperty, self).__init__( |
| 2324 | + name=name, |
| 2325 | + indexed=indexed, |
| 2326 | + repeated=repeated, |
| 2327 | + required=required, |
| 2328 | + default=default, |
| 2329 | + choices=choices, |
| 2330 | + validator=validator, |
| 2331 | + verbose_name=verbose_name, |
| 2332 | + write_empty_list=write_empty_list, |
| 2333 | + ) |
| 2334 | + if self._repeated: |
| 2335 | + if auto_now: |
| 2336 | + raise ValueError( |
| 2337 | + "DateTimeProperty {} could use auto_now and be " |
| 2338 | + "repeated, but there would be no point.".format(self._name) |
| 2339 | + ) |
| 2340 | + elif auto_now_add: |
| 2341 | + raise ValueError( |
| 2342 | + "DateTimeProperty {} could use auto_now_add and be " |
| 2343 | + "repeated, but there would be no point.".format(self._name) |
| 2344 | + ) |
| 2345 | + if auto_now is not None: |
| 2346 | + self._auto_now = auto_now |
| 2347 | + if auto_now_add is not None: |
| 2348 | + self._auto_now_add = auto_now_add |
| 2349 | + |
| 2350 | + def _validate(self, value): |
| 2351 | + """Validate a ``value`` before setting it. |
| 2352 | +
|
| 2353 | + Args: |
| 2354 | + value (~datetime.datetime): The value to check. |
| 2355 | +
|
| 2356 | + Raises: |
| 2357 | + .BadValueError: If ``value`` is not a :class:`~datetime.datetime`. |
| 2358 | + """ |
| 2359 | + if not isinstance(value, datetime.datetime): |
| 2360 | + raise exceptions.BadValueError( |
| 2361 | + "Expected datetime, got {!r}".format(value) |
| 2362 | + ) |
| 2363 | + |
| 2364 | + @staticmethod |
| 2365 | + def _now(): |
| 2366 | + """datetime.datetime: Return current time. |
| 2367 | +
|
| 2368 | + This is in place so it can be patched in tests. |
| 2369 | + """ |
| 2370 | + return datetime.datetime.utcnow() |
| 2371 | + |
| 2372 | + def _prepare_for_put(self, entity): |
| 2373 | + """Sets the current timestamp when "auto" is set. |
| 2374 | +
|
| 2375 | + If one of the following scenarios occur |
| 2376 | +
|
| 2377 | + * ``auto_now=True`` |
| 2378 | + * ``auto_now_add=True`` and the ``entity`` doesn't have a value set |
| 2379 | +
|
| 2380 | + then this hook will run before the ``entity`` is ``put()`` into |
| 2381 | + the datastore. |
| 2382 | +
|
| 2383 | + Args: |
| 2384 | + entity (Model): An entity with values. |
| 2385 | + """ |
| 2386 | + if self._auto_now or ( |
| 2387 | + self._auto_now_add and not self._has_value(entity) |
| 2388 | + ): |
| 2389 | + value = self._now() |
| 2390 | + self._store_value(entity, value) |
| 2391 | + |
| 2392 | + def _db_set_value(self, v, p, value): |
| 2393 | + """Helper for :meth:`_serialize`. |
| 2394 | +
|
| 2395 | + Raises: |
| 2396 | + NotImplementedError: Always. This method is virtual. |
| 2397 | + """ |
| 2398 | + raise NotImplementedError |
| 2399 | + |
| 2400 | + def _db_get_value(self, v, unused_p): |
| 2401 | + """Helper for :meth:`_deserialize`. |
| 2402 | +
|
| 2403 | + Raises: |
| 2404 | + NotImplementedError: Always. This method is virtual. |
| 2405 | + """ |
2255 | 2406 | raise NotImplementedError |
2256 | 2407 |
|
2257 | 2408 |
|
|
0 commit comments