diff --git a/firefly-iii-6.4.14-v1.yaml b/firefly-iii-6.4.16-v1.yaml similarity index 99% rename from firefly-iii-6.4.14-v1.yaml rename to firefly-iii-6.4.16-v1.yaml index a740909..662d357 100644 --- a/firefly-iii-6.4.14-v1.yaml +++ b/firefly-iii-6.4.16-v1.yaml @@ -5,7 +5,7 @@ servers: - description: "Local development server (do not use)" url: http://firefly.sd.internal/api info: - title: Firefly III API v6.4.14 + title: Firefly III API v6.4.16 description: | This is the documentation of the Firefly III API. Please report any bugs or issues. You may use the "Authorize" button to try the API below.

@@ -13,9 +13,9 @@ info:

To learn more about the idiosyncrasies of this API, please read about the API in the [Firefly III API documentation](https://docs.firefly-iii.org/references/firefly-iii/api/).

- This file was last generated on 2025-12-26 @ 15:17:41 (Europe/Amsterdam) + This file was last generated on 2026-01-17 @ 08:54:35 (Europe/Amsterdam) - version: "v6.4.14" + version: "v6.4.16" contact: name: James Cole email: james@firefly-iii.org @@ -148,7 +148,7 @@ paths: schema: type: string format: string - example: "2025-12-01" + example: "2026-01-01" description: If the account is an asset account or a liability, the autocomplete will also return the balance of the account on this date. - in: query name: types @@ -4873,7 +4873,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -4882,7 +4882,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" - in: query name: type description: Optional filter on the transaction type(s) returned. @@ -5985,7 +5985,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -5994,7 +5994,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" responses: "401": description: Unauthenticated @@ -6279,7 +6279,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -6288,7 +6288,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" - in: query name: type description: Optional filter on the transaction type(s) returned @@ -6374,7 +6374,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -6383,7 +6383,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" responses: "401": description: Unauthenticated @@ -6519,7 +6519,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -6528,7 +6528,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" tags: - bills responses: @@ -6822,7 +6822,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -6831,7 +6831,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" responses: "200": description: A list of budget limits applicable to this budget. @@ -7182,7 +7182,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -7191,7 +7191,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" responses: "200": description: A list of budget limits. @@ -7278,7 +7278,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -7287,7 +7287,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" - in: query name: type description: Optional filter on the transaction type(s) returned @@ -7458,7 +7458,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -7467,7 +7467,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" responses: "200": description: A list of transactions. @@ -7554,7 +7554,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -7563,7 +7563,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" responses: "200": description: A list of budgets. @@ -7699,7 +7699,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -7708,7 +7708,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" tags: - budgets responses: @@ -7928,7 +7928,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -7937,7 +7937,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" - in: query name: type description: Optional filter on the transaction type(s) returned @@ -8229,7 +8229,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -8238,7 +8238,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" tags: - categories responses: @@ -8954,7 +8954,7 @@ paths: schema: type: string format: string - example: "2025-12-01" + example: "2026-01-01" responses: "200": description: A list of currency exchange rates. @@ -9027,7 +9027,7 @@ paths: schema: type: string format: string - example: "2025-12-01" + example: "2026-01-01" responses: "204": description: "Currency exchange rate(s) deleted." @@ -9095,7 +9095,7 @@ paths: schema: type: string format: string - example: "2025-12-01" + example: "2026-01-01" tags: - currency_exchange_rates requestBody: @@ -9165,7 +9165,7 @@ paths: schema: type: string format: string - example: "2025-12-01" + example: "2026-01-01" tags: - currency_exchange_rates requestBody: @@ -9350,7 +9350,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -9359,7 +9359,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" - in: query name: type description: Optional filter on the transaction type(s) returned. @@ -11033,7 +11033,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -11042,7 +11042,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" - in: query name: type description: Optional filter on the transaction type(s) returned @@ -11616,7 +11616,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -11625,7 +11625,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" - in: query name: search_limit description: | @@ -11719,7 +11719,7 @@ paths: required: false schema: type: string - example: "2025-12-01" + example: "2026-01-01" format: date - in: query name: end @@ -11728,7 +11728,7 @@ paths: required: false schema: type: string - example: "2025-12-31" + example: "2026-01-31" format: date - in: query name: accounts[] @@ -12137,7 +12137,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -12146,7 +12146,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" - in: query name: accounts[] description: | @@ -12226,7 +12226,7 @@ paths: required: false schema: type: string - example: "2025-12-01" + example: "2026-01-01" format: date - in: query name: end @@ -12235,7 +12235,7 @@ paths: required: false schema: type: string - example: "2025-12-31" + example: "2026-01-31" format: date - in: query name: accounts[] @@ -12743,7 +12743,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -12752,7 +12752,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" - in: query name: type description: Optional filter on the transaction type(s) returned. @@ -13451,7 +13451,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: End date for the budget limit list. @@ -13459,7 +13459,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" responses: "200": description: A list of budget limits. @@ -13705,7 +13705,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -13714,7 +13714,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" - in: query name: type description: Optional filter on the transaction type(s) returned @@ -14728,7 +14728,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: end description: | @@ -14737,7 +14737,7 @@ paths: schema: type: string format: date - example: "2025-12-31" + example: "2026-01-31" - in: query name: type description: Optional filter on the transaction type(s) returned. @@ -15829,7 +15829,7 @@ paths: schema: type: string format: date - example: "2025-12-01" + example: "2026-01-01" - in: query name: force description: | @@ -18339,15 +18339,15 @@ components: date: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" start_date: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" end_date: type: string format: date-time - example: "2025-12-31T23:59:59+00:00" + example: "2026-01-31T23:59:59+00:00" type: type: string format: string @@ -19026,12 +19026,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true active: type: boolean @@ -19191,7 +19191,7 @@ components: current_balance_date: type: string format: date-time - example: "2025-12-31T23:59:59+00:00" + example: "2026-01-31T23:59:59+00:00" readOnly: true description: "The timestamp for this date is always 23:59:59, to indicate it's the balance at the very END of that particular day." notes: @@ -19203,7 +19203,7 @@ components: nullable: true type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: "Mandatory when the account_role is ccAsset. Moment at which CC payment installments are asked for by the bank." credit_card_type: $ref: '#/components/schemas/CreditCardTypeProperty' @@ -19225,7 +19225,7 @@ components: opening_balance_date: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" nullable: true description: Represents the date of the opening balance. liability_type: @@ -19266,7 +19266,7 @@ components: nullable: true type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: "Last activity of the account." AccountRead: type: object @@ -19328,7 +19328,7 @@ components: opening_balance_date: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" nullable: true description: Represents the date of the opening balance. virtual_balance: @@ -19368,7 +19368,7 @@ components: nullable: true type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: "Mandatory when the account_role is ccAsset. Moment at which CC payment installments are asked for by the bank." liability_type: $ref: '#/components/schemas/LiabilityTypeProperty' @@ -19438,7 +19438,7 @@ components: opening_balance_date: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" nullable: true virtual_balance: type: string @@ -19477,7 +19477,7 @@ components: nullable: true type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: "Mandatory when the account_role is ccAsset. Moment at which CC payment installments are asked for by the bank." liability_type: $ref: '#/components/schemas/LiabilityTypeProperty' @@ -19543,12 +19543,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true attachable_type: $ref: '#/components/schemas/AttachableType' @@ -19671,12 +19671,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true object_has_currency_setting: type: boolean @@ -19751,12 +19751,12 @@ components: start: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: Start date of the available budget. end: type: string format: date-time - example: "2025-12-31T23:59:59+00:00" + example: "2026-01-31T23:59:59+00:00" description: End date of the available budget. spent_in_budgets: type: array @@ -19813,12 +19813,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true name: type: string @@ -19918,17 +19918,17 @@ components: date: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" end_date: type: string format: date-time - example: "2025-12-31T23:59:59+00:00" + example: "2026-01-31T23:59:59+00:00" description: "The date after which this subscription is no longer valid or applicable" nullable: true extension_date: type: string format: date-time - example: "2025-12-31T23:59:59+00:00" + example: "2026-01-31T23:59:59+00:00" description: "The date before which the subscription must be renewed (or cancelled)" nullable: true repeat_freq: @@ -19994,7 +19994,7 @@ components: date: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: "Date the bill was paid." readOnly: true subscription_id: @@ -20085,13 +20085,13 @@ components: items: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true next_expected_match: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: "When the subscription is expected to be due." nullable: true readOnly: true @@ -20161,16 +20161,16 @@ components: date: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" end_date: type: string format: date-time - example: "2025-12-31T23:59:59+00:00" + example: "2026-01-31T23:59:59+00:00" description: "The date after which this bill is no longer valid or applicable" extension_date: type: string format: date-time - example: "2025-12-31T23:59:59+00:00" + example: "2026-01-31T23:59:59+00:00" description: "The date before which the bill must be renewed (or cancelled)" repeat_freq: $ref: '#/components/schemas/BillRepeatFrequency' @@ -20230,16 +20230,16 @@ components: date: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" end_date: type: string format: date-time - example: "2025-12-31T23:59:59+00:00" + example: "2026-01-31T23:59:59+00:00" description: "The date after which this bill is no longer valid or applicable" extension_date: type: string format: date-time - example: "2025-12-31T23:59:59+00:00" + example: "2026-01-31T23:59:59+00:00" description: "The date before which the bill must be renewed (or cancelled)" repeat_freq: $ref: '#/components/schemas/BillRepeatFrequency' @@ -20277,12 +20277,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true active: type: boolean @@ -20534,22 +20534,22 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true start: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: Start date of the budget limit. end: type: string format: date-time - example: "2025-12-31T23:59:59+00:00" + example: "2026-01-31T23:59:59+00:00" description: End date of the budget limit. budget_id: type: string @@ -20705,7 +20705,7 @@ components: start: type: string format: date - example: "2025-12-01" + example: "2026-01-01" description: Start date of the budget limit. period: type: string @@ -20717,7 +20717,7 @@ components: end: type: string format: date - example: "2025-12-31" + example: "2026-01-31" description: End date of the budget limit. amount: type: string @@ -20740,22 +20740,22 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true start: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: Start date of the budget limit. end: type: string format: date-time - example: "2025-12-31T23:59:59+00:00" + example: "2026-01-31T23:59:59+00:00" description: End date of the budget limit. budget_id: type: string @@ -20867,12 +20867,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true name: type: string @@ -20988,12 +20988,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true from_currency_id: type: string @@ -21065,7 +21065,7 @@ components: type: string format: date-time readOnly: true - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: "Date and time of the exchange rate." CurrencyExchangeRateRead: type: object @@ -21103,7 +21103,7 @@ components: date: type: string format: date - example: "2025-12-01" + example: "2026-01-01" description: "The date to which the exchange rate is applicable." nullable: false from: @@ -21161,7 +21161,7 @@ components: date: type: string format: date - example: "2025-12-01" + example: "2026-01-01" description: The date to which the exchange rate is applicable. nullable: false rate: @@ -21202,12 +21202,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true title: type: string @@ -21355,12 +21355,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true name: type: string @@ -21376,13 +21376,13 @@ components: start_date: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: "The date you started with this piggy bank." target_date: type: string nullable: true format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: "The date you intend to finish saving money." order: type: integer @@ -21550,13 +21550,13 @@ components: start_date: type: string format: date - example: "2025-12-01" + example: "2026-01-01" description: The date you started with this piggy bank. target_date: type: string format: date nullable: true - example: "2025-12-31" + example: "2026-01-31" description: The date you intend to finish saving money. order: type: integer @@ -21612,13 +21612,13 @@ components: start_date: type: string format: date - example: "2025-12-01" + example: "2026-01-01" description: The date you started with this piggy bank. target_date: type: string format: date nullable: true - example: "2025-12-31" + example: "2026-01-31" description: The date you intend to finish saving money. order: type: integer @@ -21651,11 +21651,11 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" amount: type: string format: amount @@ -21745,12 +21745,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true name: type: string @@ -21771,12 +21771,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true type: $ref: '#/components/schemas/RecurrenceTransactionType' @@ -21792,19 +21792,19 @@ components: first_date: type: string format: date - example: "2025-12-31" + example: "2026-01-31" description: "First time the recurring transaction will fire. Must be after today." latest_date: type: string format: date - example: "2025-12-01" + example: "2026-01-01" description: "Last time the recurring transaction has fired." nullable: true readOnly: true repeat_until: type: string format: date - example: "2025-12-31" + example: "2026-01-31" description: "Date until the recurring transaction can fire. Use either this field or repetitions." nullable: true apply_rules: @@ -21858,12 +21858,12 @@ components: first_date: type: string format: date - example: "2025-12-31" + example: "2026-01-31" description: "First time the recurring transaction will fire. Must be after today." repeat_until: type: string format: date - example: "2025-12-31" + example: "2026-01-31" description: "Date until the recurring transaction can fire. Use either this field or repetitions." nullable: true nr_of_repetitions: @@ -21908,12 +21908,12 @@ components: first_date: type: string format: date - example: "2025-12-31" + example: "2026-01-31" description: "First time the recurring transaction will fire." repeat_until: type: string format: date - example: "2025-12-31" + example: "2026-01-31" description: Date until when the recurring transaction can fire. After that date, it's basically inactive. Use either this field or repetitions. nullable: true nr_of_repetitions: @@ -21957,12 +21957,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true type: $ref: '#/components/schemas/RecurrenceRepetitionType' @@ -21976,7 +21976,7 @@ components: - For 'weekly', it is day of the week between 1 and 7 (Monday - Sunday). - For 'ndom', it is '1,2' or '4,5' or something else, where the first number is the week in the month, and the second number is the day in the week (between 1 and 7). '2,3' means: the 2nd Wednesday of the month - For 'monthly' it is the day of the month (1 - 31) - - For yearly, it is a full date, ie '2025-12-01'. The year you use does not matter. + - For yearly, it is a full date, ie '2026-01-01'. The year you use does not matter. skip: type: integer format: int32 @@ -22005,7 +22005,7 @@ components: items: type: string format: date-time - example: "2025-12-31T23:59:59+00:00" + example: "2026-01-31T23:59:59+00:00" RecurrenceRepetitionStore: type: object required: @@ -22024,7 +22024,7 @@ components: - For 'weekly', it is day of the week between 1 and 7 (Monday - Sunday). - For 'ndom', it is '1,2' or '4,5' or something else, where the first number is the week in the month, and the second number is the day in the week (between 1 and 7). '2,3' means: the 2nd Wednesday of the month - For 'monthly' it is the day of the month (1 - 31) - - For yearly, it is a full date, ie '2025-12-01'. The year you use does not matter. + - For yearly, it is a full date, ie '2026-01-01'. The year you use does not matter. skip: type: integer format: int32 @@ -22055,7 +22055,7 @@ components: - For 'weekly', it is day of the week between 1 and 7 (Monday - Sunday). - For 'ndom', it is '1,2' or '4,5' or something else, where the first number is the week in the month, and the second number is the day in the week (between 1 and 7). '2,3' means: the 2nd Wednesday of the month - For 'monthly' it is the day of the month (1 - 31) - - For yearly, it is a full date, ie '2025-12-01'. The year you use does not matter. + - For yearly, it is a full date, ie '2026-01-01'. The year you use does not matter. skip: type: integer format: int32 @@ -22461,12 +22461,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true title: type: string @@ -22628,12 +22628,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true type: $ref: '#/components/schemas/RuleActionKeyword' @@ -22719,12 +22719,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true title: type: string @@ -22797,12 +22797,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true type: $ref: '#/components/schemas/RuleTriggerKeyword' @@ -22897,12 +22897,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true tag: type: string @@ -22912,7 +22912,7 @@ components: date: type: string format: date - example: "2025-12-01" + example: "2026-01-01" description: The date to which the tag is applicable. nullable: true description: @@ -22952,7 +22952,7 @@ components: date: type: string format: date - example: "2025-12-01" + example: "2026-01-01" description: The date to which the tag is applicable. nullable: true description: @@ -22990,7 +22990,7 @@ components: date: type: string format: date - example: "2025-12-01" + example: "2026-01-01" description: The date to which the tag is applicable. nullable: true description: @@ -23026,12 +23026,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true enabled: type: boolean @@ -23133,12 +23133,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true user: type: string @@ -23217,12 +23217,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true link_type_id: type: string @@ -23385,7 +23385,7 @@ components: date: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: "Date of the transaction" order: type: integer @@ -23780,7 +23780,7 @@ components: date: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: "Date of the transaction" amount: type: string @@ -24011,7 +24011,7 @@ components: date: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" description: "Date of the transaction" amount: type: string @@ -24266,12 +24266,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true email: type: string @@ -24313,12 +24313,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true in_use: type: boolean @@ -24471,12 +24471,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true active: type: boolean @@ -24616,12 +24616,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true webhook_message_id: type: string @@ -24652,12 +24652,12 @@ components: created_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true updated_at: type: string format: date-time - example: "2025-12-01T00:00:00+00:00" + example: "2026-01-01T00:00:00+00:00" readOnly: true sent: type: boolean @@ -24820,7 +24820,7 @@ components: - has_no_tag - has_any_tag - notes_contains - - notes_start + - notes_starts - notes_end - notes_are - no_notes @@ -24991,12 +24991,12 @@ components: version: type: string format: string - example: "6.4.14" + example: "6.4.16" api_version: type: string format: string description: Same value as the version field. - example: "6.4.14" + example: "6.4.16" php_version: type: string format: string @@ -25137,6 +25137,7 @@ components: description: "Mandatory when type is liability. Period over which the interest is calculated." nullable: true enum: + - daily - weekly - monthly - quarterly diff --git a/pyproject.toml b/pyproject.toml index 2e43eb7..186f274 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ dependencies = [ "pydantic>=2.11.7", "pydantic-settings>=2.10.1", ] -description = "Add your description here" +description = "A simple MCP server for Firefly III" name = "lampyrid" readme = "README.md" requires-python = ">=3.14" @@ -13,6 +13,8 @@ version = "0.3.0" [project.scripts] lampyrid = "lampyrid.__main__:main" +update-schema = "lampyrid.scripts.update_schema:main" +lint = "lampyrid.scripts.format:main" [dependency-groups] dev = [ @@ -65,6 +67,6 @@ markers = [ ] [tool.datamodel-codegen] -input = "firefly-iii-6.4.14-v1.yaml" +input = "firefly-iii-6.4.16-v1.yaml" output = "src/lampyrid/models/firefly_models.py" output-model-type = "pydantic_v2.BaseModel" diff --git a/src/lampyrid/models/firefly_models.py b/src/lampyrid/models/firefly_models.py index a8fd7a6..1c4d5af 100644 --- a/src/lampyrid/models/firefly_models.py +++ b/src/lampyrid/models/firefly_models.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: -# filename: firefly-iii-6.4.14-v1.yaml -# timestamp: 2026-01-10T18:53:29+00:00 +# filename: firefly-iii-6.4.16-v1.yaml +# timestamp: 2026-01-29T18:10:22+00:00 from __future__ import annotations @@ -549,8 +549,8 @@ class AttachmentUpdate(BaseModel): class AvailableBudgetProperties(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) object_has_currency_setting: bool | None = Field( None, description="Indicates whether the object has a currency setting. If false, the object uses the administration's primary currency.", @@ -611,12 +611,12 @@ class AvailableBudgetProperties(BaseModel): start: AwareDatetime | None = Field( None, description='Start date of the available budget.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) end: AwareDatetime | None = Field( None, description='End date of the available budget.', - examples=['2025-12-31T23:59:59+00:00'], + examples=['2026-01-31T23:59:59+00:00'], ) spent_in_budgets: list[ArrayEntryWithCurrencyAndSum] | None = None pc_spent_in_budgets: list[ArrayEntryWithCurrencyAndSum] | None = Field( @@ -654,7 +654,7 @@ class PaidDate(BaseModel): date: AwareDatetime | None = Field( None, description='Date the bill was paid.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) subscription_id: str | None = Field( None, description='ID of this subscription.', examples=['123'] @@ -724,17 +724,17 @@ class PaidDate(BaseModel): class BudgetLimitProperties(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) start: AwareDatetime | None = Field( None, description='Start date of the budget limit.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) end: AwareDatetime | None = Field( None, description='End date of the budget limit.', - examples=['2025-12-31T23:59:59+00:00'], + examples=['2026-01-31T23:59:59+00:00'], ) budget_id: str | None = Field( None, description='The budget ID of the associated budget.', examples=['23'] @@ -837,7 +837,7 @@ class BudgetLimitStore(BaseModel): ..., description='The budget ID of the associated budget.', examples=['23'] ) start: date_aliased = Field( - ..., description='Start date of the budget limit.', examples=['2025-12-01'] + ..., description='Start date of the budget limit.', examples=['2026-01-01'] ) period: str | None = Field( None, @@ -845,7 +845,7 @@ class BudgetLimitStore(BaseModel): examples=['monthly'], ) end: date_aliased = Field( - ..., description='End date of the budget limit.', examples=['2025-12-31'] + ..., description='End date of the budget limit.', examples=['2026-01-31'] ) amount: str = Field(..., examples=['123.45']) notes: str | None = Field( @@ -861,17 +861,17 @@ class BudgetLimitStore(BaseModel): class BudgetLimitUpdate(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) start: AwareDatetime | None = Field( None, description='Start date of the budget limit.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) end: AwareDatetime | None = Field( None, description='End date of the budget limit.', - examples=['2025-12-31T23:59:59+00:00'], + examples=['2026-01-31T23:59:59+00:00'], ) budget_id: str | None = Field( None, description='The budget ID of the associated budget.', examples=['23'] @@ -952,8 +952,8 @@ class BudgetLimitUpdate(BaseModel): class CategoryProperties(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) name: str = Field(..., examples=['Lunch']) notes: str | None = Field(None, examples=['Some example notes']) object_has_currency_setting: bool | None = Field( @@ -1023,8 +1023,8 @@ class CategoryUpdate(BaseModel): class CurrencyExchangeProperties(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) from_currency_id: str | None = Field( None, description='Base currency ID for this exchange rate entry.', @@ -1083,7 +1083,7 @@ class CurrencyExchangeProperties(BaseModel): date: AwareDatetime | None = Field( None, description='Date and time of the exchange rate.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) @@ -1091,7 +1091,7 @@ class CurrencyExchangeRateStore(BaseModel): date: date_aliased = Field( ..., description='The date to which the exchange rate is applicable.', - examples=['2025-12-01'], + examples=['2026-01-01'], ) from_: str = Field(..., alias='from', description='The base currency code.', examples=['USD']) to: str = Field(..., description='The destination currency code.', examples=['EUR']) @@ -1119,7 +1119,7 @@ class CurrencyExchangeRateUpdate(BaseModel): date: date_aliased = Field( ..., description='The date to which the exchange rate is applicable.', - examples=['2025-12-01'], + examples=['2026-01-01'], ) rate: str = Field( ..., @@ -1141,8 +1141,8 @@ class CurrencyExchangeRateUpdateNoDate(BaseModel): class ObjectGroup(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) title: str = Field(..., examples=['My object group']) order: int = Field(..., description='Order of the object group', examples=[1]) @@ -1212,8 +1212,8 @@ class PiggyBankAccountUpdate(BaseModel): class PiggyBankProperties(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) name: str = Field(..., examples=['New digital camera']) percentage: int | None = Field( None, @@ -1223,12 +1223,12 @@ class PiggyBankProperties(BaseModel): start_date: AwareDatetime | None = Field( None, description='The date you started with this piggy bank.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) target_date: AwareDatetime | None = Field( None, description='The date you intend to finish saving money.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) order: int | None = Field(None, examples=[5]) active: bool | None = Field(None, examples=[True]) @@ -1322,12 +1322,12 @@ class PiggyBankStore(BaseModel): start_date: date_aliased = Field( ..., description='The date you started with this piggy bank.', - examples=['2025-12-01'], + examples=['2026-01-01'], ) target_date: date_aliased | None = Field( None, description='The date you intend to finish saving money.', - examples=['2025-12-31'], + examples=['2026-01-31'], ) order: int | None = Field(None, examples=[5]) active: bool | None = Field(None, examples=[True]) @@ -1353,12 +1353,12 @@ class PiggyBankUpdate(BaseModel): start_date: date_aliased | None = Field( None, description='The date you started with this piggy bank.', - examples=['2025-12-01'], + examples=['2026-01-01'], ) target_date: date_aliased | None = Field( None, description='The date you intend to finish saving money.', - examples=['2025-12-31'], + examples=['2026-01-31'], ) order: int | None = Field(None, examples=[5]) active: bool | None = Field(None, examples=[True]) @@ -1376,8 +1376,8 @@ class PiggyBankUpdate(BaseModel): class PiggyBankEventProperties(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) amount: str | None = Field(None, examples=['123.45']) pc_amount: str | None = Field(None, examples=['123.45']) object_has_currency_setting: bool | None = Field( @@ -1525,8 +1525,8 @@ class RecurrenceTransactionUpdate(BaseModel): class RuleGroup(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) title: str = Field(..., examples=['Default rule group']) description: str | None = Field(None, examples=['Description of this rule group']) order: int | None = Field(None, examples=[4]) @@ -1548,13 +1548,13 @@ class RuleGroupUpdate(BaseModel): class TagModel(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) tag: str = Field(..., description='The tag', examples=['expensive']) date: date_aliased | None = Field( None, description='The date to which the tag is applicable.', - examples=['2025-12-01'], + examples=['2026-01-01'], ) description: str | None = Field(None, examples=['Tag for expensive stuff']) latitude: float | None = Field( @@ -1579,7 +1579,7 @@ class TagModelStore(BaseModel): date: date_aliased | None = Field( None, description='The date to which the tag is applicable.', - examples=['2025-12-01'], + examples=['2026-01-01'], ) description: str | None = Field(None, examples=['Tag for expensive stuff']) latitude: float | None = Field( @@ -1604,7 +1604,7 @@ class TagModelUpdate(BaseModel): date: date_aliased | None = Field( None, description='The date to which the tag is applicable.', - examples=['2025-12-01'], + examples=['2026-01-01'], ) description: str | None = Field(None, examples=['Tag for expensive stuff']) latitude: float | None = Field( @@ -1625,8 +1625,8 @@ class TagModelUpdate(BaseModel): class CurrencyProperties(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) enabled: bool | None = Field(True, description='Defaults to true', examples=[True]) primary: bool | None = Field(None, description='Is the primary currency?', examples=[False]) code: str = Field(..., examples=['AMS']) @@ -1672,8 +1672,8 @@ class CurrencyUpdate(BaseModel): class TransactionLink(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) link_type_id: str = Field( ..., description='The link type ID to use. You can also use the link_type_name field.', @@ -1877,8 +1877,8 @@ class WebhookUpdate(BaseModel): class WebhookAttempt(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) webhook_message_id: str | None = Field( None, description='The ID of the webhook message this attempt belongs to.', @@ -1900,8 +1900,8 @@ class WebhookAttempt(BaseModel): class WebhookMessage(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) sent: bool | None = Field(None, description='If this message is sent yet.', examples=[False]) errored: bool | None = Field( None, description='If this message has errored out.', examples=[False] @@ -2037,7 +2037,7 @@ class RuleTriggerKeyword(Enum): has_no_tag = 'has_no_tag' has_any_tag = 'has_any_tag' notes_contains = 'notes_contains' - notes_start = 'notes_start' + notes_starts = 'notes_starts' notes_end = 'notes_end' notes_are = 'notes_are' no_notes = 'no_notes' @@ -2152,9 +2152,9 @@ class CronResultRow(BaseModel): class Data(BaseModel): - version: str | None = Field(None, examples=['6.4.14']) + version: str | None = Field(None, examples=['6.4.16']) api_version: str | None = Field( - None, description='Same value as the version field.', examples=['6.4.14'] + None, description='Same value as the version field.', examples=['6.4.16'] ) php_version: str | None = Field(None, examples=['8.1.5']) os: str | None = Field(None, examples=['Linux']) @@ -2271,6 +2271,7 @@ class CreditCardTypeProperty(RootModel[CreditCardTypePropertyEnum | None]): class InterestPeriodPropertyEnum(Enum): + daily = 'daily' weekly = 'weekly' monthly = 'monthly' quarterly = 'quarterly' @@ -2495,9 +2496,9 @@ class ChartDataSet(BaseModel): description="The currency decimal places of the administration's primary currency.", examples=[2], ) - date: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - start_date: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - end_date: AwareDatetime | None = Field(None, examples=['2025-12-31T23:59:59+00:00']) + date: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + start_date: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + end_date: AwareDatetime | None = Field(None, examples=['2026-01-31T23:59:59+00:00']) type: str | None = Field( None, description='Indicated the type of chart that is expected to be rendered. You can safely ignore this if you want.', @@ -2648,8 +2649,8 @@ class WebhookMessageSingle(BaseModel): class AccountProperties(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) active: bool | None = Field(True, examples=[False]) order: int | None = Field( None, @@ -2774,13 +2775,13 @@ class AccountProperties(BaseModel): current_balance_date: AwareDatetime | None = Field( None, description="The timestamp for this date is always 23:59:59, to indicate it's the balance at the very END of that particular day.", - examples=['2025-12-31T23:59:59+00:00'], + examples=['2026-01-31T23:59:59+00:00'], ) notes: str | None = Field(None, examples=['Some example notes']) monthly_payment_date: AwareDatetime | None = Field( None, description='Mandatory when the account_role is ccAsset. Moment at which CC payment installments are asked for by the bank.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) credit_card_type: CreditCardTypeProperty | None = None account_number: str | None = Field(None, examples=['7009312345678']) @@ -2789,7 +2790,7 @@ class AccountProperties(BaseModel): opening_balance_date: AwareDatetime | None = Field( None, description='Represents the date of the opening balance.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) liability_type: LiabilityTypeProperty | None = None liability_direction: LiabilityDirectionProperty | None = None @@ -2818,7 +2819,7 @@ class AccountProperties(BaseModel): last_activity: AwareDatetime | None = Field( None, description='Last activity of the account.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) @@ -2846,7 +2847,7 @@ class AccountStore(BaseModel): opening_balance_date: AwareDatetime | None = Field( None, description='Represents the date of the opening balance.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) virtual_balance: str | None = Field(None, examples=['123.45']) currency_id: str | None = Field( @@ -2869,7 +2870,7 @@ class AccountStore(BaseModel): monthly_payment_date: AwareDatetime | None = Field( None, description='Mandatory when the account_role is ccAsset. Moment at which CC payment installments are asked for by the bank.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) liability_type: LiabilityTypeProperty | None = None liability_direction: LiabilityDirectionProperty | None = None @@ -2903,7 +2904,7 @@ class AccountUpdate(BaseModel): bic: str | None = Field(None, examples=['BOFAUS3N']) account_number: str | None = Field(None, examples=['7009312345678']) opening_balance: str | None = Field(None, examples=['-1012.12']) - opening_balance_date: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + opening_balance_date: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) virtual_balance: str | None = Field(None, examples=['123.45']) currency_id: str | None = Field( None, @@ -2925,7 +2926,7 @@ class AccountUpdate(BaseModel): monthly_payment_date: AwareDatetime | None = Field( None, description='Mandatory when the account_role is ccAsset. Moment at which CC payment installments are asked for by the bank.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) liability_type: LiabilityTypeProperty | None = None interest: str | None = Field( @@ -2953,8 +2954,8 @@ class AccountUpdate(BaseModel): class AttachmentProperties(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) attachable_type: AttachableType | None = None attachable_id: str | None = Field( None, @@ -3003,8 +3004,8 @@ class AttachmentStore(BaseModel): class BillProperties(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) name: str | None = Field(None, description='The name of the subscription.', examples=['Rent']) object_has_currency_setting: bool | None = Field( None, @@ -3083,16 +3084,16 @@ class BillProperties(BaseModel): description="The average amount that is expected for this subscription in the administration's primary currency.", examples=['123.45'], ) - date: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + date: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) end_date: AwareDatetime | None = Field( None, description='The date after which this subscription is no longer valid or applicable', - examples=['2025-12-31T23:59:59+00:00'], + examples=['2026-01-31T23:59:59+00:00'], ) extension_date: AwareDatetime | None = Field( None, description='The date before which the subscription must be renewed (or cancelled)', - examples=['2025-12-31T23:59:59+00:00'], + examples=['2026-01-31T23:59:59+00:00'], ) repeat_freq: BillRepeatFrequency | None = None skip: int | None = Field( @@ -3128,7 +3129,7 @@ class BillProperties(BaseModel): next_expected_match: AwareDatetime | None = Field( None, description='When the subscription is expected to be due.', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) next_expected_match_diff: str | None = Field( None, @@ -3157,16 +3158,16 @@ class BillStore(BaseModel): name: str = Field(..., examples=['Rent']) amount_min: str = Field(..., examples=['123.45']) amount_max: str = Field(..., examples=['123.45']) - date: AwareDatetime = Field(..., examples=['2025-12-01T00:00:00+00:00']) + date: AwareDatetime = Field(..., examples=['2026-01-01T00:00:00+00:00']) end_date: AwareDatetime | None = Field( None, description='The date after which this bill is no longer valid or applicable', - examples=['2025-12-31T23:59:59+00:00'], + examples=['2026-01-31T23:59:59+00:00'], ) extension_date: AwareDatetime | None = Field( None, description='The date before which the bill must be renewed (or cancelled)', - examples=['2025-12-31T23:59:59+00:00'], + examples=['2026-01-31T23:59:59+00:00'], ) repeat_freq: BillRepeatFrequency skip: int | None = Field( @@ -3198,16 +3199,16 @@ class BillUpdate(BaseModel): name: str = Field(..., examples=['Rent']) amount_min: str | None = Field(None, examples=['123.45']) amount_max: str | None = Field(None, examples=['123.45']) - date: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + date: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) end_date: AwareDatetime | None = Field( None, description='The date after which this bill is no longer valid or applicable', - examples=['2025-12-31T23:59:59+00:00'], + examples=['2026-01-31T23:59:59+00:00'], ) extension_date: AwareDatetime | None = Field( None, description='The date before which the bill must be renewed (or cancelled)', - examples=['2025-12-31T23:59:59+00:00'], + examples=['2026-01-31T23:59:59+00:00'], ) repeat_freq: BillRepeatFrequency | None = None skip: int | None = Field( @@ -3230,8 +3231,8 @@ class BillUpdate(BaseModel): class BudgetProperties(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) active: bool | None = Field(None, examples=[False]) name: str = Field(..., examples=['Bills']) order: int | None = Field(None, examples=[5]) @@ -3395,12 +3396,12 @@ class CurrencyExchangeRateSingle(BaseModel): class RecurrenceRepetition(BaseModel): id: str | None = Field(None, examples=['2']) - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) type: RecurrenceRepetitionType moment: str = Field( ..., - description="Information that defined the type of repetition.\n- For 'daily', this is empty.\n- For 'weekly', it is day of the week between 1 and 7 (Monday - Sunday).\n- For 'ndom', it is '1,2' or '4,5' or something else, where the first number is the week in the month, and the second number is the day in the week (between 1 and 7). '2,3' means: the 2nd Wednesday of the month\n- For 'monthly' it is the day of the month (1 - 31)\n- For yearly, it is a full date, ie '2025-12-01'. The year you use does not matter.\n", + description="Information that defined the type of repetition.\n- For 'daily', this is empty.\n- For 'weekly', it is day of the week between 1 and 7 (Monday - Sunday).\n- For 'ndom', it is '1,2' or '4,5' or something else, where the first number is the week in the month, and the second number is the day in the week (between 1 and 7). '2,3' means: the 2nd Wednesday of the month\n- For 'monthly' it is the day of the month (1 - 31)\n- For yearly, it is a full date, ie '2026-01-01'. The year you use does not matter.\n", examples=['3'], ) skip: int | None = Field( @@ -3428,7 +3429,7 @@ class RecurrenceRepetitionStore(BaseModel): type: RecurrenceRepetitionType moment: str = Field( ..., - description="Information that defined the type of repetition.\n- For 'daily', this is empty.\n- For 'weekly', it is day of the week between 1 and 7 (Monday - Sunday).\n- For 'ndom', it is '1,2' or '4,5' or something else, where the first number is the week in the month, and the second number is the day in the week (between 1 and 7). '2,3' means: the 2nd Wednesday of the month\n- For 'monthly' it is the day of the month (1 - 31)\n- For yearly, it is a full date, ie '2025-12-01'. The year you use does not matter.\n", + description="Information that defined the type of repetition.\n- For 'daily', this is empty.\n- For 'weekly', it is day of the week between 1 and 7 (Monday - Sunday).\n- For 'ndom', it is '1,2' or '4,5' or something else, where the first number is the week in the month, and the second number is the day in the week (between 1 and 7). '2,3' means: the 2nd Wednesday of the month\n- For 'monthly' it is the day of the month (1 - 31)\n- For yearly, it is a full date, ie '2026-01-01'. The year you use does not matter.\n", examples=['3'], ) skip: int | None = Field( @@ -3447,7 +3448,7 @@ class RecurrenceRepetitionUpdate(BaseModel): type: RecurrenceRepetitionType | None = None moment: str | None = Field( None, - description="Information that defined the type of repetition.\n- For 'daily', this is empty.\n- For 'weekly', it is day of the week between 1 and 7 (Monday - Sunday).\n- For 'ndom', it is '1,2' or '4,5' or something else, where the first number is the week in the month, and the second number is the day in the week (between 1 and 7). '2,3' means: the 2nd Wednesday of the month\n- For 'monthly' it is the day of the month (1 - 31)\n- For yearly, it is a full date, ie '2025-12-01'. The year you use does not matter.\n", + description="Information that defined the type of repetition.\n- For 'daily', this is empty.\n- For 'weekly', it is day of the week between 1 and 7 (Monday - Sunday).\n- For 'ndom', it is '1,2' or '4,5' or something else, where the first number is the week in the month, and the second number is the day in the week (between 1 and 7). '2,3' means: the 2nd Wednesday of the month\n- For 'monthly' it is the day of the month (1 - 31)\n- For yearly, it is a full date, ie '2026-01-01'. The year you use does not matter.\n", examples=['3'], ) skip: int | None = Field( @@ -3583,8 +3584,8 @@ class RecurrenceTransaction(BaseModel): class RuleAction(BaseModel): id: str | None = Field(None, examples=['2']) - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) type: RuleActionKeyword value: str = Field( ..., @@ -3638,8 +3639,8 @@ class RuleActionUpdate(BaseModel): class RuleTrigger(BaseModel): id: str | None = Field(None, examples=['2']) - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) type: RuleTriggerKeyword value: str = Field( ..., @@ -3712,7 +3713,7 @@ class TransactionSplit(BaseModel): date: AwareDatetime = Field( ..., description='Date of the transaction', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) order: int | None = Field( None, @@ -3946,7 +3947,7 @@ class TransactionSplitStore(BaseModel): date: AwareDatetime = Field( ..., description='Date of the transaction', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) amount: str = Field(..., description='Amount of the transaction.', examples=['123.45']) description: str = Field( @@ -4068,7 +4069,7 @@ class TransactionSplitUpdate(BaseModel): date: AwareDatetime | None = Field( None, description='Date of the transaction', - examples=['2025-12-01T00:00:00+00:00'], + examples=['2026-01-01T00:00:00+00:00'], ) amount: str | None = Field(None, description='Amount of the transaction.', examples=['123.45']) description: str | None = Field( @@ -4186,8 +4187,8 @@ class TransactionSplitUpdate(BaseModel): class User(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) email: EmailStr = Field( ..., description='The new users email address.', @@ -4214,8 +4215,8 @@ class UserGroupReadMembers(BaseModel): class WebhookProperties(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) active: bool | None = Field( None, description='Boolean to indicate if the webhook is active', @@ -4405,8 +4406,8 @@ class WebhookSingle(BaseModel): class Preference(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) name: str = Field(..., examples=['currencyPreference']) data: PolymorphicProperty @@ -4416,8 +4417,8 @@ class PreferenceUpdate(BaseModel): class RecurrenceProperties(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) type: RecurrenceTransactionType | None = None title: str | None = Field(None, examples=['Rent']) description: str | None = Field( @@ -4428,17 +4429,17 @@ class RecurrenceProperties(BaseModel): first_date: date_aliased | None = Field( None, description='First time the recurring transaction will fire. Must be after today.', - examples=['2025-12-31'], + examples=['2026-01-31'], ) latest_date: date_aliased | None = Field( None, description='Last time the recurring transaction has fired.', - examples=['2025-12-01'], + examples=['2026-01-01'], ) repeat_until: date_aliased | None = Field( None, description='Date until the recurring transaction can fire. Use either this field or repetitions.', - examples=['2025-12-31'], + examples=['2026-01-31'], ) apply_rules: bool | None = Field( None, @@ -4469,12 +4470,12 @@ class RecurrenceStore(BaseModel): first_date: date_aliased = Field( ..., description='First time the recurring transaction will fire. Must be after today.', - examples=['2025-12-31'], + examples=['2026-01-31'], ) repeat_until: date_aliased = Field( ..., description='Date until the recurring transaction can fire. Use either this field or repetitions.', - examples=['2025-12-31'], + examples=['2026-01-31'], ) nr_of_repetitions: int | None = Field( None, @@ -4504,12 +4505,12 @@ class RecurrenceUpdate(BaseModel): first_date: date_aliased | None = Field( None, description='First time the recurring transaction will fire.', - examples=['2025-12-31'], + examples=['2026-01-31'], ) repeat_until: date_aliased | None = Field( None, description="Date until when the recurring transaction can fire. After that date, it's basically inactive. Use either this field or repetitions.", - examples=['2025-12-31'], + examples=['2026-01-31'], ) nr_of_repetitions: int | None = Field( None, @@ -4530,8 +4531,8 @@ class RecurrenceUpdate(BaseModel): class Rule(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) title: str = Field(..., examples=['First rule title.']) description: str | None = Field(None, examples=['First rule description']) rule_group_id: str = Field( @@ -4629,8 +4630,8 @@ class RuleUpdate(BaseModel): class Transaction(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) user: str | None = Field(None, description='User ID', examples=['3']) group_title: str | None = Field( None, @@ -4684,8 +4685,8 @@ class TransactionUpdate(BaseModel): class UserGroupReadAttributes(BaseModel): - created_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) - updated_at: AwareDatetime | None = Field(None, examples=['2025-12-01T00:00:00+00:00']) + created_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) + updated_at: AwareDatetime | None = Field(None, examples=['2026-01-01T00:00:00+00:00']) in_use: bool | None = Field( None, description="Is this user group ('financial administration') currently the active administration?", diff --git a/src/lampyrid/models/lampyrid_models.py b/src/lampyrid/models/lampyrid_models.py index 149bf2b..cd33faf 100644 --- a/src/lampyrid/models/lampyrid_models.py +++ b/src/lampyrid/models/lampyrid_models.py @@ -3,7 +3,7 @@ from datetime import date, datetime, timezone from typing import List, Literal, Optional -from pydantic import BaseModel, Field, model_validator +from pydantic import BaseModel, ConfigDict, Field, model_validator from .firefly_models import ( AccountRead, @@ -171,6 +171,8 @@ def to_transaction_split_store(self) -> TransactionSplitStore: class ListAccountRequest(BaseModel): """Request model for listing accounts.""" + model_config = ConfigDict(extra='forbid') + type: AccountTypeFilter = Field( ..., description=( @@ -183,6 +185,8 @@ class ListAccountRequest(BaseModel): class SearchAccountRequest(BaseModel): """Request model for searching accounts.""" + model_config = ConfigDict(extra='forbid') + query: str = Field( ..., description='Text to search for in account names (supports partial matching)', @@ -199,6 +203,8 @@ class SearchAccountRequest(BaseModel): class GetAccountRequest(BaseModel): """Request model for getting a single account.""" + model_config = ConfigDict(extra='forbid') + id: str = Field( ..., description='Unique identifier of the account (from list_accounts or search_accounts)' ) @@ -207,6 +213,8 @@ class GetAccountRequest(BaseModel): class CreateWithdrawalRequest(BaseModel): """Request model for creating a withdrawal transaction.""" + model_config = ConfigDict(extra='forbid') + amount: float = Field( ..., description='Amount to withdraw as positive number (e.g., 25.50 for $25.50 expense)' ) @@ -224,11 +232,20 @@ class CreateWithdrawalRequest(BaseModel): 'Must be an asset account you own.' ), ) + destination_id: Optional[str] = Field( + default=None, + description=( + 'ID of the expense account receiving the money (from list_accounts type=expense). ' + 'Use destination_id OR destination_name, not both. ' + 'If neither provided, defaults to Cash.' + ), + ) destination_name: Optional[str] = Field( default=None, description=( 'Where the money went ("Groceries", "Gas Station", "ATM"). ' - 'Creates expense account if new. Leave blank for cash withdrawals.' + 'Creates expense account if new. Use destination_id OR destination_name, not both. ' + 'If neither provided, defaults to Cash.' ), ) budget_id: Optional[str] = Field( @@ -239,10 +256,21 @@ class CreateWithdrawalRequest(BaseModel): description='Name of budget if ID is unknown. Will use ID if both provided.', ) + @model_validator(mode='after') + def validate_destination_mutual_exclusivity(self): + """Ensure destination_id and destination_name are not both provided.""" + if self.destination_id is not None and self.destination_name is not None: + raise ValueError( + 'Cannot specify both destination_id and destination_name. Use one or the other.' + ) + return self + class CreateDepositRequest(BaseModel): """Request model for creating a deposit transaction.""" + model_config = ConfigDict(extra='forbid') + amount: float = Field( ..., description='Amount received as positive number (e.g., 2500.00 for $2500 salary)' ) @@ -253,11 +281,20 @@ class CreateDepositRequest(BaseModel): default_factory=utc_now, description='When the income was received (defaults to current time if not specified)', ) + source_id: Optional[str] = Field( + default=None, + description=( + 'ID of the revenue account the money comes from (from list_accounts type=revenue). ' + 'Use source_id OR source_name, not both. ' + 'If neither provided, defaults to Cash.' + ), + ) source_name: Optional[str] = Field( default=None, description=( 'Where the money came from ("Employer", "Client Name", "Gift"). ' - 'Creates revenue account if new.' + 'Creates revenue account if new. Use source_id OR source_name, not both. ' + 'If neither provided, defaults to Cash.' ), ) destination_id: str = Field( @@ -268,10 +305,19 @@ class CreateDepositRequest(BaseModel): ), ) + @model_validator(mode='after') + def validate_source_mutual_exclusivity(self): + """Ensure source_id and source_name are not both provided.""" + if self.source_id is not None and self.source_name is not None: + raise ValueError('Cannot specify both source_id and source_name. Use one or the other.') + return self + class CreateTransferRequest(BaseModel): """Request model for creating a transfer transaction.""" + model_config = ConfigDict(extra='forbid') + amount: float = Field( ..., description='Amount to move as positive number (e.g., 500.00 to move $500)' ) @@ -296,6 +342,8 @@ class CreateTransferRequest(BaseModel): class GetTransactionsRequest(BaseModel): """Request model for retrieving transactions.""" + model_config = ConfigDict(extra='forbid') + account_id: Optional[str] = Field( None, description=( @@ -332,6 +380,8 @@ class GetTransactionsRequest(BaseModel): class SearchTransactionsRequest(BaseModel): """Request model for searching transactions.""" + model_config = ConfigDict(extra='forbid') + query: str | None = Field( None, description=( @@ -453,12 +503,16 @@ def validate_search_criteria(self): class DeleteTransactionRequest(BaseModel): """Request model for deleting a transaction.""" + model_config = ConfigDict(extra='forbid') + id: str = Field(..., description='Unique identifier of the transaction to permanently remove') class GetTransactionRequest(BaseModel): """Request model for getting a single transaction.""" + model_config = ConfigDict(extra='forbid') + id: str = Field(..., description='Unique identifier of the transaction to get details for') @@ -536,6 +590,8 @@ def from_transaction_array( class ListBudgetsRequest(BaseModel): """Request for listing budgets.""" + model_config = ConfigDict(extra='forbid') + active: Optional[bool] = Field( None, description='Show only active budgets (true), inactive (false), or all budgets ' @@ -546,6 +602,8 @@ class ListBudgetsRequest(BaseModel): class GetBudgetRequest(BaseModel): """Request for getting a single budget by ID.""" + model_config = ConfigDict(extra='forbid') + id: str = Field(..., description='Unique identifier of the budget to get details for') @@ -572,6 +630,8 @@ class BudgetSpending(BaseModel): class GetBudgetSpendingRequest(BaseModel): """Request for getting budget spending data.""" + model_config = ConfigDict(extra='forbid') + budget_id: str = Field( ..., description='Unique identifier of the budget to analyze spending for' ) @@ -601,6 +661,8 @@ class BudgetSummary(BaseModel): class GetBudgetSummaryRequest(BaseModel): """Request for getting budget summary.""" + model_config = ConfigDict(extra='forbid') + start_date: Optional[date] = Field( None, description='Start date for summary period (YYYY-MM-DD), inclusive' ) @@ -621,6 +683,8 @@ class AvailableBudget(BaseModel): class GetAvailableBudgetRequest(BaseModel): """Request for getting available budget.""" + model_config = ConfigDict(extra='forbid') + start_date: Optional[date] = Field( None, description='Start date for budget analysis (YYYY-MM-DD). Defaults to ' @@ -637,6 +701,8 @@ class GetAvailableBudgetRequest(BaseModel): class CreateBulkTransactionsRequest(BaseModel): """Create multiple transactions in one operation.""" + model_config = ConfigDict(extra='forbid') + transactions: List[Transaction] = Field( ..., description=( @@ -672,6 +738,8 @@ def validate_transactions(self): class UpdateTransactionRequest(BaseModel): """Update an existing transaction.""" + model_config = ConfigDict(extra='forbid') + transaction_id: str = Field(..., description='Unique identifier of the transaction to modify') amount: Optional[float] = Field(None, description='New transaction amount (positive number)') description: Optional[str] = Field( @@ -697,6 +765,8 @@ class UpdateTransactionRequest(BaseModel): class BulkUpdateTransactionsRequest(BaseModel): """Update multiple transactions in one operation.""" + model_config = ConfigDict(extra='forbid') + updates: List[UpdateTransactionRequest] = Field( ..., description='Array of transaction modifications to apply in a single operation', diff --git a/src/lampyrid/scripts/__init__.py b/src/lampyrid/scripts/__init__.py new file mode 100644 index 0000000..30e2662 --- /dev/null +++ b/src/lampyrid/scripts/__init__.py @@ -0,0 +1 @@ +"""Scripts for maintenance and development tasks.""" diff --git a/src/lampyrid/scripts/format.py b/src/lampyrid/scripts/format.py new file mode 100644 index 0000000..4136a22 --- /dev/null +++ b/src/lampyrid/scripts/format.py @@ -0,0 +1,29 @@ +"""Script to run code formatting and linting.""" + +import subprocess +import sys +from pathlib import Path + + +def main() -> int: + """Run code formatting and linting fix.""" + root_dir = Path(__file__).parent.parent.parent.parent.resolve() + + print(f'Running formatting in {root_dir}...') + print('Running ruff format...') + try: + subprocess.run(['ruff', 'format', '.'], cwd=root_dir, check=True) + print('Running ruff check --fix...') + subprocess.run(['ruff', 'check', '--fix', '.'], cwd=root_dir, check=True) + except subprocess.CalledProcessError as e: + print(f'Error during formatting: {e}') + return 1 + except FileNotFoundError: + print("Error: 'ruff' not found. Ensure it is installed in your environment.") + return 1 + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/lampyrid/scripts/update_schema.py b/src/lampyrid/scripts/update_schema.py new file mode 100644 index 0000000..b10e36f --- /dev/null +++ b/src/lampyrid/scripts/update_schema.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +"""Download the latest Firefly III API schema and regenerate Pydantic models. + +This script: +1. Fetches available versions from api-docs.firefly-iii.org +2. Identifies the latest stable version (excludes develop/beta) +3. Downloads the OpenAPI YAML schema +4. Updates pyproject.toml with the new schema path +5. Regenerates Pydantic models using datamodel-codegen +6. Cleans up the old schema file + +Usage: + uv run python scripts/update_schema.py +""" + +import re +import subprocess +import sys +from pathlib import Path + +import httpx + +# Constants +API_DOCS_URL = 'https://api-docs.firefly-iii.org/' +SCHEMA_BASE_URL = 'https://api-docs.firefly-iii.org/' +PROJECT_ROOT = Path(__file__).parent.parent.parent.parent +PYPROJECT_PATH = PROJECT_ROOT / 'pyproject.toml' +MODELS_OUTPUT = 'src/lampyrid/models/firefly_models.py' + + +def parse_versions_from_html(html: str) -> list[str]: + """Extract available schema versions from the swagger UI HTML. + + Args: + html: HTML content from the API docs page + + Returns: + List of version strings (e.g., ['6.4.16', '6.4.14', ...]) + + """ + # Match patterns like: firefly-iii-6.4.16-v1.yaml + # The HTML may have escaped slashes (\/) or regular format + pattern = r'firefly-iii-([0-9][^"\'>\s]*)-v1\.yaml' + matches = re.findall(pattern, html) + + # Filter out develop and beta versions for stable release + stable_versions = [] + seen = set() + for version in matches: + if 'develop' in version or 'beta' in version: + continue + if version not in seen: + seen.add(version) + stable_versions.append(version) + + return stable_versions + + +def parse_semver(version: str) -> tuple[int, ...]: + """Parse a version string into a tuple for comparison. + + Args: + version: Version string like '6.4.16' or '6.4.14' + + Returns: + Tuple of integers for comparison (e.g., (6, 4, 16)) + + """ + # Extract numeric parts + parts = re.findall(r'\d+', version) + return tuple(int(p) for p in parts) + + +def get_latest_stable_version(versions: list[str]) -> str: + """Find the latest stable version from a list of versions. + + Args: + versions: List of version strings + + Returns: + The latest stable version string + + """ + if not versions: + raise ValueError('No stable versions found') + + # Sort by semver, descending + sorted_versions = sorted(versions, key=parse_semver, reverse=True) + return sorted_versions[0] + + +def get_current_schema_version() -> str | None: + """Get the current schema version from pyproject.toml. + + Returns: + Current version string or None if not found + + """ + if not PYPROJECT_PATH.exists(): + return None + + content = PYPROJECT_PATH.read_text() + + # Match: input = "firefly-iii-6.4.14-v1.yaml" + match = re.search(r'input\s*=\s*"firefly-iii-([^"]+)-v1\.yaml"', content) + if match: + return match.group(1) + + return None + + +def download_schema(version: str) -> bytes: + """Download the OpenAPI schema for a specific version. + + Args: + version: Version string (e.g., '6.4.16') + + Returns: + Schema content as bytes + + """ + url = f'{SCHEMA_BASE_URL}firefly-iii-{version}-v1.yaml' + response = httpx.get(url, timeout=30.0, follow_redirects=True) + response.raise_for_status() + return response.content + + +def update_pyproject_toml(old_version: str | None, new_version: str) -> bool: + """Update the schema path in pyproject.toml. + + Args: + old_version: Previous version string (or None) + new_version: New version string + + Returns: + True if the file was updated, False otherwise + + """ + content = PYPROJECT_PATH.read_text() + original_content = content + + old_filename = f'firefly-iii-{old_version}-v1.yaml' if old_version else None + new_filename = f'firefly-iii-{new_version}-v1.yaml' + + if old_filename and old_filename in content: + content = content.replace(old_filename, new_filename) + else: + # If no existing entry, try to update any firefly schema reference + content = re.sub( + r'input\s*=\s*"firefly-iii-[^"]*\.yaml"', + f'input = "{new_filename}"', + content, + ) + + if content != original_content: + PYPROJECT_PATH.write_text(content) + return True + + return False + + +def regenerate_models() -> bool: + """Run datamodel-codegen to regenerate Pydantic models. + + Returns: + True if successful, False otherwise + + """ + try: + subprocess.run( + ['uv', 'run', 'datamodel-codegen'], + cwd=PROJECT_ROOT, + capture_output=True, + text=True, + check=True, + ) + return True + except subprocess.CalledProcessError as e: + print(f' Error running datamodel-codegen: {e.stderr}') + return False + + +def cleanup_old_schema(old_version: str | None, new_version: str) -> bool: + """Remove the old schema file if it exists and differs from new. + + Args: + old_version: Previous version string (or None) + new_version: New version string + + Returns: + True if a file was removed, False otherwise + + """ + if not old_version or old_version == new_version: + return False + + old_path = PROJECT_ROOT / f'firefly-iii-{old_version}-v1.yaml' + if old_path.exists(): + old_path.unlink() + return True + + return False + + +def main() -> int: + """Run the schema update process. + + Returns: + Exit code (0 for success, 1 for error) + + """ + print('=== Firefly III Schema Updater ===\n') + + # Step 1: Fetch available versions + print('Fetching available versions from api-docs.firefly-iii.org...') + try: + response = httpx.get(API_DOCS_URL, timeout=30.0, follow_redirects=True) + response.raise_for_status() + except httpx.HTTPError as e: + print(f'Error fetching API docs page: {e}') + return 1 + + versions = parse_versions_from_html(response.text) + if not versions: + print('Error: Could not find any schema versions') + return 1 + + latest_version = get_latest_stable_version(versions) + print(f'Found {len(versions)} stable versions, latest: {latest_version}\n') + + # Step 2: Check current version + current_version = get_current_schema_version() + current_display = current_version or '(none)' + + print(f'Current schema: firefly-iii-{current_display}-v1.yaml') + print(f'Latest schema: firefly-iii-{latest_version}-v1.yaml\n') + + # Step 3: Check if update is needed + if current_version == latest_version: + print('Already up to date!') + return 0 + + # Step 4: Download new schema + print(f'Downloading firefly-iii-{latest_version}-v1.yaml...') + try: + schema_content = download_schema(latest_version) + except httpx.HTTPError as e: + print(f'Error downloading schema: {e}') + return 1 + + schema_path = PROJECT_ROOT / f'firefly-iii-{latest_version}-v1.yaml' + schema_path.write_bytes(schema_content) + size_kb = len(schema_content) / 1024 + print(f'Schema downloaded ({size_kb:.0f}KB)') + + # Step 5: Update pyproject.toml + print('\nUpdating pyproject.toml...') + if update_pyproject_toml(current_version, latest_version): + print('Updated [tool.datamodel-codegen] input') + else: + print('No changes needed in pyproject.toml') + + # Step 6: Regenerate models + print('\nRegenerating Pydantic models...') + if regenerate_models(): + print(f'Models regenerated at {MODELS_OUTPUT}') + + # Format the generated code + print('Formatting generated code...') + try: + subprocess.run( + ['uv', 'run', 'ruff', 'format', MODELS_OUTPUT], + cwd=PROJECT_ROOT, + check=True, + capture_output=True, + ) + print('Code formatted successfully') + except subprocess.CalledProcessError as e: + print(f'Warning: Formatting failed: {e}') + else: + print('Warning: Model regeneration failed') + print('You may need to run manually: uv run datamodel-codegen') + + # Step 7: Cleanup old schema + if current_version: + print('\nCleaning up old schema...') + if cleanup_old_schema(current_version, latest_version): + print(f'Removed firefly-iii-{current_version}-v1.yaml') + else: + print('No cleanup needed') + + # Summary + print('\n=== Update Complete ===') + if current_version: + print(f'Schema updated from {current_version} to {latest_version}') + else: + print(f'Schema set to {latest_version}') + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/lampyrid/services/transactions.py b/src/lampyrid/services/transactions.py index 8259041..1d28607 100644 --- a/src/lampyrid/services/transactions.py +++ b/src/lampyrid/services/transactions.py @@ -61,6 +61,7 @@ async def create_withdrawal(self, req: CreateWithdrawalRequest) -> Transaction: type=TransactionTypeProperty.withdrawal, date=req.date, source_id=req.source_id, + destination_id=req.destination_id, destination_name=req.destination_name, budget_id=req.budget_id, budget_name=req.budget_name, @@ -90,6 +91,7 @@ async def create_deposit(self, req: CreateDepositRequest) -> Transaction: description=req.description, type=TransactionTypeProperty.deposit, date=req.date, + source_id=req.source_id, source_name=req.source_name, destination_id=req.destination_id, ) diff --git a/tests/conftest.py b/tests/conftest.py index 5be4a66..d60cbf7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -278,6 +278,25 @@ def test_expense_account() -> str: return 'Test Expense' +@pytest.fixture(scope='session') +def test_expense_account_obj() -> Account: + """Get the test expense account object with ID. + + Use this when you need the expense account ID (e.g., for destination_id + in create_withdrawal with ID instead of name). + """ + if not _cached_test_accounts: + raise RuntimeError('Test accounts not initialized. Check if _setup_test_data ran.') + + account = next( + (acct for acct in _cached_test_accounts if acct.name == 'Test Expense'), + None, + ) + if account is None: + raise RuntimeError('Test Expense account not found in cached test accounts.') + return account + + @pytest.fixture(scope='session') def test_second_expense_account() -> str: """Get second expense account name for testing. @@ -299,6 +318,30 @@ def test_revenue_account() -> str: return 'Test Revenue' +@pytest.fixture(scope='session') +def test_revenue_account_obj() -> Account: + """Get the test revenue account object with ID. + + Use this when you need the revenue account ID (e.g., for source_id + in create_deposit with ID instead of name). + """ + if not _cached_test_accounts: + raise RuntimeError('Test accounts not initialized. Check if _setup_test_data ran.') + + account = next( + (acct for acct in _cached_test_accounts if acct.name == 'Test Revenue'), + None, + ) + if account is None: + account = next( + (acct for acct in _cached_test_accounts if 'test revenue' in acct.name.lower()), + None, + ) + if account is None: + raise RuntimeError('Test Revenue account not found in cached test accounts.') + return account + + @pytest.fixture(scope='session') def test_budget() -> Budget: """Get the test budget. diff --git a/tests/fixtures/transactions.py b/tests/fixtures/transactions.py index 66e7a00..887f36c 100644 --- a/tests/fixtures/transactions.py +++ b/tests/fixtures/transactions.py @@ -23,11 +23,24 @@ def make_create_withdrawal_request( amount: float, description: str, source_id: str, - destination_name: str, + *, + destination_name: str | None = None, + destination_id: str | None = None, budget_id: str | None = None, date: datetime | None = None, ) -> CreateWithdrawalRequest: - """Create a CreateWithdrawalRequest for testing.""" + """Create a CreateWithdrawalRequest for testing. + + Args: + amount: Amount to withdraw + description: Transaction description + source_id: ID of the source asset account + destination_name: Name of expense account (optional, exclusive with destination_id) + destination_id: ID of expense account (optional, exclusive with destination_name) + budget_id: Optional budget ID + date: Transaction date (defaults to now) + + """ if date is None: date = datetime.now(timezone.utc) @@ -36,6 +49,7 @@ def make_create_withdrawal_request( description=description, date=date, source_id=source_id, + destination_id=destination_id, destination_name=destination_name, budget_id=budget_id, ) @@ -45,10 +59,22 @@ def make_create_deposit_request( amount: float, description: str, destination_id: str, - source_name: str, + *, + source_name: str | None = None, + source_id: str | None = None, date: datetime | None = None, ) -> CreateDepositRequest: - """Create a CreateDepositRequest for testing.""" + """Create a CreateDepositRequest for testing. + + Args: + amount: Amount to deposit + description: Transaction description + destination_id: ID of the destination asset account + source_name: Name of the revenue account (optional, mutually exclusive with source_id) + source_id: ID of the revenue account (optional, mutually exclusive with source_name) + date: Transaction date (defaults to now) + + """ if date is None: date = datetime.now(timezone.utc) @@ -56,6 +82,7 @@ def make_create_deposit_request( amount=amount, description=description, date=date, + source_id=source_id, source_name=source_name, destination_id=destination_id, ) diff --git a/tests/integration/test_budgets.py b/tests/integration/test_budgets.py index 2147da6..891bac2 100644 --- a/tests/integration/test_budgets.py +++ b/tests/integration/test_budgets.py @@ -110,7 +110,8 @@ async def test_get_budget_summary(mcp_client: Client): end = next_month.replace(day=1) - timedelta(days=1) result = await mcp_client.call_tool( - 'get_budget_summary', {'req': {'start': start.isoformat(), 'end': end.isoformat()}} + 'get_budget_summary', + {'req': {'start_date': start.isoformat(), 'end_date': end.isoformat()}}, ) summary = BudgetSummary.model_validate(result.structured_content) diff --git a/tests/integration/test_transactions.py b/tests/integration/test_transactions.py index 83abcba..bc34fe4 100644 --- a/tests/integration/test_transactions.py +++ b/tests/integration/test_transactions.py @@ -116,6 +116,40 @@ async def test_create_withdrawal_with_budget( ) +@pytest.mark.asyncio +@pytest.mark.transactions +@pytest.mark.integration +async def test_create_withdrawal_with_destination_id( + mcp_client: Client, + test_asset_account: Account, + test_expense_account_obj: Account, + transaction_cleanup: List[str], +): + """Test creating a withdrawal using destination_id instead of destination_name.""" + result = await mcp_client.call_tool( + 'create_withdrawal', + { + 'req': { + 'amount': 15.00, + 'description': 'Test withdrawal with destination_id', + 'source_id': test_asset_account.id, + 'destination_id': test_expense_account_obj.id, + 'date': datetime.now(timezone.utc).isoformat(), + } + }, + ) + transaction = Transaction.model_validate(result.structured_content) + assert transaction is not None + assert transaction.id is not None + transaction_cleanup.append(transaction.id) + + # Verify transaction was created with the correct destination account + assert transaction.destination_id == test_expense_account_obj.id + assert transaction.destination_name == test_expense_account_obj.name + assert transaction.amount == 15.00 + assert transaction.description == 'Test withdrawal with destination_id' + + @pytest.mark.asyncio @pytest.mark.transactions @pytest.mark.integration @@ -162,6 +196,40 @@ async def test_create_deposit_basic( ) +@pytest.mark.asyncio +@pytest.mark.transactions +@pytest.mark.integration +async def test_create_deposit_with_source_id( + mcp_client: Client, + test_asset_account: Account, + test_revenue_account_obj: Account, + transaction_cleanup: List[str], +): + """Test creating a deposit using source_id instead of source_name.""" + result = await mcp_client.call_tool( + 'create_deposit', + { + 'req': { + 'amount': 250.00, + 'description': 'Test deposit with source_id', + 'destination_id': test_asset_account.id, + 'source_id': test_revenue_account_obj.id, + 'date': datetime.now(timezone.utc).isoformat(), + } + }, + ) + transaction = Transaction.model_validate(result.structured_content) + assert transaction is not None + assert transaction.id is not None + transaction_cleanup.append(transaction.id) + + # Verify transaction was created with the correct source account + assert transaction.source_id == test_revenue_account_obj.id + assert transaction.source_name == test_revenue_account_obj.name + assert transaction.amount == 250.00 + assert transaction.description == 'Test deposit with source_id' + + @pytest.mark.asyncio @pytest.mark.transactions @pytest.mark.integration diff --git a/tests/unit/test_format.py b/tests/unit/test_format.py new file mode 100644 index 0000000..98ab7e9 --- /dev/null +++ b/tests/unit/test_format.py @@ -0,0 +1,19 @@ +"""Unit tests for format script.""" + +from unittest.mock import patch + +from lampyrid.scripts import format + + +class TestFormatScript: + """Test cases for the format script.""" + + @patch('subprocess.run') + def test_main_return_code(self, mock_run): + """Test that format.main returns correct code.""" + mock_run.return_value.returncode = 0 + + ret_code = format.main() + + assert ret_code == 0 + assert mock_run.call_count == 2 diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index 1b79bbe..f268724 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -113,3 +113,35 @@ def test_main_not_called_when_name_is_not_main(self): # But main() should NOT have been called during import # (since __name__ was not '__main__') mock_mcp.run.assert_not_called() + + def test_main_sse_transport_with_different_config(self): + """Test main() with SSE transport using different host/port.""" + with ( + patch('lampyrid.__main__.settings') as mock_settings, + patch('lampyrid.__main__.mcp') as mock_mcp, + ): + # Mock settings for SSE with different values + mock_settings.mcp_transport = 'sse' + mock_settings.mcp_host = 'example.com' + mock_settings.mcp_port = 9000 + + # Call main + main() + + # Verify mcp.run was called with correct SSE parameters + mock_mcp.run.assert_called_once_with(transport='sse', host='example.com', port=9000) + + def test_main_edge_case_empty_transport_string(self): + """Test main() with empty transport string defaults to stdio.""" + with ( + patch('lampyrid.__main__.settings') as mock_settings, + patch('lampyrid.__main__.mcp') as mock_mcp, + ): + # Mock settings with empty transport + mock_settings.mcp_transport = '' + + # Call main + main() + + # Verify mcp.run was called with default stdio + mock_mcp.run.assert_called_once_with(transport='stdio') diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py index 8158628..031bff8 100644 --- a/tests/unit/test_models.py +++ b/tests/unit/test_models.py @@ -1,9 +1,15 @@ """Unit tests for lampyrid models.""" import pytest +from pydantic import ValidationError +from lampyrid.models.firefly_models import TransactionTypeProperty from lampyrid.models.lampyrid_models import ( + CreateBulkTransactionsRequest, + CreateDepositRequest, + CreateWithdrawalRequest, SearchTransactionsRequest, + Transaction, utc_now, ) @@ -43,3 +49,116 @@ def test_search_transactions_request_with_valid_criteria(self): request = SearchTransactionsRequest(query='valid query') assert request.query == 'valid query' + + +class TestCreateWithdrawalRequest: + """Test cases for CreateWithdrawalRequest model.""" + + def test_create_withdrawal_request_allows_destination_id(self): + """Test that CreateWithdrawalRequest accepts destination_id field.""" + request = CreateWithdrawalRequest( + amount=25.50, + description='Test withdrawal', + source_id='1', + destination_id='5', + ) + + assert request.destination_id == '5' + assert request.destination_name is None + + def test_create_withdrawal_request_allows_destination_name(self): + """Test that CreateWithdrawalRequest accepts destination_name field.""" + request = CreateWithdrawalRequest( + amount=25.50, + description='Test withdrawal', + source_id='1', + destination_name='Groceries', + ) + + assert request.destination_name == 'Groceries' + assert request.destination_id is None + + def test_create_withdrawal_request_allows_neither_destination(self): + """Test that destination_id and destination_name may be omitted.""" + request = CreateWithdrawalRequest( + amount=25.50, + description='Test withdrawal', + source_id='1', + ) + + assert request.destination_id is None + assert request.destination_name is None + + +class TestCreateDepositRequest: + """Test cases for CreateDepositRequest model.""" + + def test_create_deposit_request_allows_source_id(self): + """Test that CreateDepositRequest accepts source_id field.""" + request = CreateDepositRequest( + amount=500.00, + description='Test deposit', + destination_id='1', + source_id='7', + ) + + assert request.source_id == '7' + assert request.source_name is None + + def test_create_deposit_request_allows_source_name(self): + """Test that CreateDepositRequest accepts source_name field.""" + request = CreateDepositRequest( + amount=500.00, + description='Test deposit', + destination_id='1', + source_name='Employer', + ) + + assert request.source_name == 'Employer' + assert request.source_id is None + + def test_create_deposit_request_allows_neither_source(self): + """Test that CreateDepositRequest allows neither source_id nor source_name.""" + request = CreateDepositRequest( + amount=500.00, + description='Test deposit', + destination_id='1', + ) + + assert request.source_id is None + assert request.source_name is None + + def test_create_deposit_request_mutual_exclusivity(self): + """Test that source_id and source_name cannot both be provided.""" + with pytest.raises(ValidationError) as exc_info: + CreateDepositRequest( + amount=500.00, + description='Test deposit', + destination_id='1', + source_id='7', + source_name='Employer', + ) + + errors = exc_info.value.errors() + assert len(errors) == 1 + assert 'Cannot specify both source_id and source_name' in str(errors[0]['msg']) + + +class TestCreateBulkTransactionsRequest: + """Test cases for CreateBulkTransactionsRequest model.""" + + def test_create_bulk_transactions_request_atomic_default(self): + """Test CreateBulkTransactionsRequest with default atomic value.""" + transaction = Transaction( + type=TransactionTypeProperty.withdrawal, + amount=25.50, + description='Test withdrawal', + date=utc_now(), + source_id='1', + destination_id='2', + ) + + request = CreateBulkTransactionsRequest(transactions=[transaction]) + + assert len(request.transactions) == 1 + assert request.atomic is True # Default value diff --git a/tests/unit/test_scripts.py b/tests/unit/test_scripts.py new file mode 100644 index 0000000..aacf6d8 --- /dev/null +++ b/tests/unit/test_scripts.py @@ -0,0 +1,262 @@ +"""Unit tests for maintenance scripts.""" + +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from lampyrid.scripts import format, update_schema + + +class TestFormatScript: + """Test cases for the format script.""" + + @patch('subprocess.run') + def test_main_success(self, mock_run): + """Test that format.main runs ruff commands successfully.""" + mock_run.return_value.returncode = 0 + + # Capture stdout to verify messages + with patch('sys.stdout'): + ret_code = format.main() + + assert ret_code == 0 + assert mock_run.call_count == 2 + + # Verify calls use absolute path for cwd + args, kwargs = mock_run.call_args_list[0] + assert args[0] == ['ruff', 'format', '.'] + assert kwargs.get('check') is True + assert isinstance(kwargs.get('cwd'), Path) + assert kwargs.get('cwd').name == 'LamPyrid' + + args, kwargs = mock_run.call_args_list[1] + assert args[0] == ['ruff', 'check', '--fix', '.'] + assert kwargs.get('check') is True + assert isinstance(kwargs.get('cwd'), Path) + + @patch('subprocess.run') + def test_main_formatting_error(self, mock_run): + """Test that format.main handles subprocess errors.""" + import subprocess + + mock_run.side_effect = subprocess.CalledProcessError(1, ['ruff']) + + with patch('sys.stdout'): + ret_code = format.main() + + assert ret_code == 1 + + @patch('subprocess.run') + def test_main_formatting_error_at_second_step(self, mock_run): + """Test that format.main handles subprocess errors at second step.""" + import subprocess + + mock_run.side_effect = [ + None, # First step succeeds + subprocess.CalledProcessError(1, ['ruff']), # Second step fails + ] + + with patch('sys.stdout'): + ret_code = format.main() + + assert ret_code == 1 + assert mock_run.call_count == 2 + + @patch('subprocess.run') + def test_main_file_not_found(self, mock_run): + """Test that format.main handles missing executable.""" + mock_run.side_effect = FileNotFoundError + + with patch('sys.stdout'): + ret_code = format.main() + + assert ret_code == 1 + + +class TestUpdateSchemaScript: + """Test cases for the update_schema script.""" + + def test_parse_versions_from_html(self): + """Test version parsing logic.""" + html = """ + ... + ... + ... + """ + versions = update_schema.parse_versions_from_html(html) + assert '6.4.16' in versions + assert '6.4.14' in versions + assert 'develop' not in versions + assert len(versions) == 2 + + def test_get_latest_stable_version(self): + """Test version sorting logic.""" + versions = ['6.4.14', '6.4.16', '6.5.0'] + latest = update_schema.get_latest_stable_version(versions) + assert latest == '6.5.0' + + @patch('pathlib.Path.read_text') + @patch('pathlib.Path.write_text') + def test_update_pyproject_toml_no_change(self, mock_write, mock_read): + """Test that file is not written if content hasn't changed.""" + mock_read.return_value = 'input = "firefly-iii-6.4.16-v1.yaml"' + + result = update_schema.update_pyproject_toml('6.4.16', '6.4.16') + + assert result is False + mock_write.assert_not_called() + + @patch('pathlib.Path.read_text') + @patch('pathlib.Path.write_text') + def test_update_pyproject_toml_with_change(self, mock_write, mock_read): + """Test that file is updated when version changes.""" + mock_read.return_value = 'input = "firefly-iii-6.4.14-v1.yaml"' + + result = update_schema.update_pyproject_toml('6.4.14', '6.4.16') + + assert result is True + mock_write.assert_called_once() + content = mock_write.call_args[0][0] + assert 'firefly-iii-6.4.16-v1.yaml' in content + + @patch('httpx.get') + def test_download_schema(self, mock_get): + """Test schema downloading.""" + mock_response = MagicMock() + mock_response.content = b'schema content' + mock_get.return_value = mock_response + + content = update_schema.download_schema('6.4.16') + + assert content == b'schema content' + mock_get.assert_called_with( + 'https://api-docs.firefly-iii.org/firefly-iii-6.4.16-v1.yaml', + timeout=30.0, + follow_redirects=True, + ) + + @patch('pathlib.Path.unlink') + @patch('pathlib.Path.exists') + def test_cleanup_old_schema(self, mock_exists, mock_unlink): + """Test cleanup of old schema files.""" + mock_exists.return_value = True + + result = update_schema.cleanup_old_schema('6.4.14', '6.4.16') + + assert result is True + mock_unlink.assert_called_once() + + @patch('pathlib.Path.exists') + @patch('pathlib.Path.unlink') + def test_cleanup_old_schema_no_cleanup_needed(self, mock_unlink, mock_exists): + """Test cleanup when old and new versions are the same.""" + result = update_schema.cleanup_old_schema('6.4.16', '6.4.16') + + assert result is False + mock_exists.assert_not_called() + mock_unlink.assert_not_called() + + @patch('pathlib.Path.exists') + @patch('pathlib.Path.unlink') + def test_cleanup_old_schema_no_old_version(self, mock_unlink, mock_exists): + """Test cleanup when there's no old version.""" + result = update_schema.cleanup_old_schema(None, '6.4.16') + + assert result is False + mock_exists.assert_not_called() + mock_unlink.assert_not_called() + + @patch('pathlib.Path.exists') + @patch('pathlib.Path.unlink') + def test_cleanup_old_schema_file_not_exists(self, mock_unlink, mock_exists): + """Test cleanup when old schema file doesn't exist.""" + mock_exists.return_value = False + + result = update_schema.cleanup_old_schema('6.4.14', '6.4.16') + + assert result is False + mock_exists.assert_called_once() + mock_unlink.assert_not_called() + + def test_parse_semver(self): + """Test version parsing for semver comparison.""" + assert update_schema.parse_semver('6.4.16') == (6, 4, 16) + assert update_schema.parse_semver('6.5.0') == (6, 5, 0) + assert update_schema.parse_semver('10.0.1') == (10, 0, 1) + + @patch('pathlib.Path.exists') + def test_get_current_schema_version_not_found(self, mock_exists): + """Test get_current_schema_version when file doesn't exist.""" + mock_exists.return_value = False + + version = update_schema.get_current_schema_version() + + assert version is None + + @patch('pathlib.Path.exists') + @patch('pathlib.Path.read_text') + def test_get_current_schema_version_no_match(self, mock_read, mock_exists): + """Test get_current_schema_version when no schema reference found.""" + mock_exists.return_value = True + mock_read.return_value = 'some other content without schema' + + version = update_schema.get_current_schema_version() + + assert version is None + + def test_get_latest_stable_version_empty_list(self): + """Test get_latest_stable_version with empty list.""" + with pytest.raises(ValueError, match='No stable versions found'): + update_schema.get_latest_stable_version([]) + + def test_parse_versions_from_html_no_matches(self): + """Test parse_versions_from_html with no matching versions.""" + html = '...' + + versions = update_schema.parse_versions_from_html(html) + + assert len(versions) == 0 + + def test_parse_versions_from_html_ignores_duplicates(self): + """Test parse_versions_from_html ignores duplicate versions.""" + html = """ + ... + ... + ... + """ + + versions = update_schema.parse_versions_from_html(html) + + assert versions == ['6.4.16', '6.4.14'] + assert len(versions) == 2 + + @patch('pathlib.Path.read_text') + def test_get_current_schema_version_with_pyproject_toml(self, mock_read): + """Test get_current_schema_version when pyproject.toml exists.""" + mock_read.return_value = 'some content input = "firefly-iii-6.4.16-v1.yaml" more content' + + version = update_schema.get_current_schema_version() + + assert version == '6.4.16' + + @patch('pathlib.Path.read_text') + @patch('pathlib.Path.write_text') + def test_update_pyproject_toml_regex_fallback(self, mock_write, mock_read): + """Test update_pyproject_toml when direct string replacement fails.""" + mock_read.return_value = 'input = "firefly-iii-6.4.14-v1.yaml"' + + result = update_schema.get_current_schema_version() + + assert result == '6.4.14' + + @patch('subprocess.run') + def test_regenerate_models_failure(self, mock_run): + """Test regenerate_models when subprocess fails.""" + import subprocess + + mock_run.side_effect = subprocess.CalledProcessError(1, 'datamodel-codegen', stderr='error') + + result = update_schema.regenerate_models() + + assert result is False diff --git a/tests/unit/test_transactions_service.py b/tests/unit/test_transactions_service.py deleted file mode 100644 index 2c9525b..0000000 --- a/tests/unit/test_transactions_service.py +++ /dev/null @@ -1,262 +0,0 @@ -"""Unit tests for TransactionService.""" - -from datetime import date -from unittest.mock import AsyncMock, MagicMock - -import pytest - -from lampyrid.models.lampyrid_models import SearchTransactionsRequest -from lampyrid.services.transactions import TransactionService - - -class TestTransactionServiceSearchSanitization: - """Test cases for search_transactions input sanitization.""" - - @pytest.fixture - def mock_client(self): - """Create a mock FireflyClient.""" - client = MagicMock() - client.search_transactions = AsyncMock() - # Return a mock TransactionArray with required structure - mock_response = MagicMock() - mock_response.data = [] - mock_response.meta = MagicMock() - mock_response.meta.pagination = MagicMock() - mock_response.meta.pagination.total = 0 - mock_response.meta.pagination.count = 0 - mock_response.meta.pagination.per_page = 50 - mock_response.meta.pagination.current_page = 1 - mock_response.meta.pagination.total_pages = 0 - client.search_transactions.return_value = mock_response - return client - - @pytest.fixture - def service(self, mock_client): - """Create a TransactionService with mock client.""" - return TransactionService(mock_client) - - def _get_query_from_call(self, mock_client) -> str: - """Extract the query string passed to search_transactions.""" - call_args = mock_client.search_transactions.call_args - return call_args.kwargs.get('query', call_args.args[0] if call_args.args else '') - - @pytest.mark.asyncio - async def test_search_transactions_sanitizes_description_contains(self, service, mock_client): - """Test that description_contains values are properly sanitized.""" - req = SearchTransactionsRequest(description_contains='test "quoted" value') - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - # Should escape quotes and wrap in quotes since it contains special chars - assert 'description_contains:"test \\"quoted\\" value"' in query - - @pytest.mark.asyncio - async def test_search_transactions_sanitizes_category(self, service, mock_client): - """Test that category values are properly sanitized.""" - req = SearchTransactionsRequest(category='Food & "Dining"') - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - # Should escape quotes and wrap in quotes since it contains special chars - assert 'category_is:"Food & \\"Dining\\""' in query - - @pytest.mark.asyncio - async def test_search_transactions_sanitizes_budget(self, service, mock_client): - """Test that budget values are properly sanitized.""" - req = SearchTransactionsRequest(budget='Monthly "Expenses"') - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - # Should escape quotes and wrap in quotes since it contains special chars - assert 'budget_is:"Monthly \\"Expenses\\""' in query - - @pytest.mark.asyncio - async def test_search_transactions_sanitizes_account_contains(self, service, mock_client): - """Test that account_contains values are properly sanitized.""" - req = SearchTransactionsRequest(account_contains='Bank "Account"') - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - # Should escape quotes and wrap in quotes since it contains special chars - assert 'account_contains:"Bank \\"Account\\""' in query - - @pytest.mark.asyncio - async def test_search_transactions_handles_backslashes_in_values(self, service, mock_client): - """Test that backslashes in values are properly escaped.""" - req = SearchTransactionsRequest(description_contains='path\\to\\file') - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - # Backslashes should be escaped (doubled) - assert 'description_contains:path\\\\to\\\\file' in query - - @pytest.mark.asyncio - async def test_search_transactions_handles_spaces_in_values(self, service, mock_client): - """Test that values with spaces are properly quoted.""" - req = SearchTransactionsRequest(category='Food and Drinks') - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - # Should be wrapped in quotes due to spaces - assert 'category_is:"Food and Drinks"' in query - - @pytest.mark.asyncio - async def test_search_transactions_simple_values_not_quoted(self, service, mock_client): - """Test that simple values without special chars are not unnecessarily quoted.""" - req = SearchTransactionsRequest(category='Groceries') - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - # Should NOT be wrapped in quotes (no special chars) - assert 'category_is:Groceries' in query - assert 'category_is:"Groceries"' not in query - - @pytest.mark.asyncio - async def test_search_transactions_handles_mixed_special_chars(self, service, mock_client): - """Test values with both quotes and backslashes.""" - req = SearchTransactionsRequest(description_contains='test "quoted" and\\backslash') - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - # Both quotes and backslashes should be escaped, wrapped in quotes - assert 'description_contains:"test \\"quoted\\" and\\\\backslash"' in query - - @pytest.mark.asyncio - async def test_search_transactions_handles_single_quotes(self, service, mock_client): - """Test that single quotes trigger quoting (per _sanitize_value logic).""" - req = SearchTransactionsRequest(budget="don't spend") - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - # Should be wrapped in quotes due to single quote - assert 'budget_is:"don\'t spend"' in query - - @pytest.mark.asyncio - async def test_search_transactions_multiple_sanitized_fields(self, service, mock_client): - """Test that multiple fields are all properly sanitized.""" - req = SearchTransactionsRequest( - description_contains='desc "test"', - category='cat "test"', - budget='budget "test"', - account_contains='account "test"', - ) - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - # All fields should be properly sanitized - assert 'description_contains:"desc \\"test\\""' in query - assert 'category_is:"cat \\"test\\""' in query - assert 'budget_is:"budget \\"test\\""' in query - assert 'account_contains:"account \\"test\\""' in query - - @pytest.mark.asyncio - async def test_search_transactions_non_string_fields_unchanged(self, service, mock_client): - """Test that non-string fields like account_id are not affected.""" - req = SearchTransactionsRequest(account_id='123') - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - # account_id should be formatted as-is (string ID) - assert 'account_id:123' in query - - -class TestTransactionServiceSearchQueryConstruction: - """Test cases for search_transactions query construction.""" - - @pytest.fixture - def mock_client(self): - """Create a mock FireflyClient.""" - client = MagicMock() - client.search_transactions = AsyncMock() - mock_response = MagicMock() - mock_response.data = [] - mock_response.meta = MagicMock() - mock_response.meta.pagination = MagicMock() - mock_response.meta.pagination.total = 0 - mock_response.meta.pagination.count = 0 - mock_response.meta.pagination.per_page = 50 - mock_response.meta.pagination.current_page = 1 - mock_response.meta.pagination.total_pages = 0 - client.search_transactions.return_value = mock_response - return client - - @pytest.fixture - def service(self, mock_client): - """Create a TransactionService with mock client.""" - return TransactionService(mock_client) - - def _get_query_from_call(self, mock_client) -> str: - """Extract the query string passed to search_transactions.""" - call_args = mock_client.search_transactions.call_args - return call_args.kwargs.get('query', call_args.args[0] if call_args.args else '') - - @pytest.mark.asyncio - async def test_search_transactions_with_raw_query(self, service, mock_client): - """Test that raw query is included in the final query.""" - req = SearchTransactionsRequest(query='some raw query') - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - assert 'some raw query' in query - - @pytest.mark.asyncio - async def test_search_transactions_with_type_filter(self, service, mock_client): - """Test that type filter is properly formatted.""" - req = SearchTransactionsRequest(type='withdrawal') - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - assert 'type:withdrawal' in query - - @pytest.mark.asyncio - async def test_search_transactions_with_amount_filters(self, service, mock_client): - """Test that amount filters are properly formatted.""" - req = SearchTransactionsRequest(amount_equals=100.50, amount_more=50.0, amount_less=200.0) - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - assert 'amount:100.5' in query - assert 'more:50.0' in query - assert 'less:200.0' in query - - @pytest.mark.asyncio - async def test_search_transactions_with_date_filters(self, service, mock_client): - """Test that date filters are properly formatted.""" - req = SearchTransactionsRequest( - date_on=date(2024, 1, 15), - date_after=date(2024, 1, 1), - date_before=date(2024, 1, 31), - ) - - await service.search_transactions(req) - - query = self._get_query_from_call(mock_client) - assert 'date_on:2024-01-15' in query - assert 'date_after:2024-01-01' in query - assert 'date_before:2024-01-31' in query - - @pytest.mark.asyncio - async def test_search_transactions_passes_pagination_params(self, service, mock_client): - """Test that pagination parameters are passed to client.""" - req = SearchTransactionsRequest(query='test', page=2, limit=25) - - await service.search_transactions(req) - - mock_client.search_transactions.assert_called_once() - call_kwargs = mock_client.search_transactions.call_args.kwargs - assert call_kwargs['page'] == 2 - assert call_kwargs['limit'] == 25 diff --git a/uv.lock b/uv.lock index b557869..8ed59c9 100644 --- a/uv.lock +++ b/uv.lock @@ -73,7 +73,7 @@ wheels = [ [[package]] name = "black" -version = "25.12.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -83,23 +83,23 @@ dependencies = [ { name = "platformdirs" }, { name = "pytokens" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/d9/07b458a3f1c525ac392b5edc6b191ff140b596f9d77092429417a54e249d/black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7", size = 659264, upload-time = "2025-12-08T01:40:52.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/13/88/560b11e521c522440af991d46848a2bde64b5f7202ec14e1f46f9509d328/black-26.1.0.tar.gz", hash = "sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58", size = 658785, upload-time = "2026-01-18T04:50:11.993Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/46/1d8f2542210c502e2ae1060b2e09e47af6a5e5963cb78e22ec1a11170b28/black-25.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0a0953b134f9335c2434864a643c842c44fba562155c738a2a37a4d61f00cad5", size = 1917015, upload-time = "2025-12-08T01:53:27.987Z" }, - { url = "https://files.pythonhosted.org/packages/41/37/68accadf977672beb8e2c64e080f568c74159c1aaa6414b4cd2aef2d7906/black-25.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2355bbb6c3b76062870942d8cc450d4f8ac71f9c93c40122762c8784df49543f", size = 1741830, upload-time = "2025-12-08T01:54:36.861Z" }, - { url = "https://files.pythonhosted.org/packages/ac/76/03608a9d8f0faad47a3af3a3c8c53af3367f6c0dd2d23a84710456c7ac56/black-25.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9678bd991cc793e81d19aeeae57966ee02909877cb65838ccffef24c3ebac08f", size = 1791450, upload-time = "2025-12-08T01:44:52.581Z" }, - { url = "https://files.pythonhosted.org/packages/06/99/b2a4bd7dfaea7964974f947e1c76d6886d65fe5d24f687df2d85406b2609/black-25.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:97596189949a8aad13ad12fcbb4ae89330039b96ad6742e6f6b45e75ad5cfd83", size = 1452042, upload-time = "2025-12-08T01:46:13.188Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7c/d9825de75ae5dd7795d007681b752275ea85a1c5d83269b4b9c754c2aaab/black-25.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:778285d9ea197f34704e3791ea9404cd6d07595745907dd2ce3da7a13627b29b", size = 1267446, upload-time = "2025-12-08T01:46:14.497Z" }, - { url = "https://files.pythonhosted.org/packages/68/11/21331aed19145a952ad28fca2756a1433ee9308079bd03bd898e903a2e53/black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828", size = 206191, upload-time = "2025-12-08T01:40:50.963Z" }, + { url = "https://files.pythonhosted.org/packages/6a/83/be35a175aacfce4b05584ac415fd317dd6c24e93a0af2dcedce0f686f5d8/black-26.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc8c71656a79ca49b8d3e2ce8103210c9481c57798b48deeb3a8bb02db5f115", size = 1871864, upload-time = "2026-01-18T04:59:47.586Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f5/d33696c099450b1274d925a42b7a030cd3ea1f56d72e5ca8bbed5f52759c/black-26.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b22b3810451abe359a964cc88121d57f7bce482b53a066de0f1584988ca36e79", size = 1701009, upload-time = "2026-01-18T04:59:49.443Z" }, + { url = "https://files.pythonhosted.org/packages/1b/87/670dd888c537acb53a863bc15abbd85b22b429237d9de1b77c0ed6b79c42/black-26.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53c62883b3f999f14e5d30b5a79bd437236658ad45b2f853906c7cbe79de00af", size = 1767806, upload-time = "2026-01-18T04:59:50.769Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9c/cd3deb79bfec5bcf30f9d2100ffeec63eecce826eb63e3961708b9431ff1/black-26.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:f016baaadc423dc960cdddf9acae679e71ee02c4c341f78f3179d7e4819c095f", size = 1433217, upload-time = "2026-01-18T04:59:52.218Z" }, + { url = "https://files.pythonhosted.org/packages/4e/29/f3be41a1cf502a283506f40f5d27203249d181f7a1a2abce1c6ce188035a/black-26.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:66912475200b67ef5a0ab665011964bf924745103f51977a78b4fb92a9fc1bf0", size = 1245773, upload-time = "2026-01-18T04:59:54.457Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3d/51bdb3ecbfadfaf825ec0c75e1de6077422b4afa2091c6c9ba34fbfc0c2d/black-26.1.0-py3-none-any.whl", hash = "sha256:1054e8e47ebd686e078c0bb0eaf31e6ce69c966058d122f2c0c950311f9f3ede", size = 204010, upload-time = "2026-01-18T04:50:09.978Z" }, ] [[package]] name = "cachetools" -version = "6.2.4" +version = "6.2.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/1d/ede8680603f6016887c062a2cf4fc8fdba905866a3ab8831aa8aa651320c/cachetools-6.2.4.tar.gz", hash = "sha256:82c5c05585e70b6ba2d3ae09ea60b79548872185d2f24ae1f2709d37299fd607", size = 31731, upload-time = "2025-12-15T18:24:53.744Z" } +sdist = { url = "https://files.pythonhosted.org/packages/39/91/d9ae9a66b01102a18cd16db0cf4cd54187ffe10f0865cc80071a4104fbb3/cachetools-6.2.6.tar.gz", hash = "sha256:16c33e1f276b9a9c0b49ab5782d901e3ad3de0dd6da9bf9bcd29ac5672f2f9e6", size = 32363, upload-time = "2026-01-27T20:32:59.956Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/fc/1d7b80d0eb7b714984ce40efc78859c022cd930e402f599d8ca9e39c78a4/cachetools-6.2.4-py3-none-any.whl", hash = "sha256:69a7a52634fed8b8bf6e24a050fb60bff1c9bd8f6d24572b99c32d4e71e62a51", size = 11551, upload-time = "2025-12-15T18:24:52.332Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/f458fa2c388e79dd9d8b9b0c99f1d31b568f27388f2fdba7bb66bbc0c6ed/cachetools-6.2.6-py3-none-any.whl", hash = "sha256:8c9717235b3c651603fff0076db52d6acbfd1b338b8ed50256092f7ce9c85bda", size = 11668, upload-time = "2026-01-27T20:32:58.527Z" }, ] [[package]] @@ -201,98 +201,95 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" }, - { url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" }, - { url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" }, - { url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" }, - { url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" }, - { url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" }, - { url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" }, - { url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" }, - { url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" }, - { url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" }, - { url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" }, - { url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" }, - { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, +version = "7.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/53/1da9e51a0775634b04fcc11eb25c002fc58ee4f92ce2e8512f94ac5fc5bf/coverage-7.13.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:387a825f43d680e7310e6f325b2167dd093bc8ffd933b83e9aa0983cf6e0a2ef", size = 219213, upload-time = "2026-01-25T12:59:11.909Z" }, + { url = "https://files.pythonhosted.org/packages/46/35/b3caac3ebbd10230fea5a33012b27d19e999a17c9285c4228b4b2e35b7da/coverage-7.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f0d7fea9d8e5d778cd5a9e8fc38308ad688f02040e883cdc13311ef2748cb40f", size = 219549, upload-time = "2026-01-25T12:59:13.638Z" }, + { url = "https://files.pythonhosted.org/packages/76/9c/e1cf7def1bdc72c1907e60703983a588f9558434a2ff94615747bd73c192/coverage-7.13.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080afb413be106c95c4ee96b4fffdc9e2fa56a8bbf90b5c0918e5c4449412f5", size = 250586, upload-time = "2026-01-25T12:59:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ba/49/f54ec02ed12be66c8d8897270505759e057b0c68564a65c429ccdd1f139e/coverage-7.13.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7fc042ba3c7ce25b8a9f097eb0f32a5ce1ccdb639d9eec114e26def98e1f8a4", size = 253093, upload-time = "2026-01-25T12:59:17.491Z" }, + { url = "https://files.pythonhosted.org/packages/fb/5e/aaf86be3e181d907e23c0f61fccaeb38de8e6f6b47aed92bf57d8fc9c034/coverage-7.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0ba505e021557f7f8173ee8cd6b926373d8653e5ff7581ae2efce1b11ef4c27", size = 254446, upload-time = "2026-01-25T12:59:19.752Z" }, + { url = "https://files.pythonhosted.org/packages/28/c8/a5fa01460e2d75b0c853b392080d6829d3ca8b5ab31e158fa0501bc7c708/coverage-7.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7de326f80e3451bd5cc7239ab46c73ddb658fe0b7649476bc7413572d36cd548", size = 250615, upload-time = "2026-01-25T12:59:21.928Z" }, + { url = "https://files.pythonhosted.org/packages/86/0b/6d56315a55f7062bb66410732c24879ccb2ec527ab6630246de5fe45a1df/coverage-7.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:abaea04f1e7e34841d4a7b343904a3f59481f62f9df39e2cd399d69a187a9660", size = 252452, upload-time = "2026-01-25T12:59:23.592Z" }, + { url = "https://files.pythonhosted.org/packages/30/19/9bc550363ebc6b0ea121977ee44d05ecd1e8bf79018b8444f1028701c563/coverage-7.13.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9f93959ee0c604bccd8e0697be21de0887b1f73efcc3aa73a3ec0fd13feace92", size = 250418, upload-time = "2026-01-25T12:59:25.392Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/580530a31ca2f0cc6f07a8f2ab5460785b02bb11bdf815d4c4d37a4c5169/coverage-7.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:13fe81ead04e34e105bf1b3c9f9cdf32ce31736ee5d90a8d2de02b9d3e1bcb82", size = 250231, upload-time = "2026-01-25T12:59:27.888Z" }, + { url = "https://files.pythonhosted.org/packages/e2/42/dd9093f919dc3088cb472893651884bd675e3df3d38a43f9053656dca9a2/coverage-7.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d6d16b0f71120e365741bca2cb473ca6fe38930bc5431c5e850ba949f708f892", size = 251888, upload-time = "2026-01-25T12:59:29.636Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a6/0af4053e6e819774626e133c3d6f70fae4d44884bfc4b126cb647baee8d3/coverage-7.13.2-cp314-cp314-win32.whl", hash = "sha256:9b2f4714bb7d99ba3790ee095b3b4ac94767e1347fe424278a0b10acb3ff04fe", size = 221968, upload-time = "2026-01-25T12:59:31.424Z" }, + { url = "https://files.pythonhosted.org/packages/c4/cc/5aff1e1f80d55862442855517bb8ad8ad3a68639441ff6287dde6a58558b/coverage-7.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4121a90823a063d717a96e0a0529c727fb31ea889369a0ee3ec00ed99bf6859", size = 222783, upload-time = "2026-01-25T12:59:33.118Z" }, + { url = "https://files.pythonhosted.org/packages/de/20/09abafb24f84b3292cc658728803416c15b79f9ee5e68d25238a895b07d9/coverage-7.13.2-cp314-cp314-win_arm64.whl", hash = "sha256:6873f0271b4a15a33e7590f338d823f6f66f91ed147a03938d7ce26efd04eee6", size = 221348, upload-time = "2026-01-25T12:59:34.939Z" }, + { url = "https://files.pythonhosted.org/packages/b6/60/a3820c7232db63be060e4019017cd3426751c2699dab3c62819cdbcea387/coverage-7.13.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f61d349f5b7cd95c34017f1927ee379bfbe9884300d74e07cf630ccf7a610c1b", size = 219950, upload-time = "2026-01-25T12:59:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/fd/37/e4ef5975fdeb86b1e56db9a82f41b032e3d93a840ebaf4064f39e770d5c5/coverage-7.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a43d34ce714f4ca674c0d90beb760eb05aad906f2c47580ccee9da8fe8bfb417", size = 220209, upload-time = "2026-01-25T12:59:38.339Z" }, + { url = "https://files.pythonhosted.org/packages/54/df/d40e091d00c51adca1e251d3b60a8b464112efa3004949e96a74d7c19a64/coverage-7.13.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bff1b04cb9d4900ce5c56c4942f047dc7efe57e2608cb7c3c8936e9970ccdbee", size = 261576, upload-time = "2026-01-25T12:59:40.446Z" }, + { url = "https://files.pythonhosted.org/packages/c5/44/5259c4bed54e3392e5c176121af9f71919d96dde853386e7730e705f3520/coverage-7.13.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6ae99e4560963ad8e163e819e5d77d413d331fd00566c1e0856aa252303552c1", size = 263704, upload-time = "2026-01-25T12:59:42.346Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/ae9f005827abcbe2c70157459ae86053971c9fa14617b63903abbdce26d9/coverage-7.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e79a8c7d461820257d9aa43716c4efc55366d7b292e46b5b37165be1d377405d", size = 266109, upload-time = "2026-01-25T12:59:44.073Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c0/8e279c1c0f5b1eaa3ad9b0fb7a5637fc0379ea7d85a781c0fe0bb3cfc2ab/coverage-7.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:060ee84f6a769d40c492711911a76811b4befb6fba50abb450371abb720f5bd6", size = 260686, upload-time = "2026-01-25T12:59:45.804Z" }, + { url = "https://files.pythonhosted.org/packages/b2/47/3a8112627e9d863e7cddd72894171c929e94491a597811725befdcd76bce/coverage-7.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bca209d001fd03ea2d978f8a4985093240a355c93078aee3f799852c23f561a", size = 263568, upload-time = "2026-01-25T12:59:47.929Z" }, + { url = "https://files.pythonhosted.org/packages/92/bc/7ea367d84afa3120afc3ce6de294fd2dcd33b51e2e7fbe4bbfd200f2cb8c/coverage-7.13.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6b8092aa38d72f091db61ef83cb66076f18f02da3e1a75039a4f218629600e04", size = 261174, upload-time = "2026-01-25T12:59:49.717Z" }, + { url = "https://files.pythonhosted.org/packages/33/b7/f1092dcecb6637e31cc2db099581ee5c61a17647849bae6b8261a2b78430/coverage-7.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4a3158dc2dcce5200d91ec28cd315c999eebff355437d2765840555d765a6e5f", size = 260017, upload-time = "2026-01-25T12:59:51.463Z" }, + { url = "https://files.pythonhosted.org/packages/2b/cd/f3d07d4b95fbe1a2ef0958c15da614f7e4f557720132de34d2dc3aa7e911/coverage-7.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3973f353b2d70bd9796cc12f532a05945232ccae966456c8ed7034cb96bbfd6f", size = 262337, upload-time = "2026-01-25T12:59:53.407Z" }, + { url = "https://files.pythonhosted.org/packages/e0/db/b0d5b2873a07cb1e06a55d998697c0a5a540dcefbf353774c99eb3874513/coverage-7.13.2-cp314-cp314t-win32.whl", hash = "sha256:79f6506a678a59d4ded048dc72f1859ebede8ec2b9a2d509ebe161f01c2879d3", size = 222749, upload-time = "2026-01-25T12:59:56.316Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2f/838a5394c082ac57d85f57f6aba53093b30d9089781df72412126505716f/coverage-7.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:196bfeabdccc5a020a57d5a368c681e3a6ceb0447d153aeccc1ab4d70a5032ba", size = 223857, upload-time = "2026-01-25T12:59:58.201Z" }, + { url = "https://files.pythonhosted.org/packages/44/d4/b608243e76ead3a4298824b50922b89ef793e50069ce30316a65c1b4d7ef/coverage-7.13.2-cp314-cp314t-win_arm64.whl", hash = "sha256:69269ab58783e090bfbf5b916ab3d188126e22d6070bbfc93098fdd474ef937c", size = 221881, upload-time = "2026-01-25T13:00:00.449Z" }, + { url = "https://files.pythonhosted.org/packages/d2/db/d291e30fdf7ea617a335531e72294e0c723356d7fdde8fba00610a76bda9/coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5", size = 210943, upload-time = "2026-01-25T13:00:02.388Z" }, ] [[package]] name = "cryptography" -version = "46.0.3" +version = "46.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, - { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, - { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, - { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, - { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, - { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, - { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, - { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, - { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, - { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, - { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, - { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, - { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, - { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, - { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, - { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, - { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, - { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, - { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, - { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, - { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/78/19/f748958276519adf6a0c1e79e7b8860b4830dda55ccdf29f2719b5fc499c/cryptography-46.0.4.tar.gz", hash = "sha256:bfd019f60f8abc2ed1b9be4ddc21cfef059c841d86d710bb69909a688cbb8f59", size = 749301, upload-time = "2026-01-28T00:24:37.379Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/99/157aae7949a5f30d51fcb1a9851e8ebd5c74bf99b5285d8bb4b8b9ee641e/cryptography-46.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:281526e865ed4166009e235afadf3a4c4cba6056f99336a99efba65336fd5485", size = 7173686, upload-time = "2026-01-28T00:23:07.515Z" }, + { url = "https://files.pythonhosted.org/packages/87/91/874b8910903159043b5c6a123b7e79c4559ddd1896e38967567942635778/cryptography-46.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f14fba5bf6f4390d7ff8f086c566454bff0411f6d8aa7af79c88b6f9267aecc", size = 4275871, upload-time = "2026-01-28T00:23:09.439Z" }, + { url = "https://files.pythonhosted.org/packages/c0/35/690e809be77896111f5b195ede56e4b4ed0435b428c2f2b6d35046fbb5e8/cryptography-46.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47bcd19517e6389132f76e2d5303ded6cf3f78903da2158a671be8de024f4cd0", size = 4423124, upload-time = "2026-01-28T00:23:11.529Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5b/a26407d4f79d61ca4bebaa9213feafdd8806dc69d3d290ce24996d3cfe43/cryptography-46.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:01df4f50f314fbe7009f54046e908d1754f19d0c6d3070df1e6268c5a4af09fa", size = 4277090, upload-time = "2026-01-28T00:23:13.123Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d8/4bb7aec442a9049827aa34cee1aa83803e528fa55da9a9d45d01d1bb933e/cryptography-46.0.4-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5aa3e463596b0087b3da0dbe2b2487e9fc261d25da85754e30e3b40637d61f81", size = 4947652, upload-time = "2026-01-28T00:23:14.554Z" }, + { url = "https://files.pythonhosted.org/packages/2b/08/f83e2e0814248b844265802d081f2fac2f1cbe6cd258e72ba14ff006823a/cryptography-46.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0a9ad24359fee86f131836a9ac3bffc9329e956624a2d379b613f8f8abaf5255", size = 4455157, upload-time = "2026-01-28T00:23:16.443Z" }, + { url = "https://files.pythonhosted.org/packages/0a/05/19d849cf4096448779d2dcc9bb27d097457dac36f7273ffa875a93b5884c/cryptography-46.0.4-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:dc1272e25ef673efe72f2096e92ae39dea1a1a450dd44918b15351f72c5a168e", size = 3981078, upload-time = "2026-01-28T00:23:17.838Z" }, + { url = "https://files.pythonhosted.org/packages/e6/89/f7bac81d66ba7cde867a743ea5b37537b32b5c633c473002b26a226f703f/cryptography-46.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:de0f5f4ec8711ebc555f54735d4c673fc34b65c44283895f1a08c2b49d2fd99c", size = 4276213, upload-time = "2026-01-28T00:23:19.257Z" }, + { url = "https://files.pythonhosted.org/packages/da/9f/7133e41f24edd827020ad21b068736e792bc68eecf66d93c924ad4719fb3/cryptography-46.0.4-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:eeeb2e33d8dbcccc34d64651f00a98cb41b2dc69cef866771a5717e6734dfa32", size = 4912190, upload-time = "2026-01-28T00:23:21.244Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f7/6d43cbaddf6f65b24816e4af187d211f0bc536a29961f69faedc48501d8e/cryptography-46.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3d425eacbc9aceafd2cb429e42f4e5d5633c6f873f5e567077043ef1b9bbf616", size = 4454641, upload-time = "2026-01-28T00:23:22.866Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4f/ebd0473ad656a0ac912a16bd07db0f5d85184924e14fc88feecae2492834/cryptography-46.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91627ebf691d1ea3976a031b61fb7bac1ccd745afa03602275dda443e11c8de0", size = 4405159, upload-time = "2026-01-28T00:23:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d1/f7/7923886f32dc47e27adeff8246e976d77258fd2aa3efdd1754e4e323bf49/cryptography-46.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2d08bc22efd73e8854b0b7caff402d735b354862f1145d7be3b9c0f740fef6a0", size = 4666059, upload-time = "2026-01-28T00:23:26.766Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a7/0fca0fd3591dffc297278a61813d7f661a14243dd60f499a7a5b48acb52a/cryptography-46.0.4-cp311-abi3-win32.whl", hash = "sha256:82a62483daf20b8134f6e92898da70d04d0ef9a75829d732ea1018678185f4f5", size = 3026378, upload-time = "2026-01-28T00:23:28.317Z" }, + { url = "https://files.pythonhosted.org/packages/2d/12/652c84b6f9873f0909374864a57b003686c642ea48c84d6c7e2c515e6da5/cryptography-46.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:6225d3ebe26a55dbc8ead5ad1265c0403552a63336499564675b29eb3184c09b", size = 3478614, upload-time = "2026-01-28T00:23:30.275Z" }, + { url = "https://files.pythonhosted.org/packages/b9/27/542b029f293a5cce59349d799d4d8484b3b1654a7b9a0585c266e974a488/cryptography-46.0.4-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:485e2b65d25ec0d901bca7bcae0f53b00133bf3173916d8e421f6fddde103908", size = 7116417, upload-time = "2026-01-28T00:23:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f5/559c25b77f40b6bf828eabaf988efb8b0e17b573545edb503368ca0a2a03/cryptography-46.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:078e5f06bd2fa5aea5a324f2a09f914b1484f1d0c2a4d6a8a28c74e72f65f2da", size = 4264508, upload-time = "2026-01-28T00:23:34.264Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/551fa162d33074b660dc35c9bc3616fefa21a0e8c1edd27b92559902e408/cryptography-46.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dce1e4f068f03008da7fa51cc7abc6ddc5e5de3e3d1550334eaf8393982a5829", size = 4409080, upload-time = "2026-01-28T00:23:35.793Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/4d8d129a755f5d6df1bbee69ea2f35ebfa954fa1847690d1db2e8bca46a5/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2067461c80271f422ee7bdbe79b9b4be54a5162e90345f86a23445a0cf3fd8a2", size = 4270039, upload-time = "2026-01-28T00:23:37.263Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f5/ed3fcddd0a5e39321e595e144615399e47e7c153a1fb8c4862aec3151ff9/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:c92010b58a51196a5f41c3795190203ac52edfd5dc3ff99149b4659eba9d2085", size = 4926748, upload-time = "2026-01-28T00:23:38.884Z" }, + { url = "https://files.pythonhosted.org/packages/43/ae/9f03d5f0c0c00e85ecb34f06d3b79599f20630e4db91b8a6e56e8f83d410/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:829c2b12bbc5428ab02d6b7f7e9bbfd53e33efd6672d21341f2177470171ad8b", size = 4442307, upload-time = "2026-01-28T00:23:40.56Z" }, + { url = "https://files.pythonhosted.org/packages/8b/22/e0f9f2dae8040695103369cf2283ef9ac8abe4d51f68710bec2afd232609/cryptography-46.0.4-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:62217ba44bf81b30abaeda1488686a04a702a261e26f87db51ff61d9d3510abd", size = 3959253, upload-time = "2026-01-28T00:23:42.827Z" }, + { url = "https://files.pythonhosted.org/packages/01/5b/6a43fcccc51dae4d101ac7d378a8724d1ba3de628a24e11bf2f4f43cba4d/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:9c2da296c8d3415b93e6053f5a728649a87a48ce084a9aaf51d6e46c87c7f2d2", size = 4269372, upload-time = "2026-01-28T00:23:44.655Z" }, + { url = "https://files.pythonhosted.org/packages/17/b7/0f6b8c1dd0779df2b526e78978ff00462355e31c0a6f6cff8a3e99889c90/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9b34d8ba84454641a6bf4d6762d15847ecbd85c1316c0a7984e6e4e9f748ec2e", size = 4891908, upload-time = "2026-01-28T00:23:46.48Z" }, + { url = "https://files.pythonhosted.org/packages/83/17/259409b8349aa10535358807a472c6a695cf84f106022268d31cea2b6c97/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:df4a817fa7138dd0c96c8c8c20f04b8aaa1fac3bbf610913dcad8ea82e1bfd3f", size = 4441254, upload-time = "2026-01-28T00:23:48.403Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fe/e4a1b0c989b00cee5ffa0764401767e2d1cf59f45530963b894129fd5dce/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b1de0ebf7587f28f9190b9cb526e901bf448c9e6a99655d2b07fff60e8212a82", size = 4396520, upload-time = "2026-01-28T00:23:50.26Z" }, + { url = "https://files.pythonhosted.org/packages/b3/81/ba8fd9657d27076eb40d6a2f941b23429a3c3d2f56f5a921d6b936a27bc9/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9b4d17bc7bd7cdd98e3af40b441feaea4c68225e2eb2341026c84511ad246c0c", size = 4651479, upload-time = "2026-01-28T00:23:51.674Z" }, + { url = "https://files.pythonhosted.org/packages/00/03/0de4ed43c71c31e4fe954edd50b9d28d658fef56555eba7641696370a8e2/cryptography-46.0.4-cp314-cp314t-win32.whl", hash = "sha256:c411f16275b0dea722d76544a61d6421e2cc829ad76eec79280dbdc9ddf50061", size = 3001986, upload-time = "2026-01-28T00:23:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/5c/70/81830b59df7682917d7a10f833c4dab2a5574cd664e86d18139f2b421329/cryptography-46.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:728fedc529efc1439eb6107b677f7f7558adab4553ef8669f0d02d42d7b959a7", size = 3468288, upload-time = "2026-01-28T00:23:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/56/f7/f648fdbb61d0d45902d3f374217451385edc7e7768d1b03ff1d0e5ffc17b/cryptography-46.0.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a9556ba711f7c23f77b151d5798f3ac44a13455cc68db7697a1096e6d0563cab", size = 7169583, upload-time = "2026-01-28T00:23:56.558Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cc/8f3224cbb2a928de7298d6ed4790f5ebc48114e02bdc9559196bfb12435d/cryptography-46.0.4-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8bf75b0259e87fa70bddc0b8b4078b76e7fd512fd9afae6c1193bcf440a4dbef", size = 4275419, upload-time = "2026-01-28T00:23:58.364Z" }, + { url = "https://files.pythonhosted.org/packages/17/43/4a18faa7a872d00e4264855134ba82d23546c850a70ff209e04ee200e76f/cryptography-46.0.4-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3c268a3490df22270955966ba236d6bc4a8f9b6e4ffddb78aac535f1a5ea471d", size = 4419058, upload-time = "2026-01-28T00:23:59.867Z" }, + { url = "https://files.pythonhosted.org/packages/ee/64/6651969409821d791ba12346a124f55e1b76f66a819254ae840a965d4b9c/cryptography-46.0.4-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:812815182f6a0c1d49a37893a303b44eaac827d7f0d582cecfc81b6427f22973", size = 4278151, upload-time = "2026-01-28T00:24:01.731Z" }, + { url = "https://files.pythonhosted.org/packages/20/0b/a7fce65ee08c3c02f7a8310cc090a732344066b990ac63a9dfd0a655d321/cryptography-46.0.4-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a90e43e3ef65e6dcf969dfe3bb40cbf5aef0d523dff95bfa24256be172a845f4", size = 4939441, upload-time = "2026-01-28T00:24:03.175Z" }, + { url = "https://files.pythonhosted.org/packages/db/a7/20c5701e2cd3e1dfd7a19d2290c522a5f435dd30957d431dcb531d0f1413/cryptography-46.0.4-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a05177ff6296644ef2876fce50518dffb5bcdf903c85250974fc8bc85d54c0af", size = 4451617, upload-time = "2026-01-28T00:24:05.403Z" }, + { url = "https://files.pythonhosted.org/packages/00/dc/3e16030ea9aa47b63af6524c354933b4fb0e352257c792c4deeb0edae367/cryptography-46.0.4-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:daa392191f626d50f1b136c9b4cf08af69ca8279d110ea24f5c2700054d2e263", size = 3977774, upload-time = "2026-01-28T00:24:06.851Z" }, + { url = "https://files.pythonhosted.org/packages/42/c8/ad93f14118252717b465880368721c963975ac4b941b7ef88f3c56bf2897/cryptography-46.0.4-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e07ea39c5b048e085f15923511d8121e4a9dc45cee4e3b970ca4f0d338f23095", size = 4277008, upload-time = "2026-01-28T00:24:08.926Z" }, + { url = "https://files.pythonhosted.org/packages/00/cf/89c99698151c00a4631fbfcfcf459d308213ac29e321b0ff44ceeeac82f1/cryptography-46.0.4-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d5a45ddc256f492ce42a4e35879c5e5528c09cd9ad12420828c972951d8e016b", size = 4903339, upload-time = "2026-01-28T00:24:12.009Z" }, + { url = "https://files.pythonhosted.org/packages/03/c3/c90a2cb358de4ac9309b26acf49b2a100957e1ff5cc1e98e6c4996576710/cryptography-46.0.4-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:6bb5157bf6a350e5b28aee23beb2d84ae6f5be390b2f8ee7ea179cda077e1019", size = 4451216, upload-time = "2026-01-28T00:24:13.975Z" }, + { url = "https://files.pythonhosted.org/packages/96/2c/8d7f4171388a10208671e181ca43cdc0e596d8259ebacbbcfbd16de593da/cryptography-46.0.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd5aba870a2c40f87a3af043e0dee7d9eb02d4aff88a797b48f2b43eff8c3ab4", size = 4404299, upload-time = "2026-01-28T00:24:16.169Z" }, + { url = "https://files.pythonhosted.org/packages/e9/23/cbb2036e450980f65c6e0a173b73a56ff3bccd8998965dea5cc9ddd424a5/cryptography-46.0.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:93d8291da8d71024379ab2cb0b5c57915300155ad42e07f76bea6ad838d7e59b", size = 4664837, upload-time = "2026-01-28T00:24:17.629Z" }, + { url = "https://files.pythonhosted.org/packages/0a/21/f7433d18fe6d5845329cbdc597e30caf983229c7a245bcf54afecc555938/cryptography-46.0.4-cp38-abi3-win32.whl", hash = "sha256:0563655cb3c6d05fb2afe693340bc050c30f9f34e15763361cf08e94749401fc", size = 3009779, upload-time = "2026-01-28T00:24:20.198Z" }, + { url = "https://files.pythonhosted.org/packages/3a/6a/bd2e7caa2facffedf172a45c1a02e551e6d7d4828658c9a245516a598d94/cryptography-46.0.4-cp38-abi3-win_amd64.whl", hash = "sha256:fa0900b9ef9c49728887d1576fd8d9e7e3ea872fa9b25ef9b64888adc434e976", size = 3466633, upload-time = "2026-01-28T00:24:21.851Z" }, ] [[package]] name = "cyclopts" -version = "4.5.0" +version = "4.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -300,9 +297,9 @@ dependencies = [ { name = "rich" }, { name = "rich-rst" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/7b/663f3285c1ac0e5d0854bd9db2c87caa6fa3d1a063185e3394a6cdca9151/cyclopts-4.5.0.tar.gz", hash = "sha256:717ac4235548b58d500baf7e688aa4d024caf0ee68f61a012ffd5e29db3099f9", size = 161980, upload-time = "2026-01-16T02:07:16.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/93/6085aa89c3fff78a5180987354538d72e43b0db27e66a959302d0c07821a/cyclopts-4.5.1.tar.gz", hash = "sha256:fadc45304763fd9f5d6033727f176898d17a1778e194436964661a005078a3dd", size = 162075, upload-time = "2026-01-25T15:23:54.07Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/a3/2e00fececc34a99ae3a5d5702a5dd29c5371e4ed016647301a2b9bcc1976/cyclopts-4.5.0-py3-none-any.whl", hash = "sha256:305b9aa90a9cd0916f0a450b43e50ad5df9c252680731a0719edfb9b20381bf5", size = 199772, upload-time = "2026-01-16T02:07:14.707Z" }, + { url = "https://files.pythonhosted.org/packages/1c/7c/996760c30f1302704af57c66ff2d723f7d656d0d0b93563b5528a51484bb/cyclopts-4.5.1-py3-none-any.whl", hash = "sha256:0642c93601e554ca6b7b9abd81093847ea4448b2616280f2a0952416574e8c7a", size = 199807, upload-time = "2026-01-25T15:23:55.219Z" }, ] [[package]] @@ -421,16 +418,18 @@ lua = [ [[package]] name = "fastmcp" -version = "2.14.3" +version = "2.14.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "authlib" }, { name = "cyclopts" }, { name = "exceptiongroup" }, { name = "httpx" }, + { name = "jsonref" }, { name = "jsonschema-path" }, { name = "mcp" }, { name = "openapi-pydantic" }, + { name = "packaging" }, { name = "platformdirs" }, { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] }, { name = "pydantic", extra = ["email"] }, @@ -441,9 +440,9 @@ dependencies = [ { name = "uvicorn" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/b5/7c4744dc41390ed2c17fd462ef2d42f4448a1ec53dda8fe3a01ff2872313/fastmcp-2.14.3.tar.gz", hash = "sha256:abc9113d5fcf79dfb4c060a1e1c55fccb0d4bce4a2e3eab15ca352341eec8dd6", size = 8279206, upload-time = "2026-01-12T20:00:40.789Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/a9/a57d5e5629ebd4ef82b495a7f8e346ce29ef80cc86b15c8c40570701b94d/fastmcp-2.14.4.tar.gz", hash = "sha256:c01f19845c2adda0a70d59525c9193be64a6383014c8d40ce63345ac664053ff", size = 8302239, upload-time = "2026-01-22T17:29:37.024Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/dc/f7dd14213bf511690dccaa5094d436947c253b418c86c86211d1c76e6e44/fastmcp-2.14.3-py3-none-any.whl", hash = "sha256:103c6b4c6e97a9acc251c81d303f110fe4f2bdba31353df515d66272bf1b9414", size = 416220, upload-time = "2026-01-12T20:00:42.543Z" }, + { url = "https://files.pythonhosted.org/packages/3e/41/c4d407e2218fd60d84acb6cc5131d28ff876afecf325e3fd9d27b8318581/fastmcp-2.14.4-py3-none-any.whl", hash = "sha256:5858cff5e4c8ea8107f9bca2609d71d6256e0fce74495912f6e51625e466c49a", size = 417788, upload-time = "2026-01-22T17:29:35.159Z" }, ] [[package]] @@ -622,6 +621,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "jsonref" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" }, +] + [[package]] name = "jsonschema" version = "4.26.0" @@ -800,7 +808,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.25.0" +version = "1.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -818,9 +826,9 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/2d/649d80a0ecf6a1f82632ca44bec21c0461a9d9fc8934d38cb5b319f2db5e/mcp-1.25.0.tar.gz", hash = "sha256:56310361ebf0364e2d438e5b45f7668cbb124e158bb358333cd06e49e83a6802", size = 605387, upload-time = "2025-12-19T10:19:56.985Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/fc/6dc7659c2ae5ddf280477011f4213a74f806862856b796ef08f028e664bf/mcp-1.25.0-py3-none-any.whl", hash = "sha256:b37c38144a666add0862614cc79ec276e97d72aa8ca26d622818d4e278b9721a", size = 233076, upload-time = "2025-12-19T10:19:55.416Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615, upload-time = "2026-01-24T19:40:30.652Z" }, ] [[package]] @@ -933,11 +941,11 @@ wheels = [ [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] @@ -951,11 +959,11 @@ wheels = [ [[package]] name = "pathspec" -version = "1.0.3" +version = "1.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/bb8e495d5262bfec41ab5cb18f522f1012933347fb5d9e62452d446baca2/pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d", size = 130841, upload-time = "2026-01-09T15:46:46.009Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/2b/121e912bd60eebd623f873fd090de0e84f322972ab25a7f9044c056804ed/pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c", size = 55021, upload-time = "2026-01-09T15:46:44.652Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] @@ -1037,11 +1045,11 @@ wheels = [ [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] @@ -1234,20 +1242,30 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.21" +version = "0.0.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/78/96/804520d0850c7db98e5ccb70282e29208723f0964e88ffd9d0da2f52ea09/python_multipart-0.0.21.tar.gz", hash = "sha256:7137ebd4d3bbf70ea1622998f902b97a29434a9e8dc40eb203bbcf7c2a2cba92", size = 37196, upload-time = "2025-12-17T09:24:22.446Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/76/03af049af4dcee5d27442f71b6924f01f3efb5d2bd34f23fcd563f2cc5f5/python_multipart-0.0.21-py3-none-any.whl", hash = "sha256:cf7a6713e01c87aa35387f4774e812c4361150938d20d232800f75ffcf266090", size = 24541, upload-time = "2025-12-17T09:24:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, ] [[package]] name = "pytokens" -version = "0.3.0" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, + { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, ] [[package]] @@ -1334,15 +1352,15 @@ wheels = [ [[package]] name = "rich" -version = "14.2.0" +version = "14.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/84/4831f881aa6ff3c976f6d6809b58cdfa350593ffc0dc3c58f5f6586780fb/rich-14.3.1.tar.gz", hash = "sha256:b8c5f568a3a749f9290ec6bddedf835cec33696bfc1e48bcfecb276c7386e4b8", size = 230125, upload-time = "2026-01-24T21:40:44.847Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, + { url = "https://files.pythonhosted.org/packages/87/2a/a1810c8627b9ec8c57ec5ec325d306701ae7be50235e8fd81266e002a3cc/rich-14.3.1-py3-none-any.whl", hash = "sha256:da750b1aebbff0b372557426fb3f35ba56de8ef954b3190315eb64076d6fb54e", size = 309952, upload-time = "2026-01-24T21:40:42.969Z" }, ] [[package]] @@ -1397,28 +1415,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/0a/1914efb7903174b381ee2ffeebb4253e729de57f114e63595114c8ca451f/ruff-0.14.13.tar.gz", hash = "sha256:83cd6c0763190784b99650a20fec7633c59f6ebe41c5cc9d45ee42749563ad47", size = 6059504, upload-time = "2026-01-15T20:15:16.918Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/ae/0deefbc65ca74b0ab1fd3917f94dc3b398233346a74b8bbb0a916a1a6bf6/ruff-0.14.13-py3-none-linux_armv6l.whl", hash = "sha256:76f62c62cd37c276cb03a275b198c7c15bd1d60c989f944db08a8c1c2dbec18b", size = 13062418, upload-time = "2026-01-15T20:14:50.779Z" }, - { url = "https://files.pythonhosted.org/packages/47/df/5916604faa530a97a3c154c62a81cb6b735c0cb05d1e26d5ad0f0c8ac48a/ruff-0.14.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914a8023ece0528d5cc33f5a684f5f38199bbb566a04815c2c211d8f40b5d0ed", size = 13442344, upload-time = "2026-01-15T20:15:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f3/e0e694dd69163c3a1671e102aa574a50357536f18a33375050334d5cd517/ruff-0.14.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d24899478c35ebfa730597a4a775d430ad0d5631b8647a3ab368c29b7e7bd063", size = 12354720, upload-time = "2026-01-15T20:15:09.854Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e8/67f5fcbbaee25e8fc3b56cc33e9892eca7ffe09f773c8e5907757a7e3bdb/ruff-0.14.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aaf3870f14d925bbaf18b8a2347ee0ae7d95a2e490e4d4aea6813ed15ebc80e", size = 12774493, upload-time = "2026-01-15T20:15:20.908Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ce/d2e9cb510870b52a9565d885c0d7668cc050e30fa2c8ac3fb1fda15c083d/ruff-0.14.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac5b7f63dd3b27cc811850f5ffd8fff845b00ad70e60b043aabf8d6ecc304e09", size = 12815174, upload-time = "2026-01-15T20:15:05.74Z" }, - { url = "https://files.pythonhosted.org/packages/88/00/c38e5da58beebcf4fa32d0ddd993b63dfacefd02ab7922614231330845bf/ruff-0.14.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2b1097750d90ba82ce4ba676e85230a0ed694178ca5e61aa9b459970b3eb9", size = 13680909, upload-time = "2026-01-15T20:15:14.537Z" }, - { url = "https://files.pythonhosted.org/packages/61/61/cd37c9dd5bd0a3099ba79b2a5899ad417d8f3b04038810b0501a80814fd7/ruff-0.14.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d0bf87705acbbcb8d4c24b2d77fbb73d40210a95c3903b443cd9e30824a5032", size = 15144215, upload-time = "2026-01-15T20:15:22.886Z" }, - { url = "https://files.pythonhosted.org/packages/56/8a/85502d7edbf98c2df7b8876f316c0157359165e16cdf98507c65c8d07d3d/ruff-0.14.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3eb5da8e2c9e9f13431032fdcbe7681de9ceda5835efee3269417c13f1fed5c", size = 14706067, upload-time = "2026-01-15T20:14:48.271Z" }, - { url = "https://files.pythonhosted.org/packages/7e/2f/de0df127feb2ee8c1e54354dc1179b4a23798f0866019528c938ba439aca/ruff-0.14.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:642442b42957093811cd8d2140dfadd19c7417030a7a68cf8d51fcdd5f217427", size = 14133916, upload-time = "2026-01-15T20:14:57.357Z" }, - { url = "https://files.pythonhosted.org/packages/0d/77/9b99686bb9fe07a757c82f6f95e555c7a47801a9305576a9c67e0a31d280/ruff-0.14.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4acdf009f32b46f6e8864af19cbf6841eaaed8638e65c8dac845aea0d703c841", size = 13859207, upload-time = "2026-01-15T20:14:55.111Z" }, - { url = "https://files.pythonhosted.org/packages/7d/46/2bdcb34a87a179a4d23022d818c1c236cb40e477faf0d7c9afb6813e5876/ruff-0.14.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:591a7f68860ea4e003917d19b5c4f5ac39ff558f162dc753a2c5de897fd5502c", size = 14043686, upload-time = "2026-01-15T20:14:52.841Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a9/5c6a4f56a0512c691cf143371bcf60505ed0f0860f24a85da8bd123b2bf1/ruff-0.14.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:774c77e841cc6e046fc3e91623ce0903d1cd07e3a36b1a9fe79b81dab3de506b", size = 12663837, upload-time = "2026-01-15T20:15:18.921Z" }, - { url = "https://files.pythonhosted.org/packages/fe/bb/b920016ece7651fa7fcd335d9d199306665486694d4361547ccb19394c44/ruff-0.14.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:61f4e40077a1248436772bb6512db5fc4457fe4c49e7a94ea7c5088655dd21ae", size = 12805867, upload-time = "2026-01-15T20:14:59.272Z" }, - { url = "https://files.pythonhosted.org/packages/7d/b3/0bd909851e5696cd21e32a8fc25727e5f58f1934b3596975503e6e85415c/ruff-0.14.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6d02f1428357fae9e98ac7aa94b7e966fd24151088510d32cf6f902d6c09235e", size = 13208528, upload-time = "2026-01-15T20:15:03.732Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3b/e2d94cb613f6bbd5155a75cbe072813756363eba46a3f2177a1fcd0cd670/ruff-0.14.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e399341472ce15237be0c0ae5fbceca4b04cd9bebab1a2b2c979e015455d8f0c", size = 13929242, upload-time = "2026-01-15T20:15:11.918Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c5/abd840d4132fd51a12f594934af5eba1d5d27298a6f5b5d6c3be45301caf/ruff-0.14.13-py3-none-win32.whl", hash = "sha256:ef720f529aec113968b45dfdb838ac8934e519711da53a0456038a0efecbd680", size = 12919024, upload-time = "2026-01-15T20:14:43.647Z" }, - { url = "https://files.pythonhosted.org/packages/c2/55/6384b0b8ce731b6e2ade2b5449bf07c0e4c31e8a2e68ea65b3bafadcecc5/ruff-0.14.13-py3-none-win_amd64.whl", hash = "sha256:6070bd026e409734b9257e03e3ef18c6e1a216f0435c6751d7a8ec69cb59abef", size = 14097887, upload-time = "2026-01-15T20:15:01.48Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e1/7348090988095e4e39560cfc2f7555b1b2a7357deba19167b600fdf5215d/ruff-0.14.13-py3-none-win_arm64.whl", hash = "sha256:7ab819e14f1ad9fe39f246cfcc435880ef7a9390d81a2b6ac7e01039083dd247", size = 13080224, upload-time = "2026-01-15T20:14:45.853Z" }, +version = "0.14.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, + { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, + { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, + { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, + { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, + { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, + { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, ] [[package]] @@ -1467,39 +1485,38 @@ wheels = [ [[package]] name = "starlette" -version = "0.51.0" +version = "0.52.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/65/5a1fadcc40c5fdc7df421a7506b79633af8f5d5e3a95c3e72acacec644b9/starlette-0.51.0.tar.gz", hash = "sha256:4c4fda9b1bc67f84037d3d14a5112e523509c369d9d47b111b2f984b0cc5ba6c", size = 2647658, upload-time = "2026-01-10T20:23:15.043Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/c4/09985a03dba389d4fe16a9014147a7b02fa76ef3519bf5846462a485876d/starlette-0.51.0-py3-none-any.whl", hash = "sha256:fb460a3d6fd3c958d729fdd96aee297f89a51b0181f16401fe8fd4cb6129165d", size = 74133, upload-time = "2026-01-10T20:23:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, ] [[package]] name = "ty" -version = "0.0.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/78/ba1a4ad403c748fbba8be63b7e774a90e80b67192f6443d624c64fe4aaab/ty-0.0.12.tar.gz", hash = "sha256:cd01810e106c3b652a01b8f784dd21741de9fdc47bd595d02c122a7d5cefeee7", size = 4981303, upload-time = "2026-01-14T22:30:48.537Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/8f/c21314d074dda5fb13d3300fa6733fd0d8ff23ea83a721818740665b6314/ty-0.0.12-py3-none-linux_armv6l.whl", hash = "sha256:eb9da1e2c68bd754e090eab39ed65edf95168d36cbeb43ff2bd9f86b4edd56d1", size = 9614164, upload-time = "2026-01-14T22:30:44.016Z" }, - { url = "https://files.pythonhosted.org/packages/09/28/f8a4d944d13519d70c486e8f96d6fa95647ac2aa94432e97d5cfec1f42f6/ty-0.0.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c181f42aa19b0ed7f1b0c2d559980b1f1d77cc09419f51c8321c7ddf67758853", size = 9542337, upload-time = "2026-01-14T22:30:05.687Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9c/f576e360441de7a8201daa6dc4ebc362853bc5305e059cceeb02ebdd9a48/ty-0.0.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1f829e1eecd39c3e1b032149db7ae6a3284f72fc36b42436e65243a9ed1173db", size = 8909582, upload-time = "2026-01-14T22:30:46.089Z" }, - { url = "https://files.pythonhosted.org/packages/d6/13/0898e494032a5d8af3060733d12929e3e7716db6c75eac63fa125730a3e7/ty-0.0.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45162e7826e1789cf3374627883cdeb0d56b82473a0771923e4572928e90be3", size = 9384932, upload-time = "2026-01-14T22:30:13.769Z" }, - { url = "https://files.pythonhosted.org/packages/e4/1a/b35b6c697008a11d4cedfd34d9672db2f0a0621ec80ece109e13fca4dfef/ty-0.0.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d11fec40b269bec01e751b2337d1c7ffa959a2c2090a950d7e21c2792442cccd", size = 9453140, upload-time = "2026-01-14T22:30:11.131Z" }, - { url = "https://files.pythonhosted.org/packages/dd/1e/71c9edbc79a3c88a0711324458f29c7dbf6c23452c6e760dc25725483064/ty-0.0.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09d99e37e761a4d2651ad9d5a610d11235fbcbf35dc6d4bc04abf54e7cf894f1", size = 9960680, upload-time = "2026-01-14T22:30:33.621Z" }, - { url = "https://files.pythonhosted.org/packages/0e/75/39375129f62dd22f6ad5a99cd2a42fd27d8b91b235ce2db86875cdad397d/ty-0.0.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d9ca0cdb17bd37397da7b16a7cd23423fc65c3f9691e453ad46c723d121225a1", size = 10904518, upload-time = "2026-01-14T22:30:08.464Z" }, - { url = "https://files.pythonhosted.org/packages/32/5e/26c6d88fafa11a9d31ca9f4d12989f57782ec61e7291d4802d685b5be118/ty-0.0.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcf2757b905e7eddb7e456140066335b18eb68b634a9f72d6f54a427ab042c64", size = 10525001, upload-time = "2026-01-14T22:30:16.454Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a5/2f0b91894af13187110f9ad7ee926d86e4e6efa755c9c88a820ed7f84c85/ty-0.0.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00cf34c1ebe1147efeda3021a1064baa222c18cdac114b7b050bbe42deb4ca80", size = 10307103, upload-time = "2026-01-14T22:30:41.221Z" }, - { url = "https://files.pythonhosted.org/packages/4b/77/13d0410827e4bc713ebb7fdaf6b3590b37dcb1b82e0a81717b65548f2442/ty-0.0.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb3a655bd869352e9a22938d707631ac9fbca1016242b1f6d132d78f347c851", size = 10072737, upload-time = "2026-01-14T22:30:51.783Z" }, - { url = "https://files.pythonhosted.org/packages/e1/dd/fc36d8bac806c74cf04b4ca735bca14d19967ca84d88f31e121767880df1/ty-0.0.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4658e282c7cb82be304052f8f64f9925f23c3c4f90eeeb32663c74c4b095d7ba", size = 9368726, upload-time = "2026-01-14T22:30:18.683Z" }, - { url = "https://files.pythonhosted.org/packages/54/70/9e8e461647550f83e2fe54bc632ccbdc17a4909644783cdbdd17f7296059/ty-0.0.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c167d838eaaa06e03bb66a517f75296b643d950fbd93c1d1686a187e5a8dbd1f", size = 9454704, upload-time = "2026-01-14T22:30:22.759Z" }, - { url = "https://files.pythonhosted.org/packages/04/9b/6292cf7c14a0efeca0539cf7d78f453beff0475cb039fbea0eb5d07d343d/ty-0.0.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2956e0c9ab7023533b461d8a0e6b2ea7b78e01a8dde0688e8234d0fce10c4c1c", size = 9649829, upload-time = "2026-01-14T22:30:31.234Z" }, - { url = "https://files.pythonhosted.org/packages/49/bd/472a5d2013371e4870886cff791c94abdf0b92d43d305dd0f8e06b6ff719/ty-0.0.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c6a3fd7479580009f21002f3828320621d8a82d53b7ba36993234e3ccad58c8", size = 10162814, upload-time = "2026-01-14T22:30:36.174Z" }, - { url = "https://files.pythonhosted.org/packages/31/e9/2ecbe56826759845a7c21d80aa28187865ea62bc9757b056f6cbc06f78ed/ty-0.0.12-py3-none-win32.whl", hash = "sha256:a91c24fd75c0f1796d8ede9083e2c0ec96f106dbda73a09fe3135e075d31f742", size = 9140115, upload-time = "2026-01-14T22:30:38.903Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6d/d9531eff35a5c0ec9dbc10231fac21f9dd6504814048e81d6ce1c84dc566/ty-0.0.12-py3-none-win_amd64.whl", hash = "sha256:df151894be55c22d47068b0f3b484aff9e638761e2267e115d515fcc9c5b4a4b", size = 9884532, upload-time = "2026-01-14T22:30:25.112Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f3/20b49e75967023b123a221134548ad7000f9429f13fdcdda115b4c26305f/ty-0.0.12-py3-none-win_arm64.whl", hash = "sha256:cea99d334b05629de937ce52f43278acf155d3a316ad6a35356635f886be20ea", size = 9313974, upload-time = "2026-01-14T22:30:27.44Z" }, +version = "0.0.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/57/22c3d6bf95c2229120c49ffc2f0da8d9e8823755a1c3194da56e51f1cc31/ty-0.0.14.tar.gz", hash = "sha256:a691010565f59dd7f15cf324cdcd1d9065e010c77a04f887e1ea070ba34a7de2", size = 5036573, upload-time = "2026-01-27T00:57:31.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/cb/cc6d1d8de59beb17a41f9a614585f884ec2d95450306c173b3b7cc090d2e/ty-0.0.14-py3-none-linux_armv6l.whl", hash = "sha256:32cf2a7596e693094621d3ae568d7ee16707dce28c34d1762947874060fdddaa", size = 10034228, upload-time = "2026-01-27T00:57:53.133Z" }, + { url = "https://files.pythonhosted.org/packages/f3/96/dd42816a2075a8f31542296ae687483a8d047f86a6538dfba573223eaf9a/ty-0.0.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f971bf9805f49ce8c0968ad53e29624d80b970b9eb597b7cbaba25d8a18ce9a2", size = 9939162, upload-time = "2026-01-27T00:57:43.857Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b4/73c4859004e0f0a9eead9ecb67021438b2e8e5fdd8d03e7f5aca77623992/ty-0.0.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:45448b9e4806423523268bc15e9208c4f3f2ead7c344f615549d2e2354d6e924", size = 9418661, upload-time = "2026-01-27T00:58:03.411Z" }, + { url = "https://files.pythonhosted.org/packages/58/35/839c4551b94613db4afa20ee555dd4f33bfa7352d5da74c5fa416ffa0fd2/ty-0.0.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee94a9b747ff40114085206bdb3205a631ef19a4d3fb89e302a88754cbbae54c", size = 9837872, upload-time = "2026-01-27T00:57:23.718Z" }, + { url = "https://files.pythonhosted.org/packages/41/2b/bbecf7e2faa20c04bebd35fc478668953ca50ee5847ce23e08acf20ea119/ty-0.0.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6756715a3c33182e9ab8ffca2bb314d3c99b9c410b171736e145773ee0ae41c3", size = 9848819, upload-time = "2026-01-27T00:57:58.501Z" }, + { url = "https://files.pythonhosted.org/packages/be/60/3c0ba0f19c0f647ad9d2b5b5ac68c0f0b4dc899001bd53b3a7537fb247a2/ty-0.0.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89d0038a2f698ba8b6fec5cf216a4e44e2f95e4a5095a8c0f57fe549f87087c2", size = 10324371, upload-time = "2026-01-27T00:57:29.291Z" }, + { url = "https://files.pythonhosted.org/packages/24/32/99d0a0b37d0397b0a989ffc2682493286aa3bc252b24004a6714368c2c3d/ty-0.0.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c64a83a2d669b77f50a4957039ca1450626fb474619f18f6f8a3eb885bf7544", size = 10865898, upload-time = "2026-01-27T00:57:33.542Z" }, + { url = "https://files.pythonhosted.org/packages/1a/88/30b583a9e0311bb474269cfa91db53350557ebec09002bfc3fb3fc364e8c/ty-0.0.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:242488bfb547ef080199f6fd81369ab9cb638a778bb161511d091ffd49c12129", size = 10555777, upload-time = "2026-01-27T00:58:05.853Z" }, + { url = "https://files.pythonhosted.org/packages/cd/a2/cb53fb6325dcf3d40f2b1d0457a25d55bfbae633c8e337bde8ec01a190eb/ty-0.0.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4790c3866f6c83a4f424fc7d09ebdb225c1f1131647ba8bdc6fcdc28f09ed0ff", size = 10412913, upload-time = "2026-01-27T00:57:38.834Z" }, + { url = "https://files.pythonhosted.org/packages/42/8f/f2f5202d725ed1e6a4e5ffaa32b190a1fe70c0b1a2503d38515da4130b4c/ty-0.0.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:950f320437f96d4ea9a2332bbfb5b68f1c1acd269ebfa4c09b6970cc1565bd9d", size = 9837608, upload-time = "2026-01-27T00:57:55.898Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ba/59a2a0521640c489dafa2c546ae1f8465f92956fede18660653cce73b4c5/ty-0.0.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4a0ec3ee70d83887f86925bbc1c56f4628bd58a0f47f6f32ddfe04e1f05466df", size = 9884324, upload-time = "2026-01-27T00:57:46.786Z" }, + { url = "https://files.pythonhosted.org/packages/03/95/8d2a49880f47b638743212f011088552ecc454dd7a665ddcbdabea25772a/ty-0.0.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1a4e6b6da0c58b34415955279eff754d6206b35af56a18bb70eb519d8d139ef", size = 10033537, upload-time = "2026-01-27T00:58:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/e9/40/4523b36f2ce69f92ccf783855a9e0ebbbd0f0bb5cdce6211ee1737159ed3/ty-0.0.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dc04384e874c5de4c5d743369c277c8aa73d1edea3c7fc646b2064b637db4db3", size = 10495910, upload-time = "2026-01-27T00:57:26.691Z" }, + { url = "https://files.pythonhosted.org/packages/08/d5/655beb51224d1bfd4f9ddc0bb209659bfe71ff141bcf05c418ab670698f0/ty-0.0.14-py3-none-win32.whl", hash = "sha256:b20e22cf54c66b3e37e87377635da412d9a552c9bf4ad9fc449fed8b2e19dad2", size = 9507626, upload-time = "2026-01-27T00:57:41.43Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d9/c569c9961760e20e0a4bc008eeb1415754564304fd53997a371b7cf3f864/ty-0.0.14-py3-none-win_amd64.whl", hash = "sha256:e312ff9475522d1a33186657fe74d1ec98e4a13e016d66f5758a452c90ff6409", size = 10437980, upload-time = "2026-01-27T00:57:36.422Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/186829654f5bfd9a028f6648e9caeb11271960a61de97484627d24443f91/ty-0.0.14-py3-none-win_arm64.whl", hash = "sha256:b6facdbe9b740cb2c15293a1d178e22ffc600653646452632541d01c36d5e378", size = 9885831, upload-time = "2026-01-27T00:57:49.747Z" }, ] [[package]]