Issue: ORM traversal does not reflect updates
Author: aiqc
State: closed
Created: 2022-05-27 16:54:45

UPDATE: see next comment below

---

I am using the auto-incrementing id provided implicitly by BaseModel. 

`save()` is working for my `Fold` object, but not my `Label` object. I am puzzled as to why because there is nothing different about their Models.

## Models
```python
from playhouse.sqlite_ext import SqliteExtDatabase, JSONField
from playhouse.signals import Model, pre_save
from peewee import CharField, IntegerField, BlobField, BooleanField, FloatField, \
    DateTimeField, ForeignKeyField, DeferredForeignKey


class BaseModel(Model):
    time_created = DateTimeField()
    time_updated = DateTimeField()
	
    class Meta:
        database = SqliteExtDatabase(path, pragmas={})


class Fold(BaseModel):
    fold_index = IntegerField()
    samples = JSONField()
    fitted_labelcoder = PickleField(null=True) #<--- field in question


class Label(BaseModel):
    columns = JSONField()
    column_count = IntegerField()
    fitted_labelcoder = PickleField(null=True) #<--- field in question
```

## Script

```python
if (fold is not None):
    fitted_encoders = fold.fitted_labelcoder
    item = fold
else:
    fitted_encoders = label.fitted_labelcoder
    item = label
    
labelcoder = labelcoders[-1]
if (fitted_encoders is None):
    samples_train = samples[key_train]

    fitted_encoders, _ = utils.encoding.fit_labels(
	    arr_labels=label_array, samples_train=samples_train, labelcoder=labelcoder
    )
    # Save the fit for decoding and inference.
    item.fitted_labelcoder = fitted_encoders
    item.save()

    # `Fold` saves just fine, but `Label` does not
    # I can print `item.fitted_labelcoder` and it doesn't show None.
    # tried `label.save` instead of "item"
    # tried `label.update(fitted_labelcoder=fitted_encoders)
```

## Troubleshooting

I've been using peewee for 2 years, but I am scratching my head as to why it's not working:

- Verified that the created vs updated timestamps are identical... so no update is happening, which rules out the attribute being overwritten back to `None` somewhere else in the script too
- Verified that the attribute is not overwritten back to None later in the script.
- Tried on peewee == `3.14.3` and `3.14.10`
- Tried `instance.update(field=value)` instead of `instance.save`
- Tried changing class name from `BaseModel` to `ModelBase`
- Tried deleting the empty pragmas dict
- Verified that both models are using the same db connection
- Verified that the object I am trying to save bears the `<Model: Label>` class.
- Verified that there is no `pre_save` funny business going on
- Deleted/ recreated the db and its tables several times

Okay, so when I query the object directly with `select()`, it does indeed show as being updated.

However, when accessing the object through a relationship that existed prior to the update(), the update() is not reflected. That is what was throwing me off.

---

There is only 1 Label
```python
>>> list(orm.Label.select())
[<Label: 1>]
```

```python
>>> tf_bin_tab = some query
>>> tf_bin_tab.some_operation_that_updates_label()

>>> tf_bin_tab.splitset.label.__dict__ #<-- why isn't traversing the rels running a new query?
{'__data__': {'id': 1,
  'time_created': '2022-05-27 13:02:48.587761-04:00',
  'time_updated': '2022-05-27 13:02:48.587782-04:00', #<--- unchanged, why?
  'name': None,
  'version': None,
  'description': None,
  'columns': ['object'],
  'column_count': 1,
  'unique_classes': ['R', 'M'],
  'fitted_labelcoder': None, #<--- unchanged, why?
  'dataset': 1},
 '_dirty': set(),
 '__rel__': {}}
[0]
```

Accessing it via a fresh query
```python
>>> tf_bin_tab = some query
>>> tf_bin_tab.some_operation_that_updates_label()

>>> orm.Label.select()[0].__dict__
{'__data__': {'id': 1,
  'time_created': '2022-05-27 13:02:48.587761-04:00',
  'time_updated': '2022-05-27 13:02:48.889901-04:00', #<--- changed, as expected
  'name': None,
  'version': None,
  'description': None,
  'columns': ['object'],
  'column_count': 1,
  'unique_classes': ['R', 'M'],
  'fitted_labelcoder': [LabelBinarizer()], #<--- changed, as expected
  'dataset': 1},
 '_dirty': set(),
 '__rel__': {}}
```

Okay, and if I do a fresh query of the related object, and access it - then it too bears the updates

```python
orm.Splitset.select()[0].label.__dict__
{'__data__': {'id': 1,
  'time_created': '2022-05-27 13:02:48.587761-04:00',
  'time_updated': '2022-05-27 13:02:48.889901-04:00', #<--- changed, as expected
  'name': None,
  'version': None,
  'description': None,
  'columns': ['object'],
  'column_count': 1,
  'unique_classes': ['R', 'M'],
  'fitted_labelcoder': [LabelBinarizer()], #<--- changed, as expected
  'dataset': 1},
 '_dirty': set(),
 '__rel__': {}}
```

So it looks like the querysets are stale in that they don't reflect the update. Is this to be expected?

Seems like the crux of the question is this:

```python
>>> tf_bin_tab.splitset.label.__dict__ #<-- why isn't traversing the rels running a new query?
```

If you access the related object, Peewee caches the instance in-memory so that future accesses do not repeat the query. If you update the object somewhere else (e.g. not the in-memory instance) then the cached one will not show the update. Peewee is an active record orm so this is expected.

Example:

```python
db = SqliteDatabase(':memory:')

class Base(Model):
    class Meta: database = db

class User(Base):
    username = TextField()

class Tweet(Base):
    user = ForeignKeyField(User)
    content = TextField()

db.create_tables([User, Tweet])

u = User.create(username='u')
Tweet.create(user=u, content='t1')

# Demonstrate the way related objects work:
t = Tweet.get()
t.user.username = 'ux'
t.user.save()

assert u.username == 'u'

u = User.get()
assert u.username == 'ux'
```