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: OPTIC-1367: Find opportunities for property cache #6808

Merged
merged 1 commit into from
Jan 2, 2025
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
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 @@
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 @@

@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 @@
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 @@
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 @@
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))

Check warning on line 108 in label_studio/data_import/models.py

View check run for this annotation

Codecov / codecov/patch

label_studio/data_import/models.py#L108

Added line #L108 was not covered by tests
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}}]

Check warning on line 116 in label_studio/data_import/models.py

View check run for this annotation

Codecov / codecov/patch

label_studio/data_import/models.py#L116

Added line #L116 was not covered by tests
else:
tasks = [{'data': {settings.DATA_UNDEFINED_NAME: self.url}}]
return tasks
Expand Down Expand Up @@ -149,7 +149,7 @@
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
Loading