Skip to content

Commit

Permalink
feat: OPTIC-1367: Find opportunities for property cache (#6808)
Browse files Browse the repository at this point in the history
  • Loading branch information
mcanu authored Jan 2, 2025
1 parent f961f01 commit f60188b
Show file tree
Hide file tree
Showing 8 changed files with 36 additions and 26 deletions.
7 changes: 6 additions & 1 deletion label_studio/core/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging

from django.db.models.query import QuerySet
from django.utils.functional import cached_property
from rest_framework.generics import get_object_or_404

logger = logging.getLogger(__name__)
Expand All @@ -16,7 +17,11 @@ def has_permission(self, user):
class GetParentObjectMixin:
parent_queryset = None

def get_parent_object(self):
@cached_property
def parent_object(self):
return self._get_parent_object()

def _get_parent_object(self):
"""
The same as get_object method from DRF, but for the parent object
For example if you want to get project inside /api/projects/ID/tasks handler
Expand Down
20 changes: 10 additions & 10 deletions label_studio/data_import/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from django.conf import settings
from django.db import models
from django.utils.functional import cached_property
from rest_framework.exceptions import ValidationError

logger = logging.getLogger(__name__)
Expand All @@ -36,7 +37,7 @@ def has_permission(self, user):
user.project = self.project # link for activity log
return self.project.has_permission(user)

@property
@cached_property
def filepath(self):
return self.file.name

Expand All @@ -49,10 +50,9 @@ def url(self):

@property
def format(self):
filepath = self.file.name
file_format = None
try:
file_format = os.path.splitext(filepath)[-1]
file_format = os.path.splitext(self.filepath)[-1]
except: # noqa: E722
pass
finally:
Expand All @@ -70,7 +70,7 @@ def content(self):
return body

def read_tasks_list_from_csv(self, sep=','):
logger.debug('Read tasks list from CSV file {}'.format(self.file.name))
logger.debug('Read tasks list from CSV file {}'.format(self.filepath))
tasks = pd.read_csv(self.file.open(), sep=sep).fillna('').to_dict('records')
tasks = [{'data': task} for task in tasks]
return tasks
Expand All @@ -79,13 +79,13 @@ def read_tasks_list_from_tsv(self):
return self.read_tasks_list_from_csv('\t')

def read_tasks_list_from_txt(self):
logger.debug('Read tasks list from text file {}'.format(self.file.name))
logger.debug('Read tasks list from text file {}'.format(self.filepath))
lines = self.content.splitlines()
tasks = [{'data': {settings.DATA_UNDEFINED_NAME: line}} for line in lines]
return tasks

def read_tasks_list_from_json(self):
logger.debug('Read tasks list from JSON file {}'.format(self.file.name))
logger.debug('Read tasks list from JSON file {}'.format(self.filepath))

raw_data = self.content
# Python 3.5 compatibility fix https://docs.python.org/3/whatsnew/3.6.html#json
Expand All @@ -105,15 +105,15 @@ def read_tasks_list_from_json(self):
return tasks_formatted

def read_task_from_hypertext_body(self):
logger.debug('Read 1 task from hypertext file {}'.format(self.file.name))
logger.debug('Read 1 task from hypertext file {}'.format(self.filepath))
body = self.content
tasks = [{'data': {settings.DATA_UNDEFINED_NAME: body}}]
return tasks

def read_task_from_uploaded_file(self):
logger.debug('Read 1 task from uploaded file {}'.format(self.file.name))
logger.debug('Read 1 task from uploaded file {}'.format(self.filepath))
if settings.CLOUD_FILE_STORAGE_ENABLED:
tasks = [{'data': {settings.DATA_UNDEFINED_NAME: self.file.name}}]
tasks = [{'data': {settings.DATA_UNDEFINED_NAME: self.filepath}}]
else:
tasks = [{'data': {settings.DATA_UNDEFINED_NAME: self.url}}]
return tasks
Expand Down Expand Up @@ -149,7 +149,7 @@ def read_tasks(self, file_as_tasks_list=True):
tasks = self.read_task_from_uploaded_file()

except Exception as exc:
raise ValidationError('Failed to parse input file ' + self.file.name + ': ' + str(exc))
raise ValidationError('Failed to parse input file ' + self.filepath + ': ' + str(exc))
return tasks

@classmethod
Expand Down
6 changes: 3 additions & 3 deletions label_studio/organizations/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,12 @@ def permission_classes(self):
return api_settings.DEFAULT_PERMISSION_CLASSES

def get_queryset(self):
return OrganizationMember.objects.filter(organization=self.get_parent_object())
return OrganizationMember.objects.filter(organization=self.parent_object)

def get_serializer_context(self):
return {
**super().get_serializer_context(),
'organization': self.get_parent_object(),
'organization': self.parent_object,
}

def get(self, request, pk, user_pk):
Expand All @@ -213,7 +213,7 @@ def get(self, request, pk, user_pk):
return Response(serializer.data)

def delete(self, request, pk=None, user_pk=None):
org = self.get_parent_object()
org = self.parent_object
if org != request.user.active_organization:
raise PermissionDenied('You can delete members only for your current active organization')

Expand Down
5 changes: 4 additions & 1 deletion label_studio/organizations/mixins.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from django.utils.functional import cached_property


class OrganizationMixin:
@property
@cached_property
def active_members(self):
return self.members

Expand Down
9 changes: 5 additions & 4 deletions label_studio/organizations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.db import models, transaction
from django.db.models import Count, Q
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -46,11 +47,11 @@ def find_by_user(cls, user_or_user_pk, organization_pk):
user_pk = user_or_user_pk.pk if isinstance(user_or_user_pk, User) else user_or_user_pk
return OrganizationMember.objects.get(user=user_pk, organization=organization_pk)

@property
@cached_property
def is_deleted(self):
return bool(self.deleted_at)

@property
@cached_property
def is_owner(self):
return self.user.id == self.organization.created_by.id

Expand Down Expand Up @@ -181,11 +182,11 @@ def should_verify_ssl_certs(self) -> bool:
return org_verify
return settings.VERIFY_SSL_CERTS

@property
@cached_property
def secure_mode(self):
return False

@property
@cached_property
def members(self):
return OrganizationMember.objects.filter(organization=self)

Expand Down
6 changes: 3 additions & 3 deletions label_studio/projects/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ class ProjectSummaryResetAPI(GetParentObjectMixin, generics.CreateAPIView):

@swagger_auto_schema(auto_schema=None)
def post(self, *args, **kwargs):
project = self.get_parent_object()
project = self.parent_object
summary = project.summary
start_job_async_or_sync(
recalculate_created_annotations_and_labels_from_scratch,
Expand Down Expand Up @@ -776,11 +776,11 @@ def post(self, *args, **kwargs):

def get_serializer_context(self):
context = super(ProjectTaskListAPI, self).get_serializer_context()
context['project'] = self.get_parent_object()
context['project'] = self.parent_object
return context

def perform_create(self, serializer):
project = self.get_parent_object()
project = self.parent_object
instance = serializer.save(project=project)
emit_webhooks_for_instance(
self.request.user.active_organization, project, WebhookAction.TASKS_CREATED, [instance]
Expand Down
2 changes: 1 addition & 1 deletion label_studio/tasks/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ def delete_draft(self, draft_id, annotation_id):
pass

def perform_create(self, ser):
task = self.get_parent_object()
task = self.parent_object
# annotator has write access only to annotations and it can't be checked it after serializer.save()
user = self.request.user

Expand Down
7 changes: 4 additions & 3 deletions label_studio/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from organizations.models import Organization
from rest_framework.authtoken.models import Token
Expand Down Expand Up @@ -130,7 +131,7 @@ class Meta:
models.Index(fields=['date_joined']),
]

@property
@cached_property
def avatar_url(self):
if self.avatar:
if settings.CLOUD_FILE_STORAGE_ENABLED:
Expand All @@ -148,11 +149,11 @@ def active_organization_contributed_project_number(self):
annotations = self.active_organization_annotations()
return annotations.values_list('project').distinct().count()

@property
@cached_property
def own_organization(self) -> Optional[Organization]:
return fast_first(Organization.objects.filter(created_by=self))

@property
@cached_property
def has_organization(self):
return Organization.objects.filter(created_by=self).exists()

Expand Down

0 comments on commit f60188b

Please sign in to comment.