This commit is contained in:
David Burke committed 2023-11-16 11:12:07 -05:00
1 parent 96801ef853
commit 480a404021
15 files changed
+387 -134

No files matched your search

+3 -11
View File
@@ -1,13 +1,5 @@
{
"python.formatting.provider": "black",
"python.linting.pylintArgs": [
"--enable=W0614",
"--django-settings-module=glitchtip.settings",
"--load-plugins",
"pylint_django"
],
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"editor.formatOnSave": true,
"files.trimTrailingWhitespace": true
"files.trimTrailingWhitespace": true,
"ruff.lint.run": "onSave",
"editor.formatOnSave": true
}
+8
View File
@@ -24,6 +24,7 @@ class ProcessingEvent:
event: InterchangeIssueEvent
issue_hash: str
title: str
metadata: dict[str, Any]
event_data: dict[str, Any]
issue_id: Optional[int] = None
issue_created = False
@@ -83,6 +84,10 @@ def process_issue_events(ingest_events: list[InterchangeIssueEvent]):
issue_hash = generate_hash(title, culprit, event.type, event.fingerprint)
event_data["culprit"] = culprit
event_data["metadata"] = metadata
# if breadcrumbs := event.breadcrumbs:
# event_data["breadcrumbs"] = [
# breadcrumb.dict() for breadcrumb in breadcrumbs
# ]
if exception := event.exception:
event_data["exception"] = exception.dict()
processing_events.append(
@@ -90,6 +95,7 @@ def process_issue_events(ingest_events: list[InterchangeIssueEvent]):
event=ingest_event,
issue_hash=issue_hash,
title=title,
metadata=metadata,
event_data=event_data,
)
)
@@ -106,6 +112,7 @@ def process_issue_events(ingest_events: list[InterchangeIssueEvent]):
issue_defaults = {
"type": event_type,
"title": processing_event.title,
"metadata": processing_event.metadata,
}
for hash_obj in hash_queryset:
if (
@@ -136,6 +143,7 @@ def process_issue_events(ingest_events: list[InterchangeIssueEvent]):
issue_events.append(
IssueEvent(
id=processing_event.event.event_id,
date_created=processing_event.event.payload.timestamp,
date_received=processing_event.event.received_at,
issue_id=processing_event.issue_id,
type=event_type,
+1 -1
View File
@@ -137,7 +137,7 @@ class EventBreadcrumb(Schema):
category: Optional[str] = None
message: Optional[str] = None
data: Optional[dict[str, Any]] = None
level: Optional[Level] = None
level: Annotated[Optional[Level], WrapValidator(invalid_to_none)] = None
timestamp: Optional[datetime] = None
@@ -6,7 +6,11 @@ from apps.issue_events.constants import EventStatus
from apps.issue_events.models import Issue, IssueEvent, IssueHash
from ..process_event import process_issue_events
from ..schema import ErrorIssueEventSchema, InterchangeIssueEvent, IssueEventSchema
from ..schema import (
ErrorIssueEventSchema,
InterchangeIssueEvent,
IssueEventSchema,
)
from .utils import EventIngestTestCase
COMPAT_TEST_DATA_DIR = "events/test_data"
@@ -74,6 +78,18 @@ class SentryCompatTestCase(IssueEventIngestTestCase):
)
return event, sentry_json, api_sentry_event
def get_event_json(self, event: IssueEvent):
return self.client.get(
reverse(
"api:get_event_json",
kwargs={
"organization_slug": self.organization.slug,
"issue_id": event.issue_id,
"event_id": event.id,
},
)
).json()
# Upgrade functions handle intentional differences between GlitchTip and Sentry OSS
def upgrade_title(self, value: str):
"""Sentry OSS uses ... while GlitchTip uses unicode …"""
@@ -90,6 +106,10 @@ class SentryCompatTestCase(IssueEventIngestTestCase):
for field in fields:
field_value1 = data1.get(field)
field_value2 = data2.get(field)
if field == "datetime":
# Check that it's close enough
field_value1 = field_value1[:23]
field_value2 = field_value2[:23]
if field == "title" and isinstance(field_value1, str):
field_value1 = self.upgrade_title(field_value1)
if field_value2:
@@ -110,7 +130,7 @@ class SentryCompatTestCase(IssueEventIngestTestCase):
def get_project_events_detail(self, event_id: str):
return reverse(
"api:project_issue_event_retrieve",
"api:get_project_issue_event",
kwargs={
"organization_slug": self.project.organization.slug,
"project_slug": self.project.slug,
@@ -118,18 +138,20 @@ class SentryCompatTestCase(IssueEventIngestTestCase):
},
)
def submit_event(self, event_data: dict) -> IssueEvent:
event = InterchangeIssueEvent(
event_id=event_data["event_id"],
project_id=self.project.id,
payload=ErrorIssueEventSchema(**event_data),
)
process_issue_events([event])
return IssueEvent.objects.get(pk=event.event_id)
def test_template_error(self):
sdk_error, sentry_json, sentry_data = self.get_json_test_data(
"django_template_error"
)
event = InterchangeIssueEvent(
event_id=sdk_error["event_id"],
project_id=self.project.id,
payload=ErrorIssueEventSchema(**sdk_error),
)
process_issue_events([event])
event = IssueEvent.objects.get(pk=event.event_id)
event = self.submit_event(sdk_error)
url = self.get_project_events_detail(event.id.hex)
res = self.client.get(url)
@@ -165,9 +187,32 @@ class SentryCompatTestCase(IssueEventIngestTestCase):
["env", "headers", "url", "method", "inferredContentType"],
)
# url = reverse("issue-detail", kwargs={"pk": event.issue.pk})
# res = self.client.get(url)
# self.assertEqual(res.status_code, 200)
url = reverse("api:get_issue", kwargs={"issue_id": event.issue.pk})
res = self.client.get(url)
self.assertEqual(res.status_code, 200)
res_data = res.json()
# data = self.get_json_data("events/test_data/django_template_error_issue.json")
# self.assertCompareData(res.data, data, ["title", "metadata"])
data = self.get_json_data("events/test_data/django_template_error_issue.json")
self.assertCompareData(res_data, data, ["title", "metadata"])
def test_js_sdk_with_unix_timestamp(self):
sdk_error, sentry_json, sentry_data = self.get_json_test_data(
"js_event_with_unix_timestamp"
)
event = self.submit_event(sdk_error)
self.assertNotEqual(event.date_created, sdk_error["timestamp"])
self.assertEqual(event.date_created.year, 2020)
event_json = self.get_event_json(event)
self.assertCompareData(event_json, sentry_json, ["datetime"])
# self.assertCompareData(event_json, sentry_json, ["datetime", "breadcrumbs"])
# url = self.get_project_events_detail(event.pk)
# res = self.client.get(url)
# res_data = res.json()
# self.assertCompareData(res_data, sentry_data, ["datetime"])
# self.assertEqual(res_data["entries"][1].get("type"), "breadcrumbs")
# self.assertEqual(
# res_data["entries"][1],
# self.upgrade_data(sentry_data["entries"][1]),
# )
@@ -1,4 +1,3 @@
from django.test import override_settings
from django.urls import reverse
+2
View File
@@ -0,0 +1,2 @@
from .api import router # noqa
from . import events, issues # noqa
+3
View File
@@ -0,0 +1,3 @@
from ninja import Router
router = Router()
@@ -4,14 +4,12 @@ from typing import Optional
from django.db.models import OuterRef, Subquery, Window
from django.db.models.functions import Lag
from django.http import Http404
from ninja import Router
from glitchtip.api.authentication import AuthHttpRequest
from .models import IssueEvent
from .schema import IssueEventDetailSchema, IssueEventSchema
router = Router()
from ..models import IssueEvent
from ..schema import IssueEventDetailSchema, IssueEventSchema, IssueEventJsonSchema
from . import router
def get_queryset(
@@ -34,7 +32,7 @@ def get_queryset(
@router.get(
"/issues/{int:issue_id}/events/", response=list[IssueEventSchema], by_alias=True
)
async def issue_event_list(request: AuthHttpRequest, issue_id: int):
async def list_issue_event(request: AuthHttpRequest, issue_id: int):
return [obj async for obj in get_queryset(request, issue_id=issue_id)]
@@ -43,7 +41,7 @@ async def issue_event_list(request: AuthHttpRequest, issue_id: int):
response=IssueEventDetailSchema,
by_alias=True,
)
async def issue_event_latest(request: AuthHttpRequest, issue_id: int):
async def get_latest_issue_event(request: AuthHttpRequest, issue_id: int):
qs = get_queryset(request, issue_id)
qs = qs.annotate(
previous=Window(expression=Lag("id"), order_by="date_received"),
@@ -60,9 +58,7 @@ async def issue_event_latest(request: AuthHttpRequest, issue_id: int):
response=IssueEventDetailSchema,
by_alias=True,
)
async def issue_event_retrieve(
request: AuthHttpRequest, issue_id: int, event_id: uuid.UUID
):
async def get_issue_event(request: AuthHttpRequest, issue_id: int, event_id: uuid.UUID):
qs = get_queryset(request, issue_id)
qs = qs.annotate(
previous=Subquery(
@@ -83,7 +79,7 @@ async def issue_event_retrieve(
response=list[IssueEventSchema],
by_alias=True,
)
async def project_issue_event_list(
async def list_project_issue_event(
request: AuthHttpRequest,
organization_slug: str,
project_slug: str,
@@ -101,7 +97,7 @@ async def project_issue_event_list(
response=IssueEventDetailSchema,
by_alias=True,
)
async def project_issue_event_retrieve(
async def get_project_issue_event(
request: AuthHttpRequest,
organization_slug: str,
project_slug: str,
@@ -122,3 +118,18 @@ async def project_issue_event_retrieve(
return await qs.aget(id=event_id)
except IssueEvent.DoesNotExist:
raise Http404()
@router.get(
"/organizations/{slug:organization_slug}/issues/{int:issue_id}/events/{event_id}/json/",
response=IssueEventJsonSchema,
by_alias=True,
)
async def get_event_json(
request: AuthHttpRequest, organization_slug: str, issue_id: int, event_id: uuid.UUID
):
qs = get_queryset(request, organization_slug=organization_slug, issue_id=issue_id)
try:
return await qs.aget(id=event_id)
except IssueEvent.DoesNotExist:
raise Http404()
+25
View File
@@ -0,0 +1,25 @@
from django.http import Http404
from glitchtip.api.authentication import AuthHttpRequest
from ..models import Issue
from ..schema import IssueSchema
from . import router
def get_queryset(request: AuthHttpRequest):
user_id = request.auth
return Issue.objects.filter(project__organization__users=user_id)
@router.get(
"/issues/{int:issue_id}/",
response=IssueSchema,
by_alias=True,
)
async def get_issue(request: AuthHttpRequest, issue_id: int):
qs = get_queryset(request)
try:
return await qs.filter(id=issue_id).aget()
except Issue.DoesNotExist:
raise Http404()
+176 -46
View File
@@ -10,89 +10,219 @@ import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
('projects', '0013_merge_20231017_1350'),
("projects", "0013_merge_20231017_1350"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Issue',
name="Issue",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('culprit', models.CharField(blank=True, max_length=1024, null=True)),
('is_public', models.BooleanField(default=False)),
('level', models.PositiveSmallIntegerField(choices=[(0, 'sample'), (1, 'debug'), (2, 'info'), (3, 'warning'), (4, 'error'), (5, 'fatal')], default=4)),
('title', models.CharField(max_length=255)),
('type', models.PositiveSmallIntegerField(choices=[(0, 'default'), (1, 'error'), (2, 'csp')], default=0)),
('status', models.PositiveSmallIntegerField(choices=[(0, 'unresolved'), (1, 'resolved'), (2, 'ignored')], default=0)),
('short_id', models.PositiveIntegerField(null=True)),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issues', to='projects.project')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created", models.DateTimeField(auto_now_add=True)),
("culprit", models.CharField(blank=True, max_length=1024, null=True)),
("is_public", models.BooleanField(default=False)),
(
"level",
models.PositiveSmallIntegerField(
choices=[
(0, "sample"),
(1, "debug"),
(2, "info"),
(3, "warning"),
(4, "error"),
(5, "fatal"),
],
default=4,
),
),
("metadata", models.JSONField()),
("title", models.CharField(max_length=255)),
(
"type",
models.PositiveSmallIntegerField(
choices=[(0, "default"), (1, "error"), (2, "csp")], default=0
),
),
(
"status",
models.PositiveSmallIntegerField(
choices=[(0, "unresolved"), (1, "resolved"), (2, "ignored")],
default=0,
),
),
("short_id", models.PositiveIntegerField(null=True)),
(
"project",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="issues",
to="projects.project",
),
),
],
),
migrations.CreateModel(
name='IssueHash',
name="IssueHash",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.UUIDField(db_index=True)),
('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hashes', to='issue_events.issue')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='projects.project')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("value", models.UUIDField(db_index=True)),
(
"issue",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="hashes",
to="issue_events.issue",
),
),
(
"project",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="projects.project",
),
),
],
),
migrations.CreateModel(
name='IssueEvent',
name="IssueEvent",
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('type', models.PositiveSmallIntegerField(choices=[(0, 'default'), (1, 'error'), (2, 'csp')], default=0)),
('date_created', models.DateTimeField(auto_now_add=True, help_text='Time at which event happened')),
('date_received', models.DateTimeField(auto_now_add=True, help_text='Time at which GlitchTip accepted event')),
('data', models.JSONField()),
('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='issue_events.issue')),
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"type",
models.PositiveSmallIntegerField(
choices=[(0, "default"), (1, "error"), (2, "csp")], default=0
),
),
(
"date_created",
models.DateTimeField(help_text="Time at which event happened"),
),
(
"date_received",
models.DateTimeField(
help_text="Time at which GlitchTip accepted event",
),
),
("data", models.JSONField()),
(
"issue",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="issue_events.issue",
),
),
],
options={
'abstract': False,
'base_manager_name': 'objects',
"abstract": False,
"base_manager_name": "objects",
},
managers=[
('objects', psqlextra.manager.manager.PostgresManager()),
("objects", psqlextra.manager.manager.PostgresManager()),
],
),
migrations.CreateModel(
name='Comment',
name="Comment",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('text', models.TextField(blank=True, null=True)),
('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='issue_events.issue')),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created", models.DateTimeField(auto_now_add=True)),
("text", models.TextField(blank=True, null=True)),
(
"issue",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="comments",
to="issue_events.issue",
),
),
(
"user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
'ordering': ('-created',),
"ordering": ("-created",),
},
),
migrations.CreateModel(
name='IssueStats',
name="IssueStats",
fields=[
('issue', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='issue_events.issue')),
('search_vector', django.contrib.postgres.search.SearchVectorField(editable=False, null=True)),
('search_vector_created', models.DateTimeField(auto_now_add=True)),
('count', models.PositiveIntegerField(default=1, editable=False)),
('last_seen', models.DateTimeField(auto_now_add=True, db_index=True)),
(
"issue",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
primary_key=True,
serialize=False,
to="issue_events.issue",
),
),
(
"search_vector",
django.contrib.postgres.search.SearchVectorField(
editable=False, null=True
),
),
("search_vector_created", models.DateTimeField(auto_now_add=True)),
("count", models.PositiveIntegerField(default=1, editable=False)),
("last_seen", models.DateTimeField(auto_now_add=True, db_index=True)),
],
options={
'indexes': [django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='issue_event_search__0754e8_gin')],
"indexes": [
django.contrib.postgres.indexes.GinIndex(
fields=["search_vector"], name="issue_event_search__0754e8_gin"
)
],
},
),
migrations.AddConstraint(
model_name='issuehash',
constraint=models.UniqueConstraint(fields=('project', 'value'), name='issue hash project'),
model_name="issuehash",
constraint=models.UniqueConstraint(
fields=("project", "value"), name="issue hash project"
),
),
migrations.AlterUniqueTogether(
name='issue',
unique_together={('project', 'short_id')},
name="issue",
unique_together={("project", "short_id")},
),
]
+3 -4
View File
@@ -16,6 +16,7 @@ class Issue(models.Model):
level = models.PositiveSmallIntegerField(
choices=LogLevel.choices, default=LogLevel.ERROR
)
metadata = models.JSONField()
project = models.ForeignKey(
"projects.Project", on_delete=models.CASCADE, related_name="issues"
)
@@ -75,11 +76,9 @@ class IssueEvent(PostgresPartitionedModel, models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
issue = models.ForeignKey(Issue, on_delete=models.CASCADE)
type = models.PositiveSmallIntegerField(default=0, choices=IssueEventType.choices)
date_created = models.DateTimeField(
auto_now_add=True, help_text="Time at which event happened"
)
date_created = models.DateTimeField(help_text="Time at which event happened")
date_received = models.DateTimeField(
auto_now_add=True, help_text="Time at which GlitchTip accepted event"
help_text="Time at which GlitchTip accepted event"
)
data = models.JSONField()
+28 -3
View File
@@ -1,12 +1,20 @@
from typing import Optional
from datetime import datetime
from typing import Any, Optional
from ninja import Field, ModelSchema
from ninja import Field, ModelSchema, Schema
from glitchtip.api.schema import CamelSchema
from sentry.interfaces.stacktrace import get_context
from .constants import IssueEventType
from .models import IssueEvent
from .models import Issue, IssueEvent
class IssueSchema(CamelSchema, ModelSchema):
class Config:
model = Issue
model_fields = ["id", "title", "metadata"]
populate_by_name = True
class IssueEventSchema(CamelSchema, ModelSchema):
@@ -108,3 +116,20 @@ class IssueEventDetailSchema(IssueEventSchema):
def resolve_next_event_id(obj):
if event_id := obj.next:
return event_id.hex
class IssueEventJsonSchema(Schema):
"""
Represents a more raw view of the event, built with open source (legacy) Sentry compatibility
"""
event_id: str = Field(validation_alias="id.hex")
timestamp: float = Field()
date_created: datetime = Field(serialization_alias="datetime")
breadcrumbs: Optional[Any] = Field(
validation_alias="data.breadcrumbs", default=None
)
@staticmethod
def resolve_timestamp(obj):
return obj.date_received.timestamp()
@@ -13,7 +13,7 @@ class IssueEventAPITestCase(GlitchTipTestCaseMixin, TestCase):
event = baker.make("issue_events.IssueEvent", issue__project=self.project)
baker.make("issue_events.IssueEvent", issue__project=self.project, _quantity=3)
not_my_event = baker.make("issue_events.IssueEvent")
url = reverse("api:issue_event_list", args=[event.issue_id])
url = reverse("api:list_issue_event", args=[event.issue_id])
with self.assertNumQueries(1):
res = self.client.get(url)
@@ -23,25 +23,25 @@ class IssueEventAPITestCase(GlitchTipTestCaseMixin, TestCase):
def test_retrieve(self):
event = baker.make("issue_events.IssueEvent", issue__project=self.project)
url = reverse(
"api:issue_event_retrieve",
"api:get_issue_event",
kwargs={"issue_id": event.issue_id, "event_id": "a" * 32},
)
res = self.client.get(url)
self.assertEqual(res.status_code, 404)
url = reverse(
"api:issue_event_retrieve",
"api:get_issue_event",
kwargs={"issue_id": event.issue_id, "event_id": event.id},
)
res = self.client.get(url)
self.assertContains(res, event.pk.hex)
url = reverse("api:issue_event_latest", kwargs={"issue_id": event.issue_id})
url = reverse("api:get_latest_issue_event", kwargs={"issue_id": event.issue_id})
res = self.client.get(url)
self.assertContains(res, event.pk.hex)
def test_authentication(self):
url = reverse("api:issue_event_list", args=[1])
url = reverse("api:list_issue_event", args=[1])
self.client.logout()
res = self.client.get(url)
self.assertEqual(res.status_code, 401)
Generated
+49 -34
View File
@@ -374,25 +374,25 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]]
name = "botocore"
version = "1.31.83"
version = "1.31.84"
description = "Low-level, data-driven core of boto 3."
optional = false
python-versions = ">= 3.7"
files = [
{file = "botocore-1.31.83-py3-none-any.whl", hash = "sha256:c742069e8bfd06d212d712228258ff09fb481b6ec02358e539381ce0fcad065a"},
{file = "botocore-1.31.83.tar.gz", hash = "sha256:40914b0fb28f13d709e1f8a4481e278350b77a3987be81acd23715ec8d5fedca"},
{file = "botocore-1.31.84-py3-none-any.whl", hash = "sha256:d65bc05793d1a8a8c191a739f742876b4b403c5c713dc76beef262d18f7984a2"},
{file = "botocore-1.31.84.tar.gz", hash = "sha256:8913bedb96ad0427660dee083aeaa675466eb662bbf1a47781956b5882aadcc5"},
]
[package.dependencies]
jmespath = ">=0.7.1,<2.0.0"
python-dateutil = ">=2.1,<3.0.0"
urllib3 = [
{version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""},
{version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""},
{version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""},
]
[package.extras]
crt = ["awscrt (==0.16.26)"]
crt = ["awscrt (==0.19.10)"]
[[package]]
name = "brotli"
@@ -1104,13 +1104,13 @@ Django = ">=3.2"
[[package]]
name = "django-import-export"
version = "3.3.2"
version = "3.3.3"
description = "Django application and library for importing and exporting data with included admin integration."
optional = false
python-versions = ">=3.8"
files = [
{file = "django-import-export-3.3.2.tar.gz", hash = "sha256:9a5c7c191014e4defb01573ee94864b60724a203f1b8a7e5e67a03f06b27b62d"},
{file = "django_import_export-3.3.2-py3-none-any.whl", hash = "sha256:d13e7508190f46442280bd7d04efcf49af6521350a70a6f9e06447ef28d6a41d"},
{file = "django-import-export-3.3.3.tar.gz", hash = "sha256:2c1b16e1cf2ea5f62a165d8867e7c6dcff25673ab7201fd18aaf67c9ee90367e"},
{file = "django_import_export-3.3.3-py3-none-any.whl", hash = "sha256:78973202e93897326ab0411d64eaf89b72779fcb21ee9e5f64f3fb96571a5978"},
]
[package.dependencies]
@@ -1306,6 +1306,7 @@ files = [
[package.dependencies]
django = "*"
django-stubs-ext = ">=4.2.5"
mypy = {version = ">=1.6.0,<1.7.0", optional = true, markers = "extra == \"compatible-mypy\""}
tomli = {version = "*", markers = "python_version < \"3.11\""}
types-pytz = "*"
types-PyYAML = "*"
@@ -1587,8 +1588,8 @@ files = [
[package.dependencies]
cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""}
greenlet = [
{version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""},
{version = ">=3.0rc3", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\""},
{version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""},
]
"zope.event" = "*"
"zope.interface" = "*"
@@ -1738,12 +1739,12 @@ files = [
google-auth = ">=2.14.1,<3.0.dev0"
googleapis-common-protos = ">=1.56.2,<2.0.dev0"
grpcio = [
{version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
{version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
{version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
]
grpcio-status = [
{version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
{version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
{version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
]
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0"
requests = ">=2.18.0,<3.0.0.dev0"
@@ -1790,8 +1791,8 @@ files = [
[package.dependencies]
google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]}
proto-plus = [
{version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""},
{version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""},
{version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""},
]
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev"
@@ -1846,8 +1847,8 @@ google-cloud-audit-log = ">=0.1.0,<1.0.0dev"
google-cloud-core = ">=2.0.0,<3.0.0dev"
grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev"
proto-plus = [
{version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""},
{version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""},
{version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""},
]
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev"
@@ -2479,13 +2480,13 @@ zookeeper = ["kazoo (>=2.8.0)"]
[[package]]
name = "locust"
version = "2.18.1"
version = "2.18.2"
description = "Developer friendly load testing framework"
optional = false
python-versions = ">=3.8"
files = [
{file = "locust-2.18.1-py3-none-any.whl", hash = "sha256:95cb37490487db693b9a85914ca13a962d74de7b9588519909a5a678512dfaef"},
{file = "locust-2.18.1.tar.gz", hash = "sha256:503985a240f8f8098636c9493689caca2cc923b83f241393bb2c92bde5801278"},
{file = "locust-2.18.2-py3-none-any.whl", hash = "sha256:a1916347bedcfa6598fcc6ba45022fcd31ed1352d84797650673296129223891"},
{file = "locust-2.18.2.tar.gz", hash = "sha256:802300a30b4d88555854e3687ff30a0cf5e73b35f264e45ec6f0aa2c8792569f"},
]
[package.dependencies]
@@ -2868,23 +2869,38 @@ files = [
[[package]]
name = "mypy"
version = "1.7.0"
version = "1.6.1"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "mypy-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b633f188fc5ae1b6edca39dae566974d7ef4e9aaaae00bc36efe1f855e5173ac"},
{file = "mypy-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fe46e96ae319df21359c8db77e1aecac8e5949da4773c0274c0ef3d8d1268a9"},
{file = "mypy-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:df67fbeb666ee8828f675fee724cc2cbd2e4828cc3df56703e02fe6a421b7401"},
{file = "mypy-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f65f385a6f43211effe8c682e8ec3f55d79391f70a201575def73d08db68ead1"},
{file = "mypy-1.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87c076c174e2c7ef8ab416c4e252d94c08cd4980a10967754f91571070bf5fbe"},
{file = "mypy-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cb8d5f6d0fcd9e708bb190b224089e45902cacef6f6915481806b0c77f7786d"},
{file = "mypy-1.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93e76c2256aa50d9c82a88e2f569232e9862c9982095f6d54e13509f01222fc"},
{file = "mypy-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:185cff9b9a7fec1f9f7d8352dff8a4c713b2e3eea9c6c4b5ff7f0edf46b91e41"},
{file = "mypy-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc9fe455ad58a20ec68599139ed1113b21f977b536a91b42bef3ffed5cce7391"},
{file = "mypy-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b53655a295c1ed1af9e96b462a736bf083adba7b314ae775563e3fb4e6795f5"},
{file = "mypy-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf7a2f0a6907f231d5e41adba1a82d7d88cf1f61a70335889412dec99feeb0f8"},
{file = "mypy-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:870bd1ffc8a5862e593185a4c169804f2744112b4a7c55b93eb50f48e7a77010"},
{file = "mypy-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c"},
{file = "mypy-1.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb"},
{file = "mypy-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e"},
{file = "mypy-1.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f"},
{file = "mypy-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c"},
{file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"},
{file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"},
{file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"},
{file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"},
{file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"},
{file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"},
{file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"},
{file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"},
{file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"},
{file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"},
{file = "mypy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169"},
{file = "mypy-1.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143"},
{file = "mypy-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46"},
{file = "mypy-1.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85"},
{file = "mypy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45"},
{file = "mypy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208"},
{file = "mypy-1.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd"},
{file = "mypy-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332"},
{file = "mypy-1.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f"},
{file = "mypy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30"},
{file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"},
{file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"},
]
[package.dependencies]
@@ -2895,7 +2911,6 @@ typing-extensions = ">=4.1.0"
[package.extras]
dmypy = ["psutil (>=4.0)"]
install-types = ["pip"]
mypyc = ["setuptools (>=50)"]
reports = ["lxml"]
[[package]]
@@ -3064,13 +3079,13 @@ files = [
[[package]]
name = "platformdirs"
version = "3.11.0"
version = "4.0.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false
python-versions = ">=3.7"
files = [
{file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"},
{file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"},
{file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"},
{file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"},
]
[package.extras]
@@ -4477,4 +4492,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "b6bfd51963494aee8459dd8c45d89badc99318dd72ee4be800426fc33358c51a"
content-hash = "3a538fb29d5544cedb83ba975669a1ec2b6112df82dac0c1a262a5eddb952468"
+1 -2
View File
@@ -53,9 +53,8 @@ locust = "^2.10.1"
freezegun = "^1.1.0"
rope = "^1.3.0"
model_bakery = "^1.1.1"
mypy = "^1.0.0"
django-debug-toolbar = "^4.0.0"
django-stubs = "^4.0.0"
django-stubs = {extras = ["compatible-mypy"], version = "^4.0.0"}
django-sslserver = "^0.22"
aioresponses = "^0.7.2"
requests-mock = "^1.9.3"