diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b45aa44..8a6910e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -59,19 +59,19 @@ repos: entry: yamllint --strict --config-file .yamllint.yml - repo: "https://github.com/charliermarsh/ruff-pre-commit" - rev: "v0.7.1" + rev: "v0.7.3" hooks: - id: ruff exclude: tests/ - id: ruff-format - repo: "https://github.com/crate-ci/typos" - rev: v1.26.8 + rev: typos-dict-v0.11.34 hooks: - id: typos - repo: "https://github.com/djlint/djLint" - rev: v1.35.3 + rev: v1.36.1 hooks: - id: djlint args: ["--configuration", "pyproject.toml"] diff --git a/pyproject.toml b/pyproject.toml index 5078a11..3765be5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ "numpy>=2.1.1,<2.2.0", "py-cord>=2.6.1,<2.7.0", "pydantic>=2.9.2,<3.0.0", - "pygithub>=2.4.0,<3.0.0", + "pygithub>=2.4.0", "quart-flask-patch>=0.3.0", "quart-session>=3.0.0", "quart-wtforms>=1.0.2", @@ -29,7 +29,7 @@ "redis>=5.1.0,<6.0.0", "rich>=13.8.1", "semver>=3.0.2", - "typer>=0.12.5,<0.13.0", + "typer>=0.13.0,<0.14.0", ] description = "Valentina is a Discord bot that helps you run TTRPGs." license = "AGPL-3.0-or-later" @@ -50,7 +50,7 @@ "coverage>=7.6.1", "dirty-equals>=0.8.0", "djlint>=1.35.2", - "mypy>=1.11.2,<2.0.0", + "mypy>=1.11.2", "poethepoet>=0.29.0", "polyfactory>=2.17.0", "pre-commit>=3.8.0", @@ -214,6 +214,7 @@ "ANN101", # missing-type-self "ANN204", # Missing return type annotation for special method `__init__` "ANN401", # Dynamically typed expressions (typing.Any) are disallowed, + "ARG005", # Unused lambda argument "ASYNC110", # Use `anyio.Event` instead of awaiting `anyio.sleep` in a `while` loop "B006", # mutable-argument-default "B008", # function-call-in-default-argument @@ -258,7 +259,7 @@ ], "migrations/*.py" = [ "ARG002", "PLR6301", - ], "tests/*.py" = [ + ], "tests/**/*.py" = [ "A002", "A003", "ANN201", # Missing return type annotation @@ -266,6 +267,7 @@ "D102", "ERA001", # Commented out code "F403", + "F405", # May be undefined from type imports "PLR0913", "PLR2004", "S101", diff --git a/src/valentina/controllers/__init__.py b/src/valentina/controllers/__init__.py index d18e27a..873b5c9 100644 --- a/src/valentina/controllers/__init__.py +++ b/src/valentina/controllers/__init__.py @@ -2,15 +2,17 @@ from .channel_mngr import ChannelManager from .character_sheet_builder import CharacterSheetBuilder, TraitForCreation +from .experience import total_campaign_experience from .permission_mngr import PermissionManager from .rng_chargen import RNGCharGen from .trait_modifier import TraitModifier __all__ = [ + "ChannelManager", "CharacterSheetBuilder", "PermissionManager", "RNGCharGen", + "total_campaign_experience", "TraitForCreation", "TraitModifier", - "ChannelManager", ] diff --git a/src/valentina/controllers/experience.py b/src/valentina/controllers/experience.py new file mode 100644 index 0000000..cebb375 --- /dev/null +++ b/src/valentina/controllers/experience.py @@ -0,0 +1,21 @@ +"""Controllers for experience.""" + +from valentina.models import Campaign, User + + +async def total_campaign_experience(campaign: Campaign) -> tuple[int, int, int]: + """Return the total experience for the campaign.""" + user_id_list = {character.user_owner for character in await campaign.fetch_player_characters()} + available_xp = 0 + total_xp = 0 + cool_points = 0 + + for user_id in user_id_list: + user = await User.get(int(user_id)) + user_available_xp, user_total_xp, user_cool_points = user.fetch_campaign_xp(campaign) + + available_xp += user_available_xp + total_xp += user_total_xp + cool_points += user_cool_points + + return available_xp, total_xp, cool_points diff --git a/src/valentina/webui/blueprints/HTMXPartials/blueprint.py b/src/valentina/webui/blueprints/HTMXPartials/blueprint.py index 33ad570..e7549cd 100644 --- a/src/valentina/webui/blueprints/HTMXPartials/blueprint.py +++ b/src/valentina/webui/blueprints/HTMXPartials/blueprint.py @@ -2,9 +2,9 @@ from quart import Blueprint -from valentina.webui.constants import TableType +from valentina.webui.constants import TableType, TextType -from .route import EditTableView +from .route import EditTableView, EditTextView blueprint = Blueprint("partials", __name__, url_prefix="/partials") @@ -15,3 +15,10 @@ view_func=EditTableView.as_view(i.value.route_suffix, table_type=i), methods=["GET", "POST", "DELETE", "PUT"], ) + +for t in TextType: + blueprint.add_url_rule( + f"/text/{t.value.route_suffix}", + view_func=EditTextView.as_view(t.value.route_suffix, text_type=t), + methods=["GET", "POST", "PUT"], + ) diff --git a/src/valentina/webui/blueprints/HTMXPartials/forms.py b/src/valentina/webui/blueprints/HTMXPartials/forms.py index 0348130..838142e 100644 --- a/src/valentina/webui/blueprints/HTMXPartials/forms.py +++ b/src/valentina/webui/blueprints/HTMXPartials/forms.py @@ -7,6 +7,27 @@ from valentina.constants import InventoryItemType, TraitCategory +class CampaignDescriptionForm(QuartForm): + """Form for editing a campaign description and name.""" + + title = "Campaign Overview" + + name = StringField( + "Campaign Name", + default="", + validators=[DataRequired(), Length(min=3, message="Must be at least 3 characters")], + filters=[str.strip, str.title], + ) + + campaign_description = TextAreaField( + "Description", + validators=[DataRequired(), Length(min=3, message="Must be at least 3 characters")], + description="Markdown is supported", + ) + + campaign_id = HiddenField() + + class UserMacroForm(QuartForm): """Form for a user macro.""" @@ -111,3 +132,16 @@ class CampaignNPCForm(QuartForm): uuid = HiddenField() campaign_id = HiddenField() + + +class CharacterBioForm(QuartForm): + """A form for editing the character biography.""" + + title = "The Character's Biography" + + bio = TextAreaField( + "Biography", + description="Markdown is supported.", + validators=[DataRequired(), Length(min=5, message="Must be at least 5 characters")], + ) + character_id = HiddenField() diff --git a/src/valentina/webui/blueprints/HTMXPartials/route.py b/src/valentina/webui/blueprints/HTMXPartials/route.py index fa96c1e..f4fca05 100644 --- a/src/valentina/webui/blueprints/HTMXPartials/route.py +++ b/src/valentina/webui/blueprints/HTMXPartials/route.py @@ -1,11 +1,12 @@ """Routes for handling HTMX partials.""" -from typing import TYPE_CHECKING, assert_never +from typing import assert_never from uuid import UUID from loguru import logger from quart import abort, request, session from quart.views import MethodView +from quart_wtf import QuartForm from valentina.models import ( Campaign, @@ -20,21 +21,26 @@ ) from valentina.utils import truncate_string from valentina.webui import catalog -from valentina.webui.constants import TableType -from valentina.webui.utils import create_toast, fetch_active_campaign +from valentina.webui.constants import TableType, TextType +from valentina.webui.utils import ( + create_toast, + fetch_active_campaign, + fetch_active_character, + sync_channel_to_discord, + update_session, +) from valentina.webui.utils.discord import post_to_audit_log from .forms import ( CampaignChapterForm, + CampaignDescriptionForm, CampaignNPCForm, + CharacterBioForm, InventoryItemForm, NoteForm, UserMacroForm, ) -if TYPE_CHECKING: - from quart_wtf import QuartForm - class EditTableView(MethodView): """Handle CRUD operations for editable table items in the web UI. @@ -602,3 +608,159 @@ async def delete(self) -> str: # noqa: C901, PLR0912, PLR0915 ) return create_toast(msg, level="SUCCESS") + + +class EditTextView(MethodView): + """Handle CRUD operations for text items.""" + + def __init__(self, text_type: TextType): + """Initialize view with specified text type.""" + self.text_type: TextType = text_type + + async def _build_form(self) -> "QuartForm": + """Build the appropriate form based on text type.""" + data = {} + + match self.text_type: + case TextType.BIOGRAPHY: + character = await fetch_active_character(request.args.get("parent_id")) + data["bio"] = character.bio + data["character_id"] = str(character.id) + + return await CharacterBioForm().create_form(data=data) + + case TextType.CAMPAIGN_DESCRIPTION: + campaign = await fetch_active_campaign(request.args.get("parent_id")) + data["name"] = campaign.name + data["description"] = campaign.description + data["campaign_id"] = str(campaign.id) + + return await CampaignDescriptionForm().create_form(data=data) + + case _: # pragma: no cover + assert_never(self.text_type) + + async def _update_character_bio(self, form: QuartForm) -> tuple[str, str]: + """Update the character bio. + + Args: + form: The form data + + Returns: + A tuple containing the updated text and message + """ + character = await fetch_active_character(request.args.get("parent_id")) + character.bio = form.data["bio"] + await character.save() + text = character.bio + msg = f"{character.name} bio updated" + return text, msg + + async def _update_campaign_description(self, form: QuartForm) -> tuple[str, str]: + """Update the campaign description and name. + + Args: + form: The form data + + Returns: + A tuple containing the updated text and message + """ + campaign = await fetch_active_campaign(request.args.get("parent_id")) + + is_renamed = campaign.name.strip().lower() != form.data["name"].strip().lower() + + campaign.name = form.data["name"].strip().title() + campaign.description = form.data["campaign_description"].strip() + await campaign.save() + text = form.data["campaign_description"].strip() + msg = f"{campaign.name} description updated" + + if is_renamed: + await sync_channel_to_discord(obj=campaign, update_type="update") + await update_session() + + return text, msg + + async def get(self) -> str: + """Return just the text for a text item. + + Returns: + Rendered HTML fragment containing the text suitable for HTMX integration + """ + match self.text_type: + case TextType.BIOGRAPHY: + character = await fetch_active_character(request.args.get("parent_id")) + text = character.bio + + case TextType.CAMPAIGN_DESCRIPTION: + campaign = await fetch_active_campaign(request.args.get("parent_id")) + text = campaign.description + + case _: # pragma: no cover + assert_never(self.text_type) + + return catalog.render( + "HTMXPartials.EditText.TextDisplayPartial", + TextType=self.text_type, + text=text, + ) + + async def put(self) -> str: + """Put the text item.""" + form = await self._build_form() + + if await form.validate_on_submit(): + match self.text_type: + case TextType.BIOGRAPHY: + text, msg = await self._update_character_bio(form) + + case TextType.CAMPAIGN_DESCRIPTION: + text, msg = await self._update_campaign_description(form) + + await post_to_audit_log( + msg=msg, + view=self.__class__.__name__, + ) + + return catalog.render( + "HTMXPartials.EditText.TextDisplayPartial", + TextType=self.text_type, + text=text, + ) + + return catalog.render( + "HTMXPartials.EditText.TextFormPartial", + TextType=self.text_type, + form=form, + method="PUT", + ) + + async def post(self) -> str: + """Post the text item.""" + form = await self._build_form() + + if await form.validate_on_submit(): + match self.text_type: + case TextType.BIOGRAPHY: + text, msg = await self._update_character_bio(form) + + case TextType.CAMPAIGN_DESCRIPTION: + text, msg = await self._update_campaign_description(form) + + await post_to_audit_log( + msg=msg, + view=self.__class__.__name__, + ) + + return catalog.render( + "HTMXPartials.EditText.TextDisplayPartial", + TextType=self.text_type, + text=text, + ) + + return catalog.render( + "HTMXPartials.EditText.TextFormPartial", + TextType=self.text_type, + form=form, + method="POST", + ) diff --git a/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditTable/ItemDisplayPartial.jinja b/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditTable/ItemDisplayPartial.jinja index 6c831a6..9533765 100644 --- a/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditTable/ItemDisplayPartial.jinja +++ b/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditTable/ItemDisplayPartial.jinja @@ -35,8 +35,12 @@ {{ item.trait_one | escape }} {{ item.trait_two | escape }} {%- elif TableType.name == "CHAPTER" -%} - {{ item.number | escape }} - {{ item.name | capitalize | escape }} + + {{ item.number | escape }} + + + {{ item.name | title | escape }} + {{ item.description_long | from_markdown | safe }} {%- endif -%} diff --git a/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditTable/TablePartial.jinja b/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditTable/TablePartial.jinja index 054931c..49266b5 100644 --- a/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditTable/TablePartial.jinja +++ b/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditTable/TablePartial.jinja @@ -1,8 +1,8 @@ {# def items: list[Note|NPC|InventoryItem], - TableType: TableType, can_edit: bool = True, parent_id: str = "", + TableType: TableType, header_size: Literal["h1", "h2", "h3", "h4", "h5", "h6"] = "h2", #} @@ -11,51 +11,43 @@
{%- if can_edit %} {%- endif %} - <{{ header_size }}> - {{ TableType.value.name | title | escape }} - {%- if TableType.value.description %} -    - {{ TableType.value.description | escape }} - -{%- endif %} - - -
- -{%- if items | length > 0 %} - - - {%- if TableType.value.table_headers %} - + + + + {%- if items | length > 0 %} +
+ + {%- if TableType.value.table_headers %} + + + {%- for header in TableType.value.table_headers %}{% endfor %} + {%- if can_edit %}{%- endif %} + + + {%- endif %} + + + + {%- for item in items | sort(attribute=TableType.value.sort_attribute) %} + + {%- endfor %} + + +
{{ header }}
+ {%- else %} + + - {%- for header in TableType.value.table_headers %}{% endfor %} - {%- if can_edit %}{%- endif %} + - - {%- endif %} - - - - {%- for item in items | sort(attribute=TableType.value.sort_attribute) %} - - {%- endfor %} - - -
{{ header }}No {{ TableType.value.name | lower | escape }}
-{%- else %} - - - - - - -
No {{ TableType.value.name | lower | escape }}
-{%- endif %} + + + {%- endif %}
diff --git a/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditText/TextDisplayPartial.jinja b/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditText/TextDisplayPartial.jinja new file mode 100644 index 0000000..6949103 --- /dev/null +++ b/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditText/TextDisplayPartial.jinja @@ -0,0 +1,4 @@ +{# def + text: str +#} +{{ text | from_markdown | safe }} diff --git a/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditText/TextFormPartial.jinja b/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditText/TextFormPartial.jinja new file mode 100644 index 0000000..8b7c2e2 --- /dev/null +++ b/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditText/TextFormPartial.jinja @@ -0,0 +1,47 @@ +{# def + form:QuartForm, + join_label:bool = False, + floating_label:bool = True, + TextType: TextType, + method: Literal["POST", "PUT"] = "POST", +#} + +{% set target_div = "#" ~ TextType.value.name | upper | replace(' ', '-') ~ '-TEXTBODY' %} + +
+
+ + {% if form.title is defined and form.title %}

{{ form.title }}

{% endif %} + + {% if form.description is defined and form.description %}

{{ form.description }}

{% endif %} + + + + {% if method == "POST" %} + + + + {% elif method == "PUT" %} + + + + {% endif %} + +
+
diff --git a/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditText/TextParentPartial.jinja b/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditText/TextParentPartial.jinja new file mode 100644 index 0000000..fb911ac --- /dev/null +++ b/src/valentina/webui/blueprints/HTMXPartials/templates/HTMXPartials/EditText/TextParentPartial.jinja @@ -0,0 +1,25 @@ +{# def + TextType: TextType, + parent_id: str, + text: str, + can_edit: bool = True, + header_size: Literal["h1", "h2", "h3", "h4", "h5", "h6"] = "h2", + +#} +{% if can_edit %} + +{% endif %} + + +
+ {% if text %} + {{ text | from_markdown | safe }} + {% else %} +

No {{ TextType.value.name | lower | escape }}

+ {% endif %} +
diff --git a/src/valentina/webui/blueprints/campaign/forms.py b/src/valentina/webui/blueprints/campaign/forms.py index ee1624f..3dec7e8 100644 --- a/src/valentina/webui/blueprints/campaign/forms.py +++ b/src/valentina/webui/blueprints/campaign/forms.py @@ -5,28 +5,6 @@ from wtforms.validators import DataRequired, Length -class CampaignDescriptionForm(QuartForm): - """Form for editing a campaign description.""" - - title = "Campaign Overview" - - name = StringField( - "Campaign Name", - default="", - validators=[DataRequired(), Length(min=3, message="Must be at least 3 characters")], - filters=[str.strip, str.title], - ) - - description = TextAreaField( - "Description", - validators=[DataRequired(), Length(min=3, message="Must be at least 3 characters")], - description="Markdown is supported", - ) - - item_id = HiddenField() - submit = SubmitField("Submit") - - class CampaignBookForm(QuartForm): """Form for editing a campaign book.""" diff --git a/src/valentina/webui/blueprints/campaign/route.py b/src/valentina/webui/blueprints/campaign/route.py index 81a8047..020b3c1 100644 --- a/src/valentina/webui/blueprints/campaign/route.py +++ b/src/valentina/webui/blueprints/campaign/route.py @@ -7,22 +7,14 @@ from quart.views import MethodView from quart_wtf import QuartForm -from valentina.controllers import PermissionManager +from valentina.controllers import PermissionManager, total_campaign_experience from valentina.models import Campaign, CampaignBook, Statistics from valentina.webui import catalog -from valentina.webui.constants import CampaignEditableInfo, CampaignViewTab, TableType -from valentina.webui.utils import ( - create_toast, - fetch_active_campaign, - sync_channel_to_discord, - update_session, -) +from valentina.webui.constants import CampaignEditableInfo, CampaignViewTab, TableType, TextType +from valentina.webui.utils import fetch_active_campaign, sync_channel_to_discord from valentina.webui.utils.discord import post_to_audit_log -from .forms import ( - CampaignBookForm, - CampaignDescriptionForm, -) +from .forms import CampaignBookForm class CampaignView(MethodView): @@ -35,6 +27,28 @@ def __init__(self) -> None: self.permission_manager = PermissionManager(guild_id=session["GUILD_ID"]) self.can_manage_campaign = False + async def _compute_campaign_data(self, campaign: Campaign) -> dict: + """Compute the campaign data for display in the overview tab. + + Args: + campaign (Campaign): The campaign object to compute the data for. + + Returns: + dict: The computed campaign data. + """ + books = await campaign.fetch_books() + player_characters = await campaign.fetch_player_characters() + available_xp, total_xp, cool_points = await total_campaign_experience(campaign) + return { + "available_xp": available_xp, + "total_xp": total_xp, + "cool_points": cool_points, + "num_books": len(books), + "num_player_characters": len(player_characters), + "danger": campaign.danger, + "desperation": campaign.desperation, + } + async def handle_tabs(self, campaign: Campaign) -> str: """Handle rendering of HTMX tab content for the campaign view. @@ -61,7 +75,8 @@ async def handle_tabs(self, campaign: Campaign) -> str: return catalog.render( "campaign.Overview", campaign=campaign, - CampaignEditableInfo=CampaignEditableInfo, + campaign_data=await self._compute_campaign_data(campaign), + text_type_campaign_desc=TextType.CAMPAIGN_DESCRIPTION, can_manage_campaign=self.can_manage_campaign, ) @@ -147,8 +162,9 @@ async def get(self, campaign_id: str = "") -> str: return catalog.render( "campaign.Main", campaign=campaign, + campaign_data=await self._compute_campaign_data(campaign), tabs=CampaignViewTab, - CampaignEditableInfo=CampaignEditableInfo, + text_type_campaign_desc=TextType.CAMPAIGN_DESCRIPTION, can_manage_campaign=self.can_manage_campaign, error_msg=request.args.get("error_msg", ""), success_msg=request.args.get("success_msg", ""), @@ -165,16 +181,11 @@ class CampaignEditItem(MethodView): def __init__(self, edit_type: CampaignEditableInfo) -> None: self.edit_type = edit_type - async def _build_form(self, campaign: Campaign) -> QuartForm: + async def _build_form(self) -> QuartForm: """Build the form for the campaign item.""" data = {} match self.edit_type: - case CampaignEditableInfo.DESCRIPTION: - data["name"] = campaign.name - data["description"] = campaign.description - return await CampaignDescriptionForm().create_form(data=data) - case CampaignEditableInfo.BOOK: if request.args.get("book_id"): existing_book = await CampaignBook.get(request.args.get("book_id")) @@ -210,7 +221,7 @@ async def _delete_campaign_book(self, campaign: Campaign) -> str: async def _post_campaign_book(self, campaign: Campaign) -> tuple[bool, str, QuartForm]: """Process the campaign book form.""" - form = await self._build_form(campaign) + form = await self._build_form() if await form.validate_on_submit(): if form.data.get("book_id"): @@ -249,23 +260,6 @@ async def _post_campaign_book(self, campaign: Campaign) -> tuple[bool, str, Quar return False, "", form - async def _post_campaign_description(self, campaign: Campaign) -> tuple[bool, str, QuartForm]: - """Process the campaign description form.""" - form = await self._build_form(campaign) - if await form.validate_on_submit(): - do_update_session = campaign.name.strip().lower() != form.name.data.strip().lower() - campaign.name = form.name.data.strip() - campaign.description = form.description.data.strip() - await campaign.save() - - if do_update_session: - await sync_channel_to_discord(obj=campaign, update_type="update") - await update_session() - - return True, "Campaign updated", None - - return False, "", form - async def get(self, campaign_id: str = "") -> str: """Handle GET requests for editing a campaign item.""" campaign = await fetch_active_campaign(campaign_id) @@ -273,7 +267,7 @@ async def get(self, campaign_id: str = "") -> str: return catalog.render( "campaign.FormPartial", campaign=campaign, - form=await self._build_form(campaign), + form=await self._build_form(), join_label=False, floating_label=True, post_url=url_for(self.edit_type.value.route, campaign_id=campaign_id), @@ -286,9 +280,6 @@ async def post(self, campaign_id: str = "") -> Response | str: campaign = await fetch_active_campaign(campaign_id, fetch_links=True) match self.edit_type: - case CampaignEditableInfo.DESCRIPTION: - form_is_processed, msg, form = await self._post_campaign_description(campaign) - case CampaignEditableInfo.BOOK: form_is_processed, msg, form = await self._post_campaign_book(campaign) @@ -315,14 +306,9 @@ async def delete(self, campaign_id: str = "") -> str: campaign = await fetch_active_campaign(campaign_id, fetch_links=True) match self.edit_type: - case CampaignEditableInfo.DESCRIPTION: - pass # Not implemented - case CampaignEditableInfo.BOOK: msg = await self._delete_campaign_book(campaign) return f'' case _: assert_never(self.edit_type) - - return create_toast(msg, level="SUCCESS") diff --git a/src/valentina/webui/blueprints/campaign/templates/campaign/Books.jinja b/src/valentina/webui/blueprints/campaign/templates/campaign/Books.jinja index ad927c4..68a9ea2 100644 --- a/src/valentina/webui/blueprints/campaign/templates/campaign/Books.jinja +++ b/src/valentina/webui/blueprints/campaign/templates/campaign/Books.jinja @@ -12,14 +12,15 @@ hx-get="{{ url_for(CampaignEditableInfo.BOOK.value.route, campaign_id=campaign.id) }}" hx-trigger="click" hx-target="#{{ CampaignEditableInfo.BOOK.value.div_id }}" - hx-swap="innerHTML">Add Book + hx-swap="innerHTML swap:1s">Add Book {% endif %} -

Books & Chapters

-
+ + {# We need the three divs to target HTMX partials above the entire accordion display #} -
+
+ {% for book in books | sort(attribute="number") %} @@ -43,8 +44,8 @@ hx-swap="innerHTML">Delete
{% endif %} -

{{ book.number }}. {{ book.name | escape }}

-
+ +
{{ book.description_long | from_markdown | safe }}
diff --git a/src/valentina/webui/blueprints/campaign/templates/campaign/Characters.jinja b/src/valentina/webui/blueprints/campaign/templates/campaign/Characters.jinja index bd79168..8549b2f 100644 --- a/src/valentina/webui/blueprints/campaign/templates/campaign/Characters.jinja +++ b/src/valentina/webui/blueprints/campaign/templates/campaign/Characters.jinja @@ -8,8 +8,9 @@ Add a Character -

Player Characters

-
+ + + {% if characters %}
{%- for column in (characters | sort(attribute="full_name") | slice(2)) %} diff --git a/src/valentina/webui/blueprints/campaign/templates/campaign/Main.jinja b/src/valentina/webui/blueprints/campaign/templates/campaign/Main.jinja index 8f0b7ba..c3eebea 100644 --- a/src/valentina/webui/blueprints/campaign/templates/campaign/Main.jinja +++ b/src/valentina/webui/blueprints/campaign/templates/campaign/Main.jinja @@ -1,17 +1,18 @@ {# def campaign: Campaign, tabs: CampaignViewTab, - CampaignEditableInfo: CampaignEditableInfo, + text_type_campaign_desc: TextType, can_manage_campaign: bool = False, + campaign_data: dict, #} {{ campaign.name }} {% include "campaign/tabs.html" %} -
+
{# Include the default jinjax template include here for initial page load #} - +
diff --git a/src/valentina/webui/blueprints/campaign/templates/campaign/Overview.jinja b/src/valentina/webui/blueprints/campaign/templates/campaign/Overview.jinja index 0c816d3..edcd83e 100644 --- a/src/valentina/webui/blueprints/campaign/templates/campaign/Overview.jinja +++ b/src/valentina/webui/blueprints/campaign/templates/campaign/Overview.jinja @@ -1,41 +1,69 @@ {# def campaign: Campaign, - CampaignEditableInfo: CampaignEditableInfo, can_manage_campaign: bool = False, + text_type_campaign_desc: TextType, + campaign_data: dict, #} -{% if can_manage_campaign %} - -{% endif %} -

Campaign Overview

-
+{% set danger_bg_color = "bg-danger" if campaign.danger > 3 else "bg-danger-subtle" if campaign.danger > 0 else "bg-success-subtle" %} -
-
- - - - - - - - - -
Danger{{ campaign.danger }}
Desperation{{ campaign.desperation }}
+{% set desperation_bg_color = "bg-danger" if campaign.desperation > 3 else "bg-danger-subtle" if campaign.desperation > 0 else "bg-success-subtle" %} + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Desperation Level: + {{ campaign.desperation }}
+ Danger Level: + {{ campaign.danger }}
+ Num Books: + {{ campaign_data.num_books }}
+ Num Player Characters: + {{ campaign_data.num_player_characters }}
+ Total Available XP: + {{ campaign_data.available_xp }}
+ Total XP Earned: + {{ campaign_data.total_xp }}
+ Total Cool Points Earned: + {{ campaign_data.cool_points }}
+ +
+
-
- {% if campaign.description %} - {{ campaign.description | from_markdown }} - {% else %} -

- No description yet. - {% if can_manage_campaign %}Click the edit button to add one.{% endif %} -

- {% endif %} +
+
diff --git a/src/valentina/webui/blueprints/character_edit/route_info.py b/src/valentina/webui/blueprints/character_edit/route_info.py index ce93d5d..4737dc1 100644 --- a/src/valentina/webui/blueprints/character_edit/route_info.py +++ b/src/valentina/webui/blueprints/character_edit/route_info.py @@ -4,7 +4,7 @@ from uuid import UUID from flask_discord import requires_authorization -from quart import abort, request, session, url_for +from quart import abort, request, url_for from quart.views import MethodView from quart_wtf import QuartForm from wtforms import ( @@ -15,24 +15,13 @@ ) from wtforms.validators import DataRequired, Length -from valentina.models import Character, CharacterSheetSection, Note +from valentina.models import Character, CharacterSheetSection from valentina.webui import catalog from valentina.webui.constants import CharacterEditableInfo from valentina.webui.utils import create_toast, fetch_active_character, sync_channel_to_discord from valentina.webui.utils.discord import post_to_audit_log -class BioForm(QuartForm): - """A form for editing the character biography.""" - - bio = TextAreaField( - "Biography", - description="Write a biography for the character. Markdown is supported.", - ) - character_id = HiddenField() - submit = SubmitField("Submit") - - class CustomSectionForm(QuartForm): """Form for a custom section.""" @@ -79,11 +68,6 @@ async def _build_form(self, character: Character) -> QuartForm: return await CustomSectionForm().create_form(data=data) - case CharacterEditableInfo.BIOGRAPHY: - data["bio"] = character.bio - data["character_id"] = str(character.id) - return await BioForm().create_form(data=data) - case CharacterEditableInfo.DELETE: return await DeleteCharacterForm().create_form() @@ -109,37 +93,6 @@ async def _delete_custom_section(self, character: Character) -> str: return "Custom section deleted" - async def _delete_note(self, character: Character) -> str: - """Delete the note.""" - note_id = request.args.get("note_id", None) - if not note_id: - abort(400) - - existing_note = await Note.get(note_id) - for note in character.notes: - if note == existing_note: - character.notes.remove(note) - break - - await existing_note.delete() - - await post_to_audit_log( - msg=f"Character {character.name} note `{existing_note.text}` deleted", - view=self.__class__.__name__, - ) - await character.save() - - return "Note deleted" - - async def _post_biography(self, character: Character) -> tuple[bool, str, QuartForm]: - """Process the biography form.""" - form = await self._build_form(character) - if await form.validate_on_submit(): - character.bio = form.data["bio"] - await character.save() - return True, "Biography updated", None - return False, "", form - async def _post_custom_section(self, character: Character) -> tuple[bool, str, QuartForm]: """Process the custom section form.""" form = await self._build_form(character) @@ -195,38 +148,6 @@ async def _post_delete_character(self, character: Character) -> tuple[bool, str, return True, "Character deleted", None return False, "", form - async def _post_note(self, character: Character) -> tuple[bool, str, QuartForm]: - """Process the note form.""" - form = await self._build_form(character) - - if await form.validate_on_submit(): - if not form.data.get("note_id"): - new_note = Note( - text=form.data["text"].strip(), - parent_id=str(character.id), - created_by=session["USER_ID"], - guild_id=int(session["GUILD_ID"]), - ) - await new_note.save() - character.notes.append(new_note) - await character.save() - msg = "Note Added" - else: - existing_note = await Note.get(form.data["note_id"]) - existing_note.text = form.data["text"] - existing_note.guild_id = int(session["GUILD_ID"]) - await existing_note.save() - msg = "Note Updated" - - await post_to_audit_log( - msg=f"Character {character.name} - {msg}", - view=self.__class__.__name__, - ) - - return True, msg, None - - return False, "", form - async def get(self, character_id: str) -> str: """Render the form.""" character = await fetch_active_character(character_id, fetch_links=False) @@ -249,8 +170,7 @@ async def post(self, character_id: str) -> str: match self.edit_type: case CharacterEditableInfo.CUSTOM_SECTION: form_is_processed, msg, form = await self._post_custom_section(character) - case CharacterEditableInfo.BIOGRAPHY: - form_is_processed, msg, form = await self._post_biography(character) + case CharacterEditableInfo.DELETE: form_is_processed, msg, form = await self._post_delete_character(character) # If the form is processed, redirect to the homepage with a success message b/c the character is deleted. @@ -283,7 +203,7 @@ async def delete(self, character_id: str) -> str: match self.edit_type: case CharacterEditableInfo.CUSTOM_SECTION: msg = await self._delete_custom_section(character) - case CharacterEditableInfo.BIOGRAPHY | CharacterEditableInfo.DELETE: + case CharacterEditableInfo.DELETE: pass # Not implemented case _: assert_never(self.edit_type) diff --git a/src/valentina/webui/blueprints/character_view/route.py b/src/valentina/webui/blueprints/character_view/route.py index 386464d..817534f 100644 --- a/src/valentina/webui/blueprints/character_view/route.py +++ b/src/valentina/webui/blueprints/character_view/route.py @@ -16,7 +16,7 @@ User, ) from valentina.webui import catalog -from valentina.webui.constants import CharacterEditableInfo, CharacterViewTab, TableType +from valentina.webui.constants import CharacterEditableInfo, CharacterViewTab, TableType, TextType from valentina.webui.utils import fetch_active_campaign, fetch_user, is_storyteller from valentina.webui.utils.forms import ValentinaForm @@ -111,7 +111,9 @@ async def _handle_tabs(self, character: Character, character_owner: User) -> str return catalog.render( "character_view.Biography", character=character, - CharacterEditableInfo=CharacterEditableInfo, + text_type_bio=TextType.BIOGRAPHY, + can_edit=session["IS_STORYTELLER"] + or session["USER_ID"] == character.user_owner, ) case CharacterViewTab.INFO: return catalog.render( diff --git a/src/valentina/webui/blueprints/character_view/templates/character_view/Biography.jinja b/src/valentina/webui/blueprints/character_view/templates/character_view/Biography.jinja index 6648184..e841188 100644 --- a/src/valentina/webui/blueprints/character_view/templates/character_view/Biography.jinja +++ b/src/valentina/webui/blueprints/character_view/templates/character_view/Biography.jinja @@ -1,22 +1,7 @@ {# def character: Character, - CharacterEditableInfo: CharacterEditableInfo, + text_type_bio: TextType, + can_edit: bool, #} -{% if session["IS_STORYTELLER"] or session["USER_ID"] == character.user_owner %} - -{% endif %} -

Biography

-
-
- {% if character.bio %} -
{{ character.bio | from_markdown | safe }}
- {% else %} -
-

No biography

-
- {% endif %} -
+ + diff --git a/src/valentina/webui/blueprints/character_view/templates/character_view/Info.jinja b/src/valentina/webui/blueprints/character_view/templates/character_view/Info.jinja index 31c760a..a48e2b2 100644 --- a/src/valentina/webui/blueprints/character_view/templates/character_view/Info.jinja +++ b/src/valentina/webui/blueprints/character_view/templates/character_view/Info.jinja @@ -16,14 +16,13 @@ hx-get="{{ url_for(CharacterEditableInfo.CUSTOM_SECTION.value.route, character_id=character.id) }}" hx-trigger="click" hx-target="#{{ CharacterEditableInfo.CUSTOM_SECTION.value.div_id }}" - hx-swap="innerHTML">Create New Section + hx-swap="innerHTML swap:1s">Create New Section {% endif %} -

- Sheet Sections  Information about the character. Can have multiple paragraphs and use markdown for formatting -

-
-
+ + +
{% if character.sheet_sections %}
{% for section in character.sheet_sections | sort(attribute="title") %} @@ -43,13 +42,11 @@ hx-swap="innerHTML" class="btn btn-sm btn-primary">Edit + hx-delete="{{ url_for(CharacterEditableInfo.CUSTOM_SECTION.value.route, character_id=character.id, uuid=section.uuid) }}" + hx-confirm="Are you sure?" + hx-target="closest #custom-section-item" + hx-swap="outerHTML swap:1s" + hx-trigger="click">Delete
{% endif %}
diff --git a/src/valentina/webui/blueprints/homepage/templates/homepage/Loggedin.jinja b/src/valentina/webui/blueprints/homepage/templates/homepage/Loggedin.jinja index 6884666..fa29663 100644 --- a/src/valentina/webui/blueprints/homepage/templates/homepage/Loggedin.jinja +++ b/src/valentina/webui/blueprints/homepage/templates/homepage/Loggedin.jinja @@ -11,8 +11,8 @@
-

{{ session["GUILD_NAME"] | title }} Info

-
+ +
@@ -47,8 +47,8 @@
-

Quick Links

-
+ +
Create A Character @@ -67,9 +67,7 @@
- -

Your Characters

-
+
@@ -92,8 +90,7 @@
-

Other Players' Characters

-
+
@@ -119,8 +116,7 @@
-

Campaigns

-
+
{% for campaign_name, campaign_id in session["GUILD_CAMPAIGNS"].items() %}
-

User Info

-
+
@@ -69,8 +68,8 @@
-

Experience

-
+ +
@@ -121,8 +120,7 @@ Create Character {%- endif %} -

{{ user.name }}'s characters

-
+
{%- if user.characters %} diff --git a/src/valentina/webui/constants.py b/src/valentina/webui/constants.py index 193a8a0..ab9a2a8 100644 --- a/src/valentina/webui/constants.py +++ b/src/valentina/webui/constants.py @@ -6,12 +6,12 @@ @dataclass -class TableItem: - """Class for an item in the table.""" +class EditableItem: + """Class for an item that can be edited.""" name: str route: str - sort_attribute: str + sort_attribute: str = "" description: str = "" table_headers: list[str] = field(default_factory=list) @@ -22,42 +22,51 @@ def route_suffix(self) -> str: return self.route.split(".")[1] +class TextType(Enum): + """Enum for the type of text.""" + + BIOGRAPHY = EditableItem(name="Biography", route="partials.text_biography") + CAMPAIGN_DESCRIPTION = EditableItem( + name="Description", route="partials.text_campaign_description" + ) + + class TableType(Enum): """Enum for the type of table.""" - NOTE = TableItem( + NOTE = EditableItem( name="Notes", route="partials.table_note", description="Jot down quick and dirty notes", sort_attribute="text", ) - INVENTORYITEM = TableItem( + INVENTORYITEM = EditableItem( name="Inventory Items", route="partials.table_inventory", description="Items the character owns", sort_attribute="type,name", table_headers=["Item", "Category", "Description"], ) - NPC = TableItem( + NPC = EditableItem( name="NPCs", route="partials.table_npc", description="Quick reference to campaign NPCs", sort_attribute="name", table_headers=["Name", "Class", "Description"], ) - MACRO = TableItem( + MACRO = EditableItem( name="Macros", route="partials.table_macro", description="Speed dice rolling by preselecting traits", table_headers=["Name", "Abbreviation", "Description", "Trait1", "Trait2"], sort_attribute="name", ) - CHAPTER = TableItem( + CHAPTER = EditableItem( name="Chapters", route="partials.table_chapter", description="Chapters of the campaign book", table_headers=["#", "Chapter", "Description"], - sort_attribute="name", + sort_attribute="number", ) @@ -110,12 +119,6 @@ def get_member_by_value(cls, value: str) -> "CampaignViewTab": class CampaignEditableInfo(Enum): """Enum for the editable info of a campaign. Used to build blueprint routes and template variables.""" - DESCRIPTION = EditItem( - name="description", - route="campaign.edit_description", - div_id="description", - tab=CampaignViewTab.OVERVIEW, - ) BOOK = EditItem( name="book", route="campaign.edit_book", div_id="book", tab=CampaignViewTab.BOOKS ) @@ -130,12 +133,7 @@ class CharacterEditableInfo(Enum): div_id="customsection", tab=CharacterViewTab.INFO, ) - BIOGRAPHY = EditItem( - name="biography", - route="character_edit.edit_biography", - div_id="biography", - tab=CharacterViewTab.BIOGRAPHY, - ) + DELETE = EditItem( name="delete", route="character_edit.delete_character", diff --git a/src/valentina/webui/shared/global/Statistics.jinja b/src/valentina/webui/shared/global/Statistics.jinja index ded64b9..5cb127f 100644 --- a/src/valentina/webui/shared/global/Statistics.jinja +++ b/src/valentina/webui/shared/global/Statistics.jinja @@ -1,16 +1,10 @@ {# def statistics:dict[str,str], - with_title:bool=True, - title:str="Statistics", - header_size:str = "h2", + with_title:bool = True, + title:str = "Statistics", + header_size:Literal["h1", "h2", "h3", "h4", "h5", "h6"] = "h2", #} -{% if with_title %} - - {% set header_size = header_size if header_size in ["h2", "h3", "h4", "h5", "h6"] else "h2" %} - - <{{ header_size }}>{{ title }} -
-{% endif %} +{% if with_title %}{% endif %}
diff --git a/src/valentina/webui/shared/global/Subtitle.jinja b/src/valentina/webui/shared/global/Subtitle.jinja new file mode 100644 index 0000000..b656ce4 --- /dev/null +++ b/src/valentina/webui/shared/global/Subtitle.jinja @@ -0,0 +1,18 @@ +{# def + header_size: Literal["h1", "h2", "h3", "h4", "h5", "h6"] = "h2", + title: str, + subtitle: str = "", +#} + +{% set border_size = "border-2" if header_size in ["h1", "h2", "h3"] else "border-1" %} + +<{{ header_size }}> +{{ title }} +{%- if subtitle %} +    + {{ subtitle }} + +{%- endif %} + + +
diff --git a/src/valentina/webui/utils/__init__.py b/src/valentina/webui/utils/__init__.py index a6d6eeb..add3265 100644 --- a/src/valentina/webui/utils/__init__.py +++ b/src/valentina/webui/utils/__init__.py @@ -12,6 +12,7 @@ sync_channel_to_discord, update_session, ) +from .jinjax import from_markdown, from_markdown_no_p from .responses import create_toast __all__ = [ @@ -23,6 +24,8 @@ "fetch_guild", "fetch_user_characters", "fetch_user", + "from_markdown_no_p", + "from_markdown", "is_storyteller", "sync_channel_to_discord", "update_session", diff --git a/tests/factories.py b/tests/factories.py index 7bef78e..5b09afa 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -172,10 +172,6 @@ def books(cls) -> list: def notes(cls) -> list: return [] - # @classmethod - # def characters(cls) -> list: - # return [] - @register_fixture class UserFactory(BeanieDocumentFactory[User]): @@ -300,6 +296,10 @@ class CharacterFactory(BeanieDocumentFactory[Character]): __randomize_collection_length__ = True __set_as_default_factory_for_type__ = True + @classmethod + def bio(cls) -> str | None: + return cls.__faker__.paragraph(nb_sentences=3) + @classmethod def channel(cls) -> GuildChannels: return CHANNEL_CHARACTER_ID diff --git a/tests/webui/test_htmx_partials.py b/tests/webui/test_htmx_partials.py index 0b2c0b0..d5b6f21 100644 --- a/tests/webui/test_htmx_partials.py +++ b/tests/webui/test_htmx_partials.py @@ -1,8 +1,6 @@ # type: ignore """Test the HTMX partials blueprint.""" -from typing import Any - import pytest from tests.factories import * @@ -15,11 +13,13 @@ Note, User, ) -from valentina.webui.constants import TableType +from valentina.webui.constants import TableType, TextType +from valentina.webui.utils import from_markdown @pytest.fixture async def test_setup( + debug, character_factory, mock_session, test_client, @@ -27,12 +27,19 @@ async def test_setup( guild_factory, campaign_factory, book_factory, -) -> tuple[Character, Campaign, User, CampaignBook, "TestClientProtocol"]: # noqa: F405 +) -> tuple[Character, Campaign, User, CampaignBook, "TestClientProtocol"]: """Create base test environment.""" guild = await guild_factory.build().insert() user = await user_factory.build(macros=[]).insert() - character = await character_factory.build(guild=guild.id, inventory=[], notes=[]).insert() - campaign = await campaign_factory.build(guild=guild.id, books=[], npcs=[], notes=[]).insert() + character = await character_factory.build( + guild=guild.id, + inventory=[], + notes=[], + bio=None, + ).insert() + campaign = await campaign_factory.build( + guild=guild.id, books=[], npcs=[], notes=[], description=None + ).insert() book = await book_factory.build(campaign=str(campaign.id), chapters=[], notes=[]).insert() campaign.books.append(book) await campaign.save() @@ -56,14 +63,13 @@ async def test_setup( ], ) @pytest.mark.drop_db -async def test_form_load(test_setup, table_type, id_field, parent_id_source): +async def test_edit_table_form_load(test_setup, table_type, id_field, parent_id_source): """Test form load returns empty form.""" character, campaign, user, book, test_client = test_setup - # Dynamically get parent_id from either book or character based on table type - # This allows us to test both chapter forms (which need book.id) and other forms (which need character.id) - parent_id = locals()[parent_id_source].id - # parent_id = campaign.id if table_type == TableType.NPC else parent_id + # Map source names to their corresponding objects + parent_id_map = {"character": character, "campaign": campaign, "book": book, "user": user} + parent_id = parent_id_map[parent_id_source].id url = f"/partials/table/{table_type.value.route_suffix}?parent_id={parent_id}" @@ -78,15 +84,15 @@ async def test_form_load(test_setup, table_type, id_field, parent_id_source): @pytest.mark.parametrize( ( - "table_type", - "model", - "id_field", # The field on the object that is its unique identifier. typically "id" - "parent_object", - "parent_id_field", # Field on the target object that references it's parent id. - "parent_link_field", # Field on the parent object that is a list of target objects. - "test_data", - "update_data", - "parent_selector", # New parameter to determine parent + "table_type", # TableType enum value + "model_class", # Database model for the item being edited + "primary_key_field", # Name of the primary key field in the model, typically "id" or "uuid" + "parent_model", # Database model that owns/contains this item + "parent_key_field", # Field on the parent object that contains the parent's primary key + "parent_collection", # Name of collection field in parent that stores these items + "creation_data", # Test data for creating new items + "update_data", # Test data for updating existing items + "get_parent_instance", # Function to get the parent object for testing ), [ ( @@ -98,7 +104,7 @@ async def test_form_load(test_setup, table_type, id_field, parent_id_source): "notes", {"text": "test note"}, {"text": "updated note"}, - lambda character, campaign, user, book: character, # noqa: ARG005 + lambda character, campaign, user, book: character, ), ( TableType.NOTE, @@ -109,7 +115,7 @@ async def test_form_load(test_setup, table_type, id_field, parent_id_source): "notes", {"text": "test note"}, {"text": "updated note"}, - lambda character, campaign, user, book: book, # noqa: ARG005 + lambda character, campaign, user, book: book, ), ( TableType.NOTE, @@ -120,7 +126,7 @@ async def test_form_load(test_setup, table_type, id_field, parent_id_source): "notes", {"text": "test note"}, {"text": "updated note"}, - lambda character, campaign, user, book: campaign, # noqa: ARG005 + lambda character, campaign, user, book: campaign, ), ( TableType.INVENTORYITEM, @@ -139,7 +145,7 @@ async def test_form_load(test_setup, table_type, id_field, parent_id_source): "description": "updated description", "type": "CONSUMABLE", }, - lambda character, campaign, user, book: character, # noqa: ARG005 + lambda character, campaign, user, book: character, ), ( TableType.CHAPTER, @@ -158,7 +164,7 @@ async def test_form_load(test_setup, table_type, id_field, parent_id_source): "description_short": "test short description updated", "description_long": "test long description updated", }, - lambda character, campaign, user, book: book, # noqa: ARG005 + lambda character, campaign, user, book: book, ), ( TableType.NPC, @@ -177,7 +183,7 @@ async def test_form_load(test_setup, table_type, id_field, parent_id_source): "npc_class": "test class updated", "description": "test description updated", }, - lambda character, campaign, user, book: campaign, # noqa: ARG005 + lambda character, campaign, user, book: campaign, ), ( TableType.MACRO, @@ -200,23 +206,23 @@ async def test_form_load(test_setup, table_type, id_field, parent_id_source): "trait_one": "Strength", "trait_two": "Dexterity", }, - lambda character, campaign, user, book: user, # noqa: ARG005 + lambda character, campaign, user, book: user, ), ], ) @pytest.mark.drop_db -async def test_crud_operations( +async def test_edit_table_crud_operations( debug, test_setup, table_type, - model, - id_field, - parent_object, - parent_id_field, - parent_link_field, - test_data, + model_class, + primary_key_field, + parent_model, + parent_key_field, + parent_collection, + creation_data, update_data, - parent_selector, + get_parent_instance, mocker, ): """Test create, update, and delete operations.""" @@ -226,12 +232,17 @@ async def test_crud_operations( mocker.patch( "valentina.webui.blueprints.HTMXPartials.route.post_to_audit_log", return_value=None ) - + mocker.patch( + "valentina.webui.blueprints.HTMXPartials.route.sync_channel_to_discord", return_value=None + ) + mocker.patch("valentina.webui.blueprints.HTMXPartials.route.update_session", return_value=None) # Get appropriate parent based on table type - parent = parent_selector(character, campaign, user, book) + parent = get_parent_instance(character, campaign, user, book) # Create - create_data = {**test_data, parent_id_field: str(parent.id)} if parent_id_field else test_data + create_data = ( + {**creation_data, parent_key_field: str(parent.id)} if parent_key_field else creation_data + ) response = await test_client.put( f"{base_url}?parent_id={parent.id}", json=create_data, follow_redirects=True @@ -239,13 +250,13 @@ async def test_crud_operations( assert response.status_code == 200 # Verify creation - parent = await parent_object.get(parent.id, fetch_links=True) + parent = await parent_model.get(parent.id, fetch_links=True) - items = getattr(parent, parent_link_field) + items = getattr(parent, parent_collection) assert len(items) == 1 item = items[0] - item_id = getattr(item, id_field) + item_id = getattr(item, primary_key_field) # Get the created item's table row partial response = await test_client.get( @@ -268,10 +279,118 @@ async def test_crud_operations( ) assert response.status_code == 200 - if model: - assert await model.get(item_id) is None + if model_class: + assert await model_class.get(item_id) is None # Verify deletion - parent = await parent_object.get(parent.id, fetch_links=True) - items = getattr(parent, parent_link_field) + parent = await parent_model.get(parent.id, fetch_links=True) + items = getattr(parent, parent_collection) assert len(items) == 0 + + +@pytest.mark.parametrize( + ("text_type", "get_parent_instance"), + [ + (TextType.BIOGRAPHY, lambda character, campaign, user, book: character), + (TextType.CAMPAIGN_DESCRIPTION, lambda character, campaign, user, book: campaign), + ], +) +@pytest.mark.drop_db +async def test_edit_text_form_load(debug, test_setup, text_type, get_parent_instance): + """Test form load returns empty form.""" + character, campaign, user, book, test_client = test_setup + + # Get appropriate parent based on table type + parent = get_parent_instance(character, campaign, user, book) + + url = f"/partials/text/{text_type.value.route_suffix}?parent_id={parent.id}" + + response = await test_client.put(url) + returned_text = await response.get_data(as_text=True) + + assert response.status_code == 200 + assert f'value="{parent.id}">' in returned_text + if text_type.value.description: + assert f"{text_type.value.description}" in returned_text + + +@pytest.mark.parametrize( + ( + "text_type", + "parent_model", + "parent_field", + "get_parent_instance", + "creation_data", + "update_data", + ), + [ + ( + TextType.BIOGRAPHY, + Character, + "bio", + lambda character, campaign, user, book: character, + {"bio": "A communi observantia non est recedendum."}, + {"bio": "Nec dubitamus multa iter quae et nos invenerat."}, + ), + ( + TextType.CAMPAIGN_DESCRIPTION, + Campaign, + "description", + lambda character, campaign, user, book: campaign, + {"campaign_description": "A communi observantia non est recedendum."}, + {"campaign_description": "Nec dubitamus multa iter quae et nos invenerat."}, + ), + ], +) +@pytest.mark.drop_db +async def test_edit_text_form_crud_operations( + debug, + mocker, + test_setup, + text_type, + parent_model, + parent_field, + get_parent_instance, + creation_data, + update_data, +): + """Test create, update, and delete operations.""" + mocker.patch( + "valentina.webui.blueprints.HTMXPartials.route.post_to_audit_log", return_value=None + ) + mocker.patch( + "valentina.webui.blueprints.HTMXPartials.route.sync_channel_to_discord", return_value=None + ) + mocker.patch("valentina.webui.blueprints.HTMXPartials.route.update_session", return_value=None) + + character, campaign, user, book, test_client = test_setup + parent = get_parent_instance(character, campaign, user, book) + + # Create + response = await test_client.put( + f"/partials/text/{text_type.value.route_suffix}?parent_id={parent.id}", + json=creation_data, + follow_redirects=True, + ) + assert response.status_code == 200 + parent = await parent_model.get(parent.id) + assert getattr(parent, parent_field) == creation_data[next(iter(creation_data.keys()))] + + # Get the created item's table text partial + response = await test_client.get( + f"/partials/text/{text_type.value.route_suffix}?parent_id={parent.id}", + follow_redirects=True, + ) + response_text = await response.get_data(as_text=True) + assert response.status_code == 200 + assert response_text == from_markdown(creation_data[next(iter(creation_data.keys()))]) + + # Update + response = await test_client.post( + f"/partials/text/{text_type.value.route_suffix}?parent_id={parent.id}", + json=update_data, + follow_redirects=True, + ) + assert response.status_code == 200 + parent = await parent_model.get(parent.id) + assert getattr(parent, parent_field) == update_data[next(iter(update_data.keys()))] diff --git a/uv.lock b/uv.lock index 9d1a481..81abee0 100644 --- a/uv.lock +++ b/uv.lock @@ -170,30 +170,30 @@ wheels = [ [[package]] name = "boto3" -version = "1.35.52" +version = "1.35.56" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bc/79/288f478a66e746e025e297fd8fa8e953241ac16e7ce93741274fc699714f/boto3-1.35.52.tar.gz", hash = "sha256:68299da8ab2bb37cc843d61b9f4c1c9367438406cfd65a8f593afc7b3bfe226d", size = 110977 } +sdist = { url = "https://files.pythonhosted.org/packages/45/a7/1fc5a2567f1d42f7fcc903381b5fc550b4bd6fcfb82a1a50b72cae250e1f/boto3-1.35.56.tar.gz", hash = "sha256:6fcc510a4e747e85f84046b0ba0e5b178e89ba0f8ac9e2b6ebb4cc925c68c23b", size = 111003 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/85/a82a867f27fefb22350e4c3ddc83958b9660229c49f979443ab59a873335/boto3-1.35.52-py3-none-any.whl", hash = "sha256:ec0e797441db56af63b1150bba49f114b0f885f5d76c3b6dc18075f73030d2bb", size = 139158 }, + { url = "https://files.pythonhosted.org/packages/7c/87/0f93b9f91afa0f11d98dc8ee9c0c6c56de0f22c63a20eb10096d55b12beb/boto3-1.35.56-py3-none-any.whl", hash = "sha256:d04608cf40f429025eb66b52b835bdc333436022918788853ed0bbbba6dd2f09", size = 139179 }, ] [[package]] name = "botocore" -version = "1.35.52" +version = "1.35.56" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/d4/cd01ee85cc2c4cff999599bad0de68d8fa04593ab720917c450e8631e29a/botocore-1.35.52.tar.gz", hash = "sha256:1fe7485ea13d638b089103addd818c12984ff1e4d208de15f180b1e25ad944c5", size = 12914350 } +sdist = { url = "https://files.pythonhosted.org/packages/8d/e5/222add2085839bdf738c8d039fc1773e409c0dc5ed085774f4645e9efbc8/botocore-1.35.56.tar.gz", hash = "sha256:8a9e752c8e87a423575ac528340a35d4318b8576ae4c6e0acfe5a3867f6bbccf", size = 12946406 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/28/a181e924c1e6179d9aa277bb6e96c3f32016a942aad12f84981a4058b094/botocore-1.35.52-py3-none-any.whl", hash = "sha256:cdbb5e43c9c3a977763e2a10d3b8b9c405d51279f9fcfd4ca4800763b22acba5", size = 12702991 }, + { url = "https://files.pythonhosted.org/packages/5d/6f/8a4987a28c2482eb0dd9bc05211e1e1f907759c9f4b7fbc2e80467ce3f75/botocore-1.35.56-py3-none-any.whl", hash = "sha256:4be97f7bc1fbf33ad71ee1b678cea0ecf9638e61d5f566a46f261cde969dd690", size = 12733479 }, ] [[package]] @@ -435,14 +435,12 @@ wheels = [ [[package]] name = "djlint" -version = "1.35.3" +version = "1.36.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "colorama" }, { name = "cssbeautifier" }, - { name = "html-tag-names" }, - { name = "html-void-elements" }, { name = "jsbeautifier" }, { name = "json5" }, { name = "pathspec" }, @@ -450,9 +448,13 @@ dependencies = [ { name = "regex" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a7/e6/4b633476fa2ebbd09ade007f052617227fb0c250e7f708d6e56e9f6c4b85/djlint-1.35.3.tar.gz", hash = "sha256:780ea3e25662fca89033fa96ecf656099954d6f81dce039eac90f4bba3cbe850", size = 45902 } +sdist = { url = "https://files.pythonhosted.org/packages/eb/ff/c3a7ee0c22703c03132737d1021b3c75fe1a8cfc852ce03fe74842c12966/djlint-1.36.1.tar.gz", hash = "sha256:f7260637ed72c270fa6dd4a87628e1a21c49b24a46df52e4e26f44d4934fb97c", size = 47660 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/f9/1788c861b4f8aea24bc8f216e57877354f138a3730cafb407f8ec4cc6cd2/djlint-1.35.3-py3-none-any.whl", hash = "sha256:bf2f23798909f9c5a110925c369538383de0141f9a2be37ee0d26422d41b7543", size = 50744 }, + { url = "https://files.pythonhosted.org/packages/62/51/af84a647c47437ed81a14f1f549db978e4ed79552d67153467119010850d/djlint-1.36.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7f31646435385eec1d4b03dad7bebb5e4078d9893c60d490a685535bd6303c83", size = 353871 }, + { url = "https://files.pythonhosted.org/packages/ba/00/04f1c3a618d8fe4651da2e18376e0e473867344833747d54b14be1a614de/djlint-1.36.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4399477ac51f9c8147eedbef70aa8465eccba6759d875d1feec6782744aa168a", size = 322175 }, + { url = "https://files.pythonhosted.org/packages/76/16/76de5e6228153dd58b8b16bb4435611a3eb86da22d1fffbc39b95b10b4d7/djlint-1.36.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f08c217b17d3ae3c0e3b5fff57fb708029cceda6e232f5a54ff1b3aeb43a7540", size = 408158 }, + { url = "https://files.pythonhosted.org/packages/7b/2f/9354408753840f2b81736bd4733aedc731e6e763eb93a50231f958eeb70f/djlint-1.36.1-cp313-cp313-win_amd64.whl", hash = "sha256:1577490802ca4697af3488ed13066c9214ef0f625a96aa20d4f297e37aa19303", size = 361072 }, + { url = "https://files.pythonhosted.org/packages/27/a1/4cfdd378ebe31a8897d3a052ae7127b93eb36b5d314ca6a5639ab0c25c56/djlint-1.36.1-py3-none-any.whl", hash = "sha256:950782b396dd82b74622c09d7e4c52328e56a3b03c8ac790c319708e5caa0686", size = 52118 }, ] [[package]] @@ -481,15 +483,15 @@ wheels = [ [[package]] name = "faker" -version = "30.8.1" +version = "30.8.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/4e/5da02969b0a8d9e41cdddd533844411590c679d6fcd5d78274987810f09f/faker-30.8.1.tar.gz", hash = "sha256:93e8b70813f76d05d98951154681180cb795cfbcff3eced7680d963bcc0da2a9", size = 1808667 } +sdist = { url = "https://files.pythonhosted.org/packages/c1/df/7574c0d13f0bbab725e52bec4b00783aaa14163fe9093dde11a928a4c638/faker-30.8.2.tar.gz", hash = "sha256:aa31b52cdae3673d6a78b4857c7bcdc0e98f201a5cb77d7827fa9e6b5876da94", size = 1808329 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/96/47cb47659544bb8fa4c3c55957736d57f8176d5e2c1114e56d353ac7f646/Faker-30.8.1-py3-none-any.whl", hash = "sha256:4f7f133560b9d4d2a915581f4ba86f9a6a83421b89e911f36c4c96cff58135a5", size = 1846805 }, + { url = "https://files.pythonhosted.org/packages/64/82/f7d0c0a4ab512fd1572a315eec903d50a578c75d5aa894cf3f5cc04025e5/Faker-30.8.2-py3-none-any.whl", hash = "sha256:4a82b2908cd19f3bba1a4da2060cc4eb18a40410ccdf9350d071d79dc92fe3ce", size = 1846458 }, ] [[package]] @@ -589,24 +591,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/34/e8b383f35b77c402d28563d2b8f83159319b509bc5f760b15d60b0abf165/hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c", size = 32611 }, ] -[[package]] -name = "html-tag-names" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/7c/8c0dc3c5650036127fb4629d31cadf6cbdd57e21a77f9793fa8b2c8a3241/html-tag-names-0.1.2.tar.gz", hash = "sha256:04924aca48770f36b5a41c27e4d917062507be05118acb0ba869c97389084297", size = 15173 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/e4/242318dcaa06d8525ff72332fc15cea2cffb84c06cfc73c7da6c6b45801a/html_tag_names-0.1.2-py3-none-any.whl", hash = "sha256:eeb69ef21078486b615241f0393a72b41352c5219ee648e7c61f5632d26f0420", size = 15218 }, -] - -[[package]] -name = "html-void-elements" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/80/5c/5f17d77256bf78ca98647517fadee50575e75d812daa01352c31d89d5bf2/html-void-elements-0.1.0.tar.gz", hash = "sha256:931b88f84cd606fee0b582c28fcd00e41d7149421fb673e1e1abd2f0c4f231f0", size = 14766 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/0a/373f28a1cf37f8c9aa23c82cbcac7197ddea95c88b5a3eaa564cdb8de375/html_void_elements-0.1.0-py3-none-any.whl", hash = "sha256:784cf39db03cdeb017320d9301009f8f3480f9d7b254d0974272e80e0cb5e0d2", size = 14889 }, -] - [[package]] name = "hypercorn" version = "0.17.3" @@ -904,28 +888,30 @@ wheels = [ [[package]] name = "numpy" -version = "2.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/d1/8a730ea07f4a37d94f9172f4ce1d81064b7a64766b460378be278952de75/numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c", size = 18878063 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/72/716fa1dbe92395a9a623d5049203ff8ddb0cfce65b9df9117c3696ccc011/numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d", size = 20834690 }, - { url = "https://files.pythonhosted.org/packages/1e/fb/3e85a39511586053b5c6a59a643879e376fae22230ebfef9cfabb0e032e2/numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf", size = 13507474 }, - { url = "https://files.pythonhosted.org/packages/35/eb/5677556d9ba13436dab51e129f98d4829d95cd1b6bd0e199c14485a4bdb9/numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e", size = 5074742 }, - { url = "https://files.pythonhosted.org/packages/3e/c5/6c5ef5ba41b65a7e51bed50dbf3e1483eb578055633dd013e811a28e96a1/numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3", size = 6606787 }, - { url = "https://files.pythonhosted.org/packages/08/ac/f2f29dd4fd325b379c7dc932a0ebab22f0e031dbe80b2f6019b291a3a544/numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8", size = 13601333 }, - { url = "https://files.pythonhosted.org/packages/44/26/63f5f4e5089654dfb858f4892215ed968cd1a68e6f4a83f9961f84f855cb/numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a", size = 16038090 }, - { url = "https://files.pythonhosted.org/packages/1d/21/015e0594de9c3a8d5edd24943d2bd23f102ec71aec026083f822f86497e2/numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98", size = 16410865 }, - { url = "https://files.pythonhosted.org/packages/df/01/c1bcf9e6025d79077fbf3f3ee503b50aa7bfabfcd8f4b54f5829f4c00f3f/numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe", size = 14078077 }, - { url = "https://files.pythonhosted.org/packages/ba/06/db9d127d63bd11591770ba9f3d960f8041e0f895184b9351d4b1b5b56983/numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a", size = 6234904 }, - { url = "https://files.pythonhosted.org/packages/a9/96/9f61f8f95b6e0ea0aa08633b704c75d1882bdcb331bdf8bfd63263b25b00/numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445", size = 12561910 }, - { url = "https://files.pythonhosted.org/packages/36/b8/033f627821784a48e8f75c218033471eebbaacdd933f8979c79637a1b44b/numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5", size = 20857719 }, - { url = "https://files.pythonhosted.org/packages/96/46/af5726fde5b74ed83f2f17a73386d399319b7ed4d51279fb23b721d0816d/numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0", size = 13518826 }, - { url = "https://files.pythonhosted.org/packages/db/6e/8ce677edf36da1c4dae80afe5529f47690697eb55b4864673af260ccea7b/numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17", size = 5115036 }, - { url = "https://files.pythonhosted.org/packages/6a/ba/3cce44fb1b8438042c11847048812a776f75ee0e7070179c22e4cfbf420c/numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6", size = 6628641 }, - { url = "https://files.pythonhosted.org/packages/59/c8/e722998720ccbd35ffbcf1d1b8ed0aa2304af88d3f1c38e06ebf983599b3/numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8", size = 13574803 }, - { url = "https://files.pythonhosted.org/packages/7c/8e/fc1fdd83a55476765329ac2913321c4aed5b082a7915095628c4ca30ea72/numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35", size = 16021174 }, - { url = "https://files.pythonhosted.org/packages/2a/b6/a790742aa88067adb4bd6c89a946778c1417d4deaeafce3ca928f26d4c52/numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62", size = 16400117 }, - { url = "https://files.pythonhosted.org/packages/48/6f/129e3c17e3befe7fefdeaa6890f4c4df3f3cf0831aa053802c3862da67aa/numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a", size = 14066202 }, +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ca/1166b75c21abd1da445b97bf1fa2f14f423c6cfb4fc7c4ef31dccf9f6a94/numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", size = 20166090 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/0b/620591441457e25f3404c8057eb924d04f161244cb8a3680d529419aa86e/numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", size = 20836263 }, + { url = "https://files.pythonhosted.org/packages/45/e1/210b2d8b31ce9119145433e6ea78046e30771de3fe353f313b2778142f34/numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", size = 13507771 }, + { url = "https://files.pythonhosted.org/packages/55/44/aa9ee3caee02fa5a45f2c3b95cafe59c44e4b278fbbf895a93e88b308555/numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", size = 5075805 }, + { url = "https://files.pythonhosted.org/packages/78/d6/61de6e7e31915ba4d87bbe1ae859e83e6582ea14c6add07c8f7eefd8488f/numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", size = 6608380 }, + { url = "https://files.pythonhosted.org/packages/3e/46/48bdf9b7241e317e6cf94276fe11ba673c06d1fdf115d8b4ebf616affd1a/numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", size = 13602451 }, + { url = "https://files.pythonhosted.org/packages/70/50/73f9a5aa0810cdccda9c1d20be3cbe4a4d6ea6bfd6931464a44c95eef731/numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", size = 16039822 }, + { url = "https://files.pythonhosted.org/packages/ad/cd/098bc1d5a5bc5307cfc65ee9369d0ca658ed88fbd7307b0d49fab6ca5fa5/numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", size = 16411822 }, + { url = "https://files.pythonhosted.org/packages/83/a2/7d4467a2a6d984549053b37945620209e702cf96a8bc658bc04bba13c9e2/numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", size = 14079598 }, + { url = "https://files.pythonhosted.org/packages/e9/6a/d64514dcecb2ee70bfdfad10c42b76cab657e7ee31944ff7a600f141d9e9/numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", size = 6236021 }, + { url = "https://files.pythonhosted.org/packages/bb/f9/12297ed8d8301a401e7d8eb6b418d32547f1d700ed3c038d325a605421a4/numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", size = 12560405 }, + { url = "https://files.pythonhosted.org/packages/a7/45/7f9244cd792e163b334e3a7f02dff1239d2890b6f37ebf9e82cbe17debc0/numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", size = 20859062 }, + { url = "https://files.pythonhosted.org/packages/b1/b4/a084218e7e92b506d634105b13e27a3a6645312b93e1c699cc9025adb0e1/numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", size = 13515839 }, + { url = "https://files.pythonhosted.org/packages/27/45/58ed3f88028dcf80e6ea580311dc3edefdd94248f5770deb980500ef85dd/numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", size = 5116031 }, + { url = "https://files.pythonhosted.org/packages/37/a8/eb689432eb977d83229094b58b0f53249d2209742f7de529c49d61a124a0/numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", size = 6629977 }, + { url = "https://files.pythonhosted.org/packages/42/a3/5355ad51ac73c23334c7caaed01adadfda49544f646fcbfbb4331deb267b/numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", size = 13575951 }, + { url = "https://files.pythonhosted.org/packages/c4/70/ea9646d203104e647988cb7d7279f135257a6b7e3354ea6c56f8bafdb095/numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", size = 16022655 }, + { url = "https://files.pythonhosted.org/packages/14/ce/7fc0612903e91ff9d0b3f2eda4e18ef9904814afcae5b0f08edb7f637883/numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", size = 16399902 }, + { url = "https://files.pythonhosted.org/packages/ef/62/1d3204313357591c913c32132a28f09a26357e33ea3c4e2fe81269e0dca1/numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", size = 14067180 }, + { url = "https://files.pythonhosted.org/packages/24/d7/78a40ed1d80e23a774cb8a34ae8a9493ba1b4271dde96e56ccdbab1620ef/numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", size = 6291907 }, + { url = "https://files.pythonhosted.org/packages/86/09/a5ab407bd7f5f5599e6a9261f964ace03a73e7c6928de906981c31c38082/numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", size = 12644098 }, ] [[package]] @@ -939,11 +925,11 @@ wheels = [ [[package]] name = "packaging" -version = "24.1" +version = "24.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, ] [[package]] @@ -997,15 +983,15 @@ wheels = [ [[package]] name = "polyfactory" -version = "2.17.0" +version = "2.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "faker" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/74/25adfbef58c43bdd3b0afb12a98438fabdd3dcdb41c1babaca6455eed047/polyfactory-2.17.0.tar.gz", hash = "sha256:099d86f7c79c51a2caaf7c8598cc56e7b0a57c11b5918ddf699e82380735b6b7", size = 183719 } +sdist = { url = "https://files.pythonhosted.org/packages/f4/57/cb9cbdc20b0583ce8e4e5a5e6e711eb2cca08ae868a4a677c95fbee55dde/polyfactory-2.18.0.tar.gz", hash = "sha256:04d8b4d4986e406cd4c16cc01e8bb747083842d57a5c0dba63a21ee5ef75ba66", size = 184682 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/ca/7c9b5e6270a911dd48165dd82c2e129b30dd5b8f0bf4da5379c94745c68c/polyfactory-2.17.0-py3-none-any.whl", hash = "sha256:71b677c17bb7cebad9a5631b1aca7718280bdcedc1c25278253717882d1ac294", size = 58820 }, + { url = "https://files.pythonhosted.org/packages/26/ba/353c85708c0409a0406b0c9f0f4aaa7c21fad6133e074623ba715db54ac3/polyfactory-2.18.0-py3-none-any.whl", hash = "sha256:2d9163e5e3971a4e82fac5d37048b09ae26cfc4173ebcf0047370c288e339307", size = 59086 }, ] [[package]] @@ -1139,7 +1125,7 @@ wheels = [ [[package]] name = "pygithub" -version = "2.4.0" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, @@ -1149,9 +1135,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/a0/1e8b8ca88df9857836f5bf8e3ee15dfb810d19814ef700b12f99ce11f691/pygithub-2.4.0.tar.gz", hash = "sha256:6601e22627e87bac192f1e2e39c6e6f69a43152cfb8f307cee575879320b3051", size = 3476673 } +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/aa91d30040d9552c274e7ea8bd10a977600d508d579a4bb262b95eccf961/pygithub-2.5.0.tar.gz", hash = "sha256:e1613ac508a9be710920d26eb18b1905ebd9926aa49398e88151c1b526aad3cf", size = 3552804 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/f3/e185613c411757c0c18b904ea2db173f2872397eddf444a3fe8cdde47077/PyGithub-2.4.0-py3-none-any.whl", hash = "sha256:81935aa4bdc939fba98fee1cb47422c09157c56a27966476ff92775602b9ee24", size = 362599 }, + { url = "https://files.pythonhosted.org/packages/37/05/bfbdbbc5d8aafd8dae9b3b6877edca561fccd8528ef5edc4e7b6d23721b5/PyGithub-2.5.0-py3-none-any.whl", hash = "sha256:b0b635999a658ab8e08720bdd3318893ff20e2275f6446fcf35bf3f44f2c0fd2", size = 375935 }, ] [[package]] @@ -1436,25 +1422,25 @@ wheels = [ [[package]] name = "regex" -version = "2024.9.11" +version = "2024.11.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/38/148df33b4dbca3bd069b963acab5e0fa1a9dbd6820f8c322d0dd6faeff96/regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd", size = 399403 } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/0a/d1c6b9af1ff1e36832fe38d74d5c5bab913f2bdcbbd6bc0e7f3ce8b2f577/regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784", size = 483376 }, - { url = "https://files.pythonhosted.org/packages/a4/42/5910a050c105d7f750a72dcb49c30220c3ae4e2654e54aaaa0e9bc0584cb/regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36", size = 288112 }, - { url = "https://files.pythonhosted.org/packages/8d/56/0c262aff0e9224fa7ffce47b5458d373f4d3e3ff84e99b5ff0cb15e0b5b2/regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92", size = 284608 }, - { url = "https://files.pythonhosted.org/packages/b9/54/9fe8f9aec5007bbbbce28ba3d2e3eaca425f95387b7d1e84f0d137d25237/regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86", size = 795337 }, - { url = "https://files.pythonhosted.org/packages/b2/e7/6b2f642c3cded271c4f16cc4daa7231be544d30fe2b168e0223724b49a61/regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85", size = 835848 }, - { url = "https://files.pythonhosted.org/packages/cd/9e/187363bdf5d8c0e4662117b92aa32bf52f8f09620ae93abc7537d96d3311/regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963", size = 823503 }, - { url = "https://files.pythonhosted.org/packages/f8/10/601303b8ee93589f879664b0cfd3127949ff32b17f9b6c490fb201106c4d/regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6", size = 797049 }, - { url = "https://files.pythonhosted.org/packages/ef/1c/ea200f61ce9f341763f2717ab4daebe4422d83e9fd4ac5e33435fd3a148d/regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802", size = 784144 }, - { url = "https://files.pythonhosted.org/packages/d8/5c/d2429be49ef3292def7688401d3deb11702c13dcaecdc71d2b407421275b/regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29", size = 782483 }, - { url = "https://files.pythonhosted.org/packages/12/d9/cbc30f2ff7164f3b26a7760f87c54bf8b2faed286f60efd80350a51c5b99/regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8", size = 790320 }, - { url = "https://files.pythonhosted.org/packages/19/1d/43ed03a236313639da5a45e61bc553c8d41e925bcf29b0f8ecff0c2c3f25/regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84", size = 860435 }, - { url = "https://files.pythonhosted.org/packages/34/4f/5d04da61c7c56e785058a46349f7285ae3ebc0726c6ea7c5c70600a52233/regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554", size = 859571 }, - { url = "https://files.pythonhosted.org/packages/12/7f/8398c8155a3c70703a8e91c29532558186558e1aea44144b382faa2a6f7a/regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8", size = 787398 }, - { url = "https://files.pythonhosted.org/packages/58/3a/f5903977647a9a7e46d5535e9e96c194304aeeca7501240509bde2f9e17f/regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8", size = 262035 }, - { url = "https://files.pythonhosted.org/packages/ff/80/51ba3a4b7482f6011095b3a036e07374f64de180b7d870b704ed22509002/regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f", size = 273510 }, + { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 }, + { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 }, + { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 }, + { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 }, + { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 }, + { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 }, + { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 }, + { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 }, + { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 }, + { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 }, + { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 }, + { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 }, + { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 }, + { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, + { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, ] [[package]] @@ -1487,40 +1473,40 @@ wheels = [ [[package]] name = "rich" -version = "13.9.3" +version = "13.9.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/e9/cf9ef5245d835065e6673781dbd4b8911d352fb770d56cf0879cf11b7ee1/rich-13.9.3.tar.gz", hash = "sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e", size = 222889 } +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/e2/10e9819cf4a20bd8ea2f5dabafc2e6bf4a78d6a0965daeb60a4b34d1c11f/rich-13.9.3-py3-none-any.whl", hash = "sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283", size = 242157 }, + { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, ] [[package]] name = "ruff" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/21/5c6e05e0fd3fbb41be4fb92edbc9a04de70baf60adb61435ce0c6b8c3d55/ruff-0.7.1.tar.gz", hash = "sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4", size = 3181670 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/45/8a20a9920175c9c4892b2420f80ff3cf14949cf3067118e212f9acd9c908/ruff-0.7.1-py3-none-linux_armv6l.whl", hash = "sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89", size = 10389268 }, - { url = "https://files.pythonhosted.org/packages/1b/d3/2f8382db2cf4f9488e938602e33e36287f9d26cb283aa31f11c31297ce79/ruff-0.7.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35", size = 10188348 }, - { url = "https://files.pythonhosted.org/packages/a2/31/7d14e2a88da351200f844b7be889a0845d9e797162cf76b136d21b832a23/ruff-0.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99", size = 9841448 }, - { url = "https://files.pythonhosted.org/packages/db/99/738cafdc768eceeca0bd26c6f03e213aa91203d2278e1d95b1c31c4ece41/ruff-0.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca", size = 10674864 }, - { url = "https://files.pythonhosted.org/packages/fe/12/bcf2836b50eab53c65008383e7d55201e490d75167c474f14a16e1af47d2/ruff-0.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250", size = 10192105 }, - { url = "https://files.pythonhosted.org/packages/2b/71/261d5d668bf98b6c44e89bfb5dfa4cb8cb6c8b490a201a3d8030e136ea4f/ruff-0.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c", size = 11194144 }, - { url = "https://files.pythonhosted.org/packages/90/1f/0926d18a3b566fa6e7b3b36093088e4ffef6b6ba4ea85a462d9a93f7e35c/ruff-0.7.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565", size = 11917066 }, - { url = "https://files.pythonhosted.org/packages/cd/a8/9fac41f128b6a44ab4409c1493430b4ee4b11521e8aeeca19bfe1ce851f9/ruff-0.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7", size = 11458821 }, - { url = "https://files.pythonhosted.org/packages/25/cd/59644168f086ab13fe4e02943b9489a0aa710171f66b178e179df5383554/ruff-0.7.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a", size = 12700379 }, - { url = "https://files.pythonhosted.org/packages/fb/30/3bac63619eb97174661829c07fc46b2055a053dee72da29d7c304c1cd2c0/ruff-0.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad", size = 11019813 }, - { url = "https://files.pythonhosted.org/packages/4b/af/f567b885b5cb3bcdbcca3458ebf210cc8c9c7a9f61c332d3c2a050c3b21e/ruff-0.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112", size = 10662146 }, - { url = "https://files.pythonhosted.org/packages/bc/ad/eb930d3ad117a9f2f7261969c21559ebd82bb13b6e8001c7caed0d44be5f/ruff-0.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378", size = 10256911 }, - { url = "https://files.pythonhosted.org/packages/20/d5/af292ce70a016fcec792105ca67f768b403dd480a11888bc1f418fed0dd5/ruff-0.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8", size = 10767488 }, - { url = "https://files.pythonhosted.org/packages/24/85/cc04a3bd027f433bebd2a097e63b3167653c079f7f13d8f9a1178e693412/ruff-0.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd", size = 11093368 }, - { url = "https://files.pythonhosted.org/packages/0b/fb/c39cbf32d1f3e318674b8622f989417231794926b573f76dd4d0ca49f0f1/ruff-0.7.1-py3-none-win32.whl", hash = "sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9", size = 8594180 }, - { url = "https://files.pythonhosted.org/packages/5a/71/ec8cdea34ecb90c830ca60d54ac7b509a7b5eab50fae27e001d4470fe813/ruff-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307", size = 9419751 }, - { url = "https://files.pythonhosted.org/packages/79/7b/884553415e9f0a9bf358ed52fb68b934e67ef6c5a62397ace924a1afdf9a/ruff-0.7.1-py3-none-win_arm64.whl", hash = "sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37", size = 8717402 }, +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/06/09d1276df977eece383d0ed66052fc24ec4550a61f8fbc0a11200e690496/ruff-0.7.3.tar.gz", hash = "sha256:e1d1ba2e40b6e71a61b063354d04be669ab0d39c352461f3d789cac68b54a313", size = 3243664 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/56/933d433c2489e4642487b835f53dd9ff015fb3d8fa459b09bb2ce42d7c4b/ruff-0.7.3-py3-none-linux_armv6l.whl", hash = "sha256:34f2339dc22687ec7e7002792d1f50712bf84a13d5152e75712ac08be565d344", size = 10372090 }, + { url = "https://files.pythonhosted.org/packages/20/ea/1f0a22a6bcdd3fc26c73f63a025d05bd565901b729d56bcb093c722a6c4c/ruff-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fb397332a1879b9764a3455a0bb1087bda876c2db8aca3a3cbb67b3dbce8cda0", size = 10190037 }, + { url = "https://files.pythonhosted.org/packages/16/74/aca75666e0d481fe394e76a8647c44ea919087748024924baa1a17371e3e/ruff-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37d0b619546103274e7f62643d14e1adcbccb242efda4e4bdb9544d7764782e9", size = 9811998 }, + { url = "https://files.pythonhosted.org/packages/20/a1/cf446a0d7f78ea1f0bd2b9171c11dfe746585c0c4a734b25966121eb4f5d/ruff-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59f0c3ee4d1a6787614e7135b72e21024875266101142a09a61439cb6e38a5", size = 10620626 }, + { url = "https://files.pythonhosted.org/packages/cd/c1/82b27d09286ae855f5d03b1ad37cf243f21eb0081732d4d7b0d658d439cb/ruff-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44eb93c2499a169d49fafd07bc62ac89b1bc800b197e50ff4633aed212569299", size = 10177598 }, + { url = "https://files.pythonhosted.org/packages/b9/42/c0acac22753bf74013d035a5ef6c5c4c40ad4d6686bfb3fda7c6f37d9b37/ruff-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d0242ce53f3a576c35ee32d907475a8d569944c0407f91d207c8af5be5dae4e", size = 11171963 }, + { url = "https://files.pythonhosted.org/packages/43/18/bb0befb7fb9121dd9009e6a72eb98e24f1bacb07c6f3ecb55f032ba98aed/ruff-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6b6224af8b5e09772c2ecb8dc9f3f344c1aa48201c7f07e7315367f6dd90ac29", size = 11856157 }, + { url = "https://files.pythonhosted.org/packages/5e/91/04e98d7d6e32eca9d1372be595f9abc7b7f048795e32eb2edbd8794d50bd/ruff-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c50f95a82b94421c964fae4c27c0242890a20fe67d203d127e84fbb8013855f5", size = 11440331 }, + { url = "https://files.pythonhosted.org/packages/f5/dc/3fe99f2ce10b76d389041a1b9f99e7066332e479435d4bebcceea16caff5/ruff-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f3eff9961b5d2644bcf1616c606e93baa2d6b349e8aa8b035f654df252c8c67", size = 12725354 }, + { url = "https://files.pythonhosted.org/packages/43/7b/1daa712de1c5bc6cbbf9fa60e9c41cc48cda962dc6d2c4f2a224d2c3007e/ruff-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8963cab06d130c4df2fd52c84e9f10d297826d2e8169ae0c798b6221be1d1d2", size = 11010091 }, + { url = "https://files.pythonhosted.org/packages/b6/db/1227a903587432eb569e57a95b15a4f191a71fe315cde4c0312df7bc85da/ruff-0.7.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61b46049d6edc0e4317fb14b33bd693245281a3007288b68a3f5b74a22a0746d", size = 10610687 }, + { url = "https://files.pythonhosted.org/packages/db/e2/dc41ee90c3085aadad4da614d310d834f641aaafddf3dfbba08210c616ce/ruff-0.7.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10ebce7696afe4644e8c1a23b3cf8c0f2193a310c18387c06e583ae9ef284de2", size = 10254843 }, + { url = "https://files.pythonhosted.org/packages/6f/09/5f6cac1c91542bc5bd33d40b4c13b637bf64d7bb29e091dadb01b62527fe/ruff-0.7.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3f36d56326b3aef8eeee150b700e519880d1aab92f471eefdef656fd57492aa2", size = 10730962 }, + { url = "https://files.pythonhosted.org/packages/d3/42/89a4b9a24ef7d00269e24086c417a006f9a3ffeac2c80f2629eb5ce140ee/ruff-0.7.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5d024301109a0007b78d57ab0ba190087b43dce852e552734ebf0b0b85e4fb16", size = 11101907 }, + { url = "https://files.pythonhosted.org/packages/b0/5c/efdb4777686683a8edce94ffd812783bddcd3d2454d38c5ac193fef7c500/ruff-0.7.3-py3-none-win32.whl", hash = "sha256:4ba81a5f0c5478aa61674c5a2194de8b02652f17addf8dfc40c8937e6e7d79fc", size = 8611095 }, + { url = "https://files.pythonhosted.org/packages/bb/b8/28fbc6a4efa50178f973972d1c84b2d0a33cdc731588522ab751ac3da2f5/ruff-0.7.3-py3-none-win_amd64.whl", hash = "sha256:588a9ff2fecf01025ed065fe28809cd5a53b43505f48b69a1ac7707b1b7e4088", size = 9418283 }, + { url = "https://files.pythonhosted.org/packages/3f/77/b587cba6febd5e2003374f37eb89633f79f161e71084f94057c8653b7fb3/ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c", size = 8725228 }, ] [[package]] @@ -1603,31 +1589,31 @@ wheels = [ [[package]] name = "tqdm" -version = "4.66.6" +version = "4.67.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "platform_system == 'Windows'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/34/bef135b27fe1864993a5284ad001157ee9b5538e859ac90f5b0e8cc8c9ec/tqdm-4.66.6.tar.gz", hash = "sha256:4bdd694238bef1485ce839d67967ab50af8f9272aab687c0d7702a01da0be090", size = 169533 } +sdist = { url = "https://files.pythonhosted.org/packages/e8/4f/0153c21dc5779a49a0598c445b1978126b1344bab9ee71e53e44877e14e0/tqdm-4.67.0.tar.gz", hash = "sha256:fe5a6f95e6fe0b9755e9469b77b9c3cf850048224ecaa8293d7d2d31f97d869a", size = 169739 } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/73/02342de9c2d20922115f787e101527b831c0cffd2105c946c4a4826bcfd4/tqdm-4.66.6-py3-none-any.whl", hash = "sha256:223e8b5359c2efc4b30555531f09e9f2f3589bcd7fdd389271191031b49b7a63", size = 78326 }, + { url = "https://files.pythonhosted.org/packages/2b/78/57043611a16c655c8350b4c01b8d6abfb38cc2acb475238b62c2146186d7/tqdm-4.67.0-py3-none-any.whl", hash = "sha256:0cd8af9d56911acab92182e88d763100d4788bdf421d251616040cc4d44863be", size = 78590 }, ] [[package]] name = "typeguard" -version = "4.4.0" +version = "4.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/5a/91b7c8cfc2e96962442abc9d65c650436dd831910b4d7878980d6596fb98/typeguard-4.4.0.tar.gz", hash = "sha256:463bd8697a65a4aa576a63767c369b1ecfba8a5ba735edfe3223127b6ecfa28c", size = 74399 } +sdist = { url = "https://files.pythonhosted.org/packages/62/c3/400917dd37d7b8c07e9723f3046818530423e1e759a56a22133362adab00/typeguard-4.4.1.tar.gz", hash = "sha256:0d22a89d00b453b47c49875f42b6601b961757541a2e1e0ef517b6e24213c21b", size = 74959 } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/a3/00203767544b597a9e3c57b29a84967b3230f00bdd9aa6a52a73187043b4/typeguard-4.4.0-py3-none-any.whl", hash = "sha256:8ca34c14043f53b2caae7040549ba431770869bcd6287cfa8239db7ecb882b4a", size = 35736 }, + { url = "https://files.pythonhosted.org/packages/f2/53/9465dedf2d69fe26008e7732cf6e0a385e387c240869e7d54eed49782a3c/typeguard-4.4.1-py3-none-any.whl", hash = "sha256:9324ec07a27ec67fc54a9c063020ca4c0ae6abad5e9f0f9804ca59aee68c6e21", size = 35635 }, ] [[package]] name = "typer" -version = "0.12.5" +version = "0.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1635,9 +1621,9 @@ dependencies = [ { name = "shellingham" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/58/a79003b91ac2c6890fc5d90145c662fd5771c6f11447f116b63300436bc9/typer-0.12.5.tar.gz", hash = "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722", size = 98953 } +sdist = { url = "https://files.pythonhosted.org/packages/e7/87/9eb07fdfa14e22ec7658b5b1147836d22df3848a22c85a4e18ed272303a5/typer-0.13.0.tar.gz", hash = "sha256:f1c7198347939361eec90139ffa0fd8b3df3a2259d5852a0f7400e476d95985c", size = 97572 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/2b/886d13e742e514f704c33c4caa7df0f3b89e5a25ef8db02aa9ca3d9535d5/typer-0.12.5-py3-none-any.whl", hash = "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b", size = 47288 }, + { url = "https://files.pythonhosted.org/packages/18/7e/c8bfa8cbcd3ea1d25d2beb359b5c5a3f4339a7e2e5d9e3ef3e29ba3ab3b9/typer-0.13.0-py3-none-any.whl", hash = "sha256:d85fe0b777b2517cc99c8055ed735452f2659cd45e451507c76f48ce5c1d00e2", size = 44194 }, ] [[package]] @@ -1669,19 +1655,19 @@ wheels = [ [[package]] name = "typos" -version = "1.26.8" +version = "1.27.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/64/9f13aeaf33f0ea40681508151bb3393d1d6461bceab70bd695c4ebbd4ce8/typos-1.26.8.tar.gz", hash = "sha256:b750d19531f6299d1c88d09af8db6998a5d92c2cca039220773140b3eb887cf3", size = 1112901 } +sdist = { url = "https://files.pythonhosted.org/packages/a6/42/9d4a02cae0bb546c39db51a5777aa773c5d8112283b054471fc3bc47a822/typos-1.27.2.tar.gz", hash = "sha256:94c4664c1b60abed44e6b694f3ac04e9a3e5794775b8d22e34df67d2845270b0", size = 1122036 } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/2c/b31a5be42d4ff91a0a7ba70f299cfc07826992c87a0e75cd7ba81432cf0f/typos-1.26.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:77093a1aa72b3fa34b1914e73d149fa70c02157fbe39bd13d20a0cd64a7b7fdf", size = 2990167 }, - { url = "https://files.pythonhosted.org/packages/94/29/fad80812079127beb9997424ac81c5d8c156eb6ae26f4495f116da46ec59/typos-1.26.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3d9769ec255ef3291fcfadc2d270773f6491eeaf0f3120e370ccdb08d218e600", size = 2890116 }, - { url = "https://files.pythonhosted.org/packages/05/3e/9abdb26cebe1091e2cf83627a54a555633a4aa375ed3982d4afce3159cbd/typos-1.26.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:526514dda6ac262626226ad0adbe7388c2a690c0ba972118c2c3eb245cf12e10", size = 4274983 }, - { url = "https://files.pythonhosted.org/packages/12/eb/cc320d11f77913a345b0a7d2ff682f56f467585e483fb37e3dedf8d3b909/typos-1.26.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a926bc4b2ba76eda508da0d1f46eeaf4f1446cffff5cb0721aa0246e7d20654f", size = 3358796 }, - { url = "https://files.pythonhosted.org/packages/70/c2/72da48ec76ac693fba2087def4aeed0e19facbe61713f03faa83aa066125/typos-1.26.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:985af8d669dd2fa124fc180de57ca82a5138d6ee49827784605c4717d0609105", size = 4062524 }, - { url = "https://files.pythonhosted.org/packages/c4/e4/5d39b6c0bbe94f938f2156472799c60e7bad384d78b40ceef4ea381f09cc/typos-1.26.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:34eb03b2ab984ec2e3f59f16994b7c9f7bc9f8af3d0b013e9d344ebf59018df6", size = 3885748 }, - { url = "https://files.pythonhosted.org/packages/c4/a7/a45ebf25850da3cba1abbaa4735958a52f006d25600d842fc1cd5475510b/typos-1.26.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:77006a3246d749d7fc0e46c35075607cd94b0fc64d19797ed840e6009fad5379", size = 4069568 }, - { url = "https://files.pythonhosted.org/packages/55/6b/e1711105f6e2c2b8520224d7958f8c0d2479b85d83f5cc5f4648c0323c1a/typos-1.26.8-py3-none-win32.whl", hash = "sha256:b7282faf0504dd5a1484c0edbaa7daf5b2965e264d92dd1754161691fd77ed29", size = 2419713 }, - { url = "https://files.pythonhosted.org/packages/98/30/61a936a904d99b67830cbed4f168102547fe687cac22acdb6d8a32c1b36b/typos-1.26.8-py3-none-win_amd64.whl", hash = "sha256:3da10e7560856a042de65b099f5f9bc846f3545ae3b121172872a533bea69e06", size = 2545192 }, + { url = "https://files.pythonhosted.org/packages/f8/40/134883d271eaf800f07e193d4f8829f07f0dece3f4381f9f13f559d5f648/typos-1.27.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b701adc7a72144e9f0299e63edc7460a63c1422d97f64b0c482f08e3d55a7c8e", size = 3004890 }, + { url = "https://files.pythonhosted.org/packages/50/e3/2d2a7ae07c5c950b07f5a72925be11b96707b1054a8eba9e5b9c600b6a7e/typos-1.27.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:962c3d81c717af96119d9c18b96c7d3f0f2cabb36a122619d3c17ddbb74d6fde", size = 2907600 }, + { url = "https://files.pythonhosted.org/packages/57/2b/56ddaa1e76e4407675263eee77c93d16c843019e645e2d39c10dac1d3282/typos-1.27.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ef6c948c83ac31a97c8971fc893cb47519d172c2af2c572811f01ddc86f98e", size = 4309690 }, + { url = "https://files.pythonhosted.org/packages/d3/be/e5c1908d7f9dea43dc7e6135378b8da4f73c2bd11540604bf3f8483235bc/typos-1.27.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b05bcf68283512d3af9d2f993503817132a3b5667e633202eae12addf741fb4", size = 3378784 }, + { url = "https://files.pythonhosted.org/packages/6d/db/0b7df8649cd2d6385e1ee357b51f979f1dacaa260fadfba1aa43dfd50973/typos-1.27.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f70ae6157e94b5d5f5029c179627a428930bea1918abdc17d7295f4525887d73", size = 4092986 }, + { url = "https://files.pythonhosted.org/packages/81/2a/265ea22e8f76f9e3449cb52285e0e45454d87a3dfea5071378ec4b41a856/typos-1.27.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b8a029487155ddab5050f08dea2578997b8159ffa40c69430b5da9abf61ba14a", size = 3911338 }, + { url = "https://files.pythonhosted.org/packages/db/b5/0d5836defc7b9572d4f7f03c196f7b052ee6e8cba0c671a359bd4c32926a/typos-1.27.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a70013a83347241a2250e0367df13d12d12277f44ddef2ecb85a297d735c5554", size = 4096199 }, + { url = "https://files.pythonhosted.org/packages/86/15/a9941d5be202573450687dcb931b428787e61487c9e920086c574077c0f5/typos-1.27.2-py3-none-win32.whl", hash = "sha256:31721f64446f2903a7592e2593645b2271aa17f504790935944b651b17679bf7", size = 2432079 }, + { url = "https://files.pythonhosted.org/packages/a1/8a/4d5ff525c787e27be4457a00431dbaf19f4706c06d74c19346977793f9bd/typos-1.27.2-py3-none-win_amd64.whl", hash = "sha256:5e745d2a3f3d4b2199b02d3cd5f10477df72078e449570ea1c840a69be15154c", size = 2562380 }, ] [[package]] @@ -1767,7 +1753,7 @@ requires-dist = [ { name = "numpy", specifier = ">=2.1.1,<2.2.0" }, { name = "py-cord", specifier = ">=2.6.1,<2.7.0" }, { name = "pydantic", specifier = ">=2.9.2,<3.0.0" }, - { name = "pygithub", specifier = ">=2.4.0,<3.0.0" }, + { name = "pygithub", specifier = ">=2.4.0" }, { name = "quart", specifier = ">=0.19.6" }, { name = "quart-flask-patch", specifier = ">=0.3.0" }, { name = "quart-session", specifier = ">=3.0.0" }, @@ -1775,7 +1761,7 @@ requires-dist = [ { name = "redis", specifier = ">=5.1.0,<6.0.0" }, { name = "rich", specifier = ">=13.8.1" }, { name = "semver", specifier = ">=3.0.2" }, - { name = "typer", specifier = ">=0.12.5,<0.13.0" }, + { name = "typer", specifier = ">=0.13.0,<0.14.0" }, ] [package.metadata.requires-dev] @@ -1784,7 +1770,7 @@ dev = [ { name = "coverage", specifier = ">=7.6.1" }, { name = "dirty-equals", specifier = ">=0.8.0" }, { name = "djlint", specifier = ">=1.35.2" }, - { name = "mypy", specifier = ">=1.11.2,<2.0.0" }, + { name = "mypy", specifier = ">=1.11.2" }, { name = "poethepoet", specifier = ">=0.29.0" }, { name = "polyfactory", specifier = ">=2.17.0" }, { name = "pre-commit", specifier = ">=3.8.0" }, @@ -1827,14 +1813,14 @@ wheels = [ [[package]] name = "werkzeug" -version = "3.0.6" +version = "3.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d4/f9/0ba83eaa0df9b9e9d1efeb2ea351d0677c37d41ee5d0f91e98423c7281c9/werkzeug-3.0.6.tar.gz", hash = "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d", size = 805170 } +sdist = { url = "https://files.pythonhosted.org/packages/9f/e7/58868f1a95bd6f2ffa0a26af212675fb74be2a4c4bfa3541077b0ca14ad3/werkzeug-3.1.2.tar.gz", hash = "sha256:f471a4cd167233077e9d2a8190c3471c5bc520c636a9e3c1e9300c33bced03bc", size = 806496 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/69/05837f91dfe42109203ffa3e488214ff86a6d68b2ed6c167da6cdc42349b/werkzeug-3.0.6-py3-none-any.whl", hash = "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17", size = 227979 }, + { url = "https://files.pythonhosted.org/packages/cb/ff/107697c9d5ca486c4a97e51be036d521ba08a70747c5e9fa1f4729240854/werkzeug-3.1.2-py3-none-any.whl", hash = "sha256:4f7d1a5de312c810a8a2c6f0b47e9f6a7cffb7c8322def35e4d4d9841ff85597", size = 224352 }, ] [[package]]