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

feat: cache functions and fake relations in filters #127

Merged
merged 8 commits into from
Jun 5, 2023

Conversation

lukka5
Copy link
Collaborator

@lukka5 lukka5 commented May 8, 2023

  • Update CHANGELOG.md
  • Update README.md (as needed)
  • New dependencies were added to pyproject.toml
  • pip install succeeds with a clean virtualenv
  • There are new or modified tests that cover changes
  • Test coverage is maintained or expanded

@salesforce-cla
Copy link

salesforce-cla bot commented May 8, 2023

Thanks for the contribution! It looks like @lukka5 is an internal user so signing the CLA is not required. However, we need to confirm this.

@@ -280,4 +311,5 @@ def apply_filters_to_object(inst, filter_def, expand_header=""):
expand_children=expand_dict,
klass=inst.__class__,
filter_cache={},
model_cache={},
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

decided to add a new cache instead of reusing filter_cache since they key/values types are completely different from filter cache.

Filter cache using is storing

  • key: (expandables, class, pk)
  • value: defaultdict(...) with filtered/formatted results

Model cache:

  • key: (class, pk)
  • value: model instances

logger.debug("ev=model_cache, status=miss, key=%s", cache_key)
related_instance = getattr(inst, field_name)
model_cache[cache_key] = related_instance
setattr(inst, field_name, related_instance)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

idea here is to pre-populate the related instance so if the function decides to access it or use it (normally it does) we don't do another query to the db. In example:

FILTERS = {
    'counter': lambda inst: inst.counter.count + 1
}

When that function filter is called counter has already been populated by a counter from cache or stored for later used by another filter function.

@@ -67,6 +68,34 @@ def _get_unexpanded_field_value(inst, field_name, field_type):
return {display_key: getattr(obj, display_key)}


def _is_relation(field):
return field.is_relation or getattr(field, "is_fake_relation", False)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this adds support for fake relationships. In example CharFields acting as foreign keys.

@lukka5 lukka5 changed the title feat: filter cache improvements feat: cache function and fake relations in filters May 8, 2023
@lukka5 lukka5 changed the title feat: cache function and fake relations in filters feat: cache functions and fake relations in filters May 8, 2023
@lukka5 lukka5 force-pushed the feature/improve-filter-caching branch from 4bfb611 to 4011413 Compare May 9, 2023 14:36
@rohansroy rohansroy self-requested a review May 30, 2023 22:13
else:
logger.debug("ev=model_cache, status=miss, key=%s", cache_key)
related_instance = getattr(inst, field_name)
if not isinstance(related_instance, models.Model):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems very generic, can't we use the instance type of val_cls

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good catch. yeah, I like it

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

added it

try:
field_meta = inst._meta.get_field(field_name)
except (AttributeError, FieldDoesNotExist):
return
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm curious why we're returning None here, shouldn't we bubble up the error? The user is trying to cache a field that doesn't exist

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The problem is that field_name is a consumer defined string for specifying the output field name after filtering it. So it can have aj different name of the actual field. For example:

FILTERS = {
    'my_increased_counter': lambda inst: inst.counter.count + 1
}

Here field_name would be my_increased_counter which doesn't exists in the model.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Right, but shouldn't we alert the user that the field doesn't exist somehow?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Code won't break if the field doesn't exists. It just won't get cached.

Or do mean alerting the user that using my_increased_counter as field name he won't get caching? In that case I'm not sure how much useful it is but yeah we could add some logging

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think I understand what you're saying now. This PR looks good, approved!

return
if not _is_relation(field_meta) or _is_reverse_relation(field_meta):
return # no `attname` in reverse relations
val_pk = getattr(inst, field_meta.attname)
Copy link
Collaborator

Choose a reason for hiding this comment

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

can't we just wrap this in a try catch block and log the error or reraise the error?

Copy link
Collaborator Author

@lukka5 lukka5 Jun 2, 2023

Choose a reason for hiding this comment

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

that's a good point. I had run over the code again to be sure and the problem is that some fields have attname but are not relations so we cannot cache them

Copy link
Collaborator

Choose a reason for hiding this comment

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

ah ok.

@@ -144,18 +174,17 @@ def _make_filter_cache_key(expand_children, klass, pk):
return (str(expand_children), klass, str(pk))


def _make_model_cache_key(klass, pk):
return (klass, str(pk))
Copy link
Collaborator

Choose a reason for hiding this comment

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

what will this key look like if we have to manually inspect it in cache? Can we have a more human readable cache key here, maybe something like {klass.__cache__.__name}:{str(pk)}

Copy link
Collaborator

Choose a reason for hiding this comment

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

nevermind, i see it matches the style of the cache key of the other cache

@rohansroy rohansroy self-requested a review May 31, 2023 22:14
rohansroy
rohansroy previously approved these changes May 31, 2023
@rohansroy rohansroy self-requested a review June 5, 2023 15:41
@lukka5 lukka5 merged commit 3771323 into salesforce:main Jun 5, 2023
@lukka5 lukka5 deleted the feature/improve-filter-caching branch June 5, 2023 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants