Skip to content

Commit f60188b

Browse files
authored
feat: OPTIC-1367: Find opportunities for property cache (#6808)
1 parent f961f01 commit f60188b

File tree

8 files changed

+36
-26
lines changed

8 files changed

+36
-26
lines changed

label_studio/core/mixins.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44

55
from django.db.models.query import QuerySet
6+
from django.utils.functional import cached_property
67
from rest_framework.generics import get_object_or_404
78

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

19-
def get_parent_object(self):
20+
@cached_property
21+
def parent_object(self):
22+
return self._get_parent_object()
23+
24+
def _get_parent_object(self):
2025
"""
2126
The same as get_object method from DRF, but for the parent object
2227
For example if you want to get project inside /api/projects/ID/tasks handler

label_studio/data_import/models.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from django.conf import settings
1616
from django.db import models
17+
from django.utils.functional import cached_property
1718
from rest_framework.exceptions import ValidationError
1819

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

39-
@property
40+
@cached_property
4041
def filepath(self):
4142
return self.file.name
4243

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

5051
@property
5152
def format(self):
52-
filepath = self.file.name
5353
file_format = None
5454
try:
55-
file_format = os.path.splitext(filepath)[-1]
55+
file_format = os.path.splitext(self.filepath)[-1]
5656
except: # noqa: E722
5757
pass
5858
finally:
@@ -70,7 +70,7 @@ def content(self):
7070
return body
7171

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

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

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

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

107107
def read_task_from_hypertext_body(self):
108-
logger.debug('Read 1 task from hypertext file {}'.format(self.file.name))
108+
logger.debug('Read 1 task from hypertext file {}'.format(self.filepath))
109109
body = self.content
110110
tasks = [{'data': {settings.DATA_UNDEFINED_NAME: body}}]
111111
return tasks
112112

113113
def read_task_from_uploaded_file(self):
114-
logger.debug('Read 1 task from uploaded file {}'.format(self.file.name))
114+
logger.debug('Read 1 task from uploaded file {}'.format(self.filepath))
115115
if settings.CLOUD_FILE_STORAGE_ENABLED:
116-
tasks = [{'data': {settings.DATA_UNDEFINED_NAME: self.file.name}}]
116+
tasks = [{'data': {settings.DATA_UNDEFINED_NAME: self.filepath}}]
117117
else:
118118
tasks = [{'data': {settings.DATA_UNDEFINED_NAME: self.url}}]
119119
return tasks
@@ -149,7 +149,7 @@ def read_tasks(self, file_as_tasks_list=True):
149149
tasks = self.read_task_from_uploaded_file()
150150

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

155155
@classmethod

label_studio/organizations/api.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,12 @@ def permission_classes(self):
196196
return api_settings.DEFAULT_PERMISSION_CLASSES
197197

198198
def get_queryset(self):
199-
return OrganizationMember.objects.filter(organization=self.get_parent_object())
199+
return OrganizationMember.objects.filter(organization=self.parent_object)
200200

201201
def get_serializer_context(self):
202202
return {
203203
**super().get_serializer_context(),
204-
'organization': self.get_parent_object(),
204+
'organization': self.parent_object,
205205
}
206206

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

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

label_studio/organizations/mixins.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
from django.utils.functional import cached_property
2+
3+
14
class OrganizationMixin:
2-
@property
5+
@cached_property
36
def active_members(self):
47
return self.members
58

label_studio/organizations/models.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from django.db import models, transaction
88
from django.db.models import Count, Q
99
from django.utils import timezone
10+
from django.utils.functional import cached_property
1011
from django.utils.translation import gettext_lazy as _
1112

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

49-
@property
50+
@cached_property
5051
def is_deleted(self):
5152
return bool(self.deleted_at)
5253

53-
@property
54+
@cached_property
5455
def is_owner(self):
5556
return self.user.id == self.organization.created_by.id
5657

@@ -181,11 +182,11 @@ def should_verify_ssl_certs(self) -> bool:
181182
return org_verify
182183
return settings.VERIFY_SSL_CERTS
183184

184-
@property
185+
@cached_property
185186
def secure_mode(self):
186187
return False
187188

188-
@property
189+
@cached_property
189190
def members(self):
190191
return OrganizationMember.objects.filter(organization=self)
191192

label_studio/projects/api.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ class ProjectSummaryResetAPI(GetParentObjectMixin, generics.CreateAPIView):
620620

621621
@swagger_auto_schema(auto_schema=None)
622622
def post(self, *args, **kwargs):
623-
project = self.get_parent_object()
623+
project = self.parent_object
624624
summary = project.summary
625625
start_job_async_or_sync(
626626
recalculate_created_annotations_and_labels_from_scratch,
@@ -776,11 +776,11 @@ def post(self, *args, **kwargs):
776776

777777
def get_serializer_context(self):
778778
context = super(ProjectTaskListAPI, self).get_serializer_context()
779-
context['project'] = self.get_parent_object()
779+
context['project'] = self.parent_object
780780
return context
781781

782782
def perform_create(self, serializer):
783-
project = self.get_parent_object()
783+
project = self.parent_object
784784
instance = serializer.save(project=project)
785785
emit_webhooks_for_instance(
786786
self.request.user.active_organization, project, WebhookAction.TASKS_CREATED, [instance]

label_studio/tasks/api.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ def delete_draft(self, draft_id, annotation_id):
539539
pass
540540

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

label_studio/users/models.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from django.db.models.signals import post_save
1313
from django.dispatch import receiver
1414
from django.utils import timezone
15+
from django.utils.functional import cached_property
1516
from django.utils.translation import gettext_lazy as _
1617
from organizations.models import Organization
1718
from rest_framework.authtoken.models import Token
@@ -130,7 +131,7 @@ class Meta:
130131
models.Index(fields=['date_joined']),
131132
]
132133

133-
@property
134+
@cached_property
134135
def avatar_url(self):
135136
if self.avatar:
136137
if settings.CLOUD_FILE_STORAGE_ENABLED:
@@ -148,11 +149,11 @@ def active_organization_contributed_project_number(self):
148149
annotations = self.active_organization_annotations()
149150
return annotations.values_list('project').distinct().count()
150151

151-
@property
152+
@cached_property
152153
def own_organization(self) -> Optional[Organization]:
153154
return fast_first(Organization.objects.filter(created_by=self))
154155

155-
@property
156+
@cached_property
156157
def has_organization(self):
157158
return Organization.objects.filter(created_by=self).exists()
158159

0 commit comments

Comments
 (0)