Skip to content
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

Beginnings of typed accessors for client data and outputs #719

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions shiny/session/_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,73 @@ def __instancecheck__(self, __instance: Any) -> bool:
return isinstance(__instance, SessionProxy)


class OutputInfo:
_input: Inputs
_id: Id

def __init__(self, input: Inputs, id: Id):
self._input = input
self._id = id

# TODO: Consider what to do if the outputs are not present right now. Should we
# allow a default value? Cause by default, it will throw a silent error.

def width(self):
return self._input[f".clientdata_output_{self._id}_width"]()
Comment on lines +152 to +153
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of implementing individual functions, it might be better to implement a __getattr__ method. That would provide a place to handle outputs that don't exist yet.

Another useful thing with a __getattr__ method is that it could return reactive.Value object itself, instead of only providing a way to invoke () on the object. Although... that could also be accomplished @property decorator -- and using that decorator on each individual item would also allow more specific typing, like Value[bool].

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the __getattr__ due to the inability to type or document each value (right?). The @property could work though. I don't love that reactive.Value exposes .set() that doesn't actually work, but the other methods on Value could be helpful.


def height(self):
return self._input[f".clientdata_output_{self._id}_height"]()

def hidden(self):
return self._input[f".clientdata_output_{self._id}_hidden"]()

def color_fg(self):
return self._input[f".clientdata_output_{self._id}_fg"]()

def color_bg(self):
return self._input[f".clientdata_output_{self._id}_bg"]()

def color_accent(self):
return self._input[f".clientdata_output_{self._id}_accent"]()

def font(self):
return self._input[f".clientdata_output_{self._id}_font"]()


class ClientData:
_input: Inputs

def __init__(self, input: Inputs):
self._input = input

# TODO: Consider allowing these properties to be read even if there is no reactive
# context (i.e. implicit isolate() rather than throw a "context not found" error). I
# think it's reasonable to think people would want to interrogate the path and URL
# as part of session initialization, not even being aware that those can change
# reactively.

def url_protocol(self):
return self._input[".clientdata_url_protocol"]()

def url_hostname(self):
return self._input[".clientdata_url_hostname"]()

def url_port(self):
return self._input[".clientdata_url_port"]()

def url_pathname(self):
return self._input[".clientdata_url_pathname"]()

def url_search(self):
return self._input[".clientdata_url_search"]()

def url_hash(self):
return self._input[".clientdata_url_hash"]()

def pixel_ratio(self):
return self._input[".clientdata_pixelratio"]()


class Session(object, metaclass=SessionMeta):
"""
A class representing a user session.
Expand Down Expand Up @@ -171,6 +238,7 @@ def __init__(

self.input: Inputs = Inputs(dict())
self.output: Outputs = Outputs(self, self.ns, dict(), dict())
self.client_data = ClientData(self.input)

self.user: str | None = None
self.groups: list[str] | None = None
Expand Down
Loading