refactor(category): replace cache usage with model property for min/max price
remove redundant cache lookups for `min_price` and `max_price` in the category model by leveraging cached properties. minimizes complexity and improves maintainability while ensuring consistent behavior.
This commit is contained in:
parent
7efc19e081
commit
f664b088a4
9 changed files with 90 additions and 57 deletions
|
|
@ -3,8 +3,7 @@ from contextlib import suppress
|
|||
from typing import Any
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Max, Min, QuerySet
|
||||
from django.db.models import QuerySet
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from graphene import (
|
||||
UUID,
|
||||
|
|
@ -279,21 +278,10 @@ class CategoryType(DjangoObjectType):
|
|||
return self.filterable_attributes
|
||||
|
||||
def resolve_min_max_prices(self: Category, _info):
|
||||
min_max_prices = cache.get(key=f"{self.name}_min_max_prices", default={})
|
||||
|
||||
if not min_max_prices:
|
||||
price_aggregation = Product.objects.filter(category=self).aggregate(
|
||||
min_price=Min("stocks__price"), max_price=Max("stocks__price")
|
||||
)
|
||||
min_max_prices["min_price"] = price_aggregation.get("min_price", 0.0)
|
||||
min_max_prices["max_price"] = price_aggregation.get("max_price", 0.0)
|
||||
cache.set(
|
||||
key=f"{self.name}_min_max_prices", value=min_max_prices, timeout=86400
|
||||
)
|
||||
|
||||
return {
|
||||
"min_price": min_max_prices["min_price"],
|
||||
"max_price": min_max_prices["max_price"],
|
||||
"min_price": self.min_price,
|
||||
"max_price": self.max_price,
|
||||
}
|
||||
|
||||
def resolve_brands(self: Category, info) -> QuerySet[Brand]:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class Command(BaseCommand):
|
|||
# Group stocks by (product, vendor)
|
||||
stocks_by_group = defaultdict(list)
|
||||
for stock in Stock.objects.all().order_by("modified"):
|
||||
stocks_by_group[stock.product_pk].append(stock)
|
||||
stocks_by_group[stock.product_pk].append(stock) # ty: ignore[possibly-missing-attribute]
|
||||
|
||||
stock_deletions: list[str] = []
|
||||
for group in stocks_by_group.values():
|
||||
|
|
|
|||
|
|
@ -386,9 +386,9 @@ class Command(BaseCommand):
|
|||
|
||||
if created:
|
||||
if "name_ru" in prod_data:
|
||||
product.name_ru_ru = prod_data["name_ru"]
|
||||
product.name_ru_ru = prod_data["name_ru"] # ty: ignore[invalid-assignment]
|
||||
if "description_ru" in prod_data:
|
||||
product.description_ru_ru = prod_data["description_ru"]
|
||||
product.description_ru_ru = prod_data["description_ru"] # ty: ignore[invalid-assignment]
|
||||
product.save()
|
||||
|
||||
Stock.objects.create(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import datetime
|
|||
import json
|
||||
import logging
|
||||
from contextlib import suppress
|
||||
from typing import Any, Iterable, Self
|
||||
from typing import TYPE_CHECKING, Any, Iterable, Self
|
||||
|
||||
from constance import config
|
||||
from django.conf import settings
|
||||
|
|
@ -29,6 +29,7 @@ from django.db.models import (
|
|||
JSONField,
|
||||
ManyToManyField,
|
||||
Max,
|
||||
Min,
|
||||
OneToOneField,
|
||||
PositiveIntegerField,
|
||||
QuerySet,
|
||||
|
|
@ -72,6 +73,9 @@ from engine.core.validators import validate_category_image_dimensions
|
|||
from engine.payments.models import Transaction
|
||||
from schon.utils.misc import create_object
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django.db.models import Manager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
@ -83,6 +87,9 @@ class AttributeGroup(NiceModel):
|
|||
" This can be useful for categorizing and managing attributes more effectively in acomplex system."
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
attributes: Manager["Attribute"]
|
||||
|
||||
is_publicly_visible = True
|
||||
parent = ForeignKey(
|
||||
"self",
|
||||
|
|
@ -266,6 +273,10 @@ class Category(NiceModel, MPTTModel):
|
|||
" as well as assign attributes like images, tags, or priority."
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
products: Manager["Product"]
|
||||
children: Manager["Category"]
|
||||
|
||||
is_publicly_visible = True
|
||||
|
||||
image = ImageField(
|
||||
|
|
@ -450,6 +461,20 @@ class Category(NiceModel, MPTTModel):
|
|||
is_active=True,
|
||||
).distinct()
|
||||
|
||||
@cached_property
|
||||
def min_price(self) -> float:
|
||||
return (
|
||||
self.products.filter(is_active=True).aggregate(Min("price"))["price__min"]
|
||||
or 0.0
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def max_price(self) -> float:
|
||||
return (
|
||||
self.products.filter(is_active=True).aggregate(Max("price"))["price__max"]
|
||||
or 0.0
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("category")
|
||||
verbose_name_plural = _("categories")
|
||||
|
|
@ -604,6 +629,12 @@ class Product(NiceModel):
|
|||
" its associated information within an application."
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
images: Manager["ProductImage"]
|
||||
stocks: Manager["Stock"]
|
||||
attributes: Manager["AttributeValue"]
|
||||
category_id: Any
|
||||
|
||||
is_publicly_visible = True
|
||||
|
||||
category = ForeignKey(
|
||||
|
|
@ -1274,6 +1305,10 @@ class Order(NiceModel):
|
|||
" Equally, functionality supports managing the products in the order lifecycle."
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
order_products: Manager["OrderProduct"]
|
||||
payments_transactions: Manager[Transaction]
|
||||
|
||||
is_publicly_visible = False
|
||||
|
||||
billing_address = ForeignKey(
|
||||
|
|
@ -1442,7 +1477,8 @@ class Order(NiceModel):
|
|||
|
||||
if promotions.exists():
|
||||
buy_price -= round(
|
||||
product.price * (promotions.first().discount_percent / 100), 2
|
||||
product.price * (promotions.first().discount_percent / 100), # ty: ignore[possibly-missing-attribute]
|
||||
2,
|
||||
)
|
||||
|
||||
order_product, is_created = OrderProduct.objects.get_or_create(
|
||||
|
|
@ -1487,7 +1523,7 @@ class Order(NiceModel):
|
|||
order_product.delete()
|
||||
return self
|
||||
if order_product.quantity == 1:
|
||||
self.order_products.remove(order_product)
|
||||
self.order_products.remove(order_product) # ty: ignore[unresolved-attribute]
|
||||
order_product.delete()
|
||||
else:
|
||||
order_product.quantity -= 1
|
||||
|
|
@ -1510,7 +1546,7 @@ class Order(NiceModel):
|
|||
_("you cannot remove products from an order that is not a pending one")
|
||||
)
|
||||
for order_product in self.order_products.all():
|
||||
self.order_products.remove(order_product)
|
||||
self.order_products.remove(order_product) # ty: ignore[unresolved-attribute]
|
||||
order_product.delete()
|
||||
return self
|
||||
|
||||
|
|
@ -1522,7 +1558,7 @@ class Order(NiceModel):
|
|||
try:
|
||||
product = Product.objects.get(uuid=product_uuid)
|
||||
order_product = self.order_products.get(product=product, order=self)
|
||||
self.order_products.remove(order_product)
|
||||
self.order_products.remove(order_product) # ty: ignore[unresolved-attribute]
|
||||
order_product.delete()
|
||||
except Product.DoesNotExist as dne:
|
||||
name = "Product"
|
||||
|
|
@ -1788,6 +1824,8 @@ class Order(NiceModel):
|
|||
crm_links = OrderCrmLink.objects.filter(order=self)
|
||||
if crm_links.exists():
|
||||
crm_link = crm_links.first()
|
||||
if not crm_link:
|
||||
return False
|
||||
crm_integration = create_object(
|
||||
crm_link.crm.integration_location, crm_link.crm.name
|
||||
)
|
||||
|
|
@ -1894,6 +1932,9 @@ class OrderProduct(NiceModel):
|
|||
"and stores a reference to them."
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
download: "DigitalAssetDownload"
|
||||
|
||||
is_publicly_visible = False
|
||||
|
||||
buy_price = FloatField(
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ def get_top_returned_products(
|
|||
p = product_by_id[pid]
|
||||
img = ""
|
||||
with suppress(Exception):
|
||||
img = p.images.first().image_url if p.images.exists() else ""
|
||||
img = p.images.first().image_url if p.images.exists() else "" # ty: ignore[possibly-missing-attribute]
|
||||
result.append(
|
||||
{
|
||||
"name": p.name,
|
||||
|
|
|
|||
10
engine/core/vendors/__init__.py
vendored
10
engine/core/vendors/__init__.py
vendored
|
|
@ -8,7 +8,7 @@ from datetime import datetime
|
|||
from decimal import Decimal
|
||||
from io import BytesIO
|
||||
from math import ceil, log10
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import TYPE_CHECKING, Any, TypeVar
|
||||
|
||||
from constance import config
|
||||
from django.conf import settings
|
||||
|
|
@ -36,6 +36,8 @@ from engine.payments.errors import RatesError
|
|||
from engine.payments.utils import get_rates
|
||||
from schon.utils.misc import LoggingError, LogLevel
|
||||
|
||||
_BrandOrCategory = TypeVar("_BrandOrCategory", Brand, Category)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from engine.core.models import OrderProduct
|
||||
|
||||
|
|
@ -320,8 +322,8 @@ class AbstractVendor(ABC):
|
|||
|
||||
@staticmethod
|
||||
def _auto_resolver_helper(
|
||||
model: type[Brand] | type[Category], resolving_name: str
|
||||
) -> Brand | Category | None:
|
||||
model: type[_BrandOrCategory], resolving_name: str
|
||||
) -> _BrandOrCategory | None:
|
||||
"""Internal helper for resolving Brand/Category by name with deduplication."""
|
||||
queryset = model.objects.filter(name=resolving_name)
|
||||
if not queryset.exists():
|
||||
|
|
@ -672,6 +674,8 @@ class AbstractVendor(ABC):
|
|||
.order_by("uuid")
|
||||
.first()
|
||||
)
|
||||
if not attribute:
|
||||
return None
|
||||
fields_to_update: list[str] = []
|
||||
if not attribute.is_active:
|
||||
attribute.is_active = True
|
||||
|
|
|
|||
|
|
@ -607,7 +607,7 @@ def dashboard_callback(request: HttpRequest, context: Context) -> Context:
|
|||
product = Product.objects.filter(pk=wished_first["products"]).first()
|
||||
if product:
|
||||
img = (
|
||||
product.images.first().image_url if product.images.exists() else ""
|
||||
product.images.first().image_url if product.images.exists() else "" # ty: ignore[possibly-missing-attribute]
|
||||
)
|
||||
most_wished = {
|
||||
"name": product.name,
|
||||
|
|
@ -631,7 +631,7 @@ def dashboard_callback(request: HttpRequest, context: Context) -> Context:
|
|||
if not pid or pid not in product_by_id:
|
||||
continue
|
||||
p = product_by_id[pid]
|
||||
img = p.images.first().image_url if p.images.exists() else ""
|
||||
img = p.images.first().image_url if p.images.exists() else "" # ty: ignore[possibly-missing-attribute]
|
||||
most_wished_list.append(
|
||||
{
|
||||
"name": p.name,
|
||||
|
|
@ -687,10 +687,10 @@ def dashboard_callback(request: HttpRequest, context: Context) -> Context:
|
|||
.order_by("total_qty")[:5]
|
||||
)
|
||||
for p in products:
|
||||
qty = int(p.total_qty or 0)
|
||||
qty = int(p.total_qty or 0) # ty: ignore[possibly-missing-attribute]
|
||||
img = ""
|
||||
with suppress(Exception):
|
||||
img = p.images.first().image_url if p.images.exists() else ""
|
||||
img = p.images.first().image_url if p.images.exists() else "" # ty: ignore[possibly-missing-attribute]
|
||||
low_stock_list.append(
|
||||
{
|
||||
"name": p.name,
|
||||
|
|
@ -734,7 +734,7 @@ def dashboard_callback(request: HttpRequest, context: Context) -> Context:
|
|||
product = Product.objects.filter(pk=popular_first["product"]).first()
|
||||
if product:
|
||||
img = (
|
||||
product.images.first().image_url if product.images.exists() else ""
|
||||
product.images.first().image_url if product.images.exists() else "" # ty: ignore[possibly-missing-attribute]
|
||||
)
|
||||
most_popular = {
|
||||
"name": product.name,
|
||||
|
|
@ -758,7 +758,7 @@ def dashboard_callback(request: HttpRequest, context: Context) -> Context:
|
|||
if not pid or pid not in product_by_id:
|
||||
continue
|
||||
p = product_by_id[pid]
|
||||
img = p.images.first().image_url if p.images.exists() else ""
|
||||
img = p.images.first().image_url if p.images.exists() else "" # ty: ignore[possibly-missing-attribute]
|
||||
most_popular_list.append(
|
||||
{
|
||||
"name": p.name,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ dependencies = [
|
|||
"django-elasticsearch-dsl==8.2",
|
||||
"django-extensions==4.1",
|
||||
"django-filter==25.2",
|
||||
"django-health-check==4.0.4",
|
||||
"django-health-check==4.0.6",
|
||||
"django-import-export[all]==4.4.0",
|
||||
"django-json-widget==2.1.1",
|
||||
"django-model-utils==5.0.0",
|
||||
|
|
@ -90,10 +90,10 @@ linting = [
|
|||
"types-paramiko==4.0.0.20250822",
|
||||
"types-psutil==7.2.2.20260130",
|
||||
"types-pillow==10.2.0.20240822",
|
||||
"types-docutils==0.22.3.20251115",
|
||||
"types-docutils==0.22.3.20260223",
|
||||
"types-six==1.17.0.20251009",
|
||||
]
|
||||
openai = ["openai==2.21.0"]
|
||||
openai = ["openai==2.24.0"]
|
||||
jupyter = ["jupyter==1.1.1"]
|
||||
|
||||
[tool.uv]
|
||||
|
|
|
|||
42
uv.lock
42
uv.lock
|
|
@ -388,11 +388,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.1.4"
|
||||
version = "2026.2.25"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -880,15 +880,15 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "django-health-check"
|
||||
version = "4.0.4"
|
||||
version = "4.0.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
{ name = "dnspython" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/ea/5abd492cc9ea536edba5d436a84086f1c0fcdc66fd023a1f4cc086d39a56/django_health_check-4.0.4.tar.gz", hash = "sha256:b2349ff9d75dc52e203be20f461eabae6b203f2566e5ba888bc885168decaaa9", size = 20496, upload-time = "2026-02-18T13:08:42.442Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/92/fe/718725c58fd177cff0cfb8abe3010f2cad582713f2bc52eaf7120b750dec/django_health_check-4.0.6.tar.gz", hash = "sha256:03837041ba8a235e810e16218f2ef3feb372c4af72776fa3676c16435c72171c", size = 20763, upload-time = "2026-02-23T17:11:40.625Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/12/8f1fc3b2bd8516c4e71d988b1218543ab0ef3fd21545302bdaf91a57f50d/django_health_check-4.0.4-py3-none-any.whl", hash = "sha256:6c91efa2e3b4f4b280aa5646b6347385f57010314c395aa6af3f7c64f75cd1f8", size = 25476, upload-time = "2026-02-18T13:08:40.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/44/2fa6ec47c1c18159c094f7d00397a208b6311e8b26d603dd22ba6e79b99d/django_health_check-4.0.6-py3-none-any.whl", hash = "sha256:efba106bc4f92b1b084f3af751e9eeb0b5c1af77d0af212e432ede2ba8f1e94f", size = 25813, upload-time = "2026-02-23T17:11:39.419Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1920,7 +1920,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "jupyterlab"
|
||||
version = "4.5.4"
|
||||
version = "4.5.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "async-lru" },
|
||||
|
|
@ -1937,9 +1937,9 @@ dependencies = [
|
|||
{ name = "tornado" },
|
||||
{ name = "traitlets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7c/6b/21af7c0512bdf67e0c54c121779a1f2a97a164a7657e13fced79db8fa5a0/jupyterlab-4.5.4.tar.gz", hash = "sha256:c215f48d8e4582bd2920ad61cc6a40d8ebfef7e5a517ae56b8a9413c9789fdfb", size = 23943597, upload-time = "2026-02-11T00:26:55.308Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6e/2d/953a5612a34a3c799a62566a548e711d103f631672fd49650e0f2de80870/jupyterlab-4.5.5.tar.gz", hash = "sha256:eac620698c59eb810e1729909be418d9373d18137cac66637141abba613b3fda", size = 23968441, upload-time = "2026-02-23T18:57:34.339Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/9f/a70972ece62ead2d81acc6223188f6d18a92f665ccce17796a0cdea4fcf5/jupyterlab-4.5.4-py3-none-any.whl", hash = "sha256:cc233f70539728534669fb0015331f2a3a87656207b3bb2d07916e9289192f12", size = 12391867, upload-time = "2026-02-11T00:26:51.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/52/372d3494766d690dfdd286871bf5f7fb9a6c61f7566ccaa7153a163dd1df/jupyterlab-4.5.5-py3-none-any.whl", hash = "sha256:a35694a40a8e7f2e82f387472af24e61b22adcce87b5a8ab97a5d9c486202a6d", size = 12446824, upload-time = "2026-02-23T18:57:30.398Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2281,7 +2281,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "notebook"
|
||||
version = "7.5.3"
|
||||
version = "7.5.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jupyter-server" },
|
||||
|
|
@ -2290,9 +2290,9 @@ dependencies = [
|
|||
{ name = "notebook-shim" },
|
||||
{ name = "tornado" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b8/cb/cc7f4df5cee315dd126a47eb60890690a0438d5e0dd40c32d60ce16de377/notebook-7.5.3.tar.gz", hash = "sha256:393ceb269cf9fdb02a3be607a57d7bd5c2c14604f1818a17dbeb38e04f98cbfa", size = 14073140, upload-time = "2026-01-26T07:28:36.605Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/78/08/9d446fbb49f95de316ea6d7f25d0a4bc95117dd574e35f405895ac706f29/notebook-7.5.4.tar.gz", hash = "sha256:b928b2ba22cb63aa83df2e0e76fe3697950a0c1c4a41b84ebccf1972b1bb5771", size = 14167892, upload-time = "2026-02-24T14:13:56.116Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/96/98/9286e7f35e5584ebb79f997f2fb0cb66745c86f6c5fccf15ba32aac5e908/notebook-7.5.3-py3-none-any.whl", hash = "sha256:c997bfa1a2a9eb58c9bbb7e77d50428befb1033dd6f02c482922e96851d67354", size = 14481744, upload-time = "2026-01-26T07:28:31.867Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/01/05e5387b53e0f549212d5eff58845886f3827617b5c9409c966ddc07cb6d/notebook-7.5.4-py3-none-any.whl", hash = "sha256:860e31782b3d3a25ca0819ff039f5cf77845d1bf30c78ef9528b88b25e0a9850", size = 14578014, upload-time = "2026-02-24T14:13:52.274Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2358,7 +2358,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/97/73/8ade73f6749177003
|
|||
|
||||
[[package]]
|
||||
name = "openai"
|
||||
version = "2.21.0"
|
||||
version = "2.24.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
|
|
@ -2370,9 +2370,9 @@ dependencies = [
|
|||
{ name = "tqdm" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/92/e5/3d197a0947a166649f566706d7a4c8f7fe38f1fa7b24c9bcffe4c7591d44/openai-2.21.0.tar.gz", hash = "sha256:81b48ce4b8bbb2cc3af02047ceb19561f7b1dc0d4e52d1de7f02abfd15aa59b7", size = 644374, upload-time = "2026-02-14T00:12:01.577Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/55/13/17e87641b89b74552ed408a92b231283786523edddc95f3545809fab673c/openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673", size = 658717, upload-time = "2026-02-24T20:02:07.958Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/56/0a89092a453bb2c676d66abee44f863e742b2110d4dbb1dbcca3f7e5fc33/openai-2.21.0-py3-none-any.whl", hash = "sha256:0bc1c775e5b1536c294eded39ee08f8407656537ccc71b1004104fe1602e267c", size = 1103065, upload-time = "2026-02-14T00:11:59.603Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122, upload-time = "2026-02-24T20:02:05.669Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3440,7 +3440,7 @@ requires-dist = [
|
|||
{ name = "django-elasticsearch-dsl", specifier = "==8.2" },
|
||||
{ name = "django-extensions", specifier = "==4.1" },
|
||||
{ name = "django-filter", specifier = "==25.2" },
|
||||
{ name = "django-health-check", specifier = "==4.0.4" },
|
||||
{ name = "django-health-check", specifier = "==4.0.6" },
|
||||
{ name = "django-import-export", extras = ["all"], specifier = "==4.4.0" },
|
||||
{ name = "django-json-widget", specifier = "==2.1.1" },
|
||||
{ name = "django-md-field", specifier = "==0.1.0" },
|
||||
|
|
@ -3471,7 +3471,7 @@ requires-dist = [
|
|||
{ name = "graphene-file-upload", specifier = "==1.3.0" },
|
||||
{ name = "httpx", specifier = "==0.28.1" },
|
||||
{ name = "jupyter", marker = "extra == 'jupyter'", specifier = "==1.1.1" },
|
||||
{ name = "openai", marker = "extra == 'openai'", specifier = "==2.21.0" },
|
||||
{ name = "openai", marker = "extra == 'openai'", specifier = "==2.24.0" },
|
||||
{ name = "opentelemetry-instrumentation-django", specifier = "==0.60b1" },
|
||||
{ name = "paramiko", specifier = "==4.0.0" },
|
||||
{ name = "pillow", specifier = "==12.1.1" },
|
||||
|
|
@ -3490,7 +3490,7 @@ requires-dist = [
|
|||
{ name = "six", specifier = "==1.17.0" },
|
||||
{ name = "swapper", specifier = "==1.4.0" },
|
||||
{ name = "ty", marker = "extra == 'linting'", specifier = "==0.0.16" },
|
||||
{ name = "types-docutils", marker = "extra == 'linting'", specifier = "==0.22.3.20251115" },
|
||||
{ name = "types-docutils", marker = "extra == 'linting'", specifier = "==0.22.3.20260223" },
|
||||
{ name = "types-paramiko", marker = "extra == 'linting'", specifier = "==4.0.0.20250822" },
|
||||
{ name = "types-pillow", marker = "extra == 'linting'", specifier = "==10.2.0.20240822" },
|
||||
{ name = "types-psutil", marker = "extra == 'linting'", specifier = "==7.2.2.20260130" },
|
||||
|
|
@ -3747,11 +3747,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "types-docutils"
|
||||
version = "0.22.3.20251115"
|
||||
version = "0.22.3.20260223"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/eb/d7/576ec24bf61a280f571e1f22284793adc321610b9bcfba1bf468cf7b334f/types_docutils-0.22.3.20251115.tar.gz", hash = "sha256:0f79ea6a7bd4d12d56c9f824a0090ffae0ea4204203eb0006392906850913e16", size = 56828, upload-time = "2025-11-15T02:59:57.371Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/80/33/92c0129283363e3b3ba270bf6a2b7d077d949d2f90afc4abaf6e73578563/types_docutils-0.22.3.20260223.tar.gz", hash = "sha256:e90e868da82df615ea2217cf36dff31f09660daa15fc0f956af53f89c1364501", size = 57230, upload-time = "2026-02-23T04:11:21.806Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/01/61ac9eb38f1f978b47443dc6fd2e0a3b0f647c2da741ddad30771f1b2b6f/types_docutils-0.22.3.20251115-py3-none-any.whl", hash = "sha256:c6e53715b65395d00a75a3a8a74e352c669bc63959e65a207dffaa22f4a2ad6e", size = 91951, upload-time = "2025-11-15T02:59:56.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/c7/a4ae6a75d5b07d63089d5c04d450a0de4a5d48ffcb84b95659b22d3885fe/types_docutils-0.22.3.20260223-py3-none-any.whl", hash = "sha256:cc2d6b7560a28e351903db0989091474aa619ad287843a018324baee9c4d9a8f", size = 91969, upload-time = "2026-02-23T04:11:20.966Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Reference in a new issue