-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: switch User and Content to inherit dict, add update method for User #94
Conversation
6bca54f
to
db96777
Compare
☂️ Python Coverage
Overall Coverage
New FilesNo new covered files... Modified Files
|
I've updated both User and ContentItem to inherit from dict and added all of the property accessors. I also revised the One issue I could use help with: mypy does not like this change. |
Ok @tdstein I think this is done (enough) and we should merge so we can unblock other work. |
Can we implement an def __init__(self, session: requests.Session, url: str, **kwargs):
self._session = session
self._url = url
super().__init__(**kwargs) |
Let's go ahead and defer this decision until later. I'm satisfied that |
def _update(self, body): | ||
self.get("session").patch(self.get("url"), json=body) | ||
# If the request is successful, update the local object | ||
super().update(body) | ||
# TODO(#99): that patch request returns a payload on success, | ||
# so we should instead update the local object with that payload | ||
# (includes updated_time) | ||
|
||
def update( # type: ignore | ||
self, | ||
# Not all properties are settable, so we enumerate them here | ||
# (also for type-hinting purposes) | ||
email: Optional[str] = None, | ||
username: Optional[str] = None, | ||
first_name: Optional[str] = None, | ||
last_name: Optional[str] = None, | ||
user_role: Optional[str] = None, | ||
# TODO(#100): in the API, this goes via POST /v1/users/{guid}/lock | ||
# accept it here and make that request? Or add a .lock() method? | ||
# locked: Optional[bool] = None, | ||
) -> None: | ||
kwargs = {} | ||
if email is not None: | ||
kwargs["email"] = email | ||
if username is not None: | ||
kwargs["username"] = username | ||
if first_name is not None: | ||
kwargs["first_name"] = first_name | ||
if last_name is not None: | ||
kwargs["last_name"] = last_name | ||
if user_role is not None: | ||
kwargs["user_role"] = user_role | ||
# if locked is not None: | ||
# kwargs["locked"] = locked | ||
self._update(kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good. But should we enforce these logical constraints or defer this behavior to the server? In other words, how do we handle behavioral changes to the PATCH endpoint between versions of Connect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suppose a future version of Connect adds a new field you can PATCH to update. We would add it here explicitly, and users would need to upgrade the SDK to the latest to access it. That seems fine.
Suppose a future version of Connect changed it so that user_role
didn't exist, and instead you provided a permissions
dict of named booleans. We would add a check here for the version of the Connect server, and if you provided user_role
to a version of Connect that only supported permissions
, map our understanding of our current roles to those permissions bundles (or error). We could also do the reverse and map known permission bundles back to user_role (or error).
So I think this is ok, or at least we have options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok @tdstein I think this is done (enough) and we should merge so we can unblock other work.
Agreed. Let's merge and continue iterating.
I don't think that will succeed in hiding the session and url from the dict itself since it inherits dict, but we can experiment more. Let's merge this and then your other PR, and then we can tinker with the base class. |
To help us move the discussion forward. I switched User to inherit from
dict
to see how that works, including adding an update method and wiring that up. I first triedUserDict
but it messed up the sort order of the columns in the DataFrame version, and googling a bit, it sounded like it wasn't so bad to inherit fromdict
anymore, so I figured I'd try that and see how it went. Summary of what I found so far, as reflected in the test suite:property
s for them, it doesn't break, and you can still get at them via[
. Missing fields also are fine, at least as long as you don't access them (you'd get a KeyError).update()
method works. I was worried from that blog post that you wouldn't be able to define a new one, but it was fine. I did not try setattr, and my guess is that we'd want to disable that anyway (but that's a discussion for another time).pd.DataFrame(list of Users)
works fine since it is a dict still, but: in adding thesession
andurl
to the object in order to implementupdate()
, those now show up in the data frame. AFAIK there's no way to hide them somewhere else, it's just a dict.@property
s work to access the data, but the type hints don't enforce anything. I didn't add in the datetime parsing, just to see what happens, and the methods happily return the unparsed strings. Not strictly a problem, but worth keeping in mind.__init__
method to do things like parse timestamps. As we discussed before, there are tradeoffs to doing this eagerly vs. on demand. Alternatively, if we kept it lazy (my preference), we could add ato_pandas
function or something that would parse them when you do that conversion, and leave the attribute parsing to theproperty
getters.