safrs icon indicating copy to clipboard operation
safrs copied to clipboard

Read-only Columns

Open JM0804 opened this issue 2 years ago • 2 comments

I was wondering if it's possible to create columns which are read-only when exposed via the API.

For example, I have a data model with a last_completed column which I want to restrict to being set or updated only via a call to a custom RPC call named complete, exposed with @jsonapi_rpc. It also has some computed values such as due_date which are exposed with @jsonapi_attr:

import datetime
from dateutil.relativedelta import relativedelta
from safrs import SAFRSBase, jsonapi_attr, jsonapi_rpc
from typing import Optional

from app import db


class Task(SAFRSBase, db.Model):
    __tablename__ = 'tasks'
    id = db.Column(db.Integer, primary_key=True)
    last_completed = db.Column(db.Date, nullable=True)
    name = db.Column(db.String)

    @jsonapi_rpc(http_methods=["POST"])
    def complete(self):
        self.last_completed = datetime.datetime.now().date()

    @jsonapi_attr
    def due_date(self) -> Optional[datetime.datetime]:
        if self.last_completed:
            return self.last_completed + relativedelta(
                **{self.frequency_unit.value: self.frequency_value}
            )
        else:
            return None

To clarify, I would like last_completed and due_date to be available via GET but not PATCH or POST.

Attempting to set such a restriction with last_completed.http_methods = ['GET'] as suggested for relationships here doesn't work.

Perhaps there is something similar to the hidden columns feature that I am not seeing?

Many thanks :)

JM0804 avatar Apr 25 '22 00:04 JM0804

Hi,

due_date in your example should not be available via PATCH or POST. It does show up in the swagger but it should be omitted from serialization when these requests are processe. The OAS/swagger should take this into account (i.e. not show the attribute unless a setter is defined), so I'll fix that. Anyway, it is possible to update/override the swagger manually using the custom_swagger argument to SAFRSAPI, example. So you can use this approach to remove the attributes from the OAS spec.

Indeed, you can also use hidden columns in combination with jsonapi_attr to create read-only attributes because hidden columns are also not (de)serialized. Note, column names prefixed with an underscore are automatically hidden.

thomaxxl avatar Apr 25 '22 07:04 thomaxxl

Hi @thomaxxl,

Thanks so much for the speedy reply. Apologies for not getting back to you sooner.

I have made the following modifications and can confirm that the properties in question are silently rejected (i.e. ignored) when making a POST or PATCH request (there is a slight change also from the previous code where I am computing due_date only once rather than on every request, and have made this and the last_completed properties read-only):

import datetime
from dateutil.relativedelta import relativedelta
from safrs import SAFRSBase, jsonapi_attr, jsonapi_rpc
from typing import Optional

from app import db


class Task(SAFRSBase, db.Model):
    __tablename__ = 'tasks'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    _due_date = db.Column(db.Date, nullable=True)
    _last_completed = db.Column(db.Date, nullable=True)

    @jsonapi_attr
    def due_date(self):
        return self._due_date

    @jsonapi_attr
    def last_completed(self):
        return self._last_completed

    @jsonapi_rpc(http_methods=["POST"])
    def complete(self):
        self._last_completed = datetime.datetime.now().date()
        self._due_date = self._last_completed + relativedelta(
            **{self.frequency_unit.value: self.frequency_value}
        )

I'm happy to consider this issue closed if you are :)

JM0804 avatar Apr 26 '22 20:04 JM0804