Enhanced time synchronization for drf views.
| 1 | # TimeCheck |
| 2 | |
| 3 | **TimeCheck** is a Django REST Framework (DRF) extension that wraps the standard `Request` with first-class support for timestamp-based synchronization. |
| 4 | |
| 5 | It adds a structured way to compare client and server timestamps, detect when updates are needed, and manage API headers in a consistent, testable manner. |
| 6 | |
| 7 | --- |
| 8 | |
| 9 | ## Features |
| 10 | |
| 11 | - 🧠 `TimeCheck`: context-aware timestamp diff logic (`raise_get`, etc.) |
| 12 | - ⚙️ Configurable via `TimeCheckConf` (env vars or `settings.TIMECHECK_CONF`) |
| 13 | - 🔒 Built-in error handling for missing or invalid timestamps |
| 14 | |
| 15 | --- |
| 16 | |
| 17 | ## Installation |
| 18 | |
| 19 | ```bash |
| 20 | pip install git+https://github.com/huncholane/django-timecheck |
| 21 | ```` |
| 22 | |
| 23 | --- |
| 24 | |
| 25 | ## Usage |
| 26 | |
| 27 | ### Example View |
| 28 | |
| 29 | ```python |
| 30 | from timecheck import TimeCheck |
| 31 | from rest_framework.request import Request |
| 32 | from rest_framework.response import Response |
| 33 | from rest_framework.views import APIView |
| 34 | from .models import MyModel |
| 35 | |
| 36 | class MyView(APIView): |
| 37 | def get(request: Request, pk:int): |
| 38 | instance = MyModel.objects.get(pk=pk) |
| 39 | TimeCheck(request,instance).should_get() # throws rest framework exception |
| 40 | ... |
| 41 | return Response(data) |
| 42 | |
| 43 | def put(request: Request, pk:int): |
| 44 | instance = MyModel.objects.get(pk=pk) |
| 45 | TimeCheck(request,instance).should_update() # throws rest framework exception |
| 46 | ... |
| 47 | return Response(data) |
| 48 | ``` |
| 49 | |
| 50 | --- |
| 51 | |
| 52 | ### Parameters |
| 53 | |
| 54 | TimeCheck(request, ...) has the following arguments: |
| 55 | |
| 56 | - **request** `rest_framework.request.Request`: A request to extract the client timestamp from. (required) |
| 57 | - **instance** `models.Model`: The database instance to extract the server timestamp from using the instance field. (optional) |
| 58 | - **server_timestamp** `dt.datetime`: A manual server timestamp to override TimeCheck parsing.(optional) |
| 59 | - **client_timestamp** `dt.datetime`: A manual client timestamp to override TimeCheck parsing. (optional) |
| 60 | - **header_field** `str`: Field to find client timestamp in the header (defaults to config) |
| 61 | - **body_field** `str`: Field to find client timestamp in request body (defaults to config) |
| 62 | - **instance_field** `str`: Field to find server timestamp in database model (defaults to config) |
| 63 | - **missing_action**: `"noupdate" | "continue"` (defaults to config) |
| 64 | - `noupdate` will raise a drf exception to indicate the view should stop early if the client does not provide a timestamp |
| 65 | - `continue` allows the view to continue processing data when the client does not provide a timestamp |
| 66 | - **noupdate_code** `int` The response code for exceptions raised to indicate the view should stop processing |
| 67 | - **dt_fmt** `str` The datetime format used to normalize timestamps. Useful when the client and server use mismatched time depths (defaults to config) |
| 68 | - **raise_exception** `bool` Raises and exception if the process should stop (defaults to config) |
| 69 | |
| 70 | ### TimeCheck Methods |
| 71 | |
| 72 | - `should_get` Raises a `NoUpdate` exception when the client timestamp is newer than or equal to the server timestamp. Returns a True if the client should receive data. |
| 73 | - `should_update` Raises a `NoUpdate` exception when the client timestamp is older than or equal to the server timestamp. Returns a True if the update should continue. |
| 74 | |
| 75 | --- |
| 76 | |
| 77 | ## Configuration |
| 78 | |
| 79 | TimeCheck is customizable through the django settings, env, and on a per usage basis. |
| 80 | |
| 81 | ### Django Settings Dictionairy (Defaults) |
| 82 | |
| 83 | ```python |
| 84 | TIMECHECK_CONF = { |
| 85 | "body_field": "lastUpdated", |
| 86 | "dt_fmt": "%Y-%m-%dT%H:%M:%S%z", |
| 87 | "header_field": "lastUpdated", |
| 88 | "instance_field": "lastUpdated", |
| 89 | "missing_action": "noupdate", |
| 90 | "noupdate_code": 418, |
| 91 | "raise_exception": True, |
| 92 | } |
| 93 | ``` |
| 94 | |
| 95 | ### |
| 96 | |
| 97 | Set via environment variables or in `settings.TIMECHECK_CONF`: |
| 98 | |
| 99 | | Key | Default | Description | |
| 100 | | -------------------------------------- | --------------------- | ------------------------------------------------------------- | |
| 101 | | `TIMECHECK_HEADER_FIELD` | `lastUpdated` | Header field used for client timestamp (str) | |
| 102 | | `TIMECHECK_BODY_FIELD` | `lastUpdated` | Body field used for client timestamp (str) | |
| 103 | | `TIMECHECK_INSTANCE_FIELD` | `lastUpdated` | Model field used for server timestamp (str) | |
| 104 | | `TIMECHECK_NOUPDATE_CODE` | `418` | Error code raised when update is unnecessary (int) | |
| 105 | | `TIMECHECK_MISSING_ACTION` | `noupdate` | Action if timestamp is missing (`continue`, `noupdate`) | |
| 106 | | `TIMECHECK_DT_FMT` | `%Y-%m-%dT%H:%M:%S%z` | Timestamp parsing format (str) | |
| 107 | | RAISE_EXCEPTION | True | Raises exceptions by default (bool) | |
| 108 | |
| 109 | --- |
| 110 | |
| 111 | ## Exceptions |
| 112 | |
| 113 | - `InvalidClientDatetimeField (400)` When the client timestamp is found but cannot be parsed. |
| 114 | - `InvalidServerDatetimeField (500)` When the server cannot make up a timestamp. |
| 115 | - `NoUpdate (Custom)` Custom status code used to represent no update. |
| 116 | |
| 117 | Raised when timestamps are missing, malformatted, or update should be skipped. |
| 118 | |
| 119 | --- |
| 120 | |
| 121 | ## License |
| 122 | |
| 123 | This project is licensed under the [MIT License](LICENSE). |