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

Provide more explicit docs about mutable state default values #653

Merged
merged 3 commits into from
Jul 21, 2024
Merged
Show file tree
Hide file tree
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
87 changes: 65 additions & 22 deletions docs/guides/state_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,71 @@ def page():
me.text(state.val)
```

## Use immutable default values

Similar to [regular dataclasses which disallow mutable default values](https://docs.python.org/3/library/dataclasses.html#mutable-default-values), you need to avoid mutable default values such as list and dict for state classes. Using mutable default values can result in leaking state across sessions which can be a serious privacy issue.
wwwillchen marked this conversation as resolved.
Show resolved Hide resolved

You **MUST** use immutable default values _or_ use dataclasses `field` initializer _or_ not set a default value.

???+ success "Good: immutable default value"
Setting a default value to an immutable type like str is OK.

```py
@me.stateclass
class State:
a: str = "abc"
```

???+ failure "Bad: mutable default value"

The following will raise an exception because dataclasses prevents you from using mutable collection types like `list` as the default value because this is a common footgun.

```py
@me.stateclass
class State:
a: list[str] = ["abc"]
```

If you set a default value to an instance of a custom type, an exception will not be raised, but you will be dangerously sharing the same mutable instance across sessions which could cause a serious privacy issue.

```py
@me.stateclass
class State:
a: MutableClass = MutableClass()
```

???+ success "Good: default factory"

If you want to set a field to a mutable default value, use default_factory in the `field` function from the dataclasses module to create a new instance of the mutable default value for each instance of the state class.

```py
from dataclasses import field

@me.stateclass
class State:
a: list[str] = field(default_factory=lambda: ["abc"])
```

???+ success "Good: no default value"

If you want a default of an empty list, you can just not define a default value and Mesop will automatically define an empty list default value.

For example, if you write the following:

```py
@me.stateclass
class State:
a: list[str]
```

It's the equivalent of:

```py
@me.stateclass
class State:
a: list[str] = field(default_factory=list)
```

## How State Works

`me.stateclass` is a class decorator which tells Mesop that this class can be retrieved using the `me.state` method, which will return the state instance for the current user session.
Expand Down Expand Up @@ -109,28 +174,6 @@ If you didn't explicitly annotate NestedState as a dataclass, then you would get

## Tips

### Set mutable default values (e.g. list) correctly

Similar to [regular dataclasses which disallow mutable default values](https://docs.python.org/3/library/dataclasses.html#mutable-default-values), you need to avoid mutable default values such as list and dict for state classes. Allowing mutable default values could lead to erroneously sharing state across users which would be bad!

**Bad:** Setting a mutable field directly on a state class attribute.

```py
@me.stateclass
class State:
x: list[str] = ["a"]
```

**Good:** Use dataclasses `field` method to define a default factory so a new instance of the mutable value is created with each state class instance.

```py
from dataclasses import field

@me.stateclass
class State:
x: list[str] = field(default_factory=lambda: ["a"])
```

### State performance issues

Because the state class is serialized and sent back and forth between the client and server, you should try to keep the state class reasonably sized. For example, if you store a very large string (e.g. base64-encoded image) in state, then it will degrade performance of your Mesop app. Instead, you should try to store large data outside of the state class (e.g. in-memory, filesystem, database, external service) and retrieve the data as needed for rendering.
Expand Down
5 changes: 3 additions & 2 deletions docs/stylesheets/extra.css
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,9 @@ p {
font-size: 0.8rem;
}

.md-typeset .admonition {
font-size: 0.75rem;
.md-typeset .admonition,
.md-typeset details {
font-size: 0.8rem;
}

.highlight code {
Expand Down
Loading