diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 88bcdb6..0000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -chzzk/tests/*.json linguist-generated=true -twitch/tests/*.json linguist-generated=true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2c5fb94..4e96dae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,6 +40,6 @@ repos: args: ["--py311-plus"] - repo: https://github.com/rhysd/actionlint - rev: v1.7.12 + rev: v1.7.11 hooks: - id: actionlint diff --git a/chzzk/management/__init__.py b/chzzk/management/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/chzzk/management/commands/__init__.py b/chzzk/management/commands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/chzzk/management/commands/import_chzzk_campaign.py b/chzzk/management/commands/import_chzzk_campaign.py deleted file mode 100644 index f4c0875..0000000 --- a/chzzk/management/commands/import_chzzk_campaign.py +++ /dev/null @@ -1,137 +0,0 @@ -from typing import TYPE_CHECKING -from typing import Any - -if TYPE_CHECKING: - import argparse - - from chzzk.schemas import ChzzkCampaignV1 - from chzzk.schemas import ChzzkCampaignV2 - - -from typing import TYPE_CHECKING - -import requests -from django.core.management.base import BaseCommand -from django.utils import timezone - -from chzzk.models import ChzzkCampaign -from chzzk.models import ChzzkReward -from chzzk.schemas import ChzzkApiResponseV1 -from chzzk.schemas import ChzzkApiResponseV2 - -if TYPE_CHECKING: - import argparse - -CHZZK_API_URLS: list[tuple[str, str]] = [ - ("v1", "https://api.chzzk.naver.com/service/v1/drops/campaigns/{campaign_no}"), - ("v2", "https://api.chzzk.naver.com/service/v2/drops/campaigns/{campaign_no}"), -] - -USER_AGENT = ( - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:151.0) Gecko/20100101 Firefox/151.0" -) - - -class Command(BaseCommand): - """Django management command to scrape Chzzk drops campaigns from both v1 and v2 APIs and store them in the database.""" - - help = "Scrape Chzzk drops campaigns from both v1 and v2 APIs and store them." - - def add_arguments(self, parser: argparse.ArgumentParser) -> None: - """Add command-line arguments for the management command.""" - parser.add_argument("campaign_no", type=int, help="Campaign number to fetch") - - def handle(self, **options) -> None: - """Main handler for the management command. Fetches campaign data from both API versions, validates, and stores them.""" - campaign_no: int = int(options["campaign_no"]) - for api_version, url_template in CHZZK_API_URLS: - url: str = url_template.format(campaign_no=campaign_no) - resp: requests.Response = requests.get( - url, - timeout=2, - headers={ - "Accept": "application/json", - "User-Agent": USER_AGENT, - }, - ) - resp.raise_for_status() - data: dict[str, Any] = resp.json() - - campaign_data: ChzzkCampaignV1 | ChzzkCampaignV2 - if api_version == "v1": - campaign_data = ChzzkApiResponseV1.model_validate(data).content - elif api_version == "v2": - campaign_data = ChzzkApiResponseV2.model_validate(data).content - else: - msg: str = f"Unknown API version: {api_version}" - self.stdout.write(self.style.ERROR(msg)) - continue - - # Save campaign - campaign_obj, created = ChzzkCampaign.objects.update_or_create( - campaign_no=campaign_data.campaign_no, - source_api=api_version, - defaults={ - "title": campaign_data.title, - "image_url": campaign_data.image_url, - "description": campaign_data.description, - "category_type": campaign_data.category_type, - "category_id": campaign_data.category_id, - "category_value": campaign_data.category_value, - "pc_link_url": campaign_data.pc_link_url, - "mobile_link_url": campaign_data.mobile_link_url, - "service_id": campaign_data.service_id, - "state": campaign_data.state, - "start_date": campaign_data.start_date, - "end_date": campaign_data.end_date, - "has_ios_based_reward": campaign_data.has_ios_based_reward, - "drops_campaign_not_started": campaign_data.drops_campaign_not_started, - "campaign_reward_type": getattr( - campaign_data, - "campaign_reward_type", - "", - ), - "reward_type": getattr(campaign_data, "reward_type", ""), - "account_link_url": campaign_data.account_link_url, - "scraped_at": timezone.now(), - "scrape_status": "success", - "raw_json": data, - }, - ) - if created: - self.stdout.write( - self.style.SUCCESS( - f"Created campaign {campaign_no} from {api_version}", - ), - ) - for reward in campaign_data.reward_list: - reward_, created = ChzzkReward.objects.update_or_create( - campaign=campaign_obj, - reward_no=reward.reward_no, - defaults={ - "image_url": reward.image_url, - "title": reward.title, - "reward_type": reward.reward_type, - "campaign_reward_type": getattr( - reward, - "campaign_reward_type", - "", - ), - "condition_type": reward.condition_type, - "condition_for_minutes": reward.condition_for_minutes, - "ios_based_reward": reward.ios_based_reward, - "code_remaining_count": reward.code_remaining_count, - }, - ) - if created: - self.stdout.write( - self.style.SUCCESS( - f" Created reward {reward_.reward_no} for campaign {campaign_no}", - ), - ) - - self.stdout.write( - self.style.SUCCESS( - f"Imported campaign {campaign_no} from {api_version}", - ), - ) diff --git a/chzzk/migrations/0001_initial.py b/chzzk/migrations/0001_initial.py deleted file mode 100644 index 52e6e62..0000000 --- a/chzzk/migrations/0001_initial.py +++ /dev/null @@ -1,100 +0,0 @@ -# Generated by Django 6.0.3 on 2026-03-31 19:33 - -import django.db.models.deletion -import django.utils.timezone -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - """Initial migration for ChzzkCampaign and ChzzkReward models.""" - - initial = True - - dependencies = [] - - operations = [ - migrations.CreateModel( - name="ChzzkCampaign", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("campaign_no", models.BigIntegerField(unique=True)), - ("title", models.CharField(max_length=255)), - ("image_url", models.URLField()), - ("description", models.TextField()), - ("category_type", models.CharField(max_length=64)), - ("category_id", models.CharField(max_length=128)), - ("category_value", models.CharField(max_length=128)), - ("pc_link_url", models.URLField()), - ("mobile_link_url", models.URLField()), - ("service_id", models.CharField(max_length=128)), - ("state", models.CharField(max_length=64)), - ("start_date", models.DateTimeField()), - ("end_date", models.DateTimeField()), - ("has_ios_based_reward", models.BooleanField()), - ("drops_campaign_not_started", models.BooleanField()), - ( - "campaign_reward_type", - models.CharField(blank=True, default="", max_length=64), - ), - ( - "reward_type", - models.CharField(blank=True, default="", max_length=64), - ), - ("account_link_url", models.URLField()), - ("scraped_at", models.DateTimeField(default=django.utils.timezone.now)), - ("source_api", models.CharField(max_length=16)), - ("scrape_status", models.CharField(default="success", max_length=32)), - ("raw_json", models.JSONField()), - ], - options={ - "ordering": ["-start_date"], - "unique_together": {("campaign_no", "source_api")}, - }, - ), - migrations.CreateModel( - name="ChzzkReward", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("reward_no", models.BigIntegerField()), - ("image_url", models.URLField()), - ("title", models.CharField(max_length=255)), - ("reward_type", models.CharField(max_length=64)), - ( - "campaign_reward_type", - models.CharField(blank=True, default="", max_length=64), - ), - ("condition_type", models.CharField(max_length=64)), - ("condition_for_minutes", models.IntegerField()), - ("ios_based_reward", models.BooleanField()), - ("code_remaining_count", models.IntegerField()), - ( - "campaign", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="rewards", - to="chzzk.chzzkcampaign", - ), - ), - ], - options={ - "unique_together": {("campaign", "reward_no")}, - }, - ), - ] diff --git a/chzzk/migrations/0002_alter_chzzkcampaign_campaign_no.py b/chzzk/migrations/0002_alter_chzzkcampaign_campaign_no.py deleted file mode 100644 index e35eb48..0000000 --- a/chzzk/migrations/0002_alter_chzzkcampaign_campaign_no.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 6.0.3 on 2026-03-31 19:53 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - """Alter campaign_no field in ChzzkCampaign to remove unique constraint.""" - - dependencies = [ - ("chzzk", "0001_initial"), - ] - - operations = [ - migrations.AlterField( - model_name="chzzkcampaign", - name="campaign_no", - field=models.BigIntegerField(), - ), - ] diff --git a/chzzk/models.py b/chzzk/models.py index 2aefe93..e69de29 100644 --- a/chzzk/models.py +++ b/chzzk/models.py @@ -1,63 +0,0 @@ -from django.db import models -from django.utils import timezone - - -class ChzzkCampaign(models.Model): - """Chzzk campaign, including scraping metadata.""" - - campaign_no = models.BigIntegerField() - title = models.CharField(max_length=255) - image_url = models.URLField() - description = models.TextField() - category_type = models.CharField(max_length=64) - category_id = models.CharField(max_length=128) - category_value = models.CharField(max_length=128) - pc_link_url = models.URLField() - mobile_link_url = models.URLField() - service_id = models.CharField(max_length=128) - state = models.CharField(max_length=64) - start_date = models.DateTimeField() - end_date = models.DateTimeField() - has_ios_based_reward = models.BooleanField() - drops_campaign_not_started = models.BooleanField() - campaign_reward_type = models.CharField(max_length=64, blank=True, default="") - reward_type = models.CharField(max_length=64, blank=True, default="") - account_link_url = models.URLField() - - # Scraping metadata - scraped_at = models.DateTimeField(default=timezone.now) - source_api = models.CharField(max_length=16) # 'v1' or 'v2' - scrape_status = models.CharField(max_length=32, default="success") - raw_json = models.JSONField() - - class Meta: - unique_together = ("campaign_no", "source_api") - ordering = ["-start_date"] - - def __str__(self) -> str: - return f"{self.title} (#{self.campaign_no})" - - -class ChzzkReward(models.Model): - """Chzzk reward belonging to a campaign.""" - - campaign = models.ForeignKey( - ChzzkCampaign, - related_name="rewards", - on_delete=models.CASCADE, - ) - reward_no = models.BigIntegerField() - image_url = models.URLField() - title = models.CharField(max_length=255) - reward_type = models.CharField(max_length=64) - campaign_reward_type = models.CharField(max_length=64, blank=True, default="") - condition_type = models.CharField(max_length=64) - condition_for_minutes = models.IntegerField() - ios_based_reward = models.BooleanField() - code_remaining_count = models.IntegerField() - - class Meta: - unique_together = ("campaign", "reward_no") - - def __str__(self) -> str: - return f"{self.title} (#{self.reward_no})" diff --git a/chzzk/schemas.py b/chzzk/schemas.py deleted file mode 100644 index 20a0986..0000000 --- a/chzzk/schemas.py +++ /dev/null @@ -1,107 +0,0 @@ -from typing import Any - -from pydantic import BaseModel -from pydantic import Field - - -class ChzzkRewardV1(BaseModel): - """Pydantic schema for Chzzk v1 reward object.""" - - title: str - reward_no: int = Field(..., alias="rewardNo") - image_url: str = Field(..., alias="imageUrl") - reward_type: str = Field(..., alias="rewardType") - condition_type: str = Field(..., alias="conditionType") - condition_for_minutes: int = Field(..., alias="conditionForMinutes") - ios_based_reward: bool = Field(..., alias="iosBasedReward") - code_remaining_count: int = Field(..., alias="codeRemainingCount") - - # Only in v1 API - campaign_reward_type: str | None = Field(None, alias="campaignRewardType") - - model_config = {"extra": "forbid"} - - -class ChzzkRewardV2(BaseModel): - """Pydantic schema for Chzzk v2 reward object.""" - - title: str - reward_no: int = Field(..., alias="rewardNo") - image_url: str = Field(..., alias="imageUrl") - reward_type: str = Field(..., alias="rewardType") - condition_type: str = Field(..., alias="conditionType") - condition_for_minutes: int = Field(..., alias="conditionForMinutes") - ios_based_reward: bool = Field(..., alias="iosBasedReward") - code_remaining_count: int = Field(..., alias="codeRemainingCount") - - model_config = {"extra": "forbid"} - - -class ChzzkCampaignV1(BaseModel): - """Pydantic schema for Chzzk v1 campaign object.""" - - title: str - state: str - description: str - campaign_no: int = Field(..., alias="campaignNo") - image_url: str = Field(..., alias="imageUrl") - category_type: str = Field(..., alias="categoryType") - category_id: str = Field(..., alias="categoryId") - category_value: str = Field(..., alias="categoryValue") - pc_link_url: str = Field(..., alias="pcLinkUrl") - mobile_link_url: str = Field(..., alias="mobileLinkUrl") - service_id: str = Field(..., alias="serviceId") - start_date: str = Field(..., alias="startDate") - end_date: str = Field(..., alias="endDate") - reward_list: list[ChzzkRewardV1] = Field(..., alias="rewardList") - has_ios_based_reward: bool = Field(..., alias="hasIosBasedReward") - drops_campaign_not_started: bool = Field(..., alias="dropsCampaignNotStarted") - campaign_reward_type: str | None = Field(None, alias="campaignRewardType") - account_link_url: str = Field(..., alias="accountLinkUrl") - - model_config = {"extra": "forbid"} - - -class ChzzkCampaignV2(BaseModel): - """Pydantic schema for Chzzk v2 campaign object.""" - - title: str - state: str - campaign_no: int = Field(..., alias="campaignNo") - image_url: str = Field(..., alias="imageUrl") - description: str - category_type: str = Field(..., alias="categoryType") - category_id: str = Field(..., alias="categoryId") - category_value: str = Field(..., alias="categoryValue") - pc_link_url: str = Field(..., alias="pcLinkUrl") - mobile_link_url: str = Field(..., alias="mobileLinkUrl") - service_id: str = Field(..., alias="serviceId") - start_date: str = Field(..., alias="startDate") - end_date: str = Field(..., alias="endDate") - reward_list: list[ChzzkRewardV2] = Field(..., alias="rewardList") - has_ios_based_reward: bool = Field(..., alias="hasIosBasedReward") - drops_campaign_not_started: bool = Field(..., alias="dropsCampaignNotStarted") - reward_type: str | None = Field(None, alias="rewardType") - account_link_url: str = Field(..., alias="accountLinkUrl") - - model_config = {"extra": "forbid"} - - -class ChzzkApiResponseV1(BaseModel): - """Pydantic schema for Chzzk v1 API response.""" - - code: int - message: Any | None - content: ChzzkCampaignV1 - - model_config = {"extra": "forbid"} - - -class ChzzkApiResponseV2(BaseModel): - """Pydantic schema for Chzzk v2 API response.""" - - code: int - message: Any | None - content: ChzzkCampaignV2 - - model_config = {"extra": "forbid"} diff --git a/chzzk/tests/test_schemas.py b/chzzk/tests/test_schemas.py deleted file mode 100644 index 2192fad..0000000 --- a/chzzk/tests/test_schemas.py +++ /dev/null @@ -1,40 +0,0 @@ -import json -from pathlib import Path - -import pydantic -import pytest - -from chzzk.schemas import ChzzkApiResponseV1 -from chzzk.schemas import ChzzkApiResponseV2 - -TESTS_DIR = Path(__file__).parent - - -@pytest.mark.parametrize( - ("fname", "data_model"), - [ - ("v1_905.json", ChzzkApiResponseV1), - ("v2_905.json", ChzzkApiResponseV2), - ], -) -def test_chzzk_schema_strict(fname: str, data_model: type) -> None: - """Test that the schema strictly validates the given JSON file against the provided Pydantic model.""" - with Path(TESTS_DIR / fname).open(encoding="utf-8") as f: - data = json.load(f) - # Should not raise - data_model.model_validate(data) - - -@pytest.mark.parametrize( - ("fname", "data_model"), - [ - ("v1_905.json", ChzzkApiResponseV2), - ("v2_905.json", ChzzkApiResponseV1), - ], -) -def test_chzzk_schema_cross_fail(fname: str, data_model: type) -> None: - """Test that the schema fails when validating the wrong JSON file/model combination.""" - with Path(TESTS_DIR / fname).open(encoding="utf-8") as f: - data = json.load(f) - with pytest.raises(pydantic.ValidationError): - data_model.model_validate(data) diff --git a/chzzk/tests/v1_905.json b/chzzk/tests/v1_905.json deleted file mode 100644 index 52b7479..0000000 --- a/chzzk/tests/v1_905.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "code": 200, - "message": null, - "content": { - "campaignNo": 905, - "title": "붉은사막 드롭스 #2", - "imageUrl": "https://nng-phinf.pstatic.net/MjAyNjAzMjRfMzcg/MDAxNzc0MzU4Mzk3MjM0.JQAXkEYe2ntJ5gvzr5U5egn78DalY24mi1hodGyPYcog.tko3iBzm7dOqDKdjZAZX2ozWrj-tKjVcN3v3ieQaNzQg.JPEG/KR_260319_sony_masterimage_2160x2160.jpg", - "description": "치지직에서 붉은사막 방송을 시청하고, 특별한 보상을 획득해 보세요.", - "categoryType": "GAME", - "categoryId": "CrimsonDesert", - "categoryValue": "붉은사막", - "pcLinkUrl": "https://event.pearlabyss.com/CrimsonDesert/Drops", - "mobileLinkUrl": "https://event.pearlabyss.com/CrimsonDesert/Drops", - "serviceId": "Pearl_Abyss_Event", - "state": "PROMOTED", - "startDate": "2026-03-26 09:00:00", - "endDate": "2026-04-02 08:59:00", - "rewardList": [ - { - "rewardNo": 2521, - "imageUrl": "https://nng-phinf.pstatic.net/MjAyNjAzMjRfMTIg/MDAxNzc0MzU4NTgxMTY5.WFRtLuWw0qciEmMlnm79culfGW1lj-KwvXTaZLkbzMIg.PxoTwZ6v_i1N7Vkyj--OxmeuEJsRvQDWgjT1sOB6kfog.PNG/Week-2-60-min.png", - "title": "푸른 정찰대 등자", - "rewardType": "TIME_BASED", - "campaignRewardType": "IN_APP", - "conditionType": "TIME_BASED", - "conditionForMinutes": 60, - "iosBasedReward": false, - "codeRemainingCount": 0 - }, - { - "rewardNo": 2522, - "imageUrl": "https://nng-phinf.pstatic.net/MjAyNjAzMjRfMTYw/MDAxNzc0MzU4NjEwODkz.6SMLoYofJ6A2a9Nd1IlKn_CC3JLyR8ecxkt8cO-rzcAg.kQSP_H08VoS1qXGiB_3BBMYT_0IT07YPz09n0lNmD1cg.PNG/Week-2-120-min.png", - "title": "푸른 정찰대 마면", - "rewardType": "TIME_BASED", - "campaignRewardType": "IN_APP", - "conditionType": "TIME_BASED", - "conditionForMinutes": 120, - "iosBasedReward": false, - "codeRemainingCount": 0 - }, - { - "rewardNo": 2523, - "imageUrl": "https://nng-phinf.pstatic.net/MjAyNjAzMjRfMjU3/MDAxNzc0MzU4NjM5MDgw.7u6E8ybLp7x9MNESI3od1Sf3AE_6AobSMFfb18MhsNog.M5ZvA1SSjaOObB2TB-P9Rw2rtMcV8zXipL0i7adILdwg.PNG/Week-2-180-min.png", - "title": "푸른 정찰대 안장", - "rewardType": "TIME_BASED", - "campaignRewardType": "IN_APP", - "conditionType": "TIME_BASED", - "conditionForMinutes": 180, - "iosBasedReward": false, - "codeRemainingCount": 0 - } - ], - "hasIosBasedReward": false, - "dropsCampaignNotStarted": false, - "campaignRewardType": "IN_APP", - "accountLinkUrl": "https://event.pearlabyss.com/CrimsonDesert/Drops" - } -} diff --git a/chzzk/tests/v2_905.json b/chzzk/tests/v2_905.json deleted file mode 100644 index 55dd1e4..0000000 --- a/chzzk/tests/v2_905.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "code": 200, - "message": null, - "content": { - "campaignNo": 905, - "title": "붉은사막 드롭스 #2", - "imageUrl": "https://nng-phinf.pstatic.net/MjAyNjAzMjRfMzcg/MDAxNzc0MzU4Mzk3MjM0.JQAXkEYe2ntJ5gvzr5U5egn78DalY24mi1hodGyPYcog.tko3iBzm7dOqDKdjZAZX2ozWrj-tKjVcN3v3ieQaNzQg.JPEG/KR_260319_sony_masterimage_2160x2160.jpg", - "description": "치지직에서 붉은사막 방송을 시청하고, 특별한 보상을 획득해 보세요.", - "categoryType": "GAME", - "categoryId": "CrimsonDesert", - "categoryValue": "붉은사막", - "pcLinkUrl": "https://event.pearlabyss.com/CrimsonDesert/Drops", - "mobileLinkUrl": "https://event.pearlabyss.com/CrimsonDesert/Drops", - "serviceId": "Pearl_Abyss_Event", - "state": "PROMOTED", - "startDate": "2026-03-26 09:00:00", - "endDate": "2026-04-02 08:59:00", - "rewardList": [ - { - "rewardNo": 2521, - "imageUrl": "https://nng-phinf.pstatic.net/MjAyNjAzMjRfMTIg/MDAxNzc0MzU4NTgxMTY5.WFRtLuWw0qciEmMlnm79culfGW1lj-KwvXTaZLkbzMIg.PxoTwZ6v_i1N7Vkyj--OxmeuEJsRvQDWgjT1sOB6kfog.PNG/Week-2-60-min.png", - "title": "푸른 정찰대 등자", - "rewardType": "IN_APP", - "conditionType": "TIME_BASED", - "conditionForMinutes": 60, - "iosBasedReward": false, - "codeRemainingCount": 0 - }, - { - "rewardNo": 2522, - "imageUrl": "https://nng-phinf.pstatic.net/MjAyNjAzMjRfMTYw/MDAxNzc0MzU4NjEwODkz.6SMLoYofJ6A2a9Nd1IlKn_CC3JLyR8ecxkt8cO-rzcAg.kQSP_H08VoS1qXGiB_3BBMYT_0IT07YPz09n0lNmD1cg.PNG/Week-2-120-min.png", - "title": "푸른 정찰대 마면", - "rewardType": "IN_APP", - "conditionType": "TIME_BASED", - "conditionForMinutes": 120, - "iosBasedReward": false, - "codeRemainingCount": 0 - }, - { - "rewardNo": 2523, - "imageUrl": "https://nng-phinf.pstatic.net/MjAyNjAzMjRfMjU3/MDAxNzc0MzU4NjM5MDgw.7u6E8ybLp7x9MNESI3od1Sf3AE_6AobSMFfb18MhsNog.M5ZvA1SSjaOObB2TB-P9Rw2rtMcV8zXipL0i7adILdwg.PNG/Week-2-180-min.png", - "title": "푸른 정찰대 안장", - "rewardType": "IN_APP", - "conditionType": "TIME_BASED", - "conditionForMinutes": 180, - "iosBasedReward": false, - "codeRemainingCount": 0 - } - ], - "hasIosBasedReward": false, - "dropsCampaignNotStarted": false, - "rewardType": "IN_APP", - "accountLinkUrl": "https://event.pearlabyss.com/CrimsonDesert/Drops" - } -} diff --git a/config/settings.py b/config/settings.py index dbb1285..b7347b3 100644 --- a/config/settings.py +++ b/config/settings.py @@ -140,11 +140,10 @@ INSTALLED_APPS: list[str] = [ "django.contrib.staticfiles", "django.contrib.postgres", # Internal apps - "chzzk.apps.ChzzkConfig", - "core.apps.CoreConfig", - "kick.apps.KickConfig", "twitch.apps.TwitchConfig", + "kick.apps.KickConfig", "youtube.apps.YoutubeConfig", + "core.apps.CoreConfig", # Third-party apps "django_celery_results", "django_celery_beat", @@ -172,35 +171,10 @@ TEMPLATES: list[dict[str, Any]] = [ }, ] - -def configure_databases(*, testing: bool, base_dir: Path) -> dict[str, dict[str, Any]]: - """Configure Django databases based on environment variables and testing mode. - - Args: - testing (bool): Whether the application is running in testing mode. - base_dir (Path): The base directory of the project, used for SQLite file location. - - Returns: - dict[str, dict[str, Any]]: The DATABASES setting for Django. - """ - use_sqlite: bool = env_bool("USE_SQLITE", default=False) - - if testing: - return { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": ":memory:", - }, - } - if use_sqlite: - return { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": str(base_dir / "db.sqlite3"), - }, - } - # Default: PostgreSQL - return { +DATABASES: dict[str, dict[str, Any]] = ( + {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}} + if TESTING + else { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": os.getenv("POSTGRES_DB", "ttvdrops"), @@ -213,11 +187,6 @@ def configure_databases(*, testing: bool, base_dir: Path) -> dict[str, dict[str, "OPTIONS": {"connect_timeout": env_int("DB_CONNECT_TIMEOUT", 10)}, }, } - - -DATABASES: dict[str, dict[str, Any]] = configure_databases( - testing=TESTING, - base_dir=BASE_DIR, ) if not TESTING: diff --git a/config/tests/test_settings.py b/config/tests/test_settings.py index aa84ab9..1b4a5f9 100644 --- a/config/tests/test_settings.py +++ b/config/tests/test_settings.py @@ -3,7 +3,6 @@ import os import sys from contextlib import contextmanager from typing import TYPE_CHECKING -from typing import Any import pytest from django.contrib.sessions.models import Session @@ -250,43 +249,14 @@ def test_email_settings_from_env( assert reloaded.SERVER_EMAIL == "me@example.com" -def test_database_settings_when_use_sqlite_enabled( - monkeypatch: pytest.MonkeyPatch, - reload_settings_module: Callable[..., ModuleType], -) -> None: - """When USE_SQLITE=1 and not testing, DATABASES should use SQLite file.""" - monkeypatch.setattr("sys.argv", ["manage.py", "runserver"]) - monkeypatch.delenv("PYTEST_VERSION", raising=False) - monkeypatch.setenv("USE_SQLITE", "1") - - reloaded: ModuleType = reload_settings_module( - TESTING=None, - PYTEST_VERSION=None, - POSTGRES_DB="prod_db", - POSTGRES_USER="prod_user", - POSTGRES_PASSWORD="secret", - POSTGRES_HOST="db.host", - POSTGRES_PORT="5433", - CONN_MAX_AGE="120", - CONN_HEALTH_CHECKS="0", - ) - - assert reloaded.TESTING is False - db_cfg: dict[str, Any] = reloaded.DATABASES["default"] - assert db_cfg["ENGINE"] == "django.db.backends.sqlite3" - # Should use a file, not in-memory - assert db_cfg["NAME"].endswith("db.sqlite3") - - def test_database_settings_when_not_testing( monkeypatch: pytest.MonkeyPatch, reload_settings_module: Callable[..., ModuleType], ) -> None: """When not running tests, DATABASES should use the Postgres configuration.""" - # Ensure the module believes it's not running tests and USE_SQLITE is unset + # Ensure the module believes it's not running tests monkeypatch.setattr("sys.argv", ["manage.py", "runserver"]) monkeypatch.delenv("PYTEST_VERSION", raising=False) - monkeypatch.setenv("USE_SQLITE", "0") reloaded: ModuleType = reload_settings_module( TESTING=None, @@ -301,7 +271,7 @@ def test_database_settings_when_not_testing( ) assert reloaded.TESTING is False - db_cfg: dict[str, Any] = reloaded.DATABASES["default"] + db_cfg = reloaded.DATABASES["default"] assert db_cfg["ENGINE"] == "django.db.backends.postgresql" assert db_cfg["NAME"] == "prod_db" assert db_cfg["USER"] == "prod_user" diff --git a/twitch/tests/example.json b/example.json similarity index 100% rename from twitch/tests/example.json rename to example.json diff --git a/twitch/tests/test_better_import_drops.py b/twitch/tests/test_better_import_drops.py index 92effc5..2938625 100644 --- a/twitch/tests/test_better_import_drops.py +++ b/twitch/tests/test_better_import_drops.py @@ -620,7 +620,7 @@ class ExampleJsonImportTests(TestCase): command = Command() repo_root: Path = Path(__file__).resolve().parents[2] - example_path: Path = repo_root / "twitch" / "tests" / "example.json" + example_path: Path = repo_root / "example.json" responses = json.loads(example_path.read_text(encoding="utf-8"))