irongit

Enhanced time synchronization for drf views.

121 lines4.7 KBPython
1import datetime as dt
2from django.db import models
3from rest_framework.request import Request
4
5from timecheck.exceptions import (
6 InvalidClientDatetimeField,
7 InvalidServerDatetimeField,
8 NoUpdate,
9)
10from timecheck.types import MissingAction
11from timecheck.settings import conf
12import logging
13
14from timecheck.utils import normalize_dt, parse_dt
15
16
17logger = logging.getLogger(__name__)
18
19
20class TimeCheckPrivate:
21 """Builds a set of instructions on how to interpret client and server timestamps.
22 Initialization will always raise an error if the server timestamp is missing.
23 Errors for missing client timestamps can be configured."""
24
25 _missing_action: MissingAction
26
27 def __init__(
28 self,
29 request: Request,
30 instance: models.Model | None = None,
31 server_timestamp: dt.datetime | None = None,
32 client_timestamp: dt.datetime | None = None,
33 header_field: str | None = None,
34 body_field: str | None = None,
35 instance_field: str | None = None,
36 missing_action: MissingAction | None = None,
37 noupdate_code: int | None = None,
38 dt_fmt: str | None = None,
39 raise_exception: bool | None = None,
40 ):
41 self.request = request
42 self._header_field = (
43 header_field if header_field is not None else conf["header_field"]
44 )
45 self._body_field = body_field if body_field is not None else conf["body_field"]
46 self._instance_field = (
47 instance_field if instance_field is not None else conf["instance_field"]
48 )
49 self._noupdate_code = (
50 noupdate_code if noupdate_code is not None else conf["noupdate_code"]
51 )
52 self._missing_action = (
53 missing_action if missing_action is not None else conf["missing_action"]
54 )
55 self._dt_fmt = dt_fmt if dt_fmt is not None else conf["dt_fmt"]
56 self._raise_exception = (
57 raise_exception if raise_exception is not None else conf["raise_exception"]
58 )
59 self.client_timestamp = client_timestamp
60
61 if server_timestamp:
62 self.server_timestamp = normalize_dt(server_timestamp, self._dt_fmt)
63 elif self._instance_field and hasattr(instance, self._instance_field):
64 val = getattr(instance, self._instance_field)
65 if isinstance(val, dt.datetime):
66 self.server_timestamp = normalize_dt(val, self._dt_fmt)
67 else:
68 raise InvalidServerDatetimeField(
69 self._instance_field or "No instance field",
70 instance,
71 )
72
73 if not self.client_timestamp:
74 header_str = self.request.headers.get(self._header_field, None)
75 body_str = self.request.data.get(self._body_field, None)
76 if header_str:
77 try:
78 self.client_timestamp = parse_dt(header_str)
79 except Exception:
80 raise InvalidClientDatetimeField(self._header_field, header_str)
81 elif body_str:
82 try:
83 self.client_timestamp = parse_dt(body_str)
84 except Exception:
85 raise InvalidClientDatetimeField(self._body_field, body_str)
86
87 def should_get(self):
88 """Raises a drf exception `NoUpdate` which provides details that there is no need to give data back to the user."""
89 logger.debug(
90 f"Checking get: client={self.client_timestamp}, server={self.server_timestamp}"
91 )
92 if not self.client_timestamp:
93 if self._missing_action == "noupdate":
94 if self._raise_exception:
95 raise NoUpdate(self.request.method, self._noupdate_code)
96 else:
97 return False
98 elif self.client_timestamp >= self.server_timestamp:
99 if self._raise_exception:
100 raise NoUpdate(self.request.method, self._noupdate_code)
101 else:
102 return False
103 return True
104
105 def should_update(self):
106 """Raises a drf exception `NoUpdate` which provides details that there is no need to update the server."""
107 logger.debug(
108 f"Checking update: client={self.client_timestamp}, server={self.server_timestamp}"
109 )
110 if not self.client_timestamp:
111 if self._missing_action == "noupdate":
112 if self._raise_exception:
113 raise NoUpdate(self.request.method, self._noupdate_code)
114 else:
115 return False
116 elif self.client_timestamp <= self.server_timestamp:
117 if self._raise_exception:
118 raise NoUpdate(self.request.method, self._noupdate_code)
119 else:
120 return False
121 return True