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

fix(deps): update dependency urllib3 to v2 #182

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
18 changes: 10 additions & 8 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ static3 = "^0.7.0"
tldextract = "^5.1.1"
toolz = "^0.12.0"
ulid-py = "^1.1.0"
urllib3 = "^1.25.10"
urllib3 = "^2.0.0"
uwsgi = "^2.0.21"
wrapt = "^1.16.0"
social-auth-core = {extras = ["openidconnect"], version = "^4.4.2"}
Expand Down
65 changes: 65 additions & 0 deletions system_meta/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""API functions for system metadata."""

import logging

import requests
from django.conf import settings

from system_meta.models import Product
from unified_ecommerce.utils import parse_readable_id

log = logging.getLogger(__name__)


def update_product_metadata(product_id: int) -> None:
"""Get product metadata from the Learn API."""

try:
product = Product.objects.get(id=product_id)
resource, run = parse_readable_id(product.sku)
response = requests.get(
f"{settings.MITOL_LEARN_API_URL}learning_resources/",
params={"platform": product.system.slug, "readable_id": resource},
timeout=10,
)
response.raise_for_status()
results_data = response.json()

if results_data.get("count", 0) == 0:
log.warning("No Learn results found for product %s", product)
return

course_data = results_data.get("results")[0]

image_data = course_data.get("image")
product.image_metadata = (
{
"image_url": image_data.get("url"),
"alt_text": image_data.get("alt"),
"description": image_data.get("description"),
}
if image_data
else product.image_metadata
)

product.name = course_data.get("title", product.name)
product.description = course_data.get("description", product.description)

# If there are runs, we'll overwrite this with the run's price later.
product_prices = course_data.get("prices", [])
product_prices.sort()
product.price = product_prices.pop() if len(product_prices) else product.price

runs = course_data.get("runs")
if runs:
run = next((r for r in runs if r.get("run_id") == product.sku), None)
if run:
product_prices = run.get("prices", [])
product_prices.sort()
product.price = (
product_prices.pop() if len(product_prices) else product.price
)

product.save()
except requests.RequestException:
log.exception("Failed to update metadata for product %s", product.id)
50 changes: 50 additions & 0 deletions system_meta/api_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Tests for the system_meta APIs."""

import pytest

from system_meta.api import update_product_metadata
from system_meta.factories import ProductFactory

pytestmark = pytest.mark.django_db


def test_retrieve_product_metadata(mocker):
"""Test that the retrieve_product_metadata function works."""

mocked_requests = mocker.patch("requests.get")
mocked_requests.return_value.json.return_value = {
"results": [
{
"image": {
"id": 12345,
"url": "https://example.com/image.jpg",
"alt": "Example image",
"description": "Example description",
},
"title": "Example title",
"description": "Example description",
"prices": [100, 200],
"readable_id": "course-v1:MITx+12.345x",
"runs": [
{
"run_id": "123",
"prices": [150, 250],
"readable_id": "course-v1:MITx+12.345x+2T2099",
}
],
}
]
}

product = ProductFactory.create(
sku="course-v1:MITx+12.345x+2T2099",
price=50,
description="This is the wrong description.",
)
update_product_metadata(product.id)
product.refresh_from_db()
assert product.image_metadata is not None
assert product.name == "Example title"
assert product.description == "Example description"
# This has a run price, so we should have that, and it should be the highest price.
assert product.price == 250
2 changes: 1 addition & 1 deletion system_meta/management/commands/manage_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def _add_product(
exception_message = "Product {sku} already exists in system {system_name}."
raise CommandError(exception_message, returncode=2)

system = IntegratedSystem.objects.get(name=system_name)
system = IntegratedSystem.objects.get(slug=system_name)
product = Product.objects.create(
sku=sku,
name=name,
Expand Down
33 changes: 13 additions & 20 deletions system_meta/tasks.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,33 @@
"""Tasks for the system_meta app."""

import logging
from typing import Optional

import requests
from celery import shared_task
from django.conf import settings


@shared_task
def update_products(product_id: Optional[int] = None):
"""
Update all product's image metadata. If product_id is provided, only update the
product with that ID. Otherwise, update all products.
Update product metadata from the Learn API.

Updates all products if a product_id is not provided. Pulls the image metadata,
name, and description from the Learn API. If the product has a run ID, it also
pulls the price from the specific run; otherwise, pulls the price from the
resource.
"""
from .models import Product
from system_meta.api import update_product_metadata
from system_meta.models import Product

log = logging.getLogger(__name__)
if product_id:
products = Product.objects.filter(id=product_id)
else:
products = Product.objects.all()

for product in products:
try:
response = requests.get(
f"{settings.MITOL_LEARN_API_URL}learning_resources/",
params={"platform": product.system.slug, "readable_id": product.sku},
timeout=10,
)
response.raise_for_status()
results_data = response.json()
course_data = results_data.get("results")[0]
image_data = course_data.get("image")
product.image_metadata = {
"image_url": image_data.get("url"),
"alt_text": image_data.get("alt"),
"description": image_data.get("description"),
}
product.save()
update_product_metadata(product.id)
except requests.RequestException:
log.exception("Failed to retrieve image data for product %s", product.id)
log.exception("Failed to update metdata for product %s", product.id)
23 changes: 23 additions & 0 deletions unified_ecommerce/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,3 +445,26 @@ def get_user_from_apisix_headers(request):
user.refresh_from_db()

return user


def parse_readable_id(readable_id: str) -> tuple[str, str]:
"""
Parse a readable ID into a resource ID and a run ID.

Readable IDs look like "course-v1:MITxT+12.345x" but they may also have a run
tacked onto the end ("+1T2024" for instance). If the readable ID isn't for a
run of the resource, you'll get a None in the run position.

Args:
readable_id (str): The readable ID to parse

Returns:
tuple[str, str]: The resource ID and the run ID (or None)
"""
if readable_id.count("+") > 1:
resource, run = readable_id.rsplit("+", 1)
else:
resource = readable_id
run = None

return resource, run
Loading