From 34854948bf95bb03f4949ff29c7747295c6e46f7 Mon Sep 17 00:00:00 2001 From: K2 Date: Wed, 12 Feb 2025 04:12:01 -0500 Subject: [PATCH 01/10] Looking to support uvx and easier install/deployment --- pyproject.toml | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..2f040045 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,201 @@ +[project] +name = "FiniteMonkey" +version = "0.0.1" +description = "Intelligent vulnerability mining engine." +authors = [{ name = "Xue Yue" }] +readme = "README.md" +license = { text = "Apache 2.0" } +requires-python = ">=3.10" + +dependencies = [ + "antlr4-python3-runtime>=4.13.2", + "certifi>=2024.8.30", + "charset-normalizer>=3.4.0", + "colorama>=0.4.6", + "et-xmlfile>=1.1.0", + "idna>=3.10", + "joblib>=1.4.2", + "numpy>=1.24.4", + "openpyxl>=3.1.5", + "pandas>=1.24.4", + "psycopg2-binary>=2.9.9", + "python-dateutil>=2.9.0.post0", + "python-dotenv>=1.0.1", + "pytz>=2024.2", + "requests>=2.32.3", + "scikit-learn>=1.3.2", + "scipy>=1.10.1", + "simplejson>=3.19.3", + "six>=1.16.0", + "SQLAlchemy>=2.0.35", + "threadpoolctl>=3.5.0", + "tqdm>=4.66.5", + "typing_extensions>=4.12.2", + "tzdata>=2024.2", + "urllib3>=2.2.3", +] + +[project.optional-dependencies] + +dev = ["mypy>=1.11.1", "ruff>=0.6.1"] + +[build-system] +requires = ["setuptools>=73.0.0", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +packages = ["assistant"] + +[tool.setuptools.package-dir] +"assistant" = "src/assistant" + +[tool.setuptools.package-data] +"*" = ["py.typed"] + +[tool.ruff] +lint.select = [ + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "D", # pydocstyle + "D401", # First line should be in imperative mood + "T201", + "UP", +] +lint.ignore = ["UP006", "UP007", "UP035", "D417", "E501"] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["D", "UP"] + +[tool.ruff.lint.pydocstyle] +convention = "google"] + +[tool.black] +target-version = ['py310'] +line-length = 120 +skip-string-normalization = true +skip-magic-trailing-comma = true +force-exclude = ''' +/( + | docs + | setup.py +)/ +''' + +[tool.isort] +py_version = 310 +sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] +default_section = "THIRDPARTY" +known_third_party = [] +known_first_party = [] +known_local_folder = [] +# style: black +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true +line_length = 120 +split_on_trailing_comma = true +lines_after_imports = 2 +force_single_line = true +skip_glob = ["docs/*", "setup.py"] +filter_files = true + +[tool.ruff] +target-version = "py310" +line-length = 120 +indent-width = 4 +extend-exclude = ["docs", "test", "tests"] + +[tool.ruff.lint] +select = ["F", "E"] +extend-select = ["W", "C90", "I", "N", "B", "A", "C4", "PERF", "RUF"] +ignore = ["I001"] +fixable = ["ALL"] +unfixable = [] +preview = true +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.lint.isort] +force-single-line = true +force-sort-within-sections = false +lines-after-imports = 2 +known-first-party = [] +known-local-folder = [] +known-third-party = [] +section-order = [ + "future", + "standard-library", + "third-party", + "first-party", + "local-folder", +] + +[tool.ruff.lint.mccabe] +max-complexity = 24 + +[tool.ruff.lint.pycodestyle] +ignore-overlong-task-comments = true + +[tool.ruff.lint.pydocstyle] +convention = "numpy" + +[tool.ruff.lint.flake8-annotations] +allow-star-arg-any = true +ignore-fully-untyped = true + +[tool.ruff.lint.pylint] +max-args = 5 +max-branches = 12 +max-locals = 15 +max-statements = 50 + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" +preview = false +docstring-code-format = true + +[tool.mypy] +# Platform configuration +python_version = "3.12" +# imports related +ignore_missing_imports = true +follow_imports = "silent" +# None and Optional handling +no_implicit_optional = false +strict_optional = false +# Configuring warnings +warn_unused_configs = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_no_return = true +warn_unreachable = true +warn_return_any = false +# Untyped definitions and calls +check_untyped_defs = false +disallow_untyped_calls = false +disallow_untyped_defs = false +disallow_incomplete_defs = false +disallow_untyped_decorators = false +# Disallow dynamic typing +disallow_subclassing_any = false +disallow_any_unimported = false +disallow_any_expr = false +disallow_any_decorated = false +disallow_any_explicit = false +disallow_any_generics = false +# Miscellaneous strictness flags +allow_untyped_globals = true +allow_redefinition = true +local_partial_types = false +implicit_reexport = true +strict_equality = true +# Configuring error messages +show_error_context = false +show_column_numbers = false +show_error_codes = true +exclude = ["docs", "test", "tests"] From 71d5d32d44c660613249684a04e7b63abaa3205e Mon Sep 17 00:00:00 2001 From: "yue.xue1" Date: Fri, 14 Feb 2025 01:39:12 +0800 Subject: [PATCH 02/10] add pure prompt for backup --- prompt.txt | 380 ++++++------------------- src/ai_engine.py | 2 + src/dataset/agent-v1-c4/datasets.json | 4 +- src/main.py | 4 +- src/openai_api/openai.py | 2 +- src/prompt_factory/prompt_assembler.py | 10 + 6 files changed, 109 insertions(+), 293 deletions(-) diff --git a/prompt.txt b/prompt.txt index b581d70f..b6c608ca 100644 --- a/prompt.txt +++ b/prompt.txt @@ -1,309 +1,113 @@ -:function _updateCreditDelegations( - Data storage self, - uint128[] memory connectedMarketsIdsCache, - bool shouldRehydrateCache - ) - private - returns (uint128[] memory rehydratedConnectedMarketsIdsCache, SD59x18 vaultCreditCapacityUsdX18) - { - rehydratedConnectedMarketsIdsCache = new uint128[](connectedMarketsIdsCache.length); - // cache the vault id - uint128 vaultId = self.id; - - // cache the connected markets length - uint256 connectedMarketsConfigLength = self.connectedMarkets.length; - - // loads the connected markets storage pointer by taking the last configured market ids uint set - EnumerableSet.UintSet storage connectedMarkets = self.connectedMarkets[connectedMarketsConfigLength - 1]; - - // loop over each connected market id that has been cached once again in order to update this vault's - // credit delegations - for (uint256 i; i < connectedMarketsIdsCache.length; i++) { - // rehydrate the markets ids cache if needed - if (shouldRehydrateCache) { - rehydratedConnectedMarketsIdsCache[i] = connectedMarkets.at(i).toUint128(); - } else { - rehydratedConnectedMarketsIdsCache[i] = connectedMarketsIdsCache[i]; - } - - // loads the memory cached market id - uint128 connectedMarketId = rehydratedConnectedMarketsIdsCache[i]; - - // load the credit delegation to the given market id - CreditDelegation.Data storage creditDelegation = CreditDelegation.load(vaultId, connectedMarketId); - - // cache the previous credit delegation value - UD60x18 previousCreditDelegationUsdX18 = ud60x18(creditDelegation.valueUsd); - - // cache the latest credit delegation share of the vault's credit capacity - uint128 totalCreditDelegationWeightCache = self.totalCreditDelegationWeight; - - if (totalCreditDelegationWeightCache != 0) { - // get the latest credit delegation share of the vault's credit capacity - UD60x18 creditDelegationShareX18 = - ud60x18(creditDelegation.weight).div(ud60x18(totalCreditDelegationWeightCache)); - - // stores the vault's total credit capacity to be returned - vaultCreditCapacityUsdX18 = getTotalCreditCapacityUsd(self); - - // if the vault's credit capacity went to zero or below, we set its credit delegation to that market - // to zero - UD60x18 newCreditDelegationUsdX18 = vaultCreditCapacityUsdX18.gt(SD59x18_ZERO) - ? vaultCreditCapacityUsdX18.intoUD60x18().mul(creditDelegationShareX18) - : UD60x18_ZERO; - - // calculate the delta applied to the market's total delegated credit - UD60x18 creditDeltaUsdX18 = newCreditDelegationUsdX18.sub(previousCreditDelegationUsdX18); - - // loads the market's storage pointer and update total delegated credit - Market.Data storage market = Market.load(connectedMarketId); - market.updateTotalDelegatedCredit(creditDeltaUsdX18); - - // if new credit delegation is zero, we clear the credit delegation storage - if (newCreditDelegationUsdX18.isZero()) { - creditDelegation.clear(); - } else { - // update the credit delegation stored usd value - creditDelegation.valueUsd = newCreditDelegationUsdX18.intoUint128(); - } - } - } +function getPoolDivisor(address longToken, address shortToken) internal pure returns (uint256) { + return longToken == shortToken ? 2 : 1; } -function updateVaultAndCreditDelegationWeight( - Data storage self, - uint128[] memory connectedMarketsIdsCache - ) - internal - { - // cache the connected markets length - uint256 connectedMarketsConfigLength = self.connectedMarkets.length; - - // loads the connected markets storage pointer by taking the last configured market ids uint set - EnumerableSet.UintSet storage connectedMarkets = self.connectedMarkets[connectedMarketsConfigLength - 1]; - - // get the total of shares - uint128 newWeight = uint128(IERC4626(self.indexToken).totalAssets()); - - for (uint256 i; i < connectedMarketsIdsCache.length; i++) { - // load the credit delegation to the given market id - CreditDelegation.Data storage creditDelegation = - CreditDelegation.load(self.id, connectedMarkets.at(i).toUint128()); - - // update the credit delegation weight - creditDelegation.weight = newWeight; - } - - // update the vault weight - self.totalCreditDelegationWeight = newWeight; +function openInterestKey(address market, address collateralToken, bool isLong) internal pure returns (bytes32) { + return keccak256(abi.encode( + OPEN_INTEREST, + market, + collateralToken, + isLong + )); } -function _recalculateConnectedMarketsState( - Data storage self, - uint128[] memory connectedMarketsIdsCache, - bool shouldRehydrateCache - ) - private - returns ( - uint128[] memory rehydratedConnectedMarketsIdsCache, - SD59x18 vaultTotalRealizedDebtChangeUsdX18, - SD59x18 vaultTotalUnrealizedDebtChangeUsdX18, - UD60x18 vaultTotalUsdcCreditChangeX18, - UD60x18 vaultTotalWethRewardChangeX18 - ) - { - RecalculateConnectedMarketsState_Context memory ctx; - rehydratedConnectedMarketsIdsCache = new uint128[](connectedMarketsIdsCache.length); - - // cache the vault id - ctx.vaultId = self.id; - - // cache the connected markets length - uint256 connectedMarketsConfigLength = self.connectedMarkets.length; - - // loads the connected markets storage pointer by taking the last configured market ids uint set - EnumerableSet.UintSet storage connectedMarkets = self.connectedMarkets[connectedMarketsConfigLength - 1]; - - for (uint256 i; i < connectedMarketsIdsCache.length; i++) { - if (shouldRehydrateCache) { - rehydratedConnectedMarketsIdsCache[i] = connectedMarkets.at(i).toUint128(); - } else { - rehydratedConnectedMarketsIdsCache[i] = connectedMarketsIdsCache[i]; - } - - // loads the market storage pointer - Market.Data storage market = Market.load(rehydratedConnectedMarketsIdsCache[i]); - - // first we cache the market's unrealized and realized debt - ctx.marketUnrealizedDebtUsdX18 = market.getUnrealizedDebtUsd(); - ctx.marketRealizedDebtUsdX18 = market.getRealizedDebtUsd(); - - // if market has debt distribute it - if (!ctx.marketUnrealizedDebtUsdX18.isZero() || !ctx.marketRealizedDebtUsdX18.isZero()) { - // distribute the market's debt to its connected vaults - market.distributeDebtToVaults(ctx.marketUnrealizedDebtUsdX18, ctx.marketRealizedDebtUsdX18); - } - - // load the credit delegation to the given market id - CreditDelegation.Data storage creditDelegation = - CreditDelegation.load(ctx.vaultId, rehydratedConnectedMarketsIdsCache[i]); - - // prevent division by zero - if (!market.getTotalDelegatedCreditUsd().isZero()) { - // get the vault's accumulated debt, credit and reward changes from the market to update its stored - // values - ( - ctx.realizedDebtChangeUsdX18, - ctx.unrealizedDebtChangeUsdX18, - ctx.usdcCreditChangeX18, - ctx.wethRewardChangeX18 - ) = market.getVaultAccumulatedValues( - ud60x18(creditDelegation.valueUsd), - sd59x18(creditDelegation.lastVaultDistributedRealizedDebtUsdPerShare), - sd59x18(creditDelegation.lastVaultDistributedUnrealizedDebtUsdPerShare), - ud60x18(creditDelegation.lastVaultDistributedUsdcCreditPerShare), - ud60x18(creditDelegation.lastVaultDistributedWethRewardPerShare) - ); - } - - // if there's been no change in any of the returned values, we can iterate to the next - // market id - if ( - ctx.realizedDebtChangeUsdX18.isZero() && ctx.unrealizedDebtChangeUsdX18.isZero() - && ctx.usdcCreditChangeX18.isZero() && ctx.wethRewardChangeX18.isZero() - ) { - continue; - } - - // update the vault's state by adding its share of the market's latest state variables - vaultTotalRealizedDebtChangeUsdX18 = vaultTotalRealizedDebtChangeUsdX18.add(ctx.realizedDebtChangeUsdX18); - vaultTotalUnrealizedDebtChangeUsdX18 = - vaultTotalUnrealizedDebtChangeUsdX18.add(ctx.unrealizedDebtChangeUsdX18); - vaultTotalUsdcCreditChangeX18 = vaultTotalUsdcCreditChangeX18.add(ctx.usdcCreditChangeX18); - vaultTotalWethRewardChangeX18 = vaultTotalWethRewardChangeX18.add(ctx.wethRewardChangeX18); - - // update the last distributed debt, credit and reward values to the vault's credit delegation to the - // given market id, in order to keep next calculations consistent - creditDelegation.updateVaultLastDistributedValues( - sd59x18(market.realizedDebtUsdPerVaultShare), - sd59x18(market.unrealizedDebtUsdPerVaultShare), - ud60x18(market.usdcCreditPerVaultShare), - ud60x18(market.wethRewardPerVaultShare) - ); +function sumReturnUint256(uint256 a, int256 b) internal pure returns (uint256) { + if (b > 0) { + return a + uint256(b); } + + return a - uint256(-b); } -function load(uint128 vaultId) internal pure returns (Data storage vault) { - bytes32 slot = keccak256(abi.encode(VAULT_LOCATION, vaultId)); - assembly { - vault.slot := slot - } +function getOpenInterest( + IDataStore dataStore, + MarketProps memory market + ) internal view returns (uint256) { + uint256 longOpenInterest = getOpenInterest(dataStore, market, true); + uint256 shortOpenInterest = getOpenInterest(dataStore, market, false); + + return longOpenInterest + shortOpenInterest; } -function recalculateVaultsCreditCapacity(uint256[] memory vaultsIds) internal { - for (uint256 i; i < vaultsIds.length; i++) { - // uint256 -> uint128 - uint128 vaultId = vaultsIds[i].toUint128(); - - // load the vault storage pointer - Data storage self = load(vaultId); - // make sure there are markets connected to the vault - uint256 connectedMarketsConfigLength = self.connectedMarkets.length; - if (connectedMarketsConfigLength == 0) continue; +# Solidity Code Vulnerability Analysis - // loads the connected markets storage pointer by taking the last configured market ids uint set - EnumerableSet.UintSet storage connectedMarkets = self.connectedMarkets[connectedMarketsConfigLength - 1]; +## Vulnerability: Integer Underflow in sumReturnUint256 Function - // cache the connected markets ids to avoid multiple storage reads, as we're going to loop over them twice - // at `_recalculateConnectedMarketsState` and `_updateCreditDelegations` - uint128[] memory connectedMarketsIdsCache = new uint128[](connectedMarkets.length()); +The vulnerability lies in the `sumReturnUint256` function where there's a potential integer underflow when handling negative values. The issue occurs in the second branch of the function where it subtracts the absolute value of a negative number. - // update vault and credit delegation weight - updateVaultAndCreditDelegationWeight(self, connectedMarketsIdsCache); +### Vulnerable Code - // iterate over each connected market id and distribute its debt so we can have the latest credit - // delegation of the vault id being iterated to the provided `marketId` - ( - uint128[] memory updatedConnectedMarketsIdsCache, - SD59x18 vaultTotalRealizedDebtChangeUsdX18, - SD59x18 vaultTotalUnrealizedDebtChangeUsdX18, - UD60x18 vaultTotalUsdcCreditChangeX18, - UD60x18 vaultTotalWethRewardChangeX18 - ) = _recalculateConnectedMarketsState(self, connectedMarketsIdsCache, true); - - // gas optimization: only write to storage if values have changed - // - // updates the vault's stored unsettled realized debt distributed from markets - if (!vaultTotalRealizedDebtChangeUsdX18.isZero()) { - self.marketsRealizedDebtUsd = sd59x18(self.marketsRealizedDebtUsd).add( - vaultTotalRealizedDebtChangeUsdX18 - ).intoInt256().toInt128(); - } - - // updates the vault's stored unrealized debt distributed from markets - if (!vaultTotalUnrealizedDebtChangeUsdX18.isZero()) { - self.marketsUnrealizedDebtUsd = sd59x18(self.marketsUnrealizedDebtUsd).add( - vaultTotalUnrealizedDebtChangeUsdX18 - ).intoInt256().toInt128(); - } - - // adds the vault's total USDC credit change, earned from its connected markets, to the - // `depositedUsdc` variable - if (!vaultTotalUsdcCreditChangeX18.isZero()) { - self.depositedUsdc = ud60x18(self.depositedUsdc).add(vaultTotalUsdcCreditChangeX18).intoUint128(); - } - - // distributes the vault's total WETH reward change, earned from its connected markets - if (!vaultTotalWethRewardChangeX18.isZero() && self.wethRewardDistribution.totalShares != 0) { - SD59x18 vaultTotalWethRewardChangeSD59X18 = - sd59x18(int256(vaultTotalWethRewardChangeX18.intoUint256())); - self.wethRewardDistribution.distributeValue(vaultTotalWethRewardChangeSD59X18); - } - - // update the vault's credit delegations - (, SD59x18 vaultNewCreditCapacityUsdX18) = - _updateCreditDelegations(self, updatedConnectedMarketsIdsCache, false); - - emit LogUpdateVaultCreditCapacity( - vaultId, - vaultTotalRealizedDebtChangeUsdX18.intoInt256(), - vaultTotalUnrealizedDebtChangeUsdX18.intoInt256(), - vaultTotalUsdcCreditChangeX18.intoUint256(), - vaultTotalWethRewardChangeX18.intoUint256(), - vaultNewCreditCapacityUsdX18.intoInt256() - ); - } - }You are the best solidity auditor in the world,our task is to pinpoint and correct any logical or code-error or financial related vulnerabilities present in the code.We have already confirmed that the code contains only one exploitable, \ - code-error based vulnerability due to error logic in the code, \ - and your job is to identify it. - and the vulnerability is include but [not limited] to the following vulnerabilities,1. **Liquidation Before Default:** Liquidation should only occur after a genuine default (e.g., overdue repayment or insufficient collateral), yet in cases like Sherlock’s TellerV2—where the function returns the loan’s accepted timestamp instead of the last repayment timestamp—and Hats Finance Tempus Raft—where an unchecked collateralToken parameter permits price miscalculation—the conditions enable premature liquidation before the due repayment date. - -2. **Borrower Can't Be Liquidated:** In certain implementations such as Sherlock TellerV2, neglecting to check the return value of OpenZeppelin’s EnumerableSetUpgradeable.AddressSet.add() allows the borrower to overwrite existing collateral records (even with a zero amount), thereby preventing proper liquidation on default. +```solidity +function sumReturnUint256(uint256 a, int256 b) internal pure returns (uint256) { + if (b > 0) { + return a + uint256(b); + } -3. **Debt Closed Without Repayment:** Some systems, as seen in a DebtDAO audit, allow borrowers to call the close() function with a non-existent credit ID that returns a default Credit structure (with principal 0), bypassing repayment validations and erroneously marking the loan as repaid while decrementing an internal counter. + return a - uint256(-b); // Vulnerable line +} +``` -4. **Repayments Paused While Liquidations Enabled:** In platforms like Sherlock’s Blueberry example, repay() enforces an isRepayAllowed() check while liquidate() does not, which permits liquidation operations even when repayments are deliberately paused, placing borrowers at an unfair disadvantage. +### Detailed Explanation -5. **Token Disallow Stops Existing Repayment & Liquidation:** When governance changes disallow a previously permitted token (as seen in BlueBerry updates), loans using that token for repayment or as collateral might become incapable of proper repayment or liquidation, creating inconsistencies that jeopardize both borrowers and lenders. +The vulnerability occurs because of incorrect handling of the negative number conversion. When `b` is negative and equals to `type(int256).min` (-2^255), taking its absolute value with `-b` will cause an overflow since the positive range of int256 is only up to 2^255 - 1. -6. **Borrower Immediately Liquidated After Repayments Resume:** If market conditions deteriorate during a pause in repayments, then—as soon as repayments are re-enabled without a grace period—the unchanged liquidation thresholds can trigger immediate liquidation, leaving borrowers with little to no opportunity to recover. +For example: +- If `b = type(int256).min` (-2^255) +- `-b` will overflow and still remain -2^255 +- Converting this to uint256 will result in an unexpected large number +- Subtracting this from `a` will likely cause an underflow -7. **Liquidator Takes Collateral With Insufficient Repayment:** Partial liquidation calculations that rely solely on the ratio from a specific debt position—for instance, using share/oldShare in Blueberry—can let liquidators pay a minimal portion of the debt while unjustifiably seizing a disproportionately large amount of collateral, ignoring the borrower’s entire debt profile. +### Step-by-Step Exploit -8. **Infinite Loan Rollover:** Allowing borrowers to extend (roll over) their loans indefinitely without imposing strict limits exposes lenders to prolonged credit risk and potential non-repayment, underscoring the need for capping the number or duration of rollovers. +1. Call the function with: + - `a` = any reasonable uint256 value (e.g., 1000) + - `b` = type(int256).min (-2^255) -9. **Repayment Sent to Zero Address:** In examples like Cooler’s Sherlock audit, deleting loan records before executing the repayment transfer can reset critical fields (such as loan.lender) to the zero address, resulting in repayment funds being sent to (0) and permanently lost. +2. The function will: + - Enter the second branch since b < 0 + - Try to calculate `-b` which overflows + - Convert the overflowed value to uint256 + - Attempt to subtract this massive number from `a` -10. **Borrower Permanently Unable To Repay Loan:** System logic errors or token disallowances that prevent the successful execution of a repay() call can leave borrowers incapable of repaying—forcing them into liquidation while also preventing lenders from recovering their funds. +3. This will cause an unexpected result where the subtraction either: + - Underflows if using unchecked math + - Reverts if using SafeMath -11. **Borrower Repayment Only Partially Credited:** When a borrower makes a lump-sum repayment covering multiple loans, if the system credits only the current loan without applying any overpayment to subsequent loans, it leads to partial repayments, excessive interest accrual, or misinterpreted default statuses. +### Fix -12. **No Incentive To Liquidate Small Positions:** With rising gas fees, liquidation fees for small underwater positions may be economically unattractive; consequently, liquidators might avoid these positions, allowing them to accumulate risk and threaten the platform’s overall solvency. +Here's the corrected version: -13. **Liquidation Leaves Traders Unhealthier:** Certain liquidation algorithms may inadvertently worsen a borrower’s health by prioritizing the removal of lower-risk collateral, thereby leaving behind riskier positions and potentially setting the stage for subsequent, compounding liquidations. - Follow the guidelines below for your response: - 1. Describe this practical, exploitable code vulnerability in detail. It should be logical and an error or logic missing in the code, not based on technical errors or just security advice or best practices. - 2. Show step-by-step how to exploit this vulnerability. The exploit should be beneficial for an auditor and could invalidate the code. - 3. Keep your description clear and concise. Avoid vague terms. - 4. Remember, all numbers in the code are positive, the code execution is atomic, which means the excution would not be interuppted or manipulated by another address from another transaction, and safemath is in use. - 5. dont response in "attaker use some way" the exploit method must be clear and useable - 6. Dont consider any corner case or extreme scenario, the vulnerability must be practical and exploitable. - 7. Assume that the attack can not have the role of the owner of the contract - Ensure your response is as detailed as possible, and strictly adheres to all the above requirements \ No newline at end of file +```solidity +function sumReturnUint256(uint256 a, int256 b) internal pure returns (uint256) { + if (b > 0) { + return a + uint256(b); + } + + if (b == type(int256).min) { + revert("Invalid negative value"); + } + + return a - uint256(-b); +} +``` + +The fix adds a check for the minimum int256 value before performing the negation operation. This prevents the overflow scenario and ensures the function operates safely within the bounds of the integer types. + +This vulnerability is practical and exploitable because: +1. It requires no special permissions +2. Can be triggered with normal parameter values +3. Leads to concrete numerical errors that could affect financial calculations +4. Could be used to manipulate open interest calculations or pool divisor results when these functions are used together + + First analyze this vulnerability step by step, and then determine if it really exists based on the analysis result or need deeper function code. + Please note the following points: + 0.If the vulnerability description indicates that no vulnerability is found, directly conclude that it [[does not exist]] + 1. If the vulnerability is an integer overflow vulnerability, directly conclude that the vulnerability [does not exist] + 2. If the vulnerability is a reentrancy vulnerability, directly conclude that the vulnerability [does not exist] + 3. If the vulnerability requires inserting new address transaction operations from external sources during function execution, directly determine it [does not exist], because transactions are atomic + 4. When assessing vulnerabilities in functions with permission controls, consider not only the functionality itself but also how easily these permission roles can be obtained, as functions with "permission protection" may still be vulnerable if the permissions are easily accessible + 5. If more information is needed to confirm the vulnerability, please clearly state what content needs to be understood (e.g., specific function implementations, variable usage patterns, permission check logic, etc.) + 6. If the vulnerable function has an onlyowner modifier, directly conclude that the vulnerability [does not exist] + 7. Any vulnerability or risk that could cause potential losses is valid(event small losses), it doesn't necessarily need to cause major security issues + Please format your output as follows: + 1. Detailed analysis process + 2. Whether more information is needed (if yes, please specify what content needs to be understood and why) + 3. Preliminary conclusion based on current information + diff --git a/src/ai_engine.py b/src/ai_engine.py index c5a0f8af..677ab072 100644 --- a/src/ai_engine.py +++ b/src/ai_engine.py @@ -70,6 +70,8 @@ def process_task_do_scan(self,task, filter_func = None, is_gpt4 = False): prompt=PromptAssembler.assemble_optimize_prompt(code_to_be_tested) elif os.getenv("SCAN_MODE","COMMON_VUL")=="COMMON_PROJECT": prompt=PromptAssembler.assemble_prompt_common(code_to_be_tested) + elif os.getenv("SCAN_MODE","COMMON_VUL")=="PURE_SCAN": + prompt=PromptAssembler.assemble_prompt_pure(code_to_be_tested) elif os.getenv("SCAN_MODE","COMMON_VUL")=="SPECIFIC_PROJECT": # 构建提示来判断业务类型 business_type=task.recommendation diff --git a/src/dataset/agent-v1-c4/datasets.json b/src/dataset/agent-v1-c4/datasets.json index a937f41d..2eaa64ea 100644 --- a/src/dataset/agent-v1-c4/datasets.json +++ b/src/dataset/agent-v1-c4/datasets.json @@ -213,8 +213,8 @@ "files":[], "functions":[] }, - "zaros":{ - "path":"zaros", + "gamma2":{ + "path":"gamma", "files":[], "functions":[] } diff --git a/src/main.py b/src/main.py index a5cd6727..7dd51072 100644 --- a/src/main.py +++ b/src/main.py @@ -141,14 +141,14 @@ def generate_excel(output_path, project_id): dataset_base = "./src/dataset/agent-v1-c4" projects = load_dataset(dataset_base) - project_id = 'zaros' + project_id = 'gamma2' project_path = '' project = Project(project_id, projects[project_id]) cmd = 'detect_vul' if cmd == 'detect_vul': lancedb,lance_table_name,project_audit=scan_project(project, engine) # scan - if os.getenv("SCAN_MODE","SPECIFIC_PROJECT")=="SPECIFIC_PROJECT" or os.getenv("SCAN_MODE","SPECIFIC_PROJECT")=="COMMON_PROJECT": + if os.getenv("SCAN_MODE","SPECIFIC_PROJECT") in ["SPECIFIC_PROJECT","COMMON_PROJECT","PURE_SCAN"]: check_function_vul(engine,lancedb,lance_table_name,project_audit) # confirm diff --git a/src/openai_api/openai.py b/src/openai_api/openai.py index 13bab5b9..24770f44 100644 --- a/src/openai_api/openai.py +++ b/src/openai_api/openai.py @@ -262,7 +262,7 @@ def common_get_embedding(text: str): raise ValueError("OPENAI_API_KEY environment variable is not set") api_base = os.getenv('OPENAI_API_BASE', 'api.openai.com') - model = os.getenv("PRE_TRAIN_MODEL", "text-embedding-3-large") + model = os.getenv("EMBEDDING_MODEL", "text-embedding-3-large") headers = { "Authorization": f"Bearer {api_key}", diff --git a/src/prompt_factory/prompt_assembler.py b/src/prompt_factory/prompt_assembler.py index bcb6d410..4f308e19 100644 --- a/src/prompt_factory/prompt_assembler.py +++ b/src/prompt_factory/prompt_assembler.py @@ -13,6 +13,16 @@ def assemble_prompt_common(code): +PeripheryPrompt.jailbreak_prompt() + return ret_prompt + def assemble_prompt_pure(code): + ret_prompt=code+"\n"\ + +PeripheryPrompt.role_set_solidity_common()+"\n"\ + +PeripheryPrompt.task_set_blockchain_common()+"\n"\ + +CorePrompt.core_prompt_pure()+"\n"\ + +PeripheryPrompt.guidelines()+"\n"\ + +PeripheryPrompt.jailbreak_prompt() + + return ret_prompt def assemble_prompt_for_specific_project(code, business_type): vul_prompts = [] From 1dd37e9d4137f121c2e0c739ca3ea4999fc2c4fd Mon Sep 17 00:00:00 2001 From: K2 Date: Fri, 14 Feb 2025 23:14:29 -0500 Subject: [PATCH 03/10] Not working yet, next re-org of code --- .vscode/code-explorer.json | 22 + FiniteMonkey.egg-info/PKG-INFO | 200 +++ FiniteMonkey.egg-info/SOURCES.txt | 147 ++ FiniteMonkey.egg-info/dependency_links.txt | 1 + FiniteMonkey.egg-info/entry_points.txt | 3 + FiniteMonkey.egg-info/requires.txt | 31 + FiniteMonkey.egg-info/top_level.txt | 1 + nodes_config.py | 54 + nodes_injector.py | 25 + pyproject.toml | 113 +- root_service.py | 9 + run.py | 20 + src/agents/planning_v3.py | 169 +++ src/agents/sql_generate.py | 182 +++ src/dao/db3.py | 40 + src/dao/db_model.py | 52 + src/knowledges/entity.py | 20 + src/library/routes_svc.py | 0 src/openai_api/routes.py | 56 + uv.lock | 1513 ++++++++++++++++++++ 20 files changed, 2586 insertions(+), 72 deletions(-) create mode 100644 .vscode/code-explorer.json create mode 100644 FiniteMonkey.egg-info/PKG-INFO create mode 100644 FiniteMonkey.egg-info/SOURCES.txt create mode 100644 FiniteMonkey.egg-info/dependency_links.txt create mode 100644 FiniteMonkey.egg-info/entry_points.txt create mode 100644 FiniteMonkey.egg-info/requires.txt create mode 100644 FiniteMonkey.egg-info/top_level.txt create mode 100644 nodes_config.py create mode 100644 nodes_injector.py create mode 100644 root_service.py create mode 100644 run.py create mode 100644 src/agents/planning_v3.py create mode 100644 src/agents/sql_generate.py create mode 100644 src/dao/db3.py create mode 100644 src/dao/db_model.py create mode 100644 src/knowledges/entity.py create mode 100644 src/library/routes_svc.py create mode 100644 src/openai_api/routes.py create mode 100644 uv.lock diff --git a/.vscode/code-explorer.json b/.vscode/code-explorer.json new file mode 100644 index 00000000..3503e221 --- /dev/null +++ b/.vscode/code-explorer.json @@ -0,0 +1,22 @@ +{ + "#": "NOT recommend to edit manually. Write carefully! Generated by tianjianchn.code-explorer vscode extension.", + "#markerCount": 1, + "stacks": [ + { + "title": "class root(BaseM 2025-02-14", + "isActive": true, + "createdAt": "2025-02-15T00:17:34.074Z", + "id": "98d72628b1bb5d4b6be2bb35a6f73501", + "markers": [ + { + "code": "class root(BaseModel):", + "file": "src/root.py", + "line": 3, + "column": 0, + "createdAt": "2025-02-15T00:17:53.463Z", + "id": "156f801ac1de21e620e787f199161418" + } + ] + } + ] +} \ No newline at end of file diff --git a/FiniteMonkey.egg-info/PKG-INFO b/FiniteMonkey.egg-info/PKG-INFO new file mode 100644 index 00000000..b28406ff --- /dev/null +++ b/FiniteMonkey.egg-info/PKG-INFO @@ -0,0 +1,200 @@ +Metadata-Version: 2.2 +Name: FiniteMonkey +Version: 0.0.1 +Summary: Intelligent vulnerability mining engine. +Author: Xue Yue +License: Apache 2.0 +Project-URL: Homepage, https://github.com/BradMoonUESTC/finite-monkey-engine +Project-URL: Bug Reports, https://github.com/BradMoonUESTC/finite-monkey-engine/issues +Project-URL: Funding, https://github.com/BradMoonUESTC/finite-monkey-engine +Project-URL: Say Thanks!, https://github.com/BradMoonUESTC/finite-monkey-engine +Project-URL: Source, https://github.com/BradMoonUESTC/finite-monkey-engine +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: antlr4-python3-runtime>=4.13.2 +Requires-Dist: certifi>=2024.8.30 +Requires-Dist: charset-normalizer>=3.4.0 +Requires-Dist: colorama>=0.4.6 +Requires-Dist: dependency-injector>=4.45.0 +Requires-Dist: et-xmlfile>=1.1.0 +Requires-Dist: idna>=3.10 +Requires-Dist: joblib>=1.4.2 +Requires-Dist: numpy>=1.24.4 +Requires-Dist: openpyxl>=3.1.5 +Requires-Dist: pandas>=1.24.4 +Requires-Dist: psycopg2-binary>=2.9.9 +Requires-Dist: pydantic>=2.11.0a2 +Requires-Dist: pydantic-ai>=0.0.24 +Requires-Dist: pydantic-settings>=2.7.1 +Requires-Dist: python-dateutil>=2.9.0.post0 +Requires-Dist: python-dotenv>=1.0.1 +Requires-Dist: pytz>=2024.2 +Requires-Dist: requests>=2.32.3 +Requires-Dist: scikit-learn>=1.3.2 +Requires-Dist: scipy>=1.10.1 +Requires-Dist: simplejson>=3.19.3 +Requires-Dist: six>=1.16.0 +Requires-Dist: SQLAlchemy>=2.0.35 +Requires-Dist: threadpoolctl>=3.5.0 +Requires-Dist: tqdm>=4.66.5 +Requires-Dist: tree-sitter>=0.24.0 +Requires-Dist: tree-sitter-solidity>=1.2.11 +Requires-Dist: typing_extensions>=4.12.2 +Requires-Dist: tzdata>=2024.2 +Requires-Dist: urllib3>=2.2.3 + +# FiniteMonkey + +

+ +

+ +

+ + + +

+ +FiniteMonkey is an intelligent vulnerability mining engine based on large language models, requiring no pre-trained knowledge base or fine-tuning. Its core feature is using task-driven and prompt engineering approaches to guide models in vulnerability analysis through carefully designed prompts. + +## 🌟 Core Concepts + +- **Task-driven rather than problem-driven** +- **Prompt-driven rather than code-driven** +- **Focus on prompt design rather than model design** +- **Leverage "deception" and hallucination as key mechanisms** + +## 🏆 Achievements + +As of May 2024, this tool has helped discover over $60,000 worth of bug bounties. + +## 🚀 Latest Updates + +**2024.11.19**: Released version 1.0 - Validated LLM-based auditing and productization feasibility + +**Earlier Updates:** +- 2024.08.02: Project renamed to finite-monkey-engine +- 2024.08.01: Added Func, Tact language support +- 2024.07.23: Added Cairo, Move language support +- 2024.07.01: Updated license +- 2024.06.01: Added Python language support +- 2024.05.18: Improved false positive rate (~20%) +- 2024.05.16: Added cross-contract vulnerability confirmation +- 2024.04.29: Added basic Rust language support + +## 📋 Requirements + +- PostgreSQL database +- OpenAI API access +- Python environment + +## 🛠️ Installation & Configuration + +1. Place project in `src/dataset/agent-v1-c4` directory + +2. Configure project in `datasets.json`: +```json +{ + "StEverVault2": { + "path": "StEverVault", + "files": [], + "functions": [] + } +} +``` + +3. Create database using `src/db.sql` + +4. Configure `.env`: +```env +# Database connection +DATABASE_URL=postgresql://user:password@localhost:5432/dbname + +# API settings +OPENAI_API_BASE="api.example.com" +OPENAI_API_KEY=sk-your-api-key-here + +# Model settings +VUL_MODEL_ID=gpt-4-turbo +CLAUDE_MODEL=claude-3-5-sonnet-20240620 + +# Azure configuration +AZURE_API_KEY="your-azure-api-key" +AZURE_API_BASE="https://your-resource.openai.azure.com/" +AZURE_API_VERSION="2024-02-15-preview" +AZURE_DEPLOYMENT_NAME="your-deployment" + +# API selection +AZURE_OR_OPENAI="OPENAI" # Options: OPENAI, AZURE, CLAUDE + +# Scan parameters +BUSINESS_FLOW_COUNT=4 +SWITCH_FUNCTION_CODE=False +SWITCH_BUSINESS_CODE=True +``` + +## 🌈 Supported Languages + +- Solidity (.sol) +- Rust (.rs) +- Python (.py) +- Move (.move) +- Cairo (.cairo) +- Tact (.tact) +- Func (.fc) +- Java (.java) +- Pseudo-Solidity (.fr) - For scanning Solidity pseudocode + +## 📊 Scan Results Guide + +1. If interrupted due to network/API issues, resume scanning using the same project_id in main.py +3. Results include detailed annotations: + - Focus on entries marked "yes" in result column + - Filter "dont need In-project other contract" in category column + - Check specific code in business_flow_code column + - Find code location in name column + +## 🎯 Important Notes + +- Best suited for logic vulnerability mining in real projects +- Not recommended for academic vulnerability testing +- GPT-4-turbo recommended for best results +- Average scan time for medium-sized projects: 2-3 hours +- Estimated cost for 10 iterations on medium projects: $20-30 +- Current false positive rate: 30-65% (depends on project size) + +## 🔍 Technical Notes + +1. claude 3.5 sonnet in scanning provides better results with acceptable time cost, GPT-3 not fully tested +2. Deceptive prompt theory adaptable to any language with minor modifications +3. ANTLR AST parsing recommended for better code slicing results +4. Currently supports Solidity, plans to expand language support +5. DeepSeek-R1 is recommended for better confirmation results +## 🛡️ Scanning Features + +- Excels at code understanding and logic vulnerability detection +- Weaker at control flow vulnerability detection +- Designed for real projects, not academic test cases + +## 💡 Implementation Tips + +- Progress automatically saved per scan +- claude-3.5-sonnet offers best performance in scanning compared to other models +- deepseek-R1 offers best performance in confirmation compared to other models +- 10 iterations for medium projects take about 4 hours +- Results include detailed categorization + +## 📝 License + +Apache License 2.0 + +## 🤝 Contributing + +Pull Requests welcome! + +--- + +*Note: Project name inspired by [Large Language Monkeys paper](https://arxiv.org/abs/2407.21787v1)* + +Would you like me to explain or break down the code? diff --git a/FiniteMonkey.egg-info/SOURCES.txt b/FiniteMonkey.egg-info/SOURCES.txt new file mode 100644 index 00000000..8d6da604 --- /dev/null +++ b/FiniteMonkey.egg-info/SOURCES.txt @@ -0,0 +1,147 @@ +LICENSE +README.md +nodes_config.py +pyproject.toml +root.py +./nodes_config.py +./root.py +./activate/bin/activate_this.py +./src/ai_engine.py +./src/audit_config.py +./src/buildTime.py +./src/main.py +./src/prompts.py +./src/agents/planning_v3.py +./src/codebaseQA/__init__.py +./src/codebaseQA/rag_processor.py +./src/dao/__init__.py +./src/dao/cache_manager.py +./src/dao/dao_test.py +./src/dao/entity.py +./src/dao/task_mgr.py +./src/library/ChatgptToken.py +./src/library/__init__.py +./src/library/config.py +./src/library/dataset_utils.py +./src/library/formatutils.py +./src/library/utils.py +./src/library/parsing/SolidityLexer.py +./src/library/parsing/SolidityParser.py +./src/library/parsing/__init__.py +./src/library/parsing/callgraph.py +./src/library/parsing/constantTokenId copy.py +./src/library/parsing/constantTokenId.py +./src/library/parsing/utils.py +./src/library/sgp/__init__.py +./src/library/sgp/ast_node_types.py +./src/library/sgp/ast_scanner.py +./src/library/sgp/main.py +./src/library/sgp/sgp_error_listener.py +./src/library/sgp/sgp_parser.py +./src/library/sgp/sgp_visitor.py +./src/library/sgp/tokens.py +./src/library/sgp/utils.py +./src/library/sgp/parser/SolidityLexer.py +./src/library/sgp/parser/SolidityListener.py +./src/library/sgp/parser/SolidityParser.py +./src/library/sgp/parser/SolidityVisitor.py +./src/library/sgp/parser/__init__.py +./src/library/sgp/parser/parser.py +./src/library/sgp/tool/PDG_parser.py +./src/library/sgp/tool/baseline.py +./src/library/sgp/tool/determine_type.py +./src/library/sgp/tool/extract_roles.py +./src/library/sgp/tool/main.py +./src/library/sgp/tool/sGuard_compare.py +./src/library/sgp/utilities/call_graph_generator.py +./src/library/sgp/utilities/contract_extractor.py +./src/library/sgp/utilities/micelleneous.py +./src/openai_api/openai.py +./src/planning/__init__.py +./src/planning/planning_v2.py +./src/project/__init__.py +./src/project/project_audit.py +./src/project/project_parser.py +./src/project/project_settings.py +./src/prompt_factory/core_prompt.py +./src/prompt_factory/periphery_prompt.py +./src/prompt_factory/prompt_assembler.py +./src/prompt_factory/vul_check_prompt.py +./src/prompt_factory/vul_prompt.py +./src/res_processor/__init__.py +./src/res_processor/res_processor.py +./src/scripts/migration.py +./src/scripts/task_helper.py +FiniteMonkey.egg-info/PKG-INFO +FiniteMonkey.egg-info/SOURCES.txt +FiniteMonkey.egg-info/dependency_links.txt +FiniteMonkey.egg-info/entry_points.txt +FiniteMonkey.egg-info/requires.txt +FiniteMonkey.egg-info/top_level.txt +activate/bin/activate_this.py +src/ai_engine.py +src/audit_config.py +src/buildTime.py +src/main.py +src/prompts.py +src/agents/planning_v3.py +src/codebaseQA/__init__.py +src/codebaseQA/rag_processor.py +src/dao/__init__.py +src/dao/cache_manager.py +src/dao/dao_test.py +src/dao/entity.py +src/dao/task_mgr.py +src/library/ChatgptToken.py +src/library/__init__.py +src/library/config.py +src/library/dataset_utils.py +src/library/formatutils.py +src/library/utils.py +src/library/parsing/SolidityLexer.py +src/library/parsing/SolidityParser.py +src/library/parsing/__init__.py +src/library/parsing/callgraph.py +src/library/parsing/constantTokenId copy.py +src/library/parsing/constantTokenId.py +src/library/parsing/utils.py +src/library/sgp/__init__.py +src/library/sgp/ast_node_types.py +src/library/sgp/ast_scanner.py +src/library/sgp/main.py +src/library/sgp/sgp_error_listener.py +src/library/sgp/sgp_parser.py +src/library/sgp/sgp_visitor.py +src/library/sgp/tokens.py +src/library/sgp/utils.py +src/library/sgp/parser/SolidityLexer.py +src/library/sgp/parser/SolidityListener.py +src/library/sgp/parser/SolidityParser.py +src/library/sgp/parser/SolidityVisitor.py +src/library/sgp/parser/__init__.py +src/library/sgp/parser/parser.py +src/library/sgp/tool/PDG_parser.py +src/library/sgp/tool/baseline.py +src/library/sgp/tool/determine_type.py +src/library/sgp/tool/extract_roles.py +src/library/sgp/tool/main.py +src/library/sgp/tool/sGuard_compare.py +src/library/sgp/utilities/call_graph_generator.py +src/library/sgp/utilities/contract_extractor.py +src/library/sgp/utilities/micelleneous.py +src/openai_api/openai.py +src/planning/__init__.py +src/planning/planning_v2.py +src/project/__init__.py +src/project/project_audit.py +src/project/project_parser.py +src/project/project_settings.py +src/prompt_factory/core_prompt.py +src/prompt_factory/periphery_prompt.py +src/prompt_factory/prompt_assembler.py +src/prompt_factory/vul_check_prompt.py +src/prompt_factory/vul_prompt.py +src/res_processor/__init__.py +src/res_processor/res_processor.py +src/scripts/migration.py +src/scripts/task_helper.py \ No newline at end of file diff --git a/FiniteMonkey.egg-info/dependency_links.txt b/FiniteMonkey.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/FiniteMonkey.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/FiniteMonkey.egg-info/entry_points.txt b/FiniteMonkey.egg-info/entry_points.txt new file mode 100644 index 00000000..1898c794 --- /dev/null +++ b/FiniteMonkey.egg-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +finite_monkey_engine = finite_monkey_engine:main +finite_monkey_root = root.py:root diff --git a/FiniteMonkey.egg-info/requires.txt b/FiniteMonkey.egg-info/requires.txt new file mode 100644 index 00000000..019fcd3b --- /dev/null +++ b/FiniteMonkey.egg-info/requires.txt @@ -0,0 +1,31 @@ +antlr4-python3-runtime>=4.13.2 +certifi>=2024.8.30 +charset-normalizer>=3.4.0 +colorama>=0.4.6 +dependency-injector>=4.45.0 +et-xmlfile>=1.1.0 +idna>=3.10 +joblib>=1.4.2 +numpy>=1.24.4 +openpyxl>=3.1.5 +pandas>=1.24.4 +psycopg2-binary>=2.9.9 +pydantic>=2.11.0a2 +pydantic-ai>=0.0.24 +pydantic-settings>=2.7.1 +python-dateutil>=2.9.0.post0 +python-dotenv>=1.0.1 +pytz>=2024.2 +requests>=2.32.3 +scikit-learn>=1.3.2 +scipy>=1.10.1 +simplejson>=3.19.3 +six>=1.16.0 +SQLAlchemy>=2.0.35 +threadpoolctl>=3.5.0 +tqdm>=4.66.5 +tree-sitter>=0.24.0 +tree-sitter-solidity>=1.2.11 +typing_extensions>=4.12.2 +tzdata>=2024.2 +urllib3>=2.2.3 diff --git a/FiniteMonkey.egg-info/top_level.txt b/FiniteMonkey.egg-info/top_level.txt new file mode 100644 index 00000000..2be4b43f --- /dev/null +++ b/FiniteMonkey.egg-info/top_level.txt @@ -0,0 +1 @@ +finite_monkey_engine diff --git a/nodes_config.py b/nodes_config.py new file mode 100644 index 00000000..1c9e1aed --- /dev/null +++ b/nodes_config.py @@ -0,0 +1,54 @@ +from pathlib import Path +from typing import Tuple, Type + +from pydantic_settings import ( + BaseSettings, + PydanticBaseSettingsSource, + PyprojectTomlConfigSettingsSource, + SettingsConfigDict, +) + + +class DiscoverSettings(BaseSettings): + """Example of discovering a pyproject.toml in parent directories in not in `Path.cwd()`.""" + + model_config = SettingsConfigDict(pyproject_toml_depth=2) + + @classmethod + def settings_customise_sources( + cls, + settings_cls: Type[BaseSettings], + init_settings: PydanticBaseSettingsSource, + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + ) -> Tuple[PydanticBaseSettingsSource, ...]: + return (PyprojectTomlConfigSettingsSource(settings_cls),) + + +class ExplicitFilePathSettings(BaseSettings): + """Example of explicitly providing the path to the file to load.""" + + field: str + + @classmethod + def settings_customise_sources( + cls, + settings_cls: Type[BaseSettings], + init_settings: PydanticBaseSettingsSource, + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + ) -> Tuple[PydanticBaseSettingsSource, ...]: + return ( + PyprojectTomlConfigSettingsSource( + settings_cls, Path('~/.config').resolve() / 'pyproject.toml' + ), + ) + +class nodes_config(DiscoverSettings, BaseSettings): + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + \ No newline at end of file diff --git a/nodes_injector.py b/nodes_injector.py new file mode 100644 index 00000000..c242ab80 --- /dev/null +++ b/nodes_injector.py @@ -0,0 +1,25 @@ + +from dependency_injector import containers, providers + +from .database import Database +from .repositories import UserRepository +from .services import UserService + + +class Container(containers.DeclarativeContainer): + + wiring_config = containers.WiringConfiguration(modules=[".endpoints"]) + + config = providers.Configuration(yaml_files=["config.yml"]) + + db = providers.Singleton(Database, db_url=config.db.url) + + user_repository = providers.Factory( + UserRepository, + session_factory=db.provided.session, + ) + + user_service = providers.Factory( + UserService, + user_repository=user_repository, + ) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 2f040045..77dfd494 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "certifi>=2024.8.30", "charset-normalizer>=3.4.0", "colorama>=0.4.6", + "dependency-injector>=4.45.0", "et-xmlfile>=1.1.0", "idna>=3.10", "joblib>=1.4.2", @@ -19,6 +20,9 @@ dependencies = [ "openpyxl>=3.1.5", "pandas>=1.24.4", "psycopg2-binary>=2.9.9", + "pydantic>=2.11.0a2", + "pydantic-ai>=0.0.24", + "pydantic-settings>=2.7.1", "python-dateutil>=2.9.0.post0", "python-dotenv>=1.0.1", "pytz>=2024.2", @@ -30,48 +34,58 @@ dependencies = [ "SQLAlchemy>=2.0.35", "threadpoolctl>=3.5.0", "tqdm>=4.66.5", + "tree-sitter>=0.24.0", + "tree-sitter-solidity>=1.2.11", "typing_extensions>=4.12.2", "tzdata>=2024.2", "urllib3>=2.2.3", ] -[project.optional-dependencies] +[project.urls] +"Homepage" = "https://github.com/BradMoonUESTC/finite-monkey-engine" +"Bug Reports" = "https://github.com/BradMoonUESTC/finite-monkey-engine/issues" +"Funding" = "https://github.com/BradMoonUESTC/finite-monkey-engine" +"Say Thanks!" = "https://github.com/BradMoonUESTC/finite-monkey-engine" +"Source" = "https://github.com/BradMoonUESTC/finite-monkey-engine" -dev = ["mypy>=1.11.1", "ruff>=0.6.1"] +[project.scripts] +finite_monkey_engine = "finite_monkey_engine:main" +finite_monkey_root = "root.py:root" [build-system] requires = ["setuptools>=73.0.0", "wheel"] build-backend = "setuptools.build_meta" +[tool.uv.sources] +"finite_monkey_engine" = { workspace = true } -[tool.setuptools] -packages = ["assistant"] -[tool.setuptools.package-dir] -"assistant" = "src/assistant" +[tool.hatch.build.targets.wheel] +packages = ["finite_monkey_engine"] -[tool.setuptools.package-data] -"*" = ["py.typed"] -[tool.ruff] -lint.select = [ - "E", # pycodestyle - "F", # pyflakes - "I", # isort - "D", # pydocstyle - "D401", # First line should be in imperative mood - "T201", - "UP", -] -lint.ignore = ["UP006", "UP007", "UP035", "D417", "E501"] +[tool.uv.workspace] +"members" = ["/FiniteMonkey.egg-info/entry_points.txt"] + +[tool.uv] +package = true +compile-bytecode = true +prerelease = "allow" -[tool.ruff.lint.per-file-ignores] -"tests/*" = ["D", "UP"] +[tool.uv.pip] +compile-bytecode = true +emit-build-options = true +emit-index-annotation = true +emit-marker-expression = true +python = "3.12" +python-version = "3.12" +universal = true -[tool.ruff.lint.pydocstyle] -convention = "google"] + +[tool.setuptools.package-dir] +"finite_monkey_engine" = "." [tool.black] -target-version = ['py310'] +target-version = ['py312'] line-length = 120 skip-string-normalization = true skip-magic-trailing-comma = true @@ -83,7 +97,7 @@ force-exclude = ''' ''' [tool.isort] -py_version = 310 +py_version = 312 sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] default_section = "THIRDPARTY" known_third_party = [] @@ -103,66 +117,21 @@ skip_glob = ["docs/*", "setup.py"] filter_files = true [tool.ruff] -target-version = "py310" +target-version = "py312" line-length = 120 -indent-width = 4 extend-exclude = ["docs", "test", "tests"] - -[tool.ruff.lint] -select = ["F", "E"] -extend-select = ["W", "C90", "I", "N", "B", "A", "C4", "PERF", "RUF"] ignore = ["I001"] fixable = ["ALL"] unfixable = [] preview = true dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -[tool.ruff.lint.isort] -force-single-line = true -force-sort-within-sections = false -lines-after-imports = 2 -known-first-party = [] -known-local-folder = [] -known-third-party = [] -section-order = [ - "future", - "standard-library", - "third-party", - "first-party", - "local-folder", -] - -[tool.ruff.lint.mccabe] -max-complexity = 24 - -[tool.ruff.lint.pycodestyle] -ignore-overlong-task-comments = true - -[tool.ruff.lint.pydocstyle] -convention = "numpy" - -[tool.ruff.lint.flake8-annotations] -allow-star-arg-any = true -ignore-fully-untyped = true - -[tool.ruff.lint.pylint] -max-args = 5 -max-branches = 12 -max-locals = 15 -max-statements = 50 - -[tool.ruff.format] -quote-style = "double" -indent-style = "space" -skip-magic-trailing-comma = false -line-ending = "auto" -preview = false -docstring-code-format = true [tool.mypy] # Platform configuration python_version = "3.12" # imports related +plugins = ['pydantic.mypy'] ignore_missing_imports = true follow_imports = "silent" # None and Optional handling diff --git a/root_service.py b/root_service.py new file mode 100644 index 00000000..c67b5f34 --- /dev/null +++ b/root_service.py @@ -0,0 +1,9 @@ +# /// script +# requires-python = ">=3.12" +# dependencies = [] +# /// +from dependency_injector import inject, Provide, providers +from dependency_injector.wiring import Provide, inject +from nodes_config import nodes_config +from nodes_injector import Container + diff --git a/run.py b/run.py new file mode 100644 index 00000000..f18ba425 --- /dev/null +++ b/run.py @@ -0,0 +1,20 @@ + +from fastapi import FastAPI + +from .containers import Container +from . import endpoints + + +def create_app() -> FastAPI: + container = Container() + + db = container.db() + db.create_database() + + app = FastAPI() + app.container = container + app.include_router(endpoints.router) + return app + + +app = create_app() \ No newline at end of file diff --git a/src/agents/planning_v3.py b/src/agents/planning_v3.py new file mode 100644 index 00000000..022fa1ed --- /dev/null +++ b/src/agents/planning_v3.py @@ -0,0 +1,169 @@ +import asyncio +import sys +from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager +from dataclasses import dataclass +from datetime import date +from typing import Annotated, Any, Union + +import asyncpg +import logfire +from annotated_types import MinLen +from devtools import debug +from pydantic import BaseModel, Field +from typing_extensions import TypeAlias + +from pydantic_ai import Agent, ModelRetry, RunContext +from pydantic_ai.format_as_xml import format_as_xml + +# 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured +logfire.configure(send_to_logfire='if-token-present') +logfire.instrument_asyncpg() + +DB_SCHEMA = """ +CREATE TABLE records ( + created_at timestamptz, + start_timestamp timestamptz, + end_timestamp timestamptz, + trace_id text, + span_id text, + parent_span_id text, + level log_level, + span_name text, + message text, + attributes_json_schema text, + attributes jsonb, + tags text[], + is_exception boolean, + otel_status_message text, + service_name text +); +""" +SQL_EXAMPLES = [ + { + 'request': 'show me records where foobar is false', + 'response': "SELECT * FROM records WHERE attributes->>'foobar' = false", + }, + { + 'request': 'show me records where attributes include the key "foobar"', + 'response': "SELECT * FROM records WHERE attributes ? 'foobar'", + }, + { + 'request': 'show me records from yesterday', + 'response': "SELECT * FROM records WHERE start_timestamp::date > CURRENT_TIMESTAMP - INTERVAL '1 day'", + }, + { + 'request': 'show me error records with the tag "foobar"', + 'response': "SELECT * FROM records WHERE level = 'error' and 'foobar' = ANY(tags)", + }, +] + + +@dataclass +class Deps: + conn: asyncpg.Connection + + +class Success(BaseModel): + """Response when SQL could be successfully generated.""" + + sql_query: Annotated[str, MinLen(1)] + explanation: str = Field( + '', description='Explanation of the SQL query, as markdown' + ) + + +class InvalidRequest(BaseModel): + """Response the user input didn't include enough information to generate SQL.""" + + error_message: str + + +Response: TypeAlias = Union[Success, InvalidRequest] +agent: Agent[Deps, Response] = Agent( + 'google-gla:gemini-1.5-flash', + # Type ignore while we wait for PEP-0747, nonetheless unions will work fine everywhere else + result_type=Response, # type: ignore + deps_type=Deps, +) + + +@agent.system_prompt +async def system_prompt() -> str: + return f"""\ +Given the following PostgreSQL table of records, your job is to +write a SQL query that suits the user's request. + +Database schema: + +{DB_SCHEMA} + +today's date = {date.today()} + +{format_as_xml(SQL_EXAMPLES)} +""" + + +@agent.result_validator +async def validate_result(ctx: RunContext[Deps], result: Response) -> Response: + if isinstance(result, InvalidRequest): + return result + + # gemini often adds extraneous backslashes to SQL + result.sql_query = result.sql_query.replace('\\', '') + if not result.sql_query.upper().startswith('SELECT'): + raise ModelRetry('Please create a SELECT query') + + try: + await ctx.deps.conn.execute(f'EXPLAIN {result.sql_query}') + except asyncpg.exceptions.PostgresError as e: + raise ModelRetry(f'Invalid query: {e}') from e + else: + return result + + +async def main(): + if len(sys.argv) == 1: + prompt = 'show me logs from yesterday, with level "error"' + else: + prompt = sys.argv[1] + + async with database_connect( + 'postgresql://postgres:postgres@localhost:54320', 'pydantic_ai_sql_gen' + ) as conn: + deps = Deps(conn) + result = await agent.run(prompt, deps=deps) + debug(result.data) + + +# pyright: reportUnknownMemberType=false +# pyright: reportUnknownVariableType=false +@asynccontextmanager +async def database_connect(server_dsn: str, database: str) -> AsyncGenerator[Any, None]: + with logfire.span('check and create DB'): + conn = await asyncpg.connect(server_dsn) + try: + db_exists = await conn.fetchval( + 'SELECT 1 FROM pg_database WHERE datname = $1', database + ) + if not db_exists: + await conn.execute(f'CREATE DATABASE {database}') + finally: + await conn.close() + + conn = await asyncpg.connect(f'{server_dsn}/{database}') + try: + with logfire.span('create schema'): + async with conn.transaction(): + if not db_exists: + await conn.execute( + "CREATE TYPE log_level AS ENUM ('debug', 'info', 'warning', 'error', 'critical')" + ) + await conn.execute(DB_SCHEMA) + yield conn + finally: + await conn.close() + + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/src/agents/sql_generate.py b/src/agents/sql_generate.py new file mode 100644 index 00000000..13f0d502 --- /dev/null +++ b/src/agents/sql_generate.py @@ -0,0 +1,182 @@ + +"""PydanticAI to generate SQL queries based on user input. + +Run postgres with: + + mkdir postgres-data + docker run --rm -e POSTGRES_PASSWORD=postgres -p 54320:5432 postgres + +Run with: + + uv run -m pydantic_ai_examples.sql_gen "show me logs from yesterday, with level 'error'" +""" + +import asyncio +import sys +from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager +from dataclasses import dataclass +from datetime import date +from typing import Annotated, Any, Union + +import asyncpg +import logfire +from annotated_types import MinLen +from devtools import debug +from pydantic import BaseModel, Field +from typing_extensions import TypeAlias + +from pydantic_ai import Agent, ModelRetry, RunContext +from pydantic_ai.format_as_xml import format_as_xml + +# 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured +logfire.configure(send_to_logfire='if-token-present') +logfire.instrument_asyncpg() + +DB_SCHEMA = """ +CREATE TABLE records ( + created_at timestamptz, + start_timestamp timestamptz, + end_timestamp timestamptz, + trace_id text, + span_id text, + parent_span_id text, + level log_level, + span_name text, + message text, + attributes_json_schema text, + attributes jsonb, + tags text[], + is_exception boolean, + otel_status_message text, + service_name text +); +""" +SQL_EXAMPLES = [ + { + 'request': 'show me records where foobar is false', + 'response': "SELECT * FROM records WHERE attributes->>'foobar' = false", + }, + { + 'request': 'show me records where attributes include the key "foobar"', + 'response': "SELECT * FROM records WHERE attributes ? 'foobar'", + }, + { + 'request': 'show me records from yesterday', + 'response': "SELECT * FROM records WHERE start_timestamp::date > CURRENT_TIMESTAMP - INTERVAL '1 day'", + }, + { + 'request': 'show me error records with the tag "foobar"', + 'response': "SELECT * FROM records WHERE level = 'error' and 'foobar' = ANY(tags)", + }, +] + + +@dataclass +class Deps: + conn: asyncpg.Connection + + +class Success(BaseModel): + """Response when SQL could be successfully generated.""" + + sql_query: Annotated[str, MinLen(1)] + explanation: str = Field( + '', description='Explanation of the SQL query, as markdown' + ) + + +class InvalidRequest(BaseModel): + """Response the user input didn't include enough information to generate SQL.""" + + error_message: str + + +Response: TypeAlias = Union[Success, InvalidRequest] +agent: Agent[Deps, Response] = Agent( + 'google-gla:gemini-1.5-flash', + # Type ignore while we wait for PEP-0747, nonetheless unions will work fine everywhere else + result_type=Response, # type: ignore + deps_type=Deps, +) + + +@agent.system_prompt +async def system_prompt() -> str: + return f"""\ +Given the following PostgreSQL table of records, your job is to +write a SQL query that suits the user's request. + +Database schema: + +{DB_SCHEMA} + +today's date = {date.today()} + +{format_as_xml(SQL_EXAMPLES)} +""" + + +@agent.result_validator +async def validate_result(ctx: RunContext[Deps], result: Response) -> Response: + if isinstance(result, InvalidRequest): + return result + + # gemini often adds extraneous backslashes to SQL + result.sql_query = result.sql_query.replace('\\', '') + if not result.sql_query.upper().startswith('SELECT'): + raise ModelRetry('Please create a SELECT query') + + try: + await ctx.deps.conn.execute(f'EXPLAIN {result.sql_query}') + except asyncpg.exceptions.PostgresError as e: + raise ModelRetry(f'Invalid query: {e}') from e + else: + return result + + +async def main(): + if len(sys.argv) == 1: + prompt = 'show me logs from yesterday, with level "error"' + else: + prompt = sys.argv[1] + + async with database_connect( + 'postgresql://postgres:postgres@localhost:54320', 'pydantic_ai_sql_gen' + ) as conn: + deps = Deps(conn) + result = await agent.run(prompt, deps=deps) + debug(result.data) + + +# pyright: reportUnknownMemberType=false +# pyright: reportUnknownVariableType=false +@asynccontextmanager +async def database_connect(server_dsn: str, database: str) -> AsyncGenerator[Any, None]: + with logfire.span('check and create DB'): + conn = await asyncpg.connect(server_dsn) + try: + db_exists = await conn.fetchval( + 'SELECT 1 FROM pg_database WHERE datname = $1', database + ) + if not db_exists: + await conn.execute(f'CREATE DATABASE {database}') + finally: + await conn.close() + + conn = await asyncpg.connect(f'{server_dsn}/{database}') + try: + with logfire.span('create schema'): + async with conn.transaction(): + if not db_exists: + await conn.execute( + "CREATE TYPE log_level AS ENUM ('debug', 'info', 'warning', 'error', 'critical')" + ) + await conn.execute(DB_SCHEMA) + yield conn + finally: + await conn.close() + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/src/dao/db3.py b/src/dao/db3.py new file mode 100644 index 00000000..d416bd23 --- /dev/null +++ b/src/dao/db3.py @@ -0,0 +1,40 @@ + +from contextlib import contextmanager, AbstractContextManager +from typing import Callable +import logging + +from sqlalchemy import create_engine, orm +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import Session + +logger = logging.getLogger(__name__) + +Base = declarative_base() + + +class Database: + + def __init__(self, db_url: str) -> None: + self._engine = create_engine(db_url, echo=True) + self._session_factory = orm.scoped_session( + orm.sessionmaker( + autocommit=False, + autoflush=False, + bind=self._engine, + ), + ) + + def create_database(self) -> None: + Base.metadata.create_all(self._engine) + + @contextmanager + def session(self) -> Callable[..., AbstractContextManager[Session]]: + session: Session = self._session_factory() + try: + yield session + except Exception: + logger.exception("Session rollback because of exception") + session.rollback() + raise + finally: + session.close() \ No newline at end of file diff --git a/src/dao/db_model.py b/src/dao/db_model.py new file mode 100644 index 00000000..69bdd014 --- /dev/null +++ b/src/dao/db_model.py @@ -0,0 +1,52 @@ +from contextlib import AbstractContextManager +from typing import Callable, Iterator + +from sqlalchemy.orm import Session + +from .models import User + + +class UserRepository: + + def __init__(self, session_factory: Callable[..., AbstractContextManager[Session]]) -> None: + self.session_factory = session_factory + + def get_all(self) -> Iterator[User]: + with self.session_factory() as session: + return session.query(User).all() + + def get_by_id(self, user_id: int) -> User: + with self.session_factory() as session: + user = session.query(User).filter(User.id == user_id).first() + if not user: + raise UserNotFoundError(user_id) + return user + + def add(self, email: str, password: str, is_active: bool = True) -> User: + with self.session_factory() as session: + user = User(email=email, hashed_password=password, is_active=is_active) + session.add(user) + session.commit() + session.refresh(user) + return user + + def delete_by_id(self, user_id: int) -> None: + with self.session_factory() as session: + entity: User = session.query(User).filter(User.id == user_id).first() + if not entity: + raise UserNotFoundError(user_id) + session.delete(entity) + session.commit() + + +class NotFoundError(Exception): + + entity_name: str + + def __init__(self, entity_id): + super().__init__(f"{self.entity_name} not found, id: {entity_id}") + + +class UserNotFoundError(NotFoundError): + + entity_name: str = "User" \ No newline at end of file diff --git a/src/knowledges/entity.py b/src/knowledges/entity.py new file mode 100644 index 00000000..94b2a99e --- /dev/null +++ b/src/knowledges/entity.py @@ -0,0 +1,20 @@ +from sqlalchemy import Column, String, Boolean, Integer + +from .database import Base + +; User entity +class User(Base): + + __tablename__ = "users" + + id = Column(Integer, primary_key=True) + email = Column(String, unique=True) + hashed_password = Column(String) + is_active = Column(Boolean, default=True) + + def __repr__(self): + return f"" + \ No newline at end of file diff --git a/src/library/routes_svc.py b/src/library/routes_svc.py new file mode 100644 index 00000000..e69de29b diff --git a/src/openai_api/routes.py b/src/openai_api/routes.py new file mode 100644 index 00000000..7cd471b9 --- /dev/null +++ b/src/openai_api/routes.py @@ -0,0 +1,56 @@ + +from fastapi import APIRouter, Depends, Response, status +from dependency_injector.wiring import inject, Provide + +from .containers import Container +from .services import UserService +from .repositories import NotFoundError + +router = APIRouter() + + +@router.get("/users") +@inject +def get_list( + user_service: UserService = Depends(Provide[Container.user_service]), +): + return user_service.get_users() + + +@router.get("/users/{user_id}") +@inject +def get_by_id( + user_id: int, + user_service: UserService = Depends(Provide[Container.user_service]), +): + try: + return user_service.get_user_by_id(user_id) + except NotFoundError: + return Response(status_code=status.HTTP_404_NOT_FOUND) + + +@router.post("/users", status_code=status.HTTP_201_CREATED) +@inject +def add( + user_service: UserService = Depends(Provide[Container.user_service]), +): + return user_service.create_user() + + +@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT) +@inject +def remove( + user_id: int, + user_service: UserService = Depends(Provide[Container.user_service]), +): + try: + user_service.delete_user_by_id(user_id) + except NotFoundError: + return Response(status_code=status.HTTP_404_NOT_FOUND) + else: + return Response(status_code=status.HTTP_204_NO_CONTENT) + + +@router.get("/status") +def get_status(): + return {"status": "OK"} \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..dac6bf00 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1513 @@ +version = 1 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] + +[options] +prerelease-mode = "allow" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anthropic" +version = "0.45.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/74/2b2485fc120da834c0c5be07462541ec082e9fa8851d845f2587e480535a/anthropic-0.45.2.tar.gz", hash = "sha256:32a18b9ecd12c91b2be4cae6ca2ab46a06937b5aa01b21308d97a6d29794fb5e", size = 200901 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/86/e81814e542d1eaeec84d2312bec93a99b9ef1d78d9bfae1fc5dd74abdf15/anthropic-0.45.2-py3-none-any.whl", hash = "sha256:ecd746f7274451dfcb7e1180571ead624c7e1195d1d46cb7c70143d2aedb4d35", size = 222797 }, +] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/5f/2cdf6f7aca3b20d3f316e9f505292e1f256a32089bd702034c29ebde6242/antlr4_python3_runtime-4.13.2.tar.gz", hash = "sha256:909b647e1d2fc2b70180ac586df3933e38919c85f98ccc656a96cd3f25ef3916", size = 117467 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/03/a851e84fcbb85214dc637b6378121ef9a0dd61b4c65264675d8a5c9b1ae7/antlr4_python3_runtime-4.13.2-py3-none-any.whl", hash = "sha256:fe3835eb8d33daece0e799090eda89719dbccee7aa39ef94eed3818cafa5a7e8", size = 144462 }, +] + +[[package]] +name = "anyio" +version = "4.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/73/199a98fc2dae33535d6b8e8e6ec01f8c1d76c9adb096c6b7d64823038cde/anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", size = 181126 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 }, +] + +[[package]] +name = "cachetools" +version = "5.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/74/57df1ab0ce6bc5f6fa868e08de20df8ac58f9c44330c7671ad922d2bbeae/cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95", size = 28044 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/4e/de4ff18bcf55857ba18d3a4bd48c8a9fde6bb0980c9d20b263f05387fd88/cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb", size = 9530 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, + { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, + { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, + { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, + { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, + { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, + { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, + { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, + { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, + { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, + { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, + { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, + { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "cohere" +version = "5.13.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastavro" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-core" }, + { name = "requests" }, + { name = "tokenizers" }, + { name = "types-requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/48/795c53b25b08ec353cc4f48dc5c199ac4615b3c331e716ac50c7cb07034c/cohere-5.13.12.tar.gz", hash = "sha256:97bb9ac107e580780b941acbabd3aa5e71960e6835398292c46aaa8a0a4cab88", size = 132860 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/c6/cffec9284d9713d28c6235a653a9a34c49b0f880f00cfa002252cdb8d033/cohere-5.13.12-py3-none-any.whl", hash = "sha256:2a043591a3e5280b47716a6b311e4c7f58e799364113a9cb81b50cd4f6c95f7e", size = 252856 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "dependency-injector" +version = "4.45.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/32/beb8a8aed8a07273bbc2afc6e823d2d70e5f898ed9a8cc413c45aec5642a/dependency_injector-4.45.0.tar.gz", hash = "sha256:7effdb9e45f5c2813e2fd9dc2ef2c446dd59716f66f9c772ab27b9ed16efc894", size = 1117514 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/20/b330400537783031a9e0d09865a2f47b3711f4cca1d13617ef3ebd9bc38f/dependency_injector-4.45.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7ec03cd54b79b5fc8df46ae821392358a24dc371bda01a2a68917ab0e559a96b", size = 1853439 }, + { url = "https://files.pythonhosted.org/packages/5f/fd/8708ab921876ee71fcb94778f99147251461b8772f95a17f182501241be8/dependency_injector-4.45.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:433883ccdfa353e6c8c2e20ba6eb08f77149a7e15f816f05be65bb791b86e5fe", size = 6522374 }, + { url = "https://files.pythonhosted.org/packages/df/ef/e30175362f9d601343113107a853bb1f6399d64e961004adb27e2f88a1e6/dependency_injector-4.45.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:892c40f800e19732e9a10d595237bfbe09637e07a98b55c8b0526ac7f8f7400b", size = 6571378 }, + { url = "https://files.pythonhosted.org/packages/90/97/b5b0f0ee0f7d99e0e5dc50380f109ee5b8e3180c07cf9702e4d7b80e8241/dependency_injector-4.45.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cedd4f87795b9a9712bf2b6786f0d37e8848418a6b4a59cb185fd1c3866dd888", size = 6146970 }, + { url = "https://files.pythonhosted.org/packages/ac/fc/4ef2836be45fce69d90fb0a7c6e818b52a90ae9e9aef318e1598a6abbd2a/dependency_injector-4.45.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:db61b15cfa7fc679b22abb216bbef7888cc332a3f1f0279d6a621cf899661a80", size = 6127692 }, + { url = "https://files.pythonhosted.org/packages/51/ce/b104a5f68a413be723f002b53f05d2032a417f6cad0ed4783eff48ddb7ff/dependency_injector-4.45.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:75d0f8c748efe5e1f8a0970742483b38687bfe460496a4b7e344440798d0fa99", size = 5991031 }, + { url = "https://files.pythonhosted.org/packages/1a/bc/2dbb1e5ca2c450642947d1243e7dea138adc281ad834ef851d7538306a2f/dependency_injector-4.45.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21cfd5d2f26cca02bd897069b4460be1cbac1b07875a33885a84f9aa165cead5", size = 6339597 }, + { url = "https://files.pythonhosted.org/packages/de/89/9cefb635be10244b54b57c546b93746dac19cbceb848c3f02cc50f089c10/dependency_injector-4.45.0-cp310-cp310-win32.whl", hash = "sha256:cebd7fa8a9bbfed3af38c6f98763caadf0b3c63ade142c3eefb41788247cb5d7", size = 1664866 }, + { url = "https://files.pythonhosted.org/packages/d8/25/b791d2d6a313cfb673fc2fab3b92d20870682c945023232f147f779a9bc3/dependency_injector-4.45.0-cp310-cp310-win_amd64.whl", hash = "sha256:f349e3ceb1913eba37489cab82831e9d46d9008e59c6560cfefe0c57dc1168b0", size = 1798287 }, + { url = "https://files.pythonhosted.org/packages/54/f1/8151f38a1b62651ebf089974c8c393d81c2901f2fce44eeb0b9828edcca4/dependency_injector-4.45.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a76ab1e114402f284c71039391826d9e43083169d88568ede109ff4fc85de1e", size = 1859197 }, + { url = "https://files.pythonhosted.org/packages/97/da/4013b6ea6b358cb60f1aca64b80368a4d2ddcb5e1c14d197eabb75049d46/dependency_injector-4.45.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4aefc511be8ba97d823a71fb52a3b0ccaa7f9387cb51444862ebcb919822cae", size = 7069850 }, + { url = "https://files.pythonhosted.org/packages/7f/10/9f299641f1c03a2b202c988261416531ffc1f816710f0bd9aa4971f680ea/dependency_injector-4.45.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a013c93a870a3a1ce9829dd272bd02b194ea286df48dfb9d059cf34526738b8", size = 7106114 }, + { url = "https://files.pythonhosted.org/packages/6d/58/3aefb686a5dc25ab2f376904ac37e7a04c5b9054bfd57ce8f57121129355/dependency_injector-4.45.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1641f25faf543df14b2395a3cea51bd0610bc7b2c1cb6ed1c13ec4bec0063e2", size = 6636391 }, + { url = "https://files.pythonhosted.org/packages/b6/08/66bcefa816e09deb771233e5f095721c2e061ed9406383f6ebad31379dfc/dependency_injector-4.45.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97dc40a527e488df23e9fb5b33a85f9176b730449ad2e498d6cc556dcad540ae", size = 6718172 }, + { url = "https://files.pythonhosted.org/packages/ca/62/ed699aa2d4db0d44a8f91fb730d705c6da696b63711c82d3c2be91567c00/dependency_injector-4.45.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9396c71f25744fd0ded9b171fee05ea3ca13a87c12512ddd8b7e4330f70647eb", size = 6538136 }, + { url = "https://files.pythonhosted.org/packages/ea/ec/ed00a79e89984edde7d16313c9379c9c4eea8d9d5e0d5a2071e940f33236/dependency_injector-4.45.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d98a490f8a2decf40ca2aa69d0559d01dd96131422fa2086626fd5949837335a", size = 6897940 }, + { url = "https://files.pythonhosted.org/packages/f5/72/9ced97efda3ed48e803b942482b4c195caaebd09504604ff3b1d662b3c73/dependency_injector-4.45.0-cp311-cp311-win32.whl", hash = "sha256:4cec41b8dd678555ea025ad320e6354cad92b18b6433f8cd0b423b02989668c3", size = 1665406 }, + { url = "https://files.pythonhosted.org/packages/e1/af/1690e57fae9eaf8ec285f0de409d13566759059cc5d0f6fdc42857431f95/dependency_injector-4.45.0-cp311-cp311-win_amd64.whl", hash = "sha256:5eb99c2ea049ac1065a0b4cf06166f70fac697049ddd342b88cb6a264f106faf", size = 1800647 }, + { url = "https://files.pythonhosted.org/packages/52/9f/57324d953301136b5f709e807532183c82ee15a9982d7da8972c0223d8ae/dependency_injector-4.45.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2144948b5218f480ca94c11e848762aa305539ec67406ccecfbcb556d06a61dd", size = 1865841 }, + { url = "https://files.pythonhosted.org/packages/fa/7d/02ec92c0cef097047867712d702f7edefcb566f681dac87fdd1e121f3ee2/dependency_injector-4.45.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a0c665ee9bbb9434497a9b97bbb2018a9c427c0d014bff77dda2e0586c9020", size = 6818546 }, + { url = "https://files.pythonhosted.org/packages/84/0e/abea9847d5a6bc3ecb4b0057842661e895c02524503bb41963f0b6b3d033/dependency_injector-4.45.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:403f44e2073986138dd95f4a8ac864453524b18385c44b838cba8a2f7f742f2e", size = 6946584 }, + { url = "https://files.pythonhosted.org/packages/00/9a/87155cc98e2afda2deb9f81309698f756f4ab6e9f8522ce386e65dc02fb8/dependency_injector-4.45.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2a38cf7826ff253d30958c35bc1e121af292f0f12fb5ffd1ad3ea5377b63e3b", size = 6400998 }, + { url = "https://files.pythonhosted.org/packages/31/fd/60262f2d61d0b049107f5e3bca8f6f3321858bac3190f143e61c0dcbca29/dependency_injector-4.45.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2b18413b2f965793f8e401996a6cc01e0aa0336568805fc822285cdc72cfb7d9", size = 6493477 }, + { url = "https://files.pythonhosted.org/packages/8f/dc/ac111c2e17f764c9584a99be2310c9593195fe694e6db905d8c71f22ce09/dependency_injector-4.45.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ed3e60296b3b9d29c3eb57cb44a82d7627da1a7eac68ac97fbf9bbd9e2a5cb99", size = 6317674 }, + { url = "https://files.pythonhosted.org/packages/1b/26/6546f8acc6d3da46b3adab2fa790ffa6739c236604533f4c05e66e3a7fcd/dependency_injector-4.45.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd1afbbf03ed97007f6d4cb11944776e79207d9d7634ba5c4f897f4802b382ae", size = 6724126 }, + { url = "https://files.pythonhosted.org/packages/31/c2/5580c731fd550327d9f66ff62824cb04b4a0530b5ca6997f5dd2905b383f/dependency_injector-4.45.0-cp312-cp312-win32.whl", hash = "sha256:2bfca5875aedb5a0995a4b2e5a928aef5791a58f4280d0c3003cb0720385f546", size = 1660532 }, + { url = "https://files.pythonhosted.org/packages/52/4d/185a604a96d766f7bb8ea1017ad66a136c78593d15a294163ac258778e4a/dependency_injector-4.45.0-cp312-cp312-win_amd64.whl", hash = "sha256:5510a68445741fce2af071fd7fda177c4249f2b576d2b2b2b5af23083e4208e8", size = 1776903 }, + { url = "https://files.pythonhosted.org/packages/fa/41/e8dc964575ef0f6e7abb90942be9e6f265d770616518216368165d3079d7/dependency_injector-4.45.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16c17b17d1974a288b16f0d9b4eda7fae892dc2823ff1d95a93207723b681b9a", size = 1856812 }, + { url = "https://files.pythonhosted.org/packages/30/e3/ccfe9ad88d7ee197a12954290697a8c503a0f8f049cd1e4e81f294861053/dependency_injector-4.45.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1cfd84779e718974b8d40eefc2d664ba69c142ca676ed97f487de05e733e9e", size = 6819924 }, + { url = "https://files.pythonhosted.org/packages/85/e7/c3e392f005ba3072d4c6e7a5ff60391648df0f0664ad25b3570f3283c270/dependency_injector-4.45.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b03382b9423f008bf307d4dee7e56ea1e070fc0e23481e4e327b01b9b463a9d", size = 6957754 }, + { url = "https://files.pythonhosted.org/packages/d4/87/c6907e001a0504678c5e5ed62bc6c5c86d1668302fccd5c159d2542bef58/dependency_injector-4.45.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcba70e44a3d6436196f1ffebad0b02f0a72a7c8014d85617f21bd8535434bf4", size = 6406441 }, + { url = "https://files.pythonhosted.org/packages/90/a7/2b003dc0e9e8624e3948289f7764aa87b59cbff392ca06c7f41d28be43b6/dependency_injector-4.45.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73d52ba9efdbb6a1c0ba49d9c68be9cedb0abfa0e6fbdfb942371c642cb30541", size = 6558807 }, + { url = "https://files.pythonhosted.org/packages/d1/a6/5b8f21957af82488ce2e70e321c45c3ba3707004c211c0e6b261e1c77afc/dependency_injector-4.45.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9ebcefa9218941337466cf0985bdcd5d38cf84ab1d3a4d7a3cc91845e38c0d1e", size = 6354444 }, + { url = "https://files.pythonhosted.org/packages/dd/2f/bae5beaf6ac9c5464e6016d0f94d10898c192b4528e2978a2ada44af0c21/dependency_injector-4.45.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d785dacb136479b0536e8b05f83a7375f09404d63eabd1be9b694eca6defea4", size = 6762151 }, + { url = "https://files.pythonhosted.org/packages/13/6f/068b17dc76765e5344c72b85d46711a596eca1d4ee441bac7b7275dadc94/dependency_injector-4.45.0-cp313-cp313-win32.whl", hash = "sha256:3df40ff15bd31058b24cd79781b1dc2f31e0c157ee2067566dfd7a92191969e9", size = 1660060 }, + { url = "https://files.pythonhosted.org/packages/53/d3/890c4be056256e56e97264e72b376ecad273cfa8c8dc70ce5eb623f06472/dependency_injector-4.45.0-cp313-cp313-win_amd64.whl", hash = "sha256:f018f634edf57d4b4214e007eaa2432d2410f304b170f5684bd3fdbffd244b1f", size = 1775930 }, + { url = "https://files.pythonhosted.org/packages/02/8e/915f9b6b7105a2a55933b808a2b4c2d2bd73ac2ccf320d4ebd3d2729c2a2/dependency_injector-4.45.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f2cd876d9726db63df33ecbfb097a06de0306ddae5861c6ba71363606184428e", size = 1698714 }, + { url = "https://files.pythonhosted.org/packages/b4/4f/08c59f93522429944c941f49de8d2ae99c352f84a0825bb7d580d057c522/dependency_injector-4.45.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35baac99140143aed5f7dbf098c523fc534884063ccacc3f51aaccbacc7f9f3", size = 1834874 }, + { url = "https://files.pythonhosted.org/packages/a3/aa/0ed6ae263af7e3b3f12ca69c63be13a42863b954e8e73cb66d4a6d29d4f9/dependency_injector-4.45.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c76c944e4732d72787f99b18dd5b3bcb012427efe8373d8b895f9774880f0cc", size = 1853759 }, + { url = "https://files.pythonhosted.org/packages/50/08/5c8c8bf937e84d0b020eea06811ac5c7b23e05333a5f3f3925dc23b8f6a6/dependency_injector-4.45.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beabb619969c1c87085bcb228b10e2f8751bf7cca66d77dac99c85c3b7a29847", size = 1872820 }, + { url = "https://files.pythonhosted.org/packages/cc/61/8fb364abeb494a43001856a6433cd228544af9d2aa3fc57955b8c1b1095d/dependency_injector-4.45.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d2bdf7c6e4f7a74dedf4f0aea774eec4c3d6f4f909d411c769c9cd1346243518", size = 1718692 }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059 }, +] + +[[package]] +name = "eval-type-backport" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/ea/8b0ac4469d4c347c6a385ff09dc3c048c2d021696664e26c7ee6791631b5/eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1", size = 9079 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "fastavro" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/67/7121d2221e998706cac00fa779ec44c1c943cb65e8a7ed1bd57d78d93f2c/fastavro-1.10.0.tar.gz", hash = "sha256:47bf41ac6d52cdfe4a3da88c75a802321321b37b663a900d12765101a5d6886f", size = 987970 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/e9/f5813450d672f500c4794a39a7cfea99316cb63d5ea11f215e320ea5243b/fastavro-1.10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1a9fe0672d2caf0fe54e3be659b13de3cad25a267f2073d6f4b9f8862acc31eb", size = 1037355 }, + { url = "https://files.pythonhosted.org/packages/6a/41/3f120f72e65f0c80e9bc4f855ac1c9578c8c0e2cdac4d4d4da1f91ca73b9/fastavro-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86dd0410770e0c99363788f0584523709d85e57bb457372ec5c285a482c17fe6", size = 3024739 }, + { url = "https://files.pythonhosted.org/packages/e1/e3/7d9b019158498b45c383e696ba8733b01535337136e9402b0487afeb92b6/fastavro-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:190e80dc7d77d03a6a8597a026146b32a0bbe45e3487ab4904dc8c1bebecb26d", size = 3074020 }, + { url = "https://files.pythonhosted.org/packages/36/31/7ede5629e66eeb71c234d17a799000e737fe0ffd71ef9e1d57a3510def46/fastavro-1.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bf570d63be9155c3fdc415f60a49c171548334b70fff0679a184b69c29b6bc61", size = 2968623 }, + { url = "https://files.pythonhosted.org/packages/10/13/d215411ff5d5de23d6ed62a31eb7f7fa53941681d86bcd5c6388a0918fc3/fastavro-1.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e07abb6798e95dccecaec316265e35a018b523d1f3944ad396d0a93cb95e0a08", size = 3122217 }, + { url = "https://files.pythonhosted.org/packages/6a/1d/7a54fac3f90f0dc120b92f244067976831e393789d3b78c08f2b035ccb19/fastavro-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:37203097ed11d0b8fd3c004904748777d730cafd26e278167ea602eebdef8eb2", size = 497256 }, + { url = "https://files.pythonhosted.org/packages/ac/bf/e7e8e0f841e608dc6f78c746ef2d971fb1f6fe8a9a428d0731ef0abf8b59/fastavro-1.10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d183c075f527ab695a27ae75f210d4a86bce660cda2f85ae84d5606efc15ef50", size = 1040292 }, + { url = "https://files.pythonhosted.org/packages/3a/96/43a65881f061bc5ec6dcf39e59f639a7344e822d4caadae748d076aaf4d0/fastavro-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7a95a2c0639bffd7c079b59e9a796bfc3a9acd78acff7088f7c54ade24e4a77", size = 3312624 }, + { url = "https://files.pythonhosted.org/packages/c8/45/dba0cc08cf42500dd0f1e552e0fefe1cd81c47099d99277828a1081cbd87/fastavro-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a678153b5da1b024a32ec3f611b2e7afd24deac588cb51dd1b0019935191a6d", size = 3334284 }, + { url = "https://files.pythonhosted.org/packages/76/e3/3d9b0824e2e2da56e6a435a70a4db7ed801136daa451577a819bbedc6cf8/fastavro-1.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67a597a5cfea4dddcf8b49eaf8c2b5ffee7fda15b578849185bc690ec0cd0d8f", size = 3283647 }, + { url = "https://files.pythonhosted.org/packages/a1/dc/83d985f8212194e8283ebae86491fccde8710fd81d81ef8659e5373f4f1b/fastavro-1.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fd689724760b17f69565d8a4e7785ed79becd451d1c99263c40cb2d6491f1d4", size = 3419520 }, + { url = "https://files.pythonhosted.org/packages/fd/7f/21711a9ec9937c84406e0773ba3fc6f8d66389a364da46618706f9c37d30/fastavro-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f949d463f9ac4221128a51e4e34e2562f401e5925adcadfd28637a73df6c2d8", size = 499750 }, + { url = "https://files.pythonhosted.org/packages/9c/a4/8e69c0a5cd121e5d476237de1bde5a7947f791ae45768ae52ed0d3ea8d18/fastavro-1.10.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cfe57cb0d72f304bd0dcc5a3208ca6a7363a9ae76f3073307d095c9d053b29d4", size = 1036343 }, + { url = "https://files.pythonhosted.org/packages/1e/01/aa219e2b33e5873d27b867ec0fad9f35f23d461114e1135a7e46c06786d2/fastavro-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e517440c824cb65fb29d3e3903a9406f4d7c75490cef47e55c4c82cdc66270", size = 3263368 }, + { url = "https://files.pythonhosted.org/packages/a7/ba/1766e2d7d95df2e95e9e9a089dc7a537c0616720b053a111a918fa7ee6b6/fastavro-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:203c17d44cadde76e8eecb30f2d1b4f33eb478877552d71f049265dc6f2ecd10", size = 3328933 }, + { url = "https://files.pythonhosted.org/packages/2e/40/26e56696b9696ab4fbba25a96b8037ca3f9fd8a8cc55b4b36400ef023e49/fastavro-1.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6575be7f2b5f94023b5a4e766b0251924945ad55e9a96672dc523656d17fe251", size = 3258045 }, + { url = "https://files.pythonhosted.org/packages/4e/bc/2f6c92c06c5363372abe828bccdd95762f2c1983b261509f94189c38c8a1/fastavro-1.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe471deb675ed2f01ee2aac958fbf8ebb13ea00fa4ce7f87e57710a0bc592208", size = 3418001 }, + { url = "https://files.pythonhosted.org/packages/0c/ce/cfd16546c04ebbca1be80873b533c788cec76f7bfac231bfac6786047572/fastavro-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:567ff515f2a5d26d9674b31c95477f3e6022ec206124c62169bc2ffaf0889089", size = 487855 }, + { url = "https://files.pythonhosted.org/packages/c9/c4/163cf154cc694c2dccc70cd6796db6214ac668a1260bf0310401dad188dc/fastavro-1.10.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:82263af0adfddb39c85f9517d736e1e940fe506dfcc35bc9ab9f85e0fa9236d8", size = 1022741 }, + { url = "https://files.pythonhosted.org/packages/38/01/a24598f5f31b8582a92fe9c41bf91caeed50d5b5eaa7576e6f8b23cb488d/fastavro-1.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:566c193109ff0ff84f1072a165b7106c4f96050078a4e6ac7391f81ca1ef3efa", size = 3237421 }, + { url = "https://files.pythonhosted.org/packages/a7/bf/08bcf65cfb7feb0e5b1329fafeb4a9b95b7b5ec723ba58c7dbd0d04ded34/fastavro-1.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e400d2e55d068404d9fea7c5021f8b999c6f9d9afa1d1f3652ec92c105ffcbdd", size = 3300222 }, + { url = "https://files.pythonhosted.org/packages/53/4d/a6c25f3166328f8306ec2e6be1123ed78a55b8ab774a43a661124508881f/fastavro-1.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9b8227497f71565270f9249fc9af32a93644ca683a0167cfe66d203845c3a038", size = 3233276 }, + { url = "https://files.pythonhosted.org/packages/47/1c/b2b2ce2bf866a248ae23e96a87b3b8369427ff79be9112073039bee1d245/fastavro-1.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e62d04c65461b30ac6d314e4197ad666371e97ae8cb2c16f971d802f6c7f514", size = 3388936 }, + { url = "https://files.pythonhosted.org/packages/1f/2c/43927e22a2d57587b3aa09765098a6d833246b672d34c10c5f135414745a/fastavro-1.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:86baf8c9740ab570d0d4d18517da71626fe9be4d1142bea684db52bd5adb078f", size = 483967 }, +] + +[[package]] +name = "filelock" +version = "3.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 }, +] + +[[package]] +name = "finitemonkey" +version = "0.0.1" +source = { editable = "." } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "colorama" }, + { name = "dependency-injector" }, + { name = "et-xmlfile" }, + { name = "idna" }, + { name = "joblib" }, + { name = "numpy" }, + { name = "openpyxl" }, + { name = "pandas" }, + { name = "psycopg2-binary" }, + { name = "pydantic" }, + { name = "pydantic-ai" }, + { name = "pydantic-settings" }, + { name = "python-dateutil" }, + { name = "python-dotenv" }, + { name = "pytz" }, + { name = "requests" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "simplejson" }, + { name = "six" }, + { name = "sqlalchemy" }, + { name = "threadpoolctl" }, + { name = "tqdm" }, + { name = "tree-sitter" }, + { name = "tree-sitter-solidity" }, + { name = "typing-extensions" }, + { name = "tzdata" }, + { name = "urllib3" }, +] + +[package.metadata] +requires-dist = [ + { name = "antlr4-python3-runtime", specifier = ">=4.13.2" }, + { name = "certifi", specifier = ">=2024.8.30" }, + { name = "charset-normalizer", specifier = ">=3.4.0" }, + { name = "colorama", specifier = ">=0.4.6" }, + { name = "dependency-injector", specifier = ">=4.45.0" }, + { name = "et-xmlfile", specifier = ">=1.1.0" }, + { name = "idna", specifier = ">=3.10" }, + { name = "joblib", specifier = ">=1.4.2" }, + { name = "numpy", specifier = ">=1.24.4" }, + { name = "openpyxl", specifier = ">=3.1.5" }, + { name = "pandas", specifier = ">=1.24.4" }, + { name = "psycopg2-binary", specifier = ">=2.9.9" }, + { name = "pydantic", specifier = ">=2.11.0a2" }, + { name = "pydantic-ai", specifier = ">=0.0.24" }, + { name = "pydantic-settings", specifier = ">=2.7.1" }, + { name = "python-dateutil", specifier = ">=2.9.0.post0" }, + { name = "python-dotenv", specifier = ">=1.0.1" }, + { name = "pytz", specifier = ">=2024.2" }, + { name = "requests", specifier = ">=2.32.3" }, + { name = "scikit-learn", specifier = ">=1.3.2" }, + { name = "scipy", specifier = ">=1.10.1" }, + { name = "simplejson", specifier = ">=3.19.3" }, + { name = "six", specifier = ">=1.16.0" }, + { name = "sqlalchemy", specifier = ">=2.0.35" }, + { name = "threadpoolctl", specifier = ">=3.5.0" }, + { name = "tqdm", specifier = ">=4.66.5" }, + { name = "tree-sitter", specifier = ">=0.24.0" }, + { name = "tree-sitter-solidity", specifier = ">=1.2.11" }, + { name = "typing-extensions", specifier = ">=4.12.2" }, + { name = "tzdata", specifier = ">=2024.2" }, + { name = "urllib3", specifier = ">=2.2.3" }, +] + +[[package]] +name = "fsspec" +version = "2025.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/79/68612ed99700e6413de42895aa725463e821a6b3be75c87fcce1b4af4c70/fsspec-2025.2.0.tar.gz", hash = "sha256:1c24b16eaa0a1798afa0337aa0db9b256718ab2a89c425371f5628d22c3b6afd", size = 292283 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/94/758680531a00d06e471ef649e4ec2ed6bf185356a7f9fbfbb7368a40bd49/fsspec-2025.2.0-py3-none-any.whl", hash = "sha256:9de2ad9ce1f85e1931858535bc882543171d197001a0a5eb2ddc04f1781ab95b", size = 184484 }, +] + +[[package]] +name = "google-auth" +version = "2.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/eb/d504ba1daf190af6b204a9d4714d457462b486043744901a6eeea711f913/google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4", size = 270866 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/47/603554949a37bca5b7f894d51896a9c534b9eab808e2520a748e081669d0/google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a", size = 210770 }, +] + +[[package]] +name = "greenlet" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/90/5234a78dc0ef6496a6eb97b67a42a8e96742a56f7dc808cb954a85390448/greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", size = 271235 }, + { url = "https://files.pythonhosted.org/packages/7c/16/cd631fa0ab7d06ef06387135b7549fdcc77d8d859ed770a0d28e47b20972/greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", size = 637168 }, + { url = "https://files.pythonhosted.org/packages/2f/b1/aed39043a6fec33c284a2c9abd63ce191f4f1a07319340ffc04d2ed3256f/greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", size = 648826 }, + { url = "https://files.pythonhosted.org/packages/76/25/40e0112f7f3ebe54e8e8ed91b2b9f970805143efef16d043dfc15e70f44b/greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", size = 644443 }, + { url = "https://files.pythonhosted.org/packages/fb/2f/3850b867a9af519794784a7eeed1dd5bc68ffbcc5b28cef703711025fd0a/greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", size = 643295 }, + { url = "https://files.pythonhosted.org/packages/cf/69/79e4d63b9387b48939096e25115b8af7cd8a90397a304f92436bcb21f5b2/greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", size = 599544 }, + { url = "https://files.pythonhosted.org/packages/46/1d/44dbcb0e6c323bd6f71b8c2f4233766a5faf4b8948873225d34a0b7efa71/greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", size = 1125456 }, + { url = "https://files.pythonhosted.org/packages/e0/1d/a305dce121838d0278cee39d5bb268c657f10a5363ae4b726848f833f1bb/greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", size = 1149111 }, + { url = "https://files.pythonhosted.org/packages/96/28/d62835fb33fb5652f2e98d34c44ad1a0feacc8b1d3f1aecab035f51f267d/greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", size = 298392 }, + { url = "https://files.pythonhosted.org/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", size = 272479 }, + { url = "https://files.pythonhosted.org/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", size = 640404 }, + { url = "https://files.pythonhosted.org/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", size = 652813 }, + { url = "https://files.pythonhosted.org/packages/49/93/d5f93c84241acdea15a8fd329362c2c71c79e1a507c3f142a5d67ea435ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", size = 648517 }, + { url = "https://files.pythonhosted.org/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", size = 647831 }, + { url = "https://files.pythonhosted.org/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", size = 602413 }, + { url = "https://files.pythonhosted.org/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", size = 1129619 }, + { url = "https://files.pythonhosted.org/packages/f4/fb/201e1b932e584066e0f0658b538e73c459b34d44b4bd4034f682423bc801/greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", size = 1155198 }, + { url = "https://files.pythonhosted.org/packages/12/da/b9ed5e310bb8b89661b80cbcd4db5a067903bbcd7fc854923f5ebb4144f0/greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", size = 298930 }, + { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260 }, + { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064 }, + { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420 }, + { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035 }, + { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105 }, + { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077 }, + { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975 }, + { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955 }, + { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655 }, + { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990 }, + { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175 }, + { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425 }, + { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736 }, + { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347 }, + { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583 }, + { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039 }, + { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716 }, + { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490 }, + { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731 }, + { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304 }, + { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537 }, + { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506 }, + { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753 }, + { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731 }, + { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112 }, +] + +[[package]] +name = "griffe" +version = "1.5.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/80/13b6456bfbf8bc854875e58d3a3bad297ee19ebdd693ce62a10fab007e7a/griffe-1.5.7.tar.gz", hash = "sha256:465238c86deaf1137761f700fb343edd8ffc846d72f6de43c3c345ccdfbebe92", size = 391503 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/67/b43330ed76f96be098c165338d47ccb952964ed77ba1d075247fbdf05c04/griffe-1.5.7-py3-none-any.whl", hash = "sha256:4af8ec834b64de954d447c7b6672426bb145e71605c74a4e22d510cc79fe7d8b", size = 128294 }, +] + +[[package]] +name = "groq" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/8c/e72c164474a88dfed6c7327ad53cb87ff11566b74b3a76d41dc7b94fc51c/groq-0.18.0.tar.gz", hash = "sha256:8e2ccfea406d68b3525af4b7c0e321fcb3d2a73fc60bb70b4156e6cd88c72f03", size = 117322 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/6c/5a53d632b44ef7655ac8d9b34432e13160917f9307c94b1467efd34e336e/groq-0.18.0-py3-none-any.whl", hash = "sha256:81d5ac00057a45d8ce559d23ab5d3b3893011d1f12c35187ab35a9182d826ea6", size = 121911 }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + +[[package]] +name = "httpcore" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, +] + +[[package]] +name = "huggingface-hub" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/ce/a734204aaae6c35a22f9956ebcd8d8708ae5b842e15d6f42bd6f49e634a4/huggingface_hub-0.28.1.tar.gz", hash = "sha256:893471090c98e3b6efbdfdacafe4052b20b84d59866fb6f54c33d9af18c303ae", size = 387074 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/da/6c2bea5327b640920267d3bf2c9fc114cfbd0a5de234d81cda80cc9e33c8/huggingface_hub-0.28.1-py3-none-any.whl", hash = "sha256:aa6b9a3ffdae939b72c464dbb0d7f99f56e649b55c3d52406f49e0a5a620c0a7", size = 464068 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "jiter" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/70/90bc7bd3932e651486861df5c8ffea4ca7c77d28e8532ddefe2abc561a53/jiter-0.8.2.tar.gz", hash = "sha256:cd73d3e740666d0e639f678adb176fad25c1bcbdae88d8d7b857e1783bb4212d", size = 163007 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/f3/8c11e0e87bd5934c414f9b1cfae3cbfd4a938d4669d57cb427e1c4d11a7f/jiter-0.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ca8577f6a413abe29b079bc30f907894d7eb07a865c4df69475e868d73e71c7b", size = 303381 }, + { url = "https://files.pythonhosted.org/packages/ea/28/4cd3f0bcbf40e946bc6a62a82c951afc386a25673d3d8d5ee461f1559bbe/jiter-0.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b25bd626bde7fb51534190c7e3cb97cee89ee76b76d7585580e22f34f5e3f393", size = 311718 }, + { url = "https://files.pythonhosted.org/packages/0d/17/57acab00507e60bd954eaec0837d9d7b119b4117ff49b8a62f2b646f32ed/jiter-0.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c826a221851a8dc028eb6d7d6429ba03184fa3c7e83ae01cd6d3bd1d4bd17d", size = 335465 }, + { url = "https://files.pythonhosted.org/packages/74/b9/1a3ddd2bc95ae17c815b021521020f40c60b32137730126bada962ef32b4/jiter-0.8.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d35c864c2dff13dfd79fb070fc4fc6235d7b9b359efe340e1261deb21b9fcb66", size = 355570 }, + { url = "https://files.pythonhosted.org/packages/78/69/6d29e2296a934199a7d0dde673ecccf98c9c8db44caf0248b3f2b65483cb/jiter-0.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f557c55bc2b7676e74d39d19bcb8775ca295c7a028246175d6a8b431e70835e5", size = 381383 }, + { url = "https://files.pythonhosted.org/packages/22/d7/fbc4c3fb1bf65f9be22a32759b539f88e897aeb13fe84ab0266e4423487a/jiter-0.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:580ccf358539153db147e40751a0b41688a5ceb275e6f3e93d91c9467f42b2e3", size = 390454 }, + { url = "https://files.pythonhosted.org/packages/4d/a0/3993cda2e267fe679b45d0bcc2cef0b4504b0aa810659cdae9737d6bace9/jiter-0.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af102d3372e917cffce49b521e4c32c497515119dc7bd8a75665e90a718bbf08", size = 345039 }, + { url = "https://files.pythonhosted.org/packages/b9/ef/69c18562b4c09ce88fab5df1dcaf643f6b1a8b970b65216e7221169b81c4/jiter-0.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cadcc978f82397d515bb2683fc0d50103acff2a180552654bb92d6045dec2c49", size = 376200 }, + { url = "https://files.pythonhosted.org/packages/4d/17/0b5a8de46a6ab4d836f70934036278b49b8530c292b29dde3483326d4555/jiter-0.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba5bdf56969cad2019d4e8ffd3f879b5fdc792624129741d3d83fc832fef8c7d", size = 511158 }, + { url = "https://files.pythonhosted.org/packages/6c/b2/c401a0a2554b36c9e6d6e4876b43790d75139cf3936f0222e675cbc23451/jiter-0.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3b94a33a241bee9e34b8481cdcaa3d5c2116f575e0226e421bed3f7a6ea71cff", size = 503956 }, + { url = "https://files.pythonhosted.org/packages/d4/02/a0291ed7d72c0ac130f172354ee3cf0b2556b69584de391463a8ee534f40/jiter-0.8.2-cp310-cp310-win32.whl", hash = "sha256:6e5337bf454abddd91bd048ce0dca5134056fc99ca0205258766db35d0a2ea43", size = 202846 }, + { url = "https://files.pythonhosted.org/packages/ad/20/8c988831ae4bf437e29f1671e198fc99ba8fe49f2895f23789acad1d1811/jiter-0.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:4a9220497ca0cb1fe94e3f334f65b9b5102a0b8147646118f020d8ce1de70105", size = 204414 }, + { url = "https://files.pythonhosted.org/packages/cb/b0/c1a7caa7f9dc5f1f6cfa08722867790fe2d3645d6e7170ca280e6e52d163/jiter-0.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2dd61c5afc88a4fda7d8b2cf03ae5947c6ac7516d32b7a15bf4b49569a5c076b", size = 303666 }, + { url = "https://files.pythonhosted.org/packages/f5/97/0468bc9eeae43079aaa5feb9267964e496bf13133d469cfdc135498f8dd0/jiter-0.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a6c710d657c8d1d2adbbb5c0b0c6bfcec28fd35bd6b5f016395f9ac43e878a15", size = 311934 }, + { url = "https://files.pythonhosted.org/packages/e5/69/64058e18263d9a5f1e10f90c436853616d5f047d997c37c7b2df11b085ec/jiter-0.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9584de0cd306072635fe4b89742bf26feae858a0683b399ad0c2509011b9dc0", size = 335506 }, + { url = "https://files.pythonhosted.org/packages/9d/14/b747f9a77b8c0542141d77ca1e2a7523e854754af2c339ac89a8b66527d6/jiter-0.8.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a90a923338531b7970abb063cfc087eebae6ef8ec8139762007188f6bc69a9f", size = 355849 }, + { url = "https://files.pythonhosted.org/packages/53/e2/98a08161db7cc9d0e39bc385415890928ff09709034982f48eccfca40733/jiter-0.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21974d246ed0181558087cd9f76e84e8321091ebfb3a93d4c341479a736f099", size = 381700 }, + { url = "https://files.pythonhosted.org/packages/7a/38/1674672954d35bce3b1c9af99d5849f9256ac8f5b672e020ac7821581206/jiter-0.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32475a42b2ea7b344069dc1e81445cfc00b9d0e3ca837f0523072432332e9f74", size = 389710 }, + { url = "https://files.pythonhosted.org/packages/f8/9b/92f9da9a9e107d019bcf883cd9125fa1690079f323f5a9d5c6986eeec3c0/jiter-0.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9931fd36ee513c26b5bf08c940b0ac875de175341cbdd4fa3be109f0492586", size = 345553 }, + { url = "https://files.pythonhosted.org/packages/44/a6/6d030003394e9659cd0d7136bbeabd82e869849ceccddc34d40abbbbb269/jiter-0.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0820f4a3a59ddced7fce696d86a096d5cc48d32a4183483a17671a61edfddc", size = 376388 }, + { url = "https://files.pythonhosted.org/packages/ad/8d/87b09e648e4aca5f9af89e3ab3cfb93db2d1e633b2f2931ede8dabd9b19a/jiter-0.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ffc86ae5e3e6a93765d49d1ab47b6075a9c978a2b3b80f0f32628f39caa0c88", size = 511226 }, + { url = "https://files.pythonhosted.org/packages/77/95/8008ebe4cdc82eac1c97864a8042ca7e383ed67e0ec17bfd03797045c727/jiter-0.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5127dc1abd809431172bc3fbe8168d6b90556a30bb10acd5ded41c3cfd6f43b6", size = 504134 }, + { url = "https://files.pythonhosted.org/packages/26/0d/3056a74de13e8b2562e4d526de6dac2f65d91ace63a8234deb9284a1d24d/jiter-0.8.2-cp311-cp311-win32.whl", hash = "sha256:66227a2c7b575720c1871c8800d3a0122bb8ee94edb43a5685aa9aceb2782d44", size = 203103 }, + { url = "https://files.pythonhosted.org/packages/4e/1e/7f96b798f356e531ffc0f53dd2f37185fac60fae4d6c612bbbd4639b90aa/jiter-0.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:cde031d8413842a1e7501e9129b8e676e62a657f8ec8166e18a70d94d4682855", size = 206717 }, + { url = "https://files.pythonhosted.org/packages/a1/17/c8747af8ea4e045f57d6cfd6fc180752cab9bc3de0e8a0c9ca4e8af333b1/jiter-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e6ec2be506e7d6f9527dae9ff4b7f54e68ea44a0ef6b098256ddf895218a2f8f", size = 302027 }, + { url = "https://files.pythonhosted.org/packages/3c/c1/6da849640cd35a41e91085723b76acc818d4b7d92b0b6e5111736ce1dd10/jiter-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76e324da7b5da060287c54f2fabd3db5f76468006c811831f051942bf68c9d44", size = 310326 }, + { url = "https://files.pythonhosted.org/packages/06/99/a2bf660d8ccffee9ad7ed46b4f860d2108a148d0ea36043fd16f4dc37e94/jiter-0.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:180a8aea058f7535d1c84183c0362c710f4750bef66630c05f40c93c2b152a0f", size = 334242 }, + { url = "https://files.pythonhosted.org/packages/a7/5f/cea1c17864828731f11427b9d1ab7f24764dbd9aaf4648a7f851164d2718/jiter-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025337859077b41548bdcbabe38698bcd93cfe10b06ff66617a48ff92c9aec60", size = 356654 }, + { url = "https://files.pythonhosted.org/packages/e9/13/62774b7e5e7f5d5043efe1d0f94ead66e6d0f894ae010adb56b3f788de71/jiter-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecff0dc14f409599bbcafa7e470c00b80f17abc14d1405d38ab02e4b42e55b57", size = 379967 }, + { url = "https://files.pythonhosted.org/packages/ec/fb/096b34c553bb0bd3f2289d5013dcad6074948b8d55212aa13a10d44c5326/jiter-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd9fee7d0775ebaba131f7ca2e2d83839a62ad65e8e02fe2bd8fc975cedeb9e", size = 389252 }, + { url = "https://files.pythonhosted.org/packages/17/61/beea645c0bf398ced8b199e377b61eb999d8e46e053bb285c91c3d3eaab0/jiter-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14601dcac4889e0a1c75ccf6a0e4baf70dbc75041e51bcf8d0e9274519df6887", size = 345490 }, + { url = "https://files.pythonhosted.org/packages/d5/df/834aa17ad5dcc3cf0118821da0a0cf1589ea7db9832589278553640366bc/jiter-0.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92249669925bc1c54fcd2ec73f70f2c1d6a817928480ee1c65af5f6b81cdf12d", size = 376991 }, + { url = "https://files.pythonhosted.org/packages/67/80/87d140399d382fb4ea5b3d56e7ecaa4efdca17cd7411ff904c1517855314/jiter-0.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e725edd0929fa79f8349ab4ec7f81c714df51dc4e991539a578e5018fa4a7152", size = 510822 }, + { url = "https://files.pythonhosted.org/packages/5c/37/3394bb47bac1ad2cb0465601f86828a0518d07828a650722e55268cdb7e6/jiter-0.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf55846c7b7a680eebaf9c3c48d630e1bf51bdf76c68a5f654b8524335b0ad29", size = 503730 }, + { url = "https://files.pythonhosted.org/packages/f9/e2/253fc1fa59103bb4e3aa0665d6ceb1818df1cd7bf3eb492c4dad229b1cd4/jiter-0.8.2-cp312-cp312-win32.whl", hash = "sha256:7efe4853ecd3d6110301665a5178b9856be7e2a9485f49d91aa4d737ad2ae49e", size = 203375 }, + { url = "https://files.pythonhosted.org/packages/41/69/6d4bbe66b3b3b4507e47aa1dd5d075919ad242b4b1115b3f80eecd443687/jiter-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:83c0efd80b29695058d0fd2fa8a556490dbce9804eac3e281f373bbc99045f6c", size = 204740 }, + { url = "https://files.pythonhosted.org/packages/6c/b0/bfa1f6f2c956b948802ef5a021281978bf53b7a6ca54bb126fd88a5d014e/jiter-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca1f08b8e43dc3bd0594c992fb1fd2f7ce87f7bf0d44358198d6da8034afdf84", size = 301190 }, + { url = "https://files.pythonhosted.org/packages/a4/8f/396ddb4e292b5ea57e45ade5dc48229556b9044bad29a3b4b2dddeaedd52/jiter-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5672a86d55416ccd214c778efccf3266b84f87b89063b582167d803246354be4", size = 309334 }, + { url = "https://files.pythonhosted.org/packages/7f/68/805978f2f446fa6362ba0cc2e4489b945695940656edd844e110a61c98f8/jiter-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58dc9bc9767a1101f4e5e22db1b652161a225874d66f0e5cb8e2c7d1c438b587", size = 333918 }, + { url = "https://files.pythonhosted.org/packages/b3/99/0f71f7be667c33403fa9706e5b50583ae5106d96fab997fa7e2f38ee8347/jiter-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b2998606d6dadbb5ccda959a33d6a5e853252d921fec1792fc902351bb4e2c", size = 356057 }, + { url = "https://files.pythonhosted.org/packages/8d/50/a82796e421a22b699ee4d2ce527e5bcb29471a2351cbdc931819d941a167/jiter-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ab9a87f3784eb0e098f84a32670cfe4a79cb6512fd8f42ae3d0709f06405d18", size = 379790 }, + { url = "https://files.pythonhosted.org/packages/3c/31/10fb012b00f6d83342ca9e2c9618869ab449f1aa78c8f1b2193a6b49647c/jiter-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79aec8172b9e3c6d05fd4b219d5de1ac616bd8da934107325a6c0d0e866a21b6", size = 388285 }, + { url = "https://files.pythonhosted.org/packages/c8/81/f15ebf7de57be488aa22944bf4274962aca8092e4f7817f92ffa50d3ee46/jiter-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:711e408732d4e9a0208008e5892c2966b485c783cd2d9a681f3eb147cf36c7ef", size = 344764 }, + { url = "https://files.pythonhosted.org/packages/b3/e8/0cae550d72b48829ba653eb348cdc25f3f06f8a62363723702ec18e7be9c/jiter-0.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:653cf462db4e8c41995e33d865965e79641ef45369d8a11f54cd30888b7e6ff1", size = 376620 }, + { url = "https://files.pythonhosted.org/packages/b8/50/e5478ff9d82534a944c03b63bc217c5f37019d4a34d288db0f079b13c10b/jiter-0.8.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9c63eaef32b7bebac8ebebf4dabebdbc6769a09c127294db6babee38e9f405b9", size = 510402 }, + { url = "https://files.pythonhosted.org/packages/8e/1e/3de48bbebbc8f7025bd454cedc8c62378c0e32dd483dece5f4a814a5cb55/jiter-0.8.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:eb21aaa9a200d0a80dacc7a81038d2e476ffe473ffdd9c91eb745d623561de05", size = 503018 }, + { url = "https://files.pythonhosted.org/packages/d5/cd/d5a5501d72a11fe3e5fd65c78c884e5164eefe80077680533919be22d3a3/jiter-0.8.2-cp313-cp313-win32.whl", hash = "sha256:789361ed945d8d42850f919342a8665d2dc79e7e44ca1c97cc786966a21f627a", size = 203190 }, + { url = "https://files.pythonhosted.org/packages/51/bf/e5ca301245ba951447e3ad677a02a64a8845b185de2603dabd83e1e4b9c6/jiter-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ab7f43235d71e03b941c1630f4b6e3055d46b6cb8728a17663eaac9d8e83a865", size = 203551 }, + { url = "https://files.pythonhosted.org/packages/2f/3c/71a491952c37b87d127790dd7a0b1ebea0514c6b6ad30085b16bbe00aee6/jiter-0.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b426f72cd77da3fec300ed3bc990895e2dd6b49e3bfe6c438592a3ba660e41ca", size = 308347 }, + { url = "https://files.pythonhosted.org/packages/a0/4c/c02408042e6a7605ec063daed138e07b982fdb98467deaaf1c90950cf2c6/jiter-0.8.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2dd880785088ff2ad21ffee205e58a8c1ddabc63612444ae41e5e4b321b39c0", size = 342875 }, + { url = "https://files.pythonhosted.org/packages/91/61/c80ef80ed8a0a21158e289ef70dac01e351d929a1c30cb0f49be60772547/jiter-0.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:3ac9f578c46f22405ff7f8b1f5848fb753cc4b8377fbec8470a7dc3997ca7566", size = 202374 }, +] + +[[package]] +name = "joblib" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 }, +] + +[[package]] +name = "jsonpath-python" +version = "1.0.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/49/e582e50b0c54c1b47e714241c4a4767bf28758bf90212248aea8e1ce8516/jsonpath-python-1.0.6.tar.gz", hash = "sha256:dd5be4a72d8a2995c3f583cf82bf3cd1a9544cfdabf2d22595b67aff07349666", size = 18121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/8a/d63959f4eff03893a00e6e63592e3a9f15b9266ed8e0275ab77f8c7dbc94/jsonpath_python-1.0.6-py3-none-any.whl", hash = "sha256:1e3b78df579f5efc23565293612decee04214609208a2335884b3ee3f786b575", size = 7552 }, +] + +[[package]] +name = "logfire-api" +version = "3.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/ae/44e0104022f1b52386cb5dfbcaaaa2e2e8c9a2bb335a65f609ffe34c1387/logfire_api-3.5.3.tar.gz", hash = "sha256:2c15a0ad6f12f39003f62550a35f9247c29c87b466f7839836f346867442a55c", size = 44669 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/21/7f18dc3154caa396fb2161f89c980882111537a4261dbc7a6b902cf9fc58/logfire_api-3.5.3-py3-none-any.whl", hash = "sha256:abecb0d2e43f900c58582c333f261c8f9b187e72933cf3f5aec93685666f86ae", size = 73821 }, +] + +[[package]] +name = "mistralai" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport" }, + { name = "httpx" }, + { name = "jsonpath-python" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/9d/aba193fdfe0fc7403efa380189143d965becfb1bc7df3230e5c7664f8c53/mistralai-1.5.0.tar.gz", hash = "sha256:fd94bc93bc25aad9c6dd8005b1a0bc4ba1250c6b3fbf855a49936989cc6e5c0d", size = 131647 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/e7/7147c75c383a975c58c33f8e7ee7dbbb0e7390fbcb1ecd321f63e4c73efd/mistralai-1.5.0-py3-none-any.whl", hash = "sha256:9372537719f87bd6f9feef4747d0bf1f4fbe971f8c02945ca4b4bf3c94571c97", size = 271559 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "numpy" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/90/8956572f5c4ae52201fdec7ba2044b2c882832dcec7d5d0922c9e9acf2de/numpy-2.2.3.tar.gz", hash = "sha256:dbdc15f0c81611925f382dfa97b3bd0bc2c1ce19d4fe50482cb0ddc12ba30020", size = 20262700 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/e1/1816d5d527fa870b260a1c2c5904d060caad7515637bd54f495a5ce13ccd/numpy-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbc6472e01952d3d1b2772b720428f8b90e2deea8344e854df22b0618e9cce71", size = 21232911 }, + { url = "https://files.pythonhosted.org/packages/29/46/9f25dc19b359f10c0e52b6bac25d3181eb1f4b4d04c9846a32cf5ea52762/numpy-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdfe0c22692a30cd830c0755746473ae66c4a8f2e7bd508b35fb3b6a0813d787", size = 14371955 }, + { url = "https://files.pythonhosted.org/packages/72/d7/de941296e6b09a5c81d3664ad912f1496a0ecdd2f403318e5e35604ff70f/numpy-2.2.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:e37242f5324ffd9f7ba5acf96d774f9276aa62a966c0bad8dae692deebec7716", size = 5410476 }, + { url = "https://files.pythonhosted.org/packages/36/ce/55f685995110f8a268fdca0f198c9a84fa87b39512830965cc1087af6391/numpy-2.2.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95172a21038c9b423e68be78fd0be6e1b97674cde269b76fe269a5dfa6fadf0b", size = 6945730 }, + { url = "https://files.pythonhosted.org/packages/4f/84/abdb9f6e22576d89c259401c3234d4755b322539491bbcffadc8bcb120d3/numpy-2.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b47c440210c5d1d67e1cf434124e0b5c395eee1f5806fdd89b553ed1acd0a3", size = 14350752 }, + { url = "https://files.pythonhosted.org/packages/e9/88/3870cfa9bef4dffb3a326507f430e6007eeac258ebeef6b76fc542aef66d/numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0391ea3622f5c51a2e29708877d56e3d276827ac5447d7f45e9bc4ade8923c52", size = 16399386 }, + { url = "https://files.pythonhosted.org/packages/02/10/3f629682dd0b457525c131945329c4e81e2dadeb11256e6ce4c9a1a6fb41/numpy-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f6b3dfc7661f8842babd8ea07e9897fe3d9b69a1d7e5fbb743e4160f9387833b", size = 15561826 }, + { url = "https://files.pythonhosted.org/packages/da/18/fd35673ba9751eba449d4ce5d24d94e3b612cdbfba79348da71488c0b7ac/numpy-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1ad78ce7f18ce4e7df1b2ea4019b5817a2f6a8a16e34ff2775f646adce0a5027", size = 18188593 }, + { url = "https://files.pythonhosted.org/packages/ce/4c/c0f897b580ea59484b4cc96a441fea50333b26675a60a1421bc912268b5f/numpy-2.2.3-cp310-cp310-win32.whl", hash = "sha256:5ebeb7ef54a7be11044c33a17b2624abe4307a75893c001a4800857956b41094", size = 6590421 }, + { url = "https://files.pythonhosted.org/packages/e5/5b/aaabbfc7060c5c8f0124c5deb5e114a3b413a548bbc64e372c5b5db36165/numpy-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:596140185c7fa113563c67c2e894eabe0daea18cf8e33851738c19f70ce86aeb", size = 12925667 }, + { url = "https://files.pythonhosted.org/packages/96/86/453aa3949eab6ff54e2405f9cb0c01f756f031c3dc2a6d60a1d40cba5488/numpy-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:16372619ee728ed67a2a606a614f56d3eabc5b86f8b615c79d01957062826ca8", size = 21237256 }, + { url = "https://files.pythonhosted.org/packages/20/c3/93ecceadf3e155d6a9e4464dd2392d8d80cf436084c714dc8535121c83e8/numpy-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5521a06a3148686d9269c53b09f7d399a5725c47bbb5b35747e1cb76326b714b", size = 14408049 }, + { url = "https://files.pythonhosted.org/packages/8d/29/076999b69bd9264b8df5e56f2be18da2de6b2a2d0e10737e5307592e01de/numpy-2.2.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:7c8dde0ca2f77828815fd1aedfdf52e59071a5bae30dac3b4da2a335c672149a", size = 5408655 }, + { url = "https://files.pythonhosted.org/packages/e2/a7/b14f0a73eb0fe77cb9bd5b44534c183b23d4229c099e339c522724b02678/numpy-2.2.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:77974aba6c1bc26e3c205c2214f0d5b4305bdc719268b93e768ddb17e3fdd636", size = 6949996 }, + { url = "https://files.pythonhosted.org/packages/72/2f/8063da0616bb0f414b66dccead503bd96e33e43685c820e78a61a214c098/numpy-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d42f9c36d06440e34226e8bd65ff065ca0963aeecada587b937011efa02cdc9d", size = 14355789 }, + { url = "https://files.pythonhosted.org/packages/e6/d7/3cd47b00b8ea95ab358c376cf5602ad21871410950bc754cf3284771f8b6/numpy-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2712c5179f40af9ddc8f6727f2bd910ea0eb50206daea75f58ddd9fa3f715bb", size = 16411356 }, + { url = "https://files.pythonhosted.org/packages/27/c0/a2379e202acbb70b85b41483a422c1e697ff7eee74db642ca478de4ba89f/numpy-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c8b0451d2ec95010d1db8ca733afc41f659f425b7f608af569711097fd6014e2", size = 15576770 }, + { url = "https://files.pythonhosted.org/packages/bc/63/a13ee650f27b7999e5b9e1964ae942af50bb25606d088df4229283eda779/numpy-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9b4a8148c57ecac25a16b0e11798cbe88edf5237b0df99973687dd866f05e1b", size = 18200483 }, + { url = "https://files.pythonhosted.org/packages/4c/87/e71f89935e09e8161ac9c590c82f66d2321eb163893a94af749dfa8a3cf8/numpy-2.2.3-cp311-cp311-win32.whl", hash = "sha256:1f45315b2dc58d8a3e7754fe4e38b6fce132dab284a92851e41b2b344f6441c5", size = 6588415 }, + { url = "https://files.pythonhosted.org/packages/b9/c6/cd4298729826af9979c5f9ab02fcaa344b82621e7c49322cd2d210483d3f/numpy-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f48ba6f6c13e5e49f3d3efb1b51c8193215c42ac82610a04624906a9270be6f", size = 12929604 }, + { url = "https://files.pythonhosted.org/packages/43/ec/43628dcf98466e087812142eec6d1c1a6c6bdfdad30a0aa07b872dc01f6f/numpy-2.2.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12c045f43b1d2915eca6b880a7f4a256f59d62df4f044788c8ba67709412128d", size = 20929458 }, + { url = "https://files.pythonhosted.org/packages/9b/c0/2f4225073e99a5c12350954949ed19b5d4a738f541d33e6f7439e33e98e4/numpy-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87eed225fd415bbae787f93a457af7f5990b92a334e346f72070bf569b9c9c95", size = 14115299 }, + { url = "https://files.pythonhosted.org/packages/ca/fa/d2c5575d9c734a7376cc1592fae50257ec95d061b27ee3dbdb0b3b551eb2/numpy-2.2.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:712a64103d97c404e87d4d7c47fb0c7ff9acccc625ca2002848e0d53288b90ea", size = 5145723 }, + { url = "https://files.pythonhosted.org/packages/eb/dc/023dad5b268a7895e58e791f28dc1c60eb7b6c06fcbc2af8538ad069d5f3/numpy-2.2.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a5ae282abe60a2db0fd407072aff4599c279bcd6e9a2475500fc35b00a57c532", size = 6678797 }, + { url = "https://files.pythonhosted.org/packages/3f/19/bcd641ccf19ac25abb6fb1dcd7744840c11f9d62519d7057b6ab2096eb60/numpy-2.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5266de33d4c3420973cf9ae3b98b54a2a6d53a559310e3236c4b2b06b9c07d4e", size = 14067362 }, + { url = "https://files.pythonhosted.org/packages/39/04/78d2e7402fb479d893953fb78fa7045f7deb635ec095b6b4f0260223091a/numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe", size = 16116679 }, + { url = "https://files.pythonhosted.org/packages/d0/a1/e90f7aa66512be3150cb9d27f3d9995db330ad1b2046474a13b7040dfd92/numpy-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:34c1b7e83f94f3b564b35f480f5652a47007dd91f7c839f404d03279cc8dd021", size = 15264272 }, + { url = "https://files.pythonhosted.org/packages/dc/b6/50bd027cca494de4fa1fc7bf1662983d0ba5f256fa0ece2c376b5eb9b3f0/numpy-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d8335b5f1b6e2bce120d55fb17064b0262ff29b459e8493d1785c18ae2553b8", size = 17880549 }, + { url = "https://files.pythonhosted.org/packages/96/30/f7bf4acb5f8db10a96f73896bdeed7a63373137b131ca18bd3dab889db3b/numpy-2.2.3-cp312-cp312-win32.whl", hash = "sha256:4d9828d25fb246bedd31e04c9e75714a4087211ac348cb39c8c5f99dbb6683fe", size = 6293394 }, + { url = "https://files.pythonhosted.org/packages/42/6e/55580a538116d16ae7c9aa17d4edd56e83f42126cb1dfe7a684da7925d2c/numpy-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d", size = 12626357 }, + { url = "https://files.pythonhosted.org/packages/0e/8b/88b98ed534d6a03ba8cddb316950fe80842885709b58501233c29dfa24a9/numpy-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bfdb06b395385ea9b91bf55c1adf1b297c9fdb531552845ff1d3ea6e40d5aba", size = 20916001 }, + { url = "https://files.pythonhosted.org/packages/d9/b4/def6ec32c725cc5fbd8bdf8af80f616acf075fe752d8a23e895da8c67b70/numpy-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23c9f4edbf4c065fddb10a4f6e8b6a244342d95966a48820c614891e5059bb50", size = 14130721 }, + { url = "https://files.pythonhosted.org/packages/20/60/70af0acc86495b25b672d403e12cb25448d79a2b9658f4fc45e845c397a8/numpy-2.2.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:a0c03b6be48aaf92525cccf393265e02773be8fd9551a2f9adbe7db1fa2b60f1", size = 5130999 }, + { url = "https://files.pythonhosted.org/packages/2e/69/d96c006fb73c9a47bcb3611417cf178049aae159afae47c48bd66df9c536/numpy-2.2.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:2376e317111daa0a6739e50f7ee2a6353f768489102308b0d98fcf4a04f7f3b5", size = 6665299 }, + { url = "https://files.pythonhosted.org/packages/5a/3f/d8a877b6e48103733ac224ffa26b30887dc9944ff95dffdfa6c4ce3d7df3/numpy-2.2.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fb62fe3d206d72fe1cfe31c4a1106ad2b136fcc1606093aeab314f02930fdf2", size = 14064096 }, + { url = "https://files.pythonhosted.org/packages/e4/43/619c2c7a0665aafc80efca465ddb1f260287266bdbdce517396f2f145d49/numpy-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52659ad2534427dffcc36aac76bebdd02b67e3b7a619ac67543bc9bfe6b7cdb1", size = 16114758 }, + { url = "https://files.pythonhosted.org/packages/d9/79/ee4fe4f60967ccd3897aa71ae14cdee9e3c097e3256975cc9575d393cb42/numpy-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b416af7d0ed3271cad0f0a0d0bee0911ed7eba23e66f8424d9f3dfcdcae1304", size = 15259880 }, + { url = "https://files.pythonhosted.org/packages/fb/c8/8b55cf05db6d85b7a7d414b3d1bd5a740706df00bfa0824a08bf041e52ee/numpy-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1402da8e0f435991983d0a9708b779f95a8c98c6b18a171b9f1be09005e64d9d", size = 17876721 }, + { url = "https://files.pythonhosted.org/packages/21/d6/b4c2f0564b7dcc413117b0ffbb818d837e4b29996b9234e38b2025ed24e7/numpy-2.2.3-cp313-cp313-win32.whl", hash = "sha256:136553f123ee2951bfcfbc264acd34a2fc2f29d7cdf610ce7daf672b6fbaa693", size = 6290195 }, + { url = "https://files.pythonhosted.org/packages/97/e7/7d55a86719d0de7a6a597949f3febefb1009435b79ba510ff32f05a8c1d7/numpy-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5b732c8beef1d7bc2d9e476dbba20aaff6167bf205ad9aa8d30913859e82884b", size = 12619013 }, + { url = "https://files.pythonhosted.org/packages/a6/1f/0b863d5528b9048fd486a56e0b97c18bf705e88736c8cea7239012119a54/numpy-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:435e7a933b9fda8126130b046975a968cc2d833b505475e588339e09f7672890", size = 20944621 }, + { url = "https://files.pythonhosted.org/packages/aa/99/b478c384f7a0a2e0736177aafc97dc9152fc036a3fdb13f5a3ab225f1494/numpy-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7678556eeb0152cbd1522b684dcd215250885993dd00adb93679ec3c0e6e091c", size = 14142502 }, + { url = "https://files.pythonhosted.org/packages/fb/61/2d9a694a0f9cd0a839501d362de2a18de75e3004576a3008e56bdd60fcdb/numpy-2.2.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2e8da03bd561504d9b20e7a12340870dfc206c64ea59b4cfee9fceb95070ee94", size = 5176293 }, + { url = "https://files.pythonhosted.org/packages/33/35/51e94011b23e753fa33f891f601e5c1c9a3d515448659b06df9d40c0aa6e/numpy-2.2.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:c9aa4496fd0e17e3843399f533d62857cef5900facf93e735ef65aa4bbc90ef0", size = 6691874 }, + { url = "https://files.pythonhosted.org/packages/ff/cf/06e37619aad98a9d03bd8d65b8e3041c3a639be0f5f6b0a0e2da544538d4/numpy-2.2.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4ca91d61a4bf61b0f2228f24bbfa6a9facd5f8af03759fe2a655c50ae2c6610", size = 14036826 }, + { url = "https://files.pythonhosted.org/packages/0c/93/5d7d19955abd4d6099ef4a8ee006f9ce258166c38af259f9e5558a172e3e/numpy-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deaa09cd492e24fd9b15296844c0ad1b3c976da7907e1c1ed3a0ad21dded6f76", size = 16096567 }, + { url = "https://files.pythonhosted.org/packages/af/53/d1c599acf7732d81f46a93621dab6aa8daad914b502a7a115b3f17288ab2/numpy-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:246535e2f7496b7ac85deffe932896a3577be7af8fb7eebe7146444680297e9a", size = 15242514 }, + { url = "https://files.pythonhosted.org/packages/53/43/c0f5411c7b3ea90adf341d05ace762dad8cb9819ef26093e27b15dd121ac/numpy-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:daf43a3d1ea699402c5a850e5313680ac355b4adc9770cd5cfc2940e7861f1bf", size = 17872920 }, + { url = "https://files.pythonhosted.org/packages/5b/57/6dbdd45ab277aff62021cafa1e15f9644a52f5b5fc840bc7591b4079fb58/numpy-2.2.3-cp313-cp313t-win32.whl", hash = "sha256:cf802eef1f0134afb81fef94020351be4fe1d6681aadf9c5e862af6602af64ef", size = 6346584 }, + { url = "https://files.pythonhosted.org/packages/97/9b/484f7d04b537d0a1202a5ba81c6f53f1846ae6c63c2127f8df869ed31342/numpy-2.2.3-cp313-cp313t-win_amd64.whl", hash = "sha256:aee2512827ceb6d7f517c8b85aa5d3923afe8fc7a57d028cffcd522f1c6fd082", size = 12706784 }, + { url = "https://files.pythonhosted.org/packages/0a/b5/a7839f5478be8f859cb880f13d90fcfe4b0ec7a9ebaff2bcc30d96760596/numpy-2.2.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3c2ec8a0f51d60f1e9c0c5ab116b7fc104b165ada3f6c58abf881cb2eb16044d", size = 21064244 }, + { url = "https://files.pythonhosted.org/packages/29/e8/5da32ffcaa7a72f7ecd82f90c062140a061eb823cb88e90279424e515cf4/numpy-2.2.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ed2cf9ed4e8ebc3b754d398cba12f24359f018b416c380f577bbae112ca52fc9", size = 6809418 }, + { url = "https://files.pythonhosted.org/packages/a8/a9/68aa7076c7656a7308a0f73d0a2ced8c03f282c9fd98fa7ce21c12634087/numpy-2.2.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39261798d208c3095ae4f7bc8eaeb3481ea8c6e03dc48028057d3cbdbdb8937e", size = 16215461 }, + { url = "https://files.pythonhosted.org/packages/17/7f/d322a4125405920401450118dbdc52e0384026bd669939484670ce8b2ab9/numpy-2.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4", size = 12839607 }, +] + +[[package]] +name = "openai" +version = "1.63.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/32/2049e973a646801df425aecdf88c6504ca878bdb3951fe12076fc30f2977/openai-1.63.0.tar.gz", hash = "sha256:597d7a1b35b113e5a09fcb953bdb1eef44f404a39985f3d7573b3ab09221fd66", size = 356710 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/a0/e1fe4e87218639fc0a0927da5266c2978eaa0e2eb5437479ee64a11535bb/openai-1.63.0-py3-none-any.whl", hash = "sha256:a664dfc78f0a05ca46c3e21f344f840cf6bf7174f13cfa9de214ed28bfca1dda", size = 472282 }, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pandas" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/70/c853aec59839bceed032d52010ff5f1b8d87dc3114b762e4ba2727661a3b/pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", size = 12580827 }, + { url = "https://files.pythonhosted.org/packages/99/f2/c4527768739ffa4469b2b4fff05aa3768a478aed89a2f271a79a40eee984/pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", size = 11303897 }, + { url = "https://files.pythonhosted.org/packages/ed/12/86c1747ea27989d7a4064f806ce2bae2c6d575b950be087837bdfcabacc9/pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", size = 66480908 }, + { url = "https://files.pythonhosted.org/packages/44/50/7db2cd5e6373ae796f0ddad3675268c8d59fb6076e66f0c339d61cea886b/pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", size = 13064210 }, + { url = "https://files.pythonhosted.org/packages/61/61/a89015a6d5536cb0d6c3ba02cebed51a95538cf83472975275e28ebf7d0c/pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", size = 16754292 }, + { url = "https://files.pythonhosted.org/packages/ce/0d/4cc7b69ce37fac07645a94e1d4b0880b15999494372c1523508511b09e40/pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", size = 14416379 }, + { url = "https://files.pythonhosted.org/packages/31/9e/6ebb433de864a6cd45716af52a4d7a8c3c9aaf3a98368e61db9e69e69a9c/pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", size = 11598471 }, + { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222 }, + { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274 }, + { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836 }, + { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505 }, + { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420 }, + { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457 }, + { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166 }, + { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 }, + { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 }, + { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 }, + { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 }, + { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 }, + { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 }, + { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 }, + { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 }, + { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 }, + { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 }, + { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 }, + { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 }, + { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 }, + { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 }, + { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 }, + { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 }, + { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 }, + { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 }, + { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 }, + { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/81/331257dbf2801cdb82105306042f7a1637cc752f65f2bb688188e0de5f0b/psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f", size = 3043397 }, + { url = "https://files.pythonhosted.org/packages/e7/9a/7f4f2f031010bbfe6a02b4a15c01e12eb6b9b7b358ab33229f28baadbfc1/psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906", size = 3274806 }, + { url = "https://files.pythonhosted.org/packages/e5/57/8ddd4b374fa811a0b0a0f49b6abad1cde9cb34df73ea3348cc283fcd70b4/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92", size = 2851361 }, + { url = "https://files.pythonhosted.org/packages/f9/66/d1e52c20d283f1f3a8e7e5c1e06851d432f123ef57b13043b4f9b21ffa1f/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007", size = 3080836 }, + { url = "https://files.pythonhosted.org/packages/a0/cb/592d44a9546aba78f8a1249021fe7c59d3afb8a0ba51434d6610cc3462b6/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0", size = 3264552 }, + { url = "https://files.pythonhosted.org/packages/64/33/c8548560b94b7617f203d7236d6cdf36fe1a5a3645600ada6efd79da946f/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4", size = 3019789 }, + { url = "https://files.pythonhosted.org/packages/b0/0e/c2da0db5bea88a3be52307f88b75eec72c4de62814cbe9ee600c29c06334/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1", size = 2871776 }, + { url = "https://files.pythonhosted.org/packages/15/d7/774afa1eadb787ddf41aab52d4c62785563e29949613c958955031408ae6/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5", size = 2820959 }, + { url = "https://files.pythonhosted.org/packages/5e/ed/440dc3f5991a8c6172a1cde44850ead0e483a375277a1aef7cfcec00af07/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5", size = 2919329 }, + { url = "https://files.pythonhosted.org/packages/03/be/2cc8f4282898306732d2ae7b7378ae14e8df3c1231b53579efa056aae887/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53", size = 2957659 }, + { url = "https://files.pythonhosted.org/packages/d0/12/fb8e4f485d98c570e00dad5800e9a2349cfe0f71a767c856857160d343a5/psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b", size = 1024605 }, + { url = "https://files.pythonhosted.org/packages/22/4f/217cd2471ecf45d82905dd09085e049af8de6cfdc008b6663c3226dc1c98/psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1", size = 1163817 }, + { url = "https://files.pythonhosted.org/packages/9c/8f/9feb01291d0d7a0a4c6a6bab24094135c2b59c6a81943752f632c75896d6/psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", size = 3043397 }, + { url = "https://files.pythonhosted.org/packages/15/30/346e4683532011561cd9c8dfeac6a8153dd96452fee0b12666058ab7893c/psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", size = 3274806 }, + { url = "https://files.pythonhosted.org/packages/66/6e/4efebe76f76aee7ec99166b6c023ff8abdc4e183f7b70913d7c047701b79/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", size = 2851370 }, + { url = "https://files.pythonhosted.org/packages/7f/fd/ff83313f86b50f7ca089b161b8e0a22bb3c319974096093cd50680433fdb/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", size = 3080780 }, + { url = "https://files.pythonhosted.org/packages/e6/c4/bfadd202dcda8333a7ccafdc51c541dbdfce7c2c7cda89fa2374455d795f/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", size = 3264583 }, + { url = "https://files.pythonhosted.org/packages/5d/f1/09f45ac25e704ac954862581f9f9ae21303cc5ded3d0b775532b407f0e90/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", size = 3019831 }, + { url = "https://files.pythonhosted.org/packages/9e/2e/9beaea078095cc558f215e38f647c7114987d9febfc25cb2beed7c3582a5/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", size = 2871822 }, + { url = "https://files.pythonhosted.org/packages/01/9e/ef93c5d93f3dc9fc92786ffab39e323b9aed066ba59fdc34cf85e2722271/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", size = 2820975 }, + { url = "https://files.pythonhosted.org/packages/a5/f0/049e9631e3268fe4c5a387f6fc27e267ebe199acf1bc1bc9cbde4bd6916c/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", size = 2919320 }, + { url = "https://files.pythonhosted.org/packages/dc/9a/bcb8773b88e45fb5a5ea8339e2104d82c863a3b8558fbb2aadfe66df86b3/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", size = 2957617 }, + { url = "https://files.pythonhosted.org/packages/e2/6b/144336a9bf08a67d217b3af3246abb1d027095dab726f0687f01f43e8c03/psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", size = 1024618 }, + { url = "https://files.pythonhosted.org/packages/61/69/3b3d7bd583c6d3cbe5100802efa5beacaacc86e37b653fc708bf3d6853b8/psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", size = 1163816 }, + { url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", size = 3044771 }, + { url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", size = 3275336 }, + { url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", size = 2851637 }, + { url = "https://files.pythonhosted.org/packages/0b/b1/cfedc0e0e6f9ad61f8657fd173b2f831ce261c02a08c0b09c652b127d813/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", size = 3082097 }, + { url = "https://files.pythonhosted.org/packages/18/ed/0a8e4153c9b769f59c02fb5e7914f20f0b2483a19dae7bf2db54b743d0d0/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", size = 3264776 }, + { url = "https://files.pythonhosted.org/packages/10/db/d09da68c6a0cdab41566b74e0a6068a425f077169bed0946559b7348ebe9/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", size = 3020968 }, + { url = "https://files.pythonhosted.org/packages/94/28/4d6f8c255f0dfffb410db2b3f9ac5218d959a66c715c34cac31081e19b95/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", size = 2872334 }, + { url = "https://files.pythonhosted.org/packages/05/f7/20d7bf796593c4fea95e12119d6cc384ff1f6141a24fbb7df5a668d29d29/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", size = 2822722 }, + { url = "https://files.pythonhosted.org/packages/4d/e4/0c407ae919ef626dbdb32835a03b6737013c3cc7240169843965cada2bdf/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", size = 2920132 }, + { url = "https://files.pythonhosted.org/packages/2d/70/aa69c9f69cf09a01da224909ff6ce8b68faeef476f00f7ec377e8f03be70/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", size = 2959312 }, + { url = "https://files.pythonhosted.org/packages/d3/bd/213e59854fafe87ba47814bf413ace0dcee33a89c8c8c814faca6bc7cf3c/psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", size = 1025191 }, + { url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031 }, + { url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", size = 3044699 }, + { url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", size = 3275245 }, + { url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", size = 2851631 }, + { url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", size = 3082140 }, + { url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", size = 3264762 }, + { url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", size = 3020967 }, + { url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", size = 2872326 }, + { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712 }, + { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155 }, + { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356 }, + { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135 }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/67/6afbf0d507f73c32d21084a79946bfcfca5fbc62a72057e9c23797a737c9/pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c", size = 310028 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/89/bc88a6711935ba795a679ea6ebee07e128050d6382eaa35a0a47c8032bdc/pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd", size = 181537 }, +] + +[[package]] +name = "pydantic" +version = "2.11.0a2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/71/dbe0b4f6b00e444c25c80e4fb2982be5df6552ea109ff2227c287ea9f21b/pydantic-2.11.0a2.tar.gz", hash = "sha256:5f89644a6cc2fab8ec2f7128e0b29a687e375624c3f8cb16d662d8865bab4c6c", size = 768863 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/06/127ca54e31537712c3266c72955e1cc6b7b063d0805d54e677d09b255de8/pydantic-2.11.0a2-py3-none-any.whl", hash = "sha256:3bda237b05daef87b2fa17b21cc5a77f016c54681f8549ba3ee4a90d71b197ed", size = 436784 }, +] + +[[package]] +name = "pydantic-ai" +version = "0.0.24" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic-ai-slim", extra = ["anthropic", "cohere", "groq", "mistral", "openai", "vertexai"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/8a/0a9ff28b5956077f162dec57e96eeb5ea322cb1bba9c85f02d008966e1b4/pydantic_ai-0.0.24.tar.gz", hash = "sha256:7af076cbdb5f4c6ac8fae5273f02b67130b69d1cdaed87a022f79f42449f83a9", size = 71314 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/94/e7dbc5acf86a24442d21df4b8453a267614c3a6d9cd52ce0c685de2d7d19/pydantic_ai-0.0.24-py3-none-any.whl", hash = "sha256:dc75f01be0218c2f46fe90a3d43ef4700d764bfbe6240fca9c18a4509f6b86cf", size = 9837 }, +] + +[[package]] +name = "pydantic-ai-slim" +version = "0.0.24" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport" }, + { name = "griffe" }, + { name = "httpx" }, + { name = "logfire-api" }, + { name = "pydantic" }, + { name = "pydantic-graph" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/05/baff139032ecdb930f00760c652d782934bc63da57c2ecc1c48d4c9b8898/pydantic_ai_slim-0.0.24.tar.gz", hash = "sha256:c82d5ffed6d3a149e64701d0e4cfa09256a8ef9feb296281f908af19050d2bf1", size = 73812 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/15/715b629b0386f45345ac8a8e97f0d788ab7f728cd8bbe39f724e4c32773e/pydantic_ai_slim-0.0.24-py3-none-any.whl", hash = "sha256:7cd3aec6df572780323169a6dfa114640f76c2e2b21105efcd5cce2b459c7b17", size = 95271 }, +] + +[package.optional-dependencies] +anthropic = [ + { name = "anthropic" }, +] +cohere = [ + { name = "cohere" }, +] +groq = [ + { name = "groq" }, +] +mistral = [ + { name = "mistralai" }, +] +openai = [ + { name = "openai" }, +] +vertexai = [ + { name = "google-auth" }, + { name = "requests" }, +] + +[[package]] +name = "pydantic-core" +version = "2.29.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/f6/904edea98c98c09b8d618d619749af19a50ea5a71b9199ee2131a5a03dfb/pydantic_core-2.29.0.tar.gz", hash = "sha256:05cb49f30ee6fd4a554f14dc2a06538931c039f02329621d57ba00465613d28e", size = 417255 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/27/1e6beb77f3fb0c00a87a1c59a41ab1c986be582236248592c96b96c57701/pydantic_core-2.29.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:747410cab5a3a8421f45879fe3ac38f650627b874debc1789b8df609a4c99d64", size = 2038245 }, + { url = "https://files.pythonhosted.org/packages/23/82/d81ac9a227bcf5cd9a536d0613542f402d74b97014131ca50c64711e1acd/pydantic_core-2.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c06ce6eb6dfbe653425c6aee2c02d67a4eb18c31a3b1022748cfb33904bf6a1", size = 1885138 }, + { url = "https://files.pythonhosted.org/packages/87/99/c3af71a0cddd2a550d5d56c1f9df34b74d25ef62c0240f889cdc37782489/pydantic_core-2.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4ab8d727db1e346fc3ea24d25f715b36634f3f817600bd99ad740ecdea75c49", size = 1910926 }, + { url = "https://files.pythonhosted.org/packages/46/95/ac712ed8a83f25822899f0a0df6cc7622106b8d8fda3086718bf0b9ebf80/pydantic_core-2.29.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d4e5f402328c3903b1e64a7a6bb17ac211b4ef1831cb691ebc983ba71ff3c4be", size = 1997150 }, + { url = "https://files.pythonhosted.org/packages/e7/a9/82946362dc53b3c2ba2f84e92bdb79a6c44201fafccfb42d05620837d44d/pydantic_core-2.29.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f940cd3803999dc303d89f2a9502d0b3cf5322b9625e4c8ef1c221a816c885", size = 2131731 }, + { url = "https://files.pythonhosted.org/packages/86/33/44386a10c29463c96a077967efb4d94250f6e76d8cc29d5d1e69c256e705/pydantic_core-2.29.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:489efc8c83198b5dd9ede0dec585da4ec8d548a82dae451e2e6076645fb71fee", size = 2776741 }, + { url = "https://files.pythonhosted.org/packages/7d/bd/0a27e050af68b9e3673449a626bbd24989d2edbb1a1b6db4774ce9a6fa77/pydantic_core-2.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:061ee2c0c8a0a34f51fb5359dd57c27ecf9edaa8aca13a63d154d46bede4c91c", size = 2035785 }, + { url = "https://files.pythonhosted.org/packages/1e/35/bf6bde45af0bc03f1db8fe4682cfb7ea72d849c7fe8ece1439c86be71900/pydantic_core-2.29.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf95c495629f76385e5c1c5bbf75e354c015be3c80069f3f6c1a38ac3e112128", size = 2126860 }, + { url = "https://files.pythonhosted.org/packages/b6/ea/1c394c6d2732899cf7c5cc5bcbfb0f4c5641a0efbb571f99c3e16cd0ae81/pydantic_core-2.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc876229b523beb46883a7b58bdd6da19390a56417d84083254f90f56497ba74", size = 2072419 }, + { url = "https://files.pythonhosted.org/packages/2f/c8/9e06625b3ef36b148bcad3d5e7e3445efdf16285fe9d43934063e2c2dd22/pydantic_core-2.29.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d2d05f785bf0906ec135d405b8a26c96a38098c0d942ac1698613da110548f8e", size = 2251157 }, + { url = "https://files.pythonhosted.org/packages/7d/b3/e9815f22c22d190fa74167b0eeb5af5c254dfbdcd5542906c4b280c6d547/pydantic_core-2.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3e3767663d0a51507b711acc56b6a99254c48d19b889e4a12845f6df70666778", size = 2235206 }, + { url = "https://files.pythonhosted.org/packages/af/33/208ced529108cbd585dc65553cd00359a6740ff871bff9973331ae75c210/pydantic_core-2.29.0-cp310-cp310-win32.whl", hash = "sha256:07301b455b78316c85e332d1ac5219ca315239f920c261ebfe106809eba3b01b", size = 1927276 }, + { url = "https://files.pythonhosted.org/packages/8e/95/ba277da9729920d3e169966935d6ee67a276c796af96feae01c74146a10c/pydantic_core-2.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:3493dab9cf3ed2b4bf3b3664fd4f4c3099ea270adcc45a1921513207eb557976", size = 1987638 }, + { url = "https://files.pythonhosted.org/packages/25/dd/a51066d138f742e2e28a0f306463eff2e2c7258e15f8f201005d75cc61d1/pydantic_core-2.29.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:69f8936f367e59aeeb4d0ae3f1cae564a7b9be8d04c88cc22101c57c9f25ab6c", size = 2040826 }, + { url = "https://files.pythonhosted.org/packages/18/89/35543a77b34883107453657e9f43620cbe4f66a0995f723b101d05bbf1c4/pydantic_core-2.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a246d3980e6c08af0d618e19b91259e79d46a857b0cca82f10aea55f550b265", size = 1883327 }, + { url = "https://files.pythonhosted.org/packages/c2/7a/8b63417e996d38e87248b0f1180b503ac3680074df012b1e35679d9f984b/pydantic_core-2.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58c0393cf1ebdd6a300eef431bfc1192e8866b77d5f3c1b8f2979032ca2a5ad4", size = 1912271 }, + { url = "https://files.pythonhosted.org/packages/81/4e/3dc1f66d4f24b63ffff28eedb9d81ba366fc06af61e64ee784d26296004d/pydantic_core-2.29.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aa92661beb1336fe432bcbae173abe742f6d0f9c4373b5e974ef2188168707b", size = 2000479 }, + { url = "https://files.pythonhosted.org/packages/8e/90/acb9b7d59e4bf41021cf659951cef5ce8135d5517053ed2ad8ec2702f67c/pydantic_core-2.29.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3e29395683a28294df58822e118944c6a02cba26910bc933d5e86a87bc9d819", size = 2133267 }, + { url = "https://files.pythonhosted.org/packages/69/48/aca154a2098d7ca3d2918dffa14469ef1807bb83d1a239ef0cc7017680f9/pydantic_core-2.29.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:366ac244b2552f502814a16fc5d94ad2bb23eb062a3821b8b9ba5084ff2afe41", size = 2770521 }, + { url = "https://files.pythonhosted.org/packages/cd/93/01c953fe7f99236d9f8abf80503d5f0d574a9dbfa292d12a4ae66568498c/pydantic_core-2.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc3461a85b48faa8c0f3ab4d7ac5f909a80c7c9e2a3c795d42e96c3435819993", size = 2033852 }, + { url = "https://files.pythonhosted.org/packages/28/30/927fe1493bdd605d6f2fe7d3f7422be3396e3e9ec7b6bfb82a6a3381911e/pydantic_core-2.29.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e57de4d923a3568276ce7c8b6b24b1c0157d6a51ac65290203b77e2e20c0453d", size = 2128977 }, + { url = "https://files.pythonhosted.org/packages/44/6a/dcdc780fb0b97824208993a454433135d8577e97e156aeb5a1b354e0ae30/pydantic_core-2.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:05bed77fbce07eef62f9584b0552c2b35e0321f896b28067620dd18058c89e66", size = 2073638 }, + { url = "https://files.pythonhosted.org/packages/02/f6/6f37cb0f9dd621c0056ce0ff45ab1cde9d8ba0b32e15695ea5dec1807f23/pydantic_core-2.29.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:ba675f218ba689f2bfc980bf455772947cb331905800b054a94446d9e120c4f8", size = 2254289 }, + { url = "https://files.pythonhosted.org/packages/7c/46/f17a1a02bee33f4df858fe8de71b25c24f8c26f7c3410885237b93a47ced/pydantic_core-2.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6fa3dfbd761395e32ad7b2a099ba07f2caeccc26d979a67787844c437541f9c8", size = 2236760 }, + { url = "https://files.pythonhosted.org/packages/92/86/0fd8d41fe53ff1c079dc7c331829ba9b465f28d05733bee6fb32e8c6fd28/pydantic_core-2.29.0-cp311-cp311-win32.whl", hash = "sha256:04cdcafd875638aa325706e1736c7e8667d19b5de281148d7bac4de51dd7016e", size = 1928262 }, + { url = "https://files.pythonhosted.org/packages/08/68/5c68a99cab786d14bfd85aa711c8147965a109a7250e97aecc51708322cd/pydantic_core-2.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b74ef07ba7f9a0c84e594d8aeac289469d801687139a916a7b2b3ad5196ac01", size = 1985064 }, + { url = "https://files.pythonhosted.org/packages/77/a5/243de2c677958176be9890a502bd1297eb52cc9f07dd7c1fcfd0fff00380/pydantic_core-2.29.0-cp311-cp311-win_arm64.whl", hash = "sha256:dec2e35dab27a560bab25bcbff18b479de13b6f8432527449fd03f6b1b843ffd", size = 1899103 }, + { url = "https://files.pythonhosted.org/packages/77/49/2a7e4f9f770bcab564dc03d43052a43c4e52a150a9275f658a3c75a00fb2/pydantic_core-2.29.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d588c0a4225bfdc4909bbce37631e6838e210d2899978de9d95c4972bed4f8da", size = 2018130 }, + { url = "https://files.pythonhosted.org/packages/35/7d/d585f75ad9f8e7ed042dbe08c99d2ef69ee82f550fc93080a25f0892d768/pydantic_core-2.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b773a15a6992263a7e3f49c4b9784c7939e820cdde043fba8ddb4436ea96467", size = 1878298 }, + { url = "https://files.pythonhosted.org/packages/a2/9f/5ae8a350fb287364d433b727a7ff030d1f14b83f17189f367fe2dc5639af/pydantic_core-2.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:978fb22eeaf140515d34e4f34f40bf14b8e21c07788709e10f6ba25b29e537e6", size = 1897227 }, + { url = "https://files.pythonhosted.org/packages/46/37/0591eaa91f77e5497a9be19ec7ecd447de4b0f37cf0d7ad37001db26abab/pydantic_core-2.29.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a568a0e090441b2149aa5985b68a19e89d95b8adcdf963e17e9e64954149b68", size = 1980299 }, + { url = "https://files.pythonhosted.org/packages/cd/9f/541d2907af017113ecfe42f59d0335554a2ef850520f3344892c14902034/pydantic_core-2.29.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20c3fda734d977acf65a90df411dc2fa06ad31f30d7bbcd50e37858e23b7c6a5", size = 2121471 }, + { url = "https://files.pythonhosted.org/packages/f1/20/a05040154e3aa91c260c21997718693b948179c58f3ba7e951dd9fb8ffd4/pydantic_core-2.29.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55359359017d6f7eb04e054b1390b89d94e2f6d2946ed7b92ebe6dd481c582cf", size = 2700716 }, + { url = "https://files.pythonhosted.org/packages/a7/44/1b17f5dc13164ac2282ab26748ec1ec90419163d888574d0e7440a6bec5b/pydantic_core-2.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a50a8276de8d803f52cba3d61d5714b07ae1b8e95ea9b073ea3c9bed0a81e", size = 2042224 }, + { url = "https://files.pythonhosted.org/packages/4b/ac/0faee9213e5bc4c4af6a2815ae05263333760f2311385de5e0652f5a43b9/pydantic_core-2.29.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1ce0a52365771ec5337c36761a1b3ae44872d46bf67b7367acbf9120c36125f", size = 2118278 }, + { url = "https://files.pythonhosted.org/packages/1a/e3/dcce2327c34709f1c8c4e12a9b7650540fce0630a407da255b04e114a448/pydantic_core-2.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ebbdaf3d9830b2ebe7d7302cb4db37d206b0f82a9e0701da9e2375097d8708bb", size = 2058177 }, + { url = "https://files.pythonhosted.org/packages/33/61/55fb2c3c007f4a9e18197e41d6746150bb9a7ca3855a9f311ba5ad19a58b/pydantic_core-2.29.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:7411ad901151b4805abf79835b09915eb66eae6e5c8273f2cb82da2fcb9e2da5", size = 2230114 }, + { url = "https://files.pythonhosted.org/packages/c0/c1/093eb7d36e5ad4b69005ab680e18527799cb622e3469deb9a2ac4caf89bb/pydantic_core-2.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f957efe72976e2c2eacab23c81104eedd13f9c0ab2e73f3ade9c1d6fc02005c9", size = 2228186 }, + { url = "https://files.pythonhosted.org/packages/fb/f8/8d2f114b722fa18ab4e29b4cc8a24cf427a4129518dc883e537ada1dc6b5/pydantic_core-2.29.0-cp312-cp312-win32.whl", hash = "sha256:8f981b20e7d05eba911536adc43b09c2d16e599b149146fd082c692f054daeaf", size = 1909599 }, + { url = "https://files.pythonhosted.org/packages/5f/ae/998e4873bcb9f95c930349de4efc02de71e3fd7554f86dda69410365f054/pydantic_core-2.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:c64195be924b9a0658aef39e049afe10bf491c7897de84e2282152862a4afbf6", size = 1999702 }, + { url = "https://files.pythonhosted.org/packages/64/77/5441b9c31ffe49efb4a3e2458fa886befca4201cb2237d1320646cf78047/pydantic_core-2.29.0-cp312-cp312-win_arm64.whl", hash = "sha256:e37ff0c906f4931dbc6866f8b0c42613b80306772b9847f409086b7f9d762f65", size = 1894324 }, + { url = "https://files.pythonhosted.org/packages/3a/7b/9e21b3220e4ac8435d548d67ecb136f99ed3cf5dd3773f9d18994563c570/pydantic_core-2.29.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4c35461bdb979b5073e03db9e17ad552c1e79b87ffb6fc41c8826976ea7ab8a8", size = 2023244 }, + { url = "https://files.pythonhosted.org/packages/a9/80/ceae62aae8fc9cf4925eeb5e299993235986bba6a38c55af55067b6eda71/pydantic_core-2.29.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:09cbd22633b539e0dc3f5ee4f78e3d3a09b22d620e891f4d8bcbc86a61554e00", size = 1876494 }, + { url = "https://files.pythonhosted.org/packages/e1/ef/9463d0b3b549de45638fcdbad8dd43b85ef1d2001ac09525b80fdd9cc7d9/pydantic_core-2.29.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a72188f290bc97ea4c95e59194b024b58ef39e7e6ee53ee882231194ea35a02", size = 1901663 }, + { url = "https://files.pythonhosted.org/packages/8d/50/9959a025ea0a7663b7e0c9055c2add43b67de81fcaddafb62e91bcba37ff/pydantic_core-2.29.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54922d8b3b85623f2256f6a3daa3990c47c2ff3805c3660ce9fce97f455a424a", size = 1987237 }, + { url = "https://files.pythonhosted.org/packages/5c/e5/1bc652fde35f7caf1e0e8d9beb89de6e3daee45d8fbce1637a5ae5ef08ca/pydantic_core-2.29.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a58c6a92097c62bf3cad0baf44858a1a382b5ef1a2800f3d503a75f61300794", size = 2121954 }, + { url = "https://files.pythonhosted.org/packages/7f/cb/847727eb23b6f2d91f3f50e413181f1d5b3ff930bc1a90837a355b1f5e13/pydantic_core-2.29.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4643b48b19a0bedc4c0421d8488111feedf7bc7e044389988dd9bda3ef4b78a", size = 2701427 }, + { url = "https://files.pythonhosted.org/packages/db/cf/0f993819d46324134f8af0e466a932e6b3925553b101170bbe4f04ba5475/pydantic_core-2.29.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83008a96504be140e38a30a2f51584a942419b6d0a7214c84ba760685f4bf0b5", size = 2042209 }, + { url = "https://files.pythonhosted.org/packages/6b/b4/9470f68933206af897dcecec479cb6d653b2225fac576f0d2144d5b9f100/pydantic_core-2.29.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8085e8530eb6a231b2261cd03a4a67b129cb5a78ff3b16efc7b090688647014", size = 2123243 }, + { url = "https://files.pythonhosted.org/packages/dd/03/1cd776a47e2602cdaa95cf0fbe9f3fa442edabf7b8c9c14fc0e8e8e36892/pydantic_core-2.29.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:d612c46e0f1e03a3802b8cbb6da679a8ac6c97744c65df953e8ef36cc2919f5d", size = 2063509 }, + { url = "https://files.pythonhosted.org/packages/ed/f5/60b4ac2e2430cb8787e61d04c5ffbb901bc56769c33ddc71571eff2d594b/pydantic_core-2.29.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6a978c70e42dbb90c9dbea4429ff807cceeb7c4ef7e2a9e6942f22c28e1b27b1", size = 2239943 }, + { url = "https://files.pythonhosted.org/packages/b3/c5/dfb65e2f8734ce4bd36e38a95945725e897dff4c428a58a5e80390991c8c/pydantic_core-2.29.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:daa3de513ef07c89c284dfb5e18100e082629ebde7d427fa87e02ffba8f3872e", size = 2234489 }, + { url = "https://files.pythonhosted.org/packages/c5/ba/9062e319b965fcb51f1d213d94ac174ba0c4e30a5b0734c482617f1dc069/pydantic_core-2.29.0-cp313-cp313-win32.whl", hash = "sha256:d57243e26a1f995d0eca80b0f0db9e74b2f4ad65f279f26ec8df639b0e600675", size = 1916531 }, + { url = "https://files.pythonhosted.org/packages/43/78/ba1bfc367e6197e9f986479c2759ed3047367f2fbe0b09b3bd9265232be3/pydantic_core-2.29.0-cp313-cp313-win_amd64.whl", hash = "sha256:b585cc038992f6dc6431a354e0d8293548c548f6a2c2fbc223c80c32aab24aa9", size = 1998894 }, + { url = "https://files.pythonhosted.org/packages/e4/d8/6e270f4aba96895f172e79afcceff81a4bd3e9db999476141c72132a5ba6/pydantic_core-2.29.0-cp313-cp313-win_arm64.whl", hash = "sha256:d0bf68e9ab0176869ef7d62d88f408bbb1d2a110b6abdc6317af5994722f987d", size = 1900301 }, + { url = "https://files.pythonhosted.org/packages/f4/0b/6ef557823655ddfa9e86c4c75652ed47542c69327a57f5448e847addd205/pydantic_core-2.29.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a743617f622482c1ccbba82466e175687915fea994fdc83f4188233a7405207", size = 1838099 }, + { url = "https://files.pythonhosted.org/packages/2c/bb/e2e96832582cfb47fdf596377ad70d54c490eba3b656829d53688fc8d49e/pydantic_core-2.29.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c217212ea90ce79f5cccad8c953cea341d63a92bd240f0512e67a735c21a19a6", size = 2013722 }, + { url = "https://files.pythonhosted.org/packages/82/a3/aeff32afd2fcc2be65257a5b0a012658b178820466bd4874704f35892340/pydantic_core-2.29.0-cp313-cp313t-win_amd64.whl", hash = "sha256:00d6b6af6e1de398fc225b333288817555bfe9f05dd26af9ff1707543459d03c", size = 1969827 }, + { url = "https://files.pythonhosted.org/packages/9c/38/183a6b5c005c01c1d7a5b86258fbe0fb82da865ae82b34a40c7227c7886a/pydantic_core-2.29.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:09c160565ad434b785b64985bef1d4dc5776525ca57f64107e91ea9982db70b6", size = 2035190 }, + { url = "https://files.pythonhosted.org/packages/58/c7/c8e010e3df73ec9a4ebfd85b5871aac82875d3e03c5882c951efd3d53bf7/pydantic_core-2.29.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:20321fbdeec98de3dbf923e2e6dfbf2d926d072e3f1c4a24225acf87294e83db", size = 1877210 }, + { url = "https://files.pythonhosted.org/packages/de/2a/b7ba84410bf6a6aebd54be01ca230e2bb85645bfe1aa9ae2908616002e4a/pydantic_core-2.29.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d1ca8e0b3270f3e52041bea996e055dd656e8ba527ed19933d39265d5354bd7", size = 1906153 }, + { url = "https://files.pythonhosted.org/packages/c7/67/c7b1bfd11478454c5741da48c3358f0cf126abb421a77bda25c1fc1d7af5/pydantic_core-2.29.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd38661628e88ff5c7554b60575b58f6169eca736427a71271f18547104610e3", size = 2076165 }, + { url = "https://files.pythonhosted.org/packages/d9/b4/50a58e2cde99b5ca2ad56f8f4fa344cb7ac1daf8f816778b7dbf6bb14cfc/pydantic_core-2.29.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5efd58e083222c3e9404414bae9ce69fbed811a8dfdf62733e5c3106fc32501b", size = 2123305 }, + { url = "https://files.pythonhosted.org/packages/df/a4/5303294c7eabcb51995e351c0ee2bf733da733b6e6f4148fe6d234f6d6cf/pydantic_core-2.29.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:b2031357c4b661442cfbb54ad90f46af4db24c8dfa5adc43d3164616cc165137", size = 2068711 }, + { url = "https://files.pythonhosted.org/packages/d8/0b/02c69b4251c6d568bd91b3cb15167ec2f47754b3c7c91def641f7c26e20d/pydantic_core-2.29.0-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c0e2cb7d4fdb915b6c8fcedd2993e0129cd2e0d298a8df56ec6e89c67f122017", size = 2249482 }, + { url = "https://files.pythonhosted.org/packages/e2/34/ae91ff9ec4829a70bbb4b12c581fb3645e6d66913ff3b5738009d7941a81/pydantic_core-2.29.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:28717a77ed32987754da41c65005513820282fb2d94e6a0840a91ffbf7e8b50a", size = 2232551 }, + { url = "https://files.pythonhosted.org/packages/8e/ae/870d7143fa769c3046facc4a04dc9411f144b61b33016fbc669d72b9ac4c/pydantic_core-2.29.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:12f914125c9860deb7f4a47c1581d07544e4e093bf0d2ecb0dc3f69df7ff86fb", size = 2061836 }, +] + +[[package]] +name = "pydantic-graph" +version = "0.0.24" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "logfire-api" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/8f/ad685ecca0f610fa204e92f394d190bcab8c9392f7be8f1a83914244d36b/pydantic_graph-0.0.24.tar.gz", hash = "sha256:40550e2310002f411b1fc0636206222caa5459ede7c44da5c856b3526b3096cd", size = 13694 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/f6/2f4ef49e75fcb6070f440f05db2898612ada7f5d889209e98ede2fdb3d19/pydantic_graph-0.0.24-py3-none-any.whl", hash = "sha256:29d763cb77a2533bcbd07ff34fff36316617d1e62a926a7a15df59f6103fc74e", size = 16860 }, +] + +[[package]] +name = "pydantic-settings" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/7b/c58a586cd7d9ac66d2ee4ba60ca2d241fa837c02bca9bea80a9a8c3d22a9/pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93", size = 79920 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/46/93416fdae86d40879714f72956ac14df9c7b76f7d41a4d68aa9f71a0028b/pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd", size = 29718 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, +] + +[[package]] +name = "pytz" +version = "2025.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/57/df1c9157c8d5a05117e455d66fd7cf6dbc46974f832b1058ed4856785d8a/pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e", size = 319617 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/38/ac33370d784287baa1c3d538978b5e2ea064d4c1b93ffbd12826c190dd10/pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57", size = 507930 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "rsa" +version = "4.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/65/7d973b89c4d2351d7fb232c2e452547ddfa243e93131e7cfa766da627b52/rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21", size = 29711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", size = 34315 }, +] + +[[package]] +name = "scikit-learn" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/a5/4ae3b3a0755f7b35a280ac90b28817d1f380318973cff14075ab41ef50d9/scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e", size = 7068312 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/3a/f4597eb41049110b21ebcbb0bcb43e4035017545daa5eedcfeb45c08b9c5/scikit_learn-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d056391530ccd1e501056160e3c9673b4da4805eb67eb2bdf4e983e1f9c9204e", size = 12067702 }, + { url = "https://files.pythonhosted.org/packages/37/19/0423e5e1fd1c6ec5be2352ba05a537a473c1677f8188b9306097d684b327/scikit_learn-1.6.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0c8d036eb937dbb568c6242fa598d551d88fb4399c0344d95c001980ec1c7d36", size = 11112765 }, + { url = "https://files.pythonhosted.org/packages/70/95/d5cb2297a835b0f5fc9a77042b0a2d029866379091ab8b3f52cc62277808/scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8634c4bd21a2a813e0a7e3900464e6d593162a29dd35d25bdf0103b3fce60ed5", size = 12643991 }, + { url = "https://files.pythonhosted.org/packages/b7/91/ab3c697188f224d658969f678be86b0968ccc52774c8ab4a86a07be13c25/scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:775da975a471c4f6f467725dff0ced5c7ac7bda5e9316b260225b48475279a1b", size = 13497182 }, + { url = "https://files.pythonhosted.org/packages/17/04/d5d556b6c88886c092cc989433b2bab62488e0f0dafe616a1d5c9cb0efb1/scikit_learn-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:8a600c31592bd7dab31e1c61b9bbd6dea1b3433e67d264d17ce1017dbdce8002", size = 11125517 }, + { url = "https://files.pythonhosted.org/packages/6c/2a/e291c29670795406a824567d1dfc91db7b699799a002fdaa452bceea8f6e/scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33", size = 12102620 }, + { url = "https://files.pythonhosted.org/packages/25/92/ee1d7a00bb6b8c55755d4984fd82608603a3cc59959245068ce32e7fb808/scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d", size = 11116234 }, + { url = "https://files.pythonhosted.org/packages/30/cd/ed4399485ef364bb25f388ab438e3724e60dc218c547a407b6e90ccccaef/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2", size = 12592155 }, + { url = "https://files.pythonhosted.org/packages/a8/f3/62fc9a5a659bb58a03cdd7e258956a5824bdc9b4bb3c5d932f55880be569/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8", size = 13497069 }, + { url = "https://files.pythonhosted.org/packages/a1/a6/c5b78606743a1f28eae8f11973de6613a5ee87366796583fb74c67d54939/scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415", size = 11139809 }, + { url = "https://files.pythonhosted.org/packages/0a/18/c797c9b8c10380d05616db3bfb48e2a3358c767affd0857d56c2eb501caa/scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b", size = 12104516 }, + { url = "https://files.pythonhosted.org/packages/c4/b7/2e35f8e289ab70108f8cbb2e7a2208f0575dc704749721286519dcf35f6f/scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2", size = 11167837 }, + { url = "https://files.pythonhosted.org/packages/a4/f6/ff7beaeb644bcad72bcfd5a03ff36d32ee4e53a8b29a639f11bcb65d06cd/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f", size = 12253728 }, + { url = "https://files.pythonhosted.org/packages/29/7a/8bce8968883e9465de20be15542f4c7e221952441727c4dad24d534c6d99/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86", size = 13147700 }, + { url = "https://files.pythonhosted.org/packages/62/27/585859e72e117fe861c2079bcba35591a84f801e21bc1ab85bce6ce60305/scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52", size = 11110613 }, + { url = "https://files.pythonhosted.org/packages/2e/59/8eb1872ca87009bdcdb7f3cdc679ad557b992c12f4b61f9250659e592c63/scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322", size = 12010001 }, + { url = "https://files.pythonhosted.org/packages/9d/05/f2fc4effc5b32e525408524c982c468c29d22f828834f0625c5ef3d601be/scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1", size = 11096360 }, + { url = "https://files.pythonhosted.org/packages/c8/e4/4195d52cf4f113573fb8ebc44ed5a81bd511a92c0228889125fac2f4c3d1/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348", size = 12209004 }, + { url = "https://files.pythonhosted.org/packages/94/be/47e16cdd1e7fcf97d95b3cb08bde1abb13e627861af427a3651fcb80b517/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97", size = 13171776 }, + { url = "https://files.pythonhosted.org/packages/34/b0/ca92b90859070a1487827dbc672f998da95ce83edce1270fc23f96f1f61a/scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb", size = 11071865 }, + { url = "https://files.pythonhosted.org/packages/12/ae/993b0fb24a356e71e9a894e42b8a9eec528d4c70217353a1cd7a48bc25d4/scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236", size = 11955804 }, + { url = "https://files.pythonhosted.org/packages/d6/54/32fa2ee591af44507eac86406fa6bba968d1eb22831494470d0a2e4a1eb1/scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35", size = 11100530 }, + { url = "https://files.pythonhosted.org/packages/3f/58/55856da1adec655bdce77b502e94a267bf40a8c0b89f8622837f89503b5a/scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691", size = 12433852 }, + { url = "https://files.pythonhosted.org/packages/ff/4f/c83853af13901a574f8f13b645467285a48940f185b690936bb700a50863/scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f", size = 11337256 }, +] + +[[package]] +name = "scipy" +version = "1.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/c6/8eb0654ba0c7d0bb1bf67bf8fbace101a8e4f250f7722371105e8b6f68fc/scipy-1.15.1.tar.gz", hash = "sha256:033a75ddad1463970c96a88063a1df87ccfddd526437136b6ee81ff0312ebdf6", size = 59407493 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/53/b204ce5a4433f1864001b9d16f103b9c25f5002a602ae83585d0ea5f9c4a/scipy-1.15.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:c64ded12dcab08afff9e805a67ff4480f5e69993310e093434b10e85dc9d43e1", size = 41414518 }, + { url = "https://files.pythonhosted.org/packages/c7/fc/54ffa7a8847f7f303197a6ba65a66104724beba2e38f328135a78f0dc480/scipy-1.15.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5b190b935e7db569960b48840e5bef71dc513314cc4e79a1b7d14664f57fd4ff", size = 32519265 }, + { url = "https://files.pythonhosted.org/packages/f1/77/a98b8ba03d6f371dc31a38719affd53426d4665729dcffbed4afe296784a/scipy-1.15.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:4b17d4220df99bacb63065c76b0d1126d82bbf00167d1730019d2a30d6ae01ea", size = 24792859 }, + { url = "https://files.pythonhosted.org/packages/a7/78/70bb9f0df7444b18b108580934bfef774822e28fd34a68e5c263c7d2828a/scipy-1.15.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:63b9b6cd0333d0eb1a49de6f834e8aeaefe438df8f6372352084535ad095219e", size = 27886506 }, + { url = "https://files.pythonhosted.org/packages/14/a7/f40f6033e06de4176ddd6cc8c3ae9f10a226c3bca5d6b4ab883bc9914a14/scipy-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f151e9fb60fbf8e52426132f473221a49362091ce7a5e72f8aa41f8e0da4f25", size = 38375041 }, + { url = "https://files.pythonhosted.org/packages/17/03/390a1c5c61fd76b0fa4b3c5aa3bdd7e60f6c46f712924f1a9df5705ec046/scipy-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e10b1dd56ce92fba3e786007322542361984f8463c6d37f6f25935a5a6ef52", size = 40597556 }, + { url = "https://files.pythonhosted.org/packages/4e/70/fa95b3ae026b97eeca58204a90868802e5155ac71b9d7bdee92b68115dd3/scipy-1.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5dff14e75cdbcf07cdaa1c7707db6017d130f0af9ac41f6ce443a93318d6c6e0", size = 42938505 }, + { url = "https://files.pythonhosted.org/packages/d6/07/427859116bdd71847c898180f01802691f203c3e2455a1eb496130ff07c5/scipy-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:f82fcf4e5b377f819542fbc8541f7b5fbcf1c0017d0df0bc22c781bf60abc4d8", size = 43909663 }, + { url = "https://files.pythonhosted.org/packages/8e/2e/7b71312da9c2dabff53e7c9a9d08231bc34d9d8fdabe88a6f1155b44591c/scipy-1.15.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:5bd8d27d44e2c13d0c1124e6a556454f52cd3f704742985f6b09e75e163d20d2", size = 41424362 }, + { url = "https://files.pythonhosted.org/packages/81/8c/ab85f1aa1cc200c796532a385b6ebf6a81089747adc1da7482a062acc46c/scipy-1.15.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:be3deeb32844c27599347faa077b359584ba96664c5c79d71a354b80a0ad0ce0", size = 32535910 }, + { url = "https://files.pythonhosted.org/packages/3b/9c/6f4b787058daa8d8da21ddff881b4320e28de4704a65ec147adb50cb2230/scipy-1.15.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:5eb0ca35d4b08e95da99a9f9c400dc9f6c21c424298a0ba876fdc69c7afacedf", size = 24809398 }, + { url = "https://files.pythonhosted.org/packages/16/2b/949460a796df75fc7a1ee1becea202cf072edbe325ebe29f6d2029947aa7/scipy-1.15.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:74bb864ff7640dea310a1377d8567dc2cb7599c26a79ca852fc184cc851954ac", size = 27918045 }, + { url = "https://files.pythonhosted.org/packages/5f/36/67fe249dd7ccfcd2a38b25a640e3af7e59d9169c802478b6035ba91dfd6d/scipy-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:667f950bf8b7c3a23b4199db24cb9bf7512e27e86d0e3813f015b74ec2c6e3df", size = 38332074 }, + { url = "https://files.pythonhosted.org/packages/fc/da/452e1119e6f720df3feb588cce3c42c5e3d628d4bfd4aec097bd30b7de0c/scipy-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395be70220d1189756068b3173853029a013d8c8dd5fd3d1361d505b2aa58fa7", size = 40588469 }, + { url = "https://files.pythonhosted.org/packages/7f/71/5f94aceeac99a4941478af94fe9f459c6752d497035b6b0761a700f5f9ff/scipy-1.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce3a000cd28b4430426db2ca44d96636f701ed12e2b3ca1f2b1dd7abdd84b39a", size = 42965214 }, + { url = "https://files.pythonhosted.org/packages/af/25/caa430865749d504271757cafd24066d596217e83326155993980bc22f97/scipy-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:3fe1d95944f9cf6ba77aa28b82dd6bb2a5b52f2026beb39ecf05304b8392864b", size = 43896034 }, + { url = "https://files.pythonhosted.org/packages/d8/6e/a9c42d0d39e09ed7fd203d0ac17adfea759cba61ab457671fe66e523dbec/scipy-1.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c09aa9d90f3500ea4c9b393ee96f96b0ccb27f2f350d09a47f533293c78ea776", size = 41478318 }, + { url = "https://files.pythonhosted.org/packages/04/ee/e3e535c81828618878a7433992fecc92fa4df79393f31a8fea1d05615091/scipy-1.15.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0ac102ce99934b162914b1e4a6b94ca7da0f4058b6d6fd65b0cef330c0f3346f", size = 32596696 }, + { url = "https://files.pythonhosted.org/packages/c4/5e/b1b0124be8e76f87115f16b8915003eec4b7060298117715baf13f51942c/scipy-1.15.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:09c52320c42d7f5c7748b69e9f0389266fd4f82cf34c38485c14ee976cb8cb04", size = 24870366 }, + { url = "https://files.pythonhosted.org/packages/14/36/c00cb73eefda85946172c27913ab995c6ad4eee00fa4f007572e8c50cd51/scipy-1.15.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:cdde8414154054763b42b74fe8ce89d7f3d17a7ac5dd77204f0e142cdc9239e9", size = 28007461 }, + { url = "https://files.pythonhosted.org/packages/68/94/aff5c51b3799349a9d1e67a056772a0f8a47db371e83b498d43467806557/scipy-1.15.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c9d8fc81d6a3b6844235e6fd175ee1d4c060163905a2becce8e74cb0d7554ce", size = 38068174 }, + { url = "https://files.pythonhosted.org/packages/b0/3c/0de11ca154e24a57b579fb648151d901326d3102115bc4f9a7a86526ce54/scipy-1.15.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fb57b30f0017d4afa5fe5f5b150b8f807618819287c21cbe51130de7ccdaed2", size = 40249869 }, + { url = "https://files.pythonhosted.org/packages/15/09/472e8d0a6b33199d1bb95e49bedcabc0976c3724edd9b0ef7602ccacf41e/scipy-1.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:491d57fe89927fa1aafbe260f4cfa5ffa20ab9f1435025045a5315006a91b8f5", size = 42629068 }, + { url = "https://files.pythonhosted.org/packages/ff/ba/31c7a8131152822b3a2cdeba76398ffb404d81d640de98287d236da90c49/scipy-1.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:900f3fa3db87257510f011c292a5779eb627043dd89731b9c461cd16ef76ab3d", size = 43621992 }, + { url = "https://files.pythonhosted.org/packages/2b/bf/dd68965a4c5138a630eeed0baec9ae96e5d598887835bdde96cdd2fe4780/scipy-1.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:100193bb72fbff37dbd0bf14322314fc7cbe08b7ff3137f11a34d06dc0ee6b85", size = 41441136 }, + { url = "https://files.pythonhosted.org/packages/ef/5e/4928581312922d7e4d416d74c416a660addec4dd5ea185401df2269ba5a0/scipy-1.15.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:2114a08daec64980e4b4cbdf5bee90935af66d750146b1d2feb0d3ac30613692", size = 32533699 }, + { url = "https://files.pythonhosted.org/packages/32/90/03f99c43041852837686898c66767787cd41c5843d7a1509c39ffef683e9/scipy-1.15.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6b3e71893c6687fc5e29208d518900c24ea372a862854c9888368c0b267387ab", size = 24807289 }, + { url = "https://files.pythonhosted.org/packages/9d/52/bfe82b42ae112eaba1af2f3e556275b8727d55ac6e4932e7aef337a9d9d4/scipy-1.15.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:837299eec3d19b7e042923448d17d95a86e43941104d33f00da7e31a0f715d3c", size = 27929844 }, + { url = "https://files.pythonhosted.org/packages/f6/77/54ff610bad600462c313326acdb035783accc6a3d5f566d22757ad297564/scipy-1.15.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82add84e8a9fb12af5c2c1a3a3f1cb51849d27a580cb9e6bd66226195142be6e", size = 38031272 }, + { url = "https://files.pythonhosted.org/packages/f1/26/98585cbf04c7cf503d7eb0a1966df8a268154b5d923c5fe0c1ed13154c49/scipy-1.15.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070d10654f0cb6abd295bc96c12656f948e623ec5f9a4eab0ddb1466c000716e", size = 40210217 }, + { url = "https://files.pythonhosted.org/packages/fd/3f/3d2285eb6fece8bc5dbb2f9f94d61157d61d155e854fd5fea825b8218f12/scipy-1.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55cc79ce4085c702ac31e49b1e69b27ef41111f22beafb9b49fea67142b696c4", size = 42587785 }, + { url = "https://files.pythonhosted.org/packages/48/7d/5b5251984bf0160d6533695a74a5fddb1fa36edd6f26ffa8c871fbd4782a/scipy-1.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:c352c1b6d7cac452534517e022f8f7b8d139cd9f27e6fbd9f3cbd0bfd39f5bef", size = 43640439 }, + { url = "https://files.pythonhosted.org/packages/e7/b8/0e092f592d280496de52e152582030f8a270b194f87f890e1a97c5599b81/scipy-1.15.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0458839c9f873062db69a03de9a9765ae2e694352c76a16be44f93ea45c28d2b", size = 41619862 }, + { url = "https://files.pythonhosted.org/packages/f6/19/0b6e1173aba4db9e0b7aa27fe45019857fb90d6904038b83927cbe0a6c1d/scipy-1.15.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:af0b61c1de46d0565b4b39c6417373304c1d4f5220004058bdad3061c9fa8a95", size = 32610387 }, + { url = "https://files.pythonhosted.org/packages/e7/02/754aae3bd1fa0f2479ade3cfdf1732ecd6b05853f63eee6066a32684563a/scipy-1.15.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:71ba9a76c2390eca6e359be81a3e879614af3a71dfdabb96d1d7ab33da6f2364", size = 24883814 }, + { url = "https://files.pythonhosted.org/packages/1f/ac/d7906201604a2ea3b143bb0de51b3966f66441ba50b7dc182c4505b3edf9/scipy-1.15.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14eaa373c89eaf553be73c3affb11ec6c37493b7eaaf31cf9ac5dffae700c2e0", size = 27944865 }, + { url = "https://files.pythonhosted.org/packages/84/9d/8f539002b5e203723af6a6f513a45e0a7671e9dabeedb08f417ac17e4edc/scipy-1.15.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f735bc41bd1c792c96bc426dece66c8723283695f02df61dcc4d0a707a42fc54", size = 39883261 }, + { url = "https://files.pythonhosted.org/packages/97/c0/62fd3bab828bcccc9b864c5997645a3b86372a35941cdaf677565c25c98d/scipy-1.15.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2722a021a7929d21168830790202a75dbb20b468a8133c74a2c0230c72626b6c", size = 42093299 }, + { url = "https://files.pythonhosted.org/packages/e4/1f/5d46a8d94e9f6d2c913cbb109e57e7eed914de38ea99e2c4d69a9fc93140/scipy-1.15.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc7136626261ac1ed988dca56cfc4ab5180f75e0ee52e58f1e6aa74b5f3eacd5", size = 43181730 }, +] + +[[package]] +name = "simplejson" +version = "3.19.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/29/085111f19717f865eceaf0d4397bf3e76b08d60428b076b64e2a1903706d/simplejson-3.19.3.tar.gz", hash = "sha256:8e086896c36210ab6050f2f9f095a5f1e03c83fa0e7f296d6cba425411364680", size = 85237 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/24/260ad03435ce8ef2436031951134659c7161776ec3a78094b35b9375ceea/simplejson-3.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:50d8b742d74c449c4dcac570d08ce0f21f6a149d2d9cf7652dbf2ba9a1bc729a", size = 93660 }, + { url = "https://files.pythonhosted.org/packages/63/a1/dee207f357bcd6b106f2ca5129ee916c24993ba08b7dfbf9a37c22442ea9/simplejson-3.19.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd011fc3c1d88b779645495fdb8189fb318a26981eebcce14109460e062f209b", size = 75546 }, + { url = "https://files.pythonhosted.org/packages/80/7b/45ef1da43f54d209ce2ef59b7356cda13f810186c381f38ae23a4d2b1337/simplejson-3.19.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:637c4d4b81825c1f4d651e56210bd35b5604034b192b02d2d8f17f7ce8c18f42", size = 75602 }, + { url = "https://files.pythonhosted.org/packages/7f/4b/9a132382982f8127bc7ce5212a5585d83c174707c9dd698d0cb6a0d41882/simplejson-3.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f56eb03bc9e432bb81adc8ecff2486d39feb371abb442964ffb44f6db23b332", size = 138632 }, + { url = "https://files.pythonhosted.org/packages/76/37/012f5ad2f38afa28f8a6ad9da01dc0b64492ffbaf2a3f2f8a0e1fddf9c1d/simplejson-3.19.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef59a53be400c1fad2c914b8d74c9d42384fed5174f9321dd021b7017fd40270", size = 146740 }, + { url = "https://files.pythonhosted.org/packages/69/b3/89640bd676e26ea2315b5aaf80712a6fbbb4338e4caf872d91448502a19b/simplejson-3.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72e8abbc86fcac83629a030888b45fed3a404d54161118be52cb491cd6975d3e", size = 134440 }, + { url = "https://files.pythonhosted.org/packages/61/20/0035a288deaff05397d6cc0145b33f3dd2429b99cdc880de4c5eca41ca72/simplejson-3.19.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8efb03ca77bd7725dfacc9254df00d73e6f43013cf39bd37ef1a8ed0ebb5165", size = 137949 }, + { url = "https://files.pythonhosted.org/packages/5d/de/5b03fafe3003e32d179588953d38183af6c3747e95c7dcc668c4f9eb886a/simplejson-3.19.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:add8850db04b98507a8b62d248a326ecc8561e6d24336d1ca5c605bbfaab4cad", size = 139992 }, + { url = "https://files.pythonhosted.org/packages/d1/ce/e493116ff49fd215f7baa25195b8f684c91e65c153e2a57e04dc3f3a466b/simplejson-3.19.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fc3dc9fb413fc34c396f52f4c87de18d0bd5023804afa8ab5cc224deeb6a9900", size = 140320 }, + { url = "https://files.pythonhosted.org/packages/86/f3/a18b98a7a27548829f672754dd3940fb637a27981399838128d3e560087f/simplejson-3.19.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4dfa420bb9225dd33b6efdabde7c6a671b51150b9b1d9c4e5cd74d3b420b3fe1", size = 148625 }, + { url = "https://files.pythonhosted.org/packages/0f/55/d3da33ee3e708133da079b9d537693d7fef281e6f0d27921cc7e5b3ec523/simplejson-3.19.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7b5c472099b39b274dcde27f1113db8d818c9aa3ba8f78cbb8ad04a4c1ac2118", size = 141287 }, + { url = "https://files.pythonhosted.org/packages/17/e8/56184ab4d66bb64a6ff569f069b3796dfd943f9b961268fe0d403526fc17/simplejson-3.19.3-cp310-cp310-win32.whl", hash = "sha256:817abad79241ed4a507b3caf4d3f2be5079f39d35d4c550a061988986bffd2ec", size = 74143 }, + { url = "https://files.pythonhosted.org/packages/be/8f/a0089eff060f10a925f08b0a0f50854321484f1ac54b1895bbf4c9213dfe/simplejson-3.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:dd5b9b1783e14803e362a558680d88939e830db2466f3fa22df5c9319f8eea94", size = 75643 }, + { url = "https://files.pythonhosted.org/packages/8c/bb/9ee3959e6929d228cf669b3f13f0edd43c5261b6cd69598640748b19ca35/simplejson-3.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e88abff510dcff903a18d11c2a75f9964e768d99c8d147839913886144b2065e", size = 91930 }, + { url = "https://files.pythonhosted.org/packages/ac/ae/a06523928af3a6783e2638cd4f6035c3e32de1c1063d563d9060c8d2f1ad/simplejson-3.19.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:934a50a614fb831614db5dbfba35127ee277624dda4d15895c957d2f5d48610c", size = 74787 }, + { url = "https://files.pythonhosted.org/packages/c3/58/fea732e48a7540035fe46d39e6fd77679f5810311d31da8661ce7a18210a/simplejson-3.19.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:212fce86a22188b0c7f53533b0f693ea9605c1a0f02c84c475a30616f55a744d", size = 74612 }, + { url = "https://files.pythonhosted.org/packages/ab/4d/15718f20cb0e3875b8af9597d6bb3bfbcf1383834b82b6385ee9ac0b72a9/simplejson-3.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d9e8f836688a8fabe6a6b41b334aa550a6823f7b4ac3d3712fc0ad8655be9a8", size = 143550 }, + { url = "https://files.pythonhosted.org/packages/93/44/815a4343774760f7a82459c8f6a4d8268b4b6d23f81e7b922a5e2ca79171/simplejson-3.19.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23228037dc5d41c36666384062904d74409a62f52283d9858fa12f4c22cffad1", size = 153284 }, + { url = "https://files.pythonhosted.org/packages/9d/52/d3202d9bba95444090d1c98e43da3c10907875babf63ed3c134d1b9437e3/simplejson-3.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0791f64fed7d4abad639491f8a6b1ba56d3c604eb94b50f8697359b92d983f36", size = 141518 }, + { url = "https://files.pythonhosted.org/packages/b7/d4/850948bcbcfe0b4a6c69dfde10e245d3a1ea45252f16a1e2308a3b06b1da/simplejson-3.19.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f614581b61a26fbbba232a1391f6cee82bc26f2abbb6a0b44a9bba25c56a1c", size = 144688 }, + { url = "https://files.pythonhosted.org/packages/58/d2/b8dcb0a07d9cd54c47f9fe8733dbb83891d1efe4fc786d9dfc8781cc04f9/simplejson-3.19.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1df0aaf1cb787fdf34484ed4a1f0c545efd8811f6028623290fef1a53694e597", size = 144534 }, + { url = "https://files.pythonhosted.org/packages/a9/95/1e92d99039041f596e0923ec4f9153244acaf3830944dc69a7c11b23ceaa/simplejson-3.19.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:951095be8d4451a7182403354c22ec2de3e513e0cc40408b689af08d02611588", size = 146565 }, + { url = "https://files.pythonhosted.org/packages/21/04/c96aeb3a74031255e4cbcc0ca1b6ebfb5549902f0a065f06d65ce8447c0c/simplejson-3.19.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a954b30810988feeabde843e3263bf187697e0eb5037396276db3612434049b", size = 155014 }, + { url = "https://files.pythonhosted.org/packages/b7/41/e28a28593afc4a75d8999d057bfb7c73a103e35f927e66f4bb92571787ae/simplejson-3.19.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c40df31a75de98db2cdfead6074d4449cd009e79f54c1ebe5e5f1f153c68ad20", size = 148092 }, + { url = "https://files.pythonhosted.org/packages/2b/82/1c81a3af06f937afb6d2e9d74a465c0e0ae6db444d1bf2a436ea26de1965/simplejson-3.19.3-cp311-cp311-win32.whl", hash = "sha256:7e2a098c21ad8924076a12b6c178965d88a0ad75d1de67e1afa0a66878f277a5", size = 73942 }, + { url = "https://files.pythonhosted.org/packages/65/be/d8ab9717f471be3c114f16abd8be21d9a6a0a09b9b49177d93d64d3717d9/simplejson-3.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:c9bedebdc5fdad48af8783022bae307746d54006b783007d1d3c38e10872a2c6", size = 75469 }, + { url = "https://files.pythonhosted.org/packages/20/15/513fea93fafbdd4993eacfcb762965b2ff3d29e618c029e2956174d68c4b/simplejson-3.19.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:66a0399e21c2112acacfebf3d832ebe2884f823b1c7e6d1363f2944f1db31a99", size = 92921 }, + { url = "https://files.pythonhosted.org/packages/a4/4f/998a907ae1a6c104dc0ee48aa248c2478490152808d34d8e07af57f396c3/simplejson-3.19.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6ef9383c5e05f445be60f1735c1816163c874c0b1ede8bb4390aff2ced34f333", size = 75311 }, + { url = "https://files.pythonhosted.org/packages/db/44/acd6122201e927451869d45952b9ab1d3025cdb5e61548d286d08fbccc08/simplejson-3.19.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:42e5acf80d4d971238d4df97811286a044d720693092b20a56d5e56b7dcc5d09", size = 74964 }, + { url = "https://files.pythonhosted.org/packages/27/ca/d0a1e8f16e1bbdc0b8c6d88166f45f565ed7285f53928cfef3b6ce78f14d/simplejson-3.19.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0b0efc7279d768db7c74d3d07f0b5c81280d16ae3fb14e9081dc903e8360771", size = 150106 }, + { url = "https://files.pythonhosted.org/packages/63/59/0554b78cf26c98e2b9cae3f44723bd72c2394e2afec1a14eedc6211f7187/simplejson-3.19.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0552eb06e7234da892e1d02365cd2b7b2b1f8233aa5aabdb2981587b7cc92ea0", size = 158347 }, + { url = "https://files.pythonhosted.org/packages/b2/fe/9f30890352e431e8508cc569912d3322147d3e7e4f321e48c0adfcb4c97d/simplejson-3.19.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf6a3b9a7d7191471b464fe38f684df10eb491ec9ea454003edb45a011ab187", size = 148456 }, + { url = "https://files.pythonhosted.org/packages/37/e3/663a09542ee021d4131162f7a164cb2e7f04ef48433a67591738afbf12ea/simplejson-3.19.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7017329ca8d4dca94ad5e59f496e5fc77630aecfc39df381ffc1d37fb6b25832", size = 152190 }, + { url = "https://files.pythonhosted.org/packages/31/20/4e0c4d35e10ff6465003bec304316d822a559a1c38c66ef6892ca199c207/simplejson-3.19.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:67a20641afebf4cfbcff50061f07daad1eace6e7b31d7622b6fa2c40d43900ba", size = 149846 }, + { url = "https://files.pythonhosted.org/packages/08/7a/46e2e072cac3987cbb05946f25167f0ad2fe536748e7405953fd6661a486/simplejson-3.19.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:dd6a7dabcc4c32daf601bc45e01b79175dde4b52548becea4f9545b0a4428169", size = 151714 }, + { url = "https://files.pythonhosted.org/packages/7f/7d/dbeeac10eb61d5d8858d0bb51121a21050d281dc83af4c557f86da28746c/simplejson-3.19.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:08f9b443a94e72dd02c87098c96886d35790e79e46b24e67accafbf13b73d43b", size = 158777 }, + { url = "https://files.pythonhosted.org/packages/fc/8f/a98bdbb799c6a4a884b5823db31785a96ba895b4b0f4d8ac345d6fe98bbf/simplejson-3.19.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa97278ae6614346b5ca41a45a911f37a3261b57dbe4a00602048652c862c28b", size = 154230 }, + { url = "https://files.pythonhosted.org/packages/b1/db/852eebceb85f969ae40e06babed1a93d3bacb536f187d7a80ff5823a5979/simplejson-3.19.3-cp312-cp312-win32.whl", hash = "sha256:ef28c3b328d29b5e2756903aed888960bc5df39b4c2eab157ae212f70ed5bf74", size = 74002 }, + { url = "https://files.pythonhosted.org/packages/fe/68/9f0e5df0651cb79ef83cba1378765a00ee8038e6201cc82b8e7178a7778e/simplejson-3.19.3-cp312-cp312-win_amd64.whl", hash = "sha256:1e662336db50ad665777e6548b5076329a94a0c3d4a0472971c588b3ef27de3a", size = 75596 }, + { url = "https://files.pythonhosted.org/packages/93/3a/5896821ed543899fcb9c4256c7e71bb110048047349a00f42bc8b8fb379f/simplejson-3.19.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0959e6cb62e3994b5a40e31047ff97ef5c4138875fae31659bead691bed55896", size = 92931 }, + { url = "https://files.pythonhosted.org/packages/39/15/5d33d269440912ee40d856db0c8be2b91aba7a219690ab01f86cb0edd590/simplejson-3.19.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a7bfad839c624e139a4863007233a3f194e7c51551081f9789cba52e4da5167", size = 75318 }, + { url = "https://files.pythonhosted.org/packages/2a/8d/2e7483a2bf7ec53acf7e012bafbda79d7b34f90471dda8e424544a59d484/simplejson-3.19.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afab2f7f2486a866ff04d6d905e9386ca6a231379181a3838abce1f32fbdcc37", size = 74971 }, + { url = "https://files.pythonhosted.org/packages/4d/9d/9bdf34437c8834a7cf7246f85e9d5122e30579f512c10a0c2560e994294f/simplejson-3.19.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00313681015ac498e1736b304446ee6d1c72c5b287cd196996dad84369998f7", size = 150112 }, + { url = "https://files.pythonhosted.org/packages/a7/e2/1f2ae2d89eaf85f6163c82150180aae5eaa18085cfaf892f8a57d4c51cbd/simplejson-3.19.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d936ae682d5b878af9d9eb4d8bb1fdd5e41275c8eb59ceddb0aeed857bb264a2", size = 158354 }, + { url = "https://files.pythonhosted.org/packages/60/83/26f610adf234c8492b3f30501e12f2271e67790f946c6898fe0c58aefe99/simplejson-3.19.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c6657485393f2e9b8177c77a7634f13ebe70d5e6de150aae1677d91516ce6b", size = 148455 }, + { url = "https://files.pythonhosted.org/packages/b5/4b/109af50006af77133653c55b5b91b4bd2d579ff8254ce11216c0b75f911b/simplejson-3.19.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a6a750d3c7461b1c47cfc6bba8d9e57a455e7c5f80057d2a82f738040dd1129", size = 152191 }, + { url = "https://files.pythonhosted.org/packages/75/dc/108872a8825cbd99ae6f4334e0490ff1580367baf12198bcaf988f6820ba/simplejson-3.19.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ea7a4a998c87c5674a27089e022110a1a08a7753f21af3baf09efe9915c23c3c", size = 149954 }, + { url = "https://files.pythonhosted.org/packages/eb/be/deec1d947a5d0472276ab4a4d1a9378dc5ee27f3dc9e54d4f62ffbad7a08/simplejson-3.19.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6300680d83a399be2b8f3b0ef7ef90b35d2a29fe6e9c21438097e0938bbc1564", size = 151812 }, + { url = "https://files.pythonhosted.org/packages/e9/58/4ee130702d36b1551ef66e7587eefe56651f3669255bf748cd71691e2434/simplejson-3.19.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ab69f811a660c362651ae395eba8ce84f84c944cea0df5718ea0ba9d1e4e7252", size = 158880 }, + { url = "https://files.pythonhosted.org/packages/0f/e1/59cc6a371b60f89e3498d9f4c8109f6b7359094d453f5fe80b2677b777b0/simplejson-3.19.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:256e09d0f94d9c3d177d9e95fd27a68c875a4baa2046633df387b86b652f5747", size = 154344 }, + { url = "https://files.pythonhosted.org/packages/79/45/1b36044670016f5cb25ebd92497427d2d1711ecb454d00f71eb9a00b77cc/simplejson-3.19.3-cp313-cp313-win32.whl", hash = "sha256:2c78293470313aefa9cfc5e3f75ca0635721fb016fb1121c1c5b0cb8cc74712a", size = 74002 }, + { url = "https://files.pythonhosted.org/packages/e2/58/b06226e6b0612f2b1fa13d5273551da259f894566b1eef32249ddfdcce44/simplejson-3.19.3-cp313-cp313-win_amd64.whl", hash = "sha256:3bbcdc438dc1683b35f7a8dc100960c721f922f9ede8127f63bed7dfded4c64c", size = 75599 }, + { url = "https://files.pythonhosted.org/packages/0d/e7/f9fafbd4f39793a20cc52e77bbd766f7384312526d402c382928dc7667f6/simplejson-3.19.3-py3-none-any.whl", hash = "sha256:49cc4c7b940d43bd12bf87ec63f28cbc4964fc4e12c031cc8cd01650f43eb94e", size = 57004 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.38" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/08/9a90962ea72acd532bda71249a626344d855c4032603924b1b547694b837/sqlalchemy-2.0.38.tar.gz", hash = "sha256:e5a4d82bdb4bf1ac1285a68eab02d253ab73355d9f0fe725a97e1e0fa689decb", size = 9634782 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/10/16ed1503e18c0ec4e17a1819ff44604368607eed3db1e1d89d33269fe5b9/SQLAlchemy-2.0.38-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5e1d9e429028ce04f187a9f522818386c8b076723cdbe9345708384f49ebcec6", size = 2105151 }, + { url = "https://files.pythonhosted.org/packages/79/e5/2e9a0807cba2e625204d04bc39a18a47478e4bacae353ae8a7f2e784c341/SQLAlchemy-2.0.38-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b87a90f14c68c925817423b0424381f0e16d80fc9a1a1046ef202ab25b19a444", size = 2096335 }, + { url = "https://files.pythonhosted.org/packages/c1/97/8fa5cc6ed994eab611dcf0bc431161308f297c6f896f02a3ebb8d8aa06ea/SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:402c2316d95ed90d3d3c25ad0390afa52f4d2c56b348f212aa9c8d072a40eee5", size = 3078705 }, + { url = "https://files.pythonhosted.org/packages/a9/99/505feb8a9bc7027addaa2b312b8b306319cacbbd8a5231c4123ca1fa082a/SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6493bc0eacdbb2c0f0d260d8988e943fee06089cd239bd7f3d0c45d1657a70e2", size = 3086958 }, + { url = "https://files.pythonhosted.org/packages/39/26/fb7cef8198bb2627ac527b2cf6c576588db09856d634d4f1017280f8ab64/SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0561832b04c6071bac3aad45b0d3bb6d2c4f46a8409f0a7a9c9fa6673b41bc03", size = 3042798 }, + { url = "https://files.pythonhosted.org/packages/cc/7c/b6f9e0ee4e8e993fdce42477f9290b2b8373e672fb1dc0272179f0aeafb4/SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:49aa2cdd1e88adb1617c672a09bf4ebf2f05c9448c6dbeba096a3aeeb9d4d443", size = 3068318 }, + { url = "https://files.pythonhosted.org/packages/e6/22/903497e8202960c4249ffc340ec8de63f7fbdd4856bdfe854f617e124e90/SQLAlchemy-2.0.38-cp310-cp310-win32.whl", hash = "sha256:64aa8934200e222f72fcfd82ee71c0130a9c07d5725af6fe6e919017d095b297", size = 2077434 }, + { url = "https://files.pythonhosted.org/packages/20/a8/08f6ceccff5e0abb4a22e2e91c44b0e39911fda06b5d0c905dfc642de57a/SQLAlchemy-2.0.38-cp310-cp310-win_amd64.whl", hash = "sha256:c57b8e0841f3fce7b703530ed70c7c36269c6d180ea2e02e36b34cb7288c50c7", size = 2101608 }, + { url = "https://files.pythonhosted.org/packages/00/6c/9d3a638f297fce288ba12a4e5dbd08ef1841d119abee9300c100eba00217/SQLAlchemy-2.0.38-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf89e0e4a30714b357f5d46b6f20e0099d38b30d45fa68ea48589faf5f12f62d", size = 2106330 }, + { url = "https://files.pythonhosted.org/packages/0e/57/d5fdee56f418491267701965795805662b1744de40915d4764451390536d/SQLAlchemy-2.0.38-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8455aa60da49cb112df62b4721bd8ad3654a3a02b9452c783e651637a1f21fa2", size = 2096730 }, + { url = "https://files.pythonhosted.org/packages/42/84/205f423f8b28329c47237b7e130a7f93c234a49fab20b4534bd1ff26a06a/SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53c0d6a859b2db58332e0e6a921582a02c1677cc93d4cbb36fdf49709b327b2", size = 3215023 }, + { url = "https://files.pythonhosted.org/packages/77/41/94a558d47bffae5a361b0cfb3721324ea4154829dd5432f80bd4cfeecbc9/SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c4817dff8cef5697f5afe5fec6bc1783994d55a68391be24cb7d80d2dbc3a6", size = 3214991 }, + { url = "https://files.pythonhosted.org/packages/74/a0/cc3c030e7440bd17ce67c1875f50edb41d0ef17b9c76fbc290ef27bbe37f/SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9cea5b756173bb86e2235f2f871b406a9b9d722417ae31e5391ccaef5348f2c", size = 3151854 }, + { url = "https://files.pythonhosted.org/packages/24/ab/8ba2588c2eb1d092944551354d775ef4fc0250badede324d786a4395d10e/SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40e9cdbd18c1f84631312b64993f7d755d85a3930252f6276a77432a2b25a2f3", size = 3172158 }, + { url = "https://files.pythonhosted.org/packages/e0/73/2a3d6217e8e6abb553ed410ce5adc0bdec7effd684716f0fbaee5831d677/SQLAlchemy-2.0.38-cp311-cp311-win32.whl", hash = "sha256:cb39ed598aaf102251483f3e4675c5dd6b289c8142210ef76ba24aae0a8f8aba", size = 2076965 }, + { url = "https://files.pythonhosted.org/packages/a4/17/364a99c8c5698492c7fa40fc463bf388f05b0b03b74028828b71a79dc89d/SQLAlchemy-2.0.38-cp311-cp311-win_amd64.whl", hash = "sha256:f9d57f1b3061b3e21476b0ad5f0397b112b94ace21d1f439f2db472e568178ae", size = 2102169 }, + { url = "https://files.pythonhosted.org/packages/5a/f8/6d0424af1442c989b655a7b5f608bc2ae5e4f94cdf6df9f6054f629dc587/SQLAlchemy-2.0.38-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12d5b06a1f3aeccf295a5843c86835033797fea292c60e72b07bcb5d820e6dd3", size = 2104927 }, + { url = "https://files.pythonhosted.org/packages/25/80/fc06e65fca0a19533e2bfab633a5633ed8b6ee0b9c8d580acf84609ce4da/SQLAlchemy-2.0.38-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e036549ad14f2b414c725349cce0772ea34a7ab008e9cd67f9084e4f371d1f32", size = 2095317 }, + { url = "https://files.pythonhosted.org/packages/98/2d/5d66605f76b8e344813237dc160a01f03b987201e974b46056a7fb94a874/SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee3bee874cb1fadee2ff2b79fc9fc808aa638670f28b2145074538d4a6a5028e", size = 3244735 }, + { url = "https://files.pythonhosted.org/packages/73/8d/b0539e8dce90861efc38fea3eefb15a5d0cfeacf818614762e77a9f192f9/SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e185ea07a99ce8b8edfc788c586c538c4b1351007e614ceb708fd01b095ef33e", size = 3255581 }, + { url = "https://files.pythonhosted.org/packages/ac/a5/94e1e44bf5bdffd1782807fcc072542b110b950f0be53f49e68b5f5eca1b/SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b79ee64d01d05a5476d5cceb3c27b5535e6bb84ee0f872ba60d9a8cd4d0e6579", size = 3190877 }, + { url = "https://files.pythonhosted.org/packages/91/13/f08b09996dce945aec029c64f61c13b4788541ac588d9288e31e0d3d8850/SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afd776cf1ebfc7f9aa42a09cf19feadb40a26366802d86c1fba080d8e5e74bdd", size = 3217485 }, + { url = "https://files.pythonhosted.org/packages/13/8f/8cfe2ba5ba6d8090f4de0e658330c53be6b7bf430a8df1b141c2b180dcdf/SQLAlchemy-2.0.38-cp312-cp312-win32.whl", hash = "sha256:a5645cd45f56895cfe3ca3459aed9ff2d3f9aaa29ff7edf557fa7a23515a3725", size = 2075254 }, + { url = "https://files.pythonhosted.org/packages/c2/5c/e3c77fae41862be1da966ca98eec7fbc07cdd0b00f8b3e1ef2a13eaa6cca/SQLAlchemy-2.0.38-cp312-cp312-win_amd64.whl", hash = "sha256:1052723e6cd95312f6a6eff9a279fd41bbae67633415373fdac3c430eca3425d", size = 2100865 }, + { url = "https://files.pythonhosted.org/packages/21/77/caa875a1f5a8a8980b564cc0e6fee1bc992d62d29101252561d0a5e9719c/SQLAlchemy-2.0.38-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ecef029b69843b82048c5b347d8e6049356aa24ed644006c9a9d7098c3bd3bfd", size = 2100201 }, + { url = "https://files.pythonhosted.org/packages/f4/ec/94bb036ec78bf9a20f8010c807105da9152dd84f72e8c51681ad2f30b3fd/SQLAlchemy-2.0.38-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c8bcad7fc12f0cc5896d8e10fdf703c45bd487294a986903fe032c72201596b", size = 2090678 }, + { url = "https://files.pythonhosted.org/packages/7b/61/63ff1893f146e34d3934c0860209fdd3925c25ee064330e6c2152bacc335/SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0ef3f98175d77180ffdc623d38e9f1736e8d86b6ba70bff182a7e68bed7727", size = 3177107 }, + { url = "https://files.pythonhosted.org/packages/a9/4f/b933bea41a602b5f274065cc824fae25780ed38664d735575192490a021b/SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ac78898c50e2574e9f938d2e5caa8fe187d7a5b69b65faa1ea4648925b096", size = 3190435 }, + { url = "https://files.pythonhosted.org/packages/f5/23/9e654b4059e385988de08c5d3b38a369ea042f4c4d7c8902376fd737096a/SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9eb4fa13c8c7a2404b6a8e3772c17a55b1ba18bc711e25e4d6c0c9f5f541b02a", size = 3123648 }, + { url = "https://files.pythonhosted.org/packages/83/59/94c6d804e76ebc6412a08d2b086a8cb3e5a056cd61508e18ddaf3ec70100/SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5dba1cdb8f319084f5b00d41207b2079822aa8d6a4667c0f369fce85e34b0c86", size = 3151789 }, + { url = "https://files.pythonhosted.org/packages/b2/27/17f143013aabbe1256dce19061eafdce0b0142465ce32168cdb9a18c04b1/SQLAlchemy-2.0.38-cp313-cp313-win32.whl", hash = "sha256:eae27ad7580529a427cfdd52c87abb2dfb15ce2b7a3e0fc29fbb63e2ed6f8120", size = 2073023 }, + { url = "https://files.pythonhosted.org/packages/e2/3e/259404b03c3ed2e7eee4c179e001a07d9b61070334be91124cf4ad32eec7/SQLAlchemy-2.0.38-cp313-cp313-win_amd64.whl", hash = "sha256:b335a7c958bc945e10c522c069cd6e5804f4ff20f9a744dd38e748eb602cbbda", size = 2096908 }, + { url = "https://files.pythonhosted.org/packages/aa/e4/592120713a314621c692211eba034d09becaf6bc8848fabc1dc2a54d8c16/SQLAlchemy-2.0.38-py3-none-any.whl", hash = "sha256:63178c675d4c80def39f1febd625a6333f44c0ba269edd8a468b156394b27753", size = 1896347 }, +] + +[[package]] +name = "threadpoolctl" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/55/b5148dcbf72f5cde221f8bfe3b6a540da7aa1842f6b491ad979a6c8b84af/threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107", size = 41936 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/2c/ffbf7a134b9ab11a67b0cf0726453cedd9c5043a4fe7a35d1cefa9a1bcfb/threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467", size = 18414 }, +] + +[[package]] +name = "tokenizers" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/41/c2be10975ca37f6ec40d7abd7e98a5213bb04f284b869c1a24e6504fd94d/tokenizers-0.21.0.tar.gz", hash = "sha256:ee0894bf311b75b0c03079f33859ae4b2334d675d4e93f5a4132e1eae2834fe4", size = 343021 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/5c/8b09607b37e996dc47e70d6a7b6f4bdd4e4d5ab22fe49d7374565c7fefaf/tokenizers-0.21.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3c4c93eae637e7d2aaae3d376f06085164e1660f89304c0ab2b1d08a406636b2", size = 2647461 }, + { url = "https://files.pythonhosted.org/packages/22/7a/88e58bb297c22633ed1c9d16029316e5b5ac5ee44012164c2edede599a5e/tokenizers-0.21.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:f53ea537c925422a2e0e92a24cce96f6bc5046bbef24a1652a5edc8ba975f62e", size = 2563639 }, + { url = "https://files.pythonhosted.org/packages/f7/14/83429177c19364df27d22bc096d4c2e431e0ba43e56c525434f1f9b0fd00/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b177fb54c4702ef611de0c069d9169f0004233890e0c4c5bd5508ae05abf193", size = 2903304 }, + { url = "https://files.pythonhosted.org/packages/7e/db/3433eab42347e0dc5452d8fcc8da03f638c9accffefe5a7c78146666964a/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b43779a269f4629bebb114e19c3fca0223296ae9fea8bb9a7a6c6fb0657ff8e", size = 2804378 }, + { url = "https://files.pythonhosted.org/packages/57/8b/7da5e6f89736c2ade02816b4733983fca1c226b0c42980b1ae9dc8fcf5cc/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aeb255802be90acfd363626753fda0064a8df06031012fe7d52fd9a905eb00e", size = 3095488 }, + { url = "https://files.pythonhosted.org/packages/4d/f6/5ed6711093dc2c04a4e03f6461798b12669bc5a17c8be7cce1240e0b5ce8/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b09dbeb7a8d73ee204a70f94fc06ea0f17dcf0844f16102b9f414f0b7463ba", size = 3121410 }, + { url = "https://files.pythonhosted.org/packages/81/42/07600892d48950c5e80505b81411044a2d969368cdc0d929b1c847bf6697/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:400832c0904f77ce87c40f1a8a27493071282f785724ae62144324f171377273", size = 3388821 }, + { url = "https://files.pythonhosted.org/packages/22/06/69d7ce374747edaf1695a4f61b83570d91cc8bbfc51ccfecf76f56ab4aac/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84ca973b3a96894d1707e189c14a774b701596d579ffc7e69debfc036a61a04", size = 3008868 }, + { url = "https://files.pythonhosted.org/packages/c8/69/54a0aee4d576045b49a0eb8bffdc495634309c823bf886042e6f46b80058/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eb7202d231b273c34ec67767378cd04c767e967fda12d4a9e36208a34e2f137e", size = 8975831 }, + { url = "https://files.pythonhosted.org/packages/f7/f3/b776061e4f3ebf2905ba1a25d90380aafd10c02d406437a8ba22d1724d76/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:089d56db6782a73a27fd8abf3ba21779f5b85d4a9f35e3b493c7bbcbbf0d539b", size = 8920746 }, + { url = "https://files.pythonhosted.org/packages/d8/ee/ce83d5ec8b6844ad4c3ecfe3333d58ecc1adc61f0878b323a15355bcab24/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:c87ca3dc48b9b1222d984b6b7490355a6fdb411a2d810f6f05977258400ddb74", size = 9161814 }, + { url = "https://files.pythonhosted.org/packages/18/07/3e88e65c0ed28fa93aa0c4d264988428eef3df2764c3126dc83e243cb36f/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4145505a973116f91bc3ac45988a92e618a6f83eb458f49ea0790df94ee243ff", size = 9357138 }, + { url = "https://files.pythonhosted.org/packages/15/b0/dc4572ca61555fc482ebc933f26cb407c6aceb3dc19c301c68184f8cad03/tokenizers-0.21.0-cp39-abi3-win32.whl", hash = "sha256:eb1702c2f27d25d9dd5b389cc1f2f51813e99f8ca30d9e25348db6585a97e24a", size = 2202266 }, + { url = "https://files.pythonhosted.org/packages/44/69/d21eb253fa91622da25585d362a874fa4710be600f0ea9446d8d0217cec1/tokenizers-0.21.0-cp39-abi3-win_amd64.whl", hash = "sha256:87841da5a25a3a5f70c102de371db120f41873b854ba65e52bccd57df5a3780c", size = 2389192 }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, +] + +[[package]] +name = "tree-sitter" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/a2/698b9d31d08ad5558f8bfbfe3a0781bd4b1f284e89bde3ad18e05101a892/tree-sitter-0.24.0.tar.gz", hash = "sha256:abd95af65ca2f4f7eca356343391ed669e764f37748b5352946f00f7fc78e734", size = 168304 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/9a/bd627a02e41671af73222316e1fcf87772c7804dc2fba99405275eb1f3eb/tree_sitter-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f3f00feff1fc47a8e4863561b8da8f5e023d382dd31ed3e43cd11d4cae445445", size = 140890 }, + { url = "https://files.pythonhosted.org/packages/5b/9b/b1ccfb187f8be78e2116176a091a2f2abfd043a06d78f80c97c97f315b37/tree_sitter-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f9691be48d98c49ef8f498460278884c666b44129222ed6217477dffad5d4831", size = 134413 }, + { url = "https://files.pythonhosted.org/packages/01/39/e25b0042a049eb27e991133a7aa7c49bb8e49a8a7b44ca34e7e6353ba7ac/tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:098a81df9f89cf254d92c1cd0660a838593f85d7505b28249216661d87adde4a", size = 560427 }, + { url = "https://files.pythonhosted.org/packages/1c/59/4d132f1388da5242151b90acf32cc56af779bfba063923699ab28b276b62/tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b26bf9e958da6eb7e74a081aab9d9c7d05f9baeaa830dbb67481898fd16f1f5", size = 574327 }, + { url = "https://files.pythonhosted.org/packages/ec/97/3914e45ab9e0ff0f157e493caa91791372508488b97ff0961a0640a37d25/tree_sitter-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2a84ff87a2f2a008867a1064aba510ab3bd608e3e0cd6e8fef0379efee266c73", size = 577171 }, + { url = "https://files.pythonhosted.org/packages/c5/b0/266a529c3eef171137b73cde8ad7aa282734354609a8b2f5564428e8f12d/tree_sitter-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c012e4c345c57a95d92ab5a890c637aaa51ab3b7ff25ed7069834b1087361c95", size = 120260 }, + { url = "https://files.pythonhosted.org/packages/c1/c3/07bfaa345e0037ff75d98b7a643cf940146e4092a1fd54eed0359836be03/tree_sitter-0.24.0-cp310-cp310-win_arm64.whl", hash = "sha256:033506c1bc2ba7bd559b23a6bdbeaf1127cee3c68a094b82396718596dfe98bc", size = 108416 }, + { url = "https://files.pythonhosted.org/packages/66/08/82aaf7cbea7286ee2a0b43e9b75cb93ac6ac132991b7d3c26ebe5e5235a3/tree_sitter-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de0fb7c18c6068cacff46250c0a0473e8fc74d673e3e86555f131c2c1346fb13", size = 140733 }, + { url = "https://files.pythonhosted.org/packages/8c/bd/1a84574911c40734d80327495e6e218e8f17ef318dd62bb66b55c1e969f5/tree_sitter-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7c9c89666dea2ce2b2bf98e75f429d2876c569fab966afefdcd71974c6d8538", size = 134243 }, + { url = "https://files.pythonhosted.org/packages/46/c1/c2037af2c44996d7bde84eb1c9e42308cc84b547dd6da7f8a8bea33007e1/tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddb113e6b8b3e3b199695b1492a47d87d06c538e63050823d90ef13cac585fd", size = 562030 }, + { url = "https://files.pythonhosted.org/packages/4c/aa/2fb4d81886df958e6ec7e370895f7106d46d0bbdcc531768326124dc8972/tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ea01a7003b88b92f7f875da6ba9d5d741e0c84bb1bd92c503c0eecd0ee6409", size = 575585 }, + { url = "https://files.pythonhosted.org/packages/e3/3c/5f997ce34c0d1b744e0f0c0757113bdfc173a2e3dadda92c751685cfcbd1/tree_sitter-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:464fa5b2cac63608915a9de8a6efd67a4da1929e603ea86abaeae2cb1fe89921", size = 578203 }, + { url = "https://files.pythonhosted.org/packages/d5/1f/f2bc7fa7c3081653ea4f2639e06ff0af4616c47105dbcc0746137da7620d/tree_sitter-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b1f3cbd9700e1fba0be2e7d801527e37c49fc02dc140714669144ef6ab58dce", size = 120147 }, + { url = "https://files.pythonhosted.org/packages/c0/4c/9add771772c4d72a328e656367ca948e389432548696a3819b69cdd6f41e/tree_sitter-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:f3f08a2ca9f600b3758792ba2406971665ffbad810847398d180c48cee174ee2", size = 108302 }, + { url = "https://files.pythonhosted.org/packages/e9/57/3a590f287b5aa60c07d5545953912be3d252481bf5e178f750db75572bff/tree_sitter-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14beeff5f11e223c37be7d5d119819880601a80d0399abe8c738ae2288804afc", size = 140788 }, + { url = "https://files.pythonhosted.org/packages/61/0b/fc289e0cba7dbe77c6655a4dd949cd23c663fd62a8b4d8f02f97e28d7fe5/tree_sitter-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26a5b130f70d5925d67b47db314da209063664585a2fd36fa69e0717738efaf4", size = 133945 }, + { url = "https://files.pythonhosted.org/packages/86/d7/80767238308a137e0b5b5c947aa243e3c1e3e430e6d0d5ae94b9a9ffd1a2/tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fc5c3c26d83c9d0ecb4fc4304fba35f034b7761d35286b936c1db1217558b4e", size = 564819 }, + { url = "https://files.pythonhosted.org/packages/bf/b3/6c5574f4b937b836601f5fb556b24804b0a6341f2eb42f40c0e6464339f4/tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:772e1bd8c0931c866b848d0369b32218ac97c24b04790ec4b0e409901945dd8e", size = 579303 }, + { url = "https://files.pythonhosted.org/packages/0a/f4/bd0ddf9abe242ea67cca18a64810f8af230fc1ea74b28bb702e838ccd874/tree_sitter-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:24a8dd03b0d6b8812425f3b84d2f4763322684e38baf74e5bb766128b5633dc7", size = 581054 }, + { url = "https://files.pythonhosted.org/packages/8c/1c/ff23fa4931b6ef1bbeac461b904ca7e49eaec7e7e5398584e3eef836ec96/tree_sitter-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9e8b1605ab60ed43803100f067eed71b0b0e6c1fb9860a262727dbfbbb74751", size = 120221 }, + { url = "https://files.pythonhosted.org/packages/b2/2a/9979c626f303177b7612a802237d0533155bf1e425ff6f73cc40f25453e2/tree_sitter-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:f733a83d8355fc95561582b66bbea92ffd365c5d7a665bc9ebd25e049c2b2abb", size = 108234 }, + { url = "https://files.pythonhosted.org/packages/61/cd/2348339c85803330ce38cee1c6cbbfa78a656b34ff58606ebaf5c9e83bd0/tree_sitter-0.24.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d4a6416ed421c4210f0ca405a4834d5ccfbb8ad6692d4d74f7773ef68f92071", size = 140781 }, + { url = "https://files.pythonhosted.org/packages/8b/a3/1ea9d8b64e8dcfcc0051028a9c84a630301290995cd6e947bf88267ef7b1/tree_sitter-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0992d483677e71d5c5d37f30dfb2e3afec2f932a9c53eec4fca13869b788c6c", size = 133928 }, + { url = "https://files.pythonhosted.org/packages/fe/ae/55c1055609c9428a4aedf4b164400ab9adb0b1bf1538b51f4b3748a6c983/tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57277a12fbcefb1c8b206186068d456c600dbfbc3fd6c76968ee22614c5cd5ad", size = 564497 }, + { url = "https://files.pythonhosted.org/packages/ce/d0/f2ffcd04882c5aa28d205a787353130cbf84b2b8a977fd211bdc3b399ae3/tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25fa22766d63f73716c6fec1a31ee5cf904aa429484256bd5fdf5259051ed74", size = 578917 }, + { url = "https://files.pythonhosted.org/packages/af/82/aebe78ea23a2b3a79324993d4915f3093ad1af43d7c2208ee90be9273273/tree_sitter-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d5d9537507e1c8c5fa9935b34f320bfec4114d675e028f3ad94f11cf9db37b9", size = 581148 }, + { url = "https://files.pythonhosted.org/packages/a1/b4/6b0291a590c2b0417cfdb64ccb8ea242f270a46ed429c641fbc2bfab77e0/tree_sitter-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:f58bb4956917715ec4d5a28681829a8dad5c342cafd4aea269f9132a83ca9b34", size = 120207 }, + { url = "https://files.pythonhosted.org/packages/a8/18/542fd844b75272630229c9939b03f7db232c71a9d82aadc59c596319ea6a/tree_sitter-0.24.0-cp313-cp313-win_arm64.whl", hash = "sha256:23641bd25dcd4bb0b6fa91b8fb3f46cc9f1c9f475efe4d536d3f1f688d1b84c8", size = 108232 }, +] + +[[package]] +name = "tree-sitter-solidity" +version = "1.2.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/a7/79cac34bc3edaf21e6eac066a7f46e39db51bca57890c9b1fbac5b9c70e5/tree_sitter_solidity-1.2.11.tar.gz", hash = "sha256:1dbc0ec400c23a56af4d8441a6f0da0593433e875e963319cb0cbd19aec01b98", size = 144334 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/0e/c819d542d58a02e7b8030d016db0f8b26be21c78e9626aeab4472b290c0a/tree_sitter_solidity-1.2.11-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0f8fa2a304299620b35df00ed56fcdcb25211c87b6e3cf35a8e7a80102729ee3", size = 55584 }, + { url = "https://files.pythonhosted.org/packages/e3/89/28a819ee9b8a8596b09701a8f07819508419994b88ef9cbb75bd24c97b24/tree_sitter_solidity-1.2.11-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:46dfc6a989d3b42efde1f5cd141e41495e9f89b480dcad01b7719f875dd31e7e", size = 57902 }, + { url = "https://files.pythonhosted.org/packages/28/5b/e5d1cad769756b9daeabd4f05ff30a1d18b6679ae4403f85a0e79a35b327/tree_sitter_solidity-1.2.11-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0478d2311fe792cb5a926859209f2042b6e108778505692c2654aaf35c61ff59", size = 90232 }, + { url = "https://files.pythonhosted.org/packages/80/cf/94ba0f07b260da6b4616b558d4f0a2351d08b1d11023707711b14c8d1188/tree_sitter_solidity-1.2.11-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ce8e726808dfef3aba452a6abd9f476a252555296149bd6b6bc79b9e3f271a0", size = 88092 }, + { url = "https://files.pythonhosted.org/packages/e9/1c/52f07e9561ad09afd29efa05a5b1ac4bb86142b28093da62a8ed84c94661/tree_sitter_solidity-1.2.11-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:83f763768dce265a83e9ac068fa09d0e9b0ab705b8b6e321560ef9f7b174c393", size = 86820 }, + { url = "https://files.pythonhosted.org/packages/c6/4a/49999d394ed2216c753c5c617098c79fb059caac1f9eef1c08c708945fc8/tree_sitter_solidity-1.2.11-cp38-abi3-win_amd64.whl", hash = "sha256:b3c831b1e425bcd3131310ac307dd2cdc4f71b806a4e39eaae5f29c24b0ef02d", size = 64010 }, + { url = "https://files.pythonhosted.org/packages/14/53/58658502ab5bcc14830d65b01501628dfa6d33fdffbbc492e39fd720c0f2/tree_sitter_solidity-1.2.11-cp38-abi3-win_arm64.whl", hash = "sha256:20b0714fba7b09f882b9a5512b6fb6845ae2406ed53c173e1459a1f802cd7bb1", size = 57726 }, +] + +[[package]] +name = "types-requests" +version = "2.32.0.20241016" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/3c/4f2a430c01a22abd49a583b6b944173e39e7d01b688190a5618bd59a2e22/types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95", size = 18065 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/01/485b3026ff90e5190b5e24f1711522e06c79f4a56c8f4b95848ac072e20f/types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747", size = 15836 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 }, +] + +[[package]] +name = "tzdata" +version = "2025.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/0f/fa4723f22942480be4ca9527bbde8d43f6c3f2fe8412f00e7f5f6746bc8b/tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694", size = 194950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762 }, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] From 658f884787e76c5ab4092133400affa1dd380c29 Mon Sep 17 00:00:00 2001 From: bradmoonuestc Date: Tue, 18 Feb 2025 02:54:56 +0800 Subject: [PATCH 04/10] add philosophy --- liquidity_pool.move | 2072 ----------------- philosophy_of_monkey.md | 121 + pics/image.png | Bin 0 -> 373486 bytes ...5\220\215\346\226\207\344\273\266(48).png" | Bin 0 -> 43906 bytes prompt.txt | 113 - 5 files changed, 121 insertions(+), 2185 deletions(-) delete mode 100644 liquidity_pool.move create mode 100644 philosophy_of_monkey.md create mode 100644 pics/image.png create mode 100644 "pics/\346\234\252\345\221\275\345\220\215\346\226\207\344\273\266(48).png" delete mode 100644 prompt.txt diff --git a/liquidity_pool.move b/liquidity_pool.move deleted file mode 100644 index 79b1118d..00000000 --- a/liquidity_pool.move +++ /dev/null @@ -1,2072 +0,0 @@ -module yuzuswap::liquidity_pool { - - use std::bcs; - use std::signer; - use std::string; - use std::vector; - - use aptos_std::aptos_hash; - use aptos_std::math128; - use aptos_std::math64; - use aptos_std::string_utils; - use aptos_std::table::{Self, Table}; - use aptos_framework::account::{Self, SignerCapability}; - use aptos_framework::event; - use aptos_framework::fungible_asset::{Self, FungibleStore, Metadata, FungibleAsset}; - use aptos_framework::object::{Self, Object, ExtendRef}; - use aptos_framework::primary_fungible_store; - - use yuzuswap::fixed_point; - use yuzuswap::config; - use yuzuswap::emergency; - use yuzuswap::fa_helper; - use yuzuswap::fee_tier; - use yuzuswap::i128::{Self, I128}; - use yuzuswap::sqrt_price_math; - use yuzuswap::swap_math; - use yuzuswap::tick_bitmap; - use yuzuswap::tick_math; - use yuzuswap::tick; - - friend yuzuswap::position_nft_manager; - friend yuzuswap::router; - friend yuzuswap::reward_manager; - - // Errors codes. - - /// Wrong tokens ordering. - const E_UNREACHABLE_CODE: u64 = 100; - /// Wrong tokens ordering. - const E_WRONG_TOKENS_ORDERING: u64 = 101; - /// The pool is locked. - const E_LOCKED_POOL: u64 = 102; - /// The tick lower must be less than the tick upper. - const E_TICK_LOWER_MUST_LESS_THAN_TICK_UPPER: u64 = 103; - /// The tick is not spaced. - const E_TICK_NOT_SPACED: u64 = 104; - /// The tick exceeds the maximum tick. - const E_EXCEED_MAX_TICK: u64 = 105; - /// The liquidity exceeds the maximum liquidity per tick. - const E_EXCEED_MAX_LIQUIDITY_PER_TICK: u64 = 106; - /// Mismatch token. - const E_TOKEN_MISMATCH: u64 = 107; - /// Not enough token to add liquidity. - const E_NOT_ENOUGH_TOKEN_TO_ADD_LIQUIDITY: u64 = 108; - /// The swap amount must be greater than zero. - const E_SWAP_AMOUNT_MUST_GREATER_THAN_ZERO: u64 = 109; - /// Invalid limit sqrt price. - const E_INVALID_LIMIT_SQRT_PRICE: u64 = 110; - /// Invalid token to pay swap. - const E_INVALID_PAY_SWAP_TOKEN: u64 = 111; - /// Invalid pay swap amount. - const E_INVALID_PAY_SWAP_AMOUNT: u64 = 112; - /// The position does not exist. - const E_POSITION_NOT_EXIST: u64 = 113; - /// The position still has liquidity. - const E_NOT_EMPTY_LIQUIDITY_POSITION: u64 = 114; - /// The position still has owed fees. - const E_NOT_EMPTY_FEE_POSITION: u64 = 115; - /// The position still has rewards. - const E_NOT_EMPTY_REWARD_POSITION: u64 = 116; - /// Exceed max rewards per pool. - const E_EXCEED_MAX_REWARDS_PER_POOL: u64 = 117; - /// The user is not reward manager. - const E_NOT_REWARD_MANAGER: u64 = 118; - /// Invalid reward token. - const E_INVALID_REWARD_TOKEN: u64 = 119; - /// Not enough reward. - const E_NOT_ENOUGH_REWARD: u64 = 120; - - // Constants. - - const MAX_U128: u256 = 0xffffffffffffffffffffffffffffffff; - - const MAX_REWARDS_PER_POOL: u64 = 3; - - // Structs. - - /// Stores resource account signer capability under Yuzuswap account. - struct PoolAccountCap has key { - signer_cap: SignerCapability, - } - - struct LiquidityPools has key { - all_pools: vector>, - } - - #[resource_group_member(group = aptos_framework::object::ObjectGroup)] - struct LiquidityPool has key { - token_0_reserve: Object, - token_1_reserve: Object, - current_tick: u32, - current_sqrt_price: u128, - liquidity: u128, - tick_bitmap: Table, - ticks: Table, - positions: Table, Position>, - next_position_id: u64, - fee_growth_global_0_x64: u128, - fee_growth_global_1_x64: u128, - protocol_fee_amount_0: u64, - protocol_fee_amount_1: u64, - reward_infos: vector, - reward_last_updated_at_seconds: u64, - fee_rate: u64, - tick_spacing: u32, - max_liquidity_per_tick: u128, - unlocked: bool, - extend_ref: ExtendRef, - } - - struct Position has store, drop { - id: u64, - tick_lower: u32, - tick_upper: u32, - liquidity: u128, - fee_growth_inside_0_last_x64: u128, - fee_growth_inside_1_last_x64: u128, - tokens_owed_0: u64, - tokens_owed_1: u64, - reward_infos: vector, - } - - struct TickInfo has store, drop { - liquditiy_gross: u128, - liquidity_net: I128, - fee_growth_outside_0_x64: u128, - fee_growth_outside_1_x64: u128, - reward_growths_outside: vector, - initialized: bool, - } - - struct PoolRewardInfo has copy, drop, store { - token_metadata: Object, - remaining_reward: u64, - emissions_per_second: u64, - growth_global: u128, - manager: address, - } - - struct PositionRewardInfo has copy, drop, store { - reward_growth_inside_last: u128, - amount_owed: u64, - } - - struct SwapReciept { - pool: Object, - token_metadata: Object, - amount_in: u64, - } - - // Events. - - #[event] - struct CreatePoolEvent has drop, store { - creator: address, - pool: address, - token_x: Object, - token_y: Object, - fee: u64, - tick_spacing: u32, - } - - #[event] - struct AddLiquidityEvent has drop, store { - user: address, - pool: address, - position_id: u64, - liquidity: u128, - amount_0: u64, - amount_1: u64, - } - - #[event] - struct RemoveLiquidityEvent has drop, store { - user: address, - pool: address, - position_id: u64, - liquidity: u128, - amount_0: u64, - amount_1: u64, - } - - #[event] - struct SwapEvent has drop, store { - pool: address, - zero_for_one: bool, - is_exact_in: bool, - amount_in: u64, - amount_out: u64, - fee_amount: u64, - sqrt_price_after: u128, - liquidity_after: u128, - tick_after: u32, - } - - #[event] - struct CollectFeeEvent has drop, store { - user: address, - pool: address, - position_id: u64, - amount_0: u64, - amount_1: u64, - } - - #[event] - struct CollectProtocolFee has drop, store { - admin: address, - pool: address, - amount_0: u64, - amount_1: u64, - } - - #[event] - struct InitRewardEvent has drop, store { - pool: address, - reward_index: u64, - manager: address, - } - - #[event] - struct UpdateRewardManagerEvent has drop, store { - pool: address, - reward_index: u64, - manager: address, - } - - #[event] - struct UpdateRewardEmissionsEvent has drop, store { - pool: address, - reward_index: u64, - manager: address, - emissions_per_second: u64, - } - - #[event] - struct AddRewardEvent has drop, store { - pool: address, - reward_index: u64, - manager: address, - amount: u64, - } - - #[event] - struct RemoveRewardEvent has drop, store { - pool: address, - reward_index: u64, - manager: address, - amount: u64, - } - - #[event] - struct CollectRewardEvent has drop, store { - user: address, - pool: address, - position_id: u64, - reward_index: u64, - amount: u64, - } - - // Module initialization. - - fun init_module(owner: &signer) { - let (_, signer_cap) = account::create_resource_account(owner, b"pool_account"); - move_to(owner, PoolAccountCap { signer_cap }); - move_to(owner, LiquidityPools { all_pools: vector[] }); - } - - // Public functions. - - public(friend) fun create_pool( - sender: &signer, - token_0: Object, - token_1: Object, - fee_rate: u64, - sqrt_price: u128, - ): Object - acquires PoolAccountCap, LiquidityPools { - emergency::assert_no_emergency(); - - assert!(fa_helper::is_sorted(token_0, token_1), E_WRONG_TOKENS_ORDERING); - - let tick_spacing = fee_tier::get_tick_spacing(fee_rate); - let current_tick = tick_math::get_tick_at_sqrt_price(sqrt_price); - - let pool_account_cap = borrow_global(@yuzuswap); - let pool_account = account::create_signer_with_capability(&pool_account_cap.signer_cap); - - let pool_seed = get_pool_seed(token_0, token_1, fee_rate); - let pool_constructor_ref = &object::create_named_object(&pool_account, pool_seed); - let pool_signer = &object::generate_signer(pool_constructor_ref); - - move_to(pool_signer, LiquidityPool { - token_0_reserve: create_token_store(pool_signer, token_0), - token_1_reserve: create_token_store(pool_signer, token_1), - current_tick, - current_sqrt_price: sqrt_price, - liquidity: 0, - tick_bitmap: table::new(), - ticks: table::new(), - positions: table::new(), - next_position_id: 1, - fee_growth_global_0_x64: 0, - fee_growth_global_1_x64: 0, - protocol_fee_amount_0: 0, - protocol_fee_amount_1: 0, - reward_infos: vector[], - reward_last_updated_at_seconds: 0, - fee_rate, - tick_spacing, - max_liquidity_per_tick: tick::tick_spacing_to_max_liquidity_per_tick(tick_spacing), - unlocked: true, - extend_ref: object::generate_extend_ref(pool_constructor_ref), - }); - - vector::push_back( - &mut borrow_global_mut(@yuzuswap).all_pools, - object::object_from_constructor_ref(pool_constructor_ref), - ); - - event::emit( - CreatePoolEvent { - creator: signer::address_of(sender), - pool: object::address_from_constructor_ref(pool_constructor_ref), - token_x: token_0, - token_y: token_1, - fee: fee_rate, - tick_spacing, - }, - ); - - object::object_from_constructor_ref(pool_constructor_ref) - } - - public fun open_position( - user: &signer, - pool: Object, - tick_lower: u32, - tick_upper: u32, - ): u64 - acquires LiquidityPool { - emergency::assert_no_emergency(); - - let pool_data = pool_data_mut(&pool); - assert!(pool_data.unlocked, E_LOCKED_POOL); - - assert_ticks(tick_lower, tick_upper, pool_data.tick_spacing); - - let position_id = pool_data.next_position_id; - let position_key = get_position_key(&signer::address_of(user), position_id); - let position = Position { - id: position_id, - tick_lower, - tick_upper, - liquidity: 0, - fee_growth_inside_0_last_x64: 0, - fee_growth_inside_1_last_x64: 0, - tokens_owed_0: 0, - tokens_owed_1: 0, - reward_infos: vector[], - }; - - table::add(&mut pool_data.positions, position_key, position); - pool_data.next_position_id = pool_data.next_position_id + 1; - - position_id - } - - public fun close_position( - user: &signer, - pool: Object, - position_id: u64, - ) acquires LiquidityPool { - emergency::assert_no_emergency(); - - let pool_data = pool_data_mut(&pool); - assert!(pool_data.unlocked, E_LOCKED_POOL); - - let user_address = signer::address_of(user); - let position_key = get_position_key(&user_address, position_id); - assert!(table::contains(&pool_data.positions, position_key), E_POSITION_NOT_EXIST); - - let position = table::borrow_mut(&mut pool_data.positions, position_key); - - assert!(position.liquidity == 0, E_NOT_EMPTY_LIQUIDITY_POSITION); - assert!(position.tokens_owed_0 == 0, E_NOT_EMPTY_FEE_POSITION); - assert!(position.tokens_owed_1 == 0, E_NOT_EMPTY_FEE_POSITION); - for (i in 0..vector::length(&position.reward_infos)) { - let reward_info = vector::borrow(&position.reward_infos, i); - assert!(reward_info.amount_owed == 0, E_NOT_EMPTY_REWARD_POSITION); - }; - - table::remove(&mut pool_data.positions, position_key); - } - - public fun add_liquidity( - user: &signer, - pool: Object, - position_id: u64, - liquidity_delta: u128, - token_0: &mut FungibleAsset, - token_1: &mut FungibleAsset, - ) acquires LiquidityPool { - emergency::assert_no_emergency(); - - let pool_data = pool_data_mut(&pool); - assert!(pool_data.unlocked, E_LOCKED_POOL); - - assert!( - fungible_asset::metadata_from_asset(token_0) == fungible_asset::store_metadata(pool_data.token_0_reserve) - && fungible_asset::metadata_from_asset(token_1) == fungible_asset::store_metadata(pool_data.token_1_reserve), - E_TOKEN_MISMATCH, - ); - - let user_address = signer::address_of(user); - - let (amount_0, amount_1) = modify_position( - pool_data, - user_address, - position_id, - i128::new(liquidity_delta, false), - ); - - assert!(fungible_asset::amount(token_0) >= amount_0, E_NOT_ENOUGH_TOKEN_TO_ADD_LIQUIDITY); - assert!(fungible_asset::amount(token_1) >= amount_1, E_NOT_ENOUGH_TOKEN_TO_ADD_LIQUIDITY); - - let token_0_in = fungible_asset::extract(token_0, amount_0); - let token_1_in = fungible_asset::extract(token_1, amount_1); - fungible_asset::deposit(pool_data.token_0_reserve, token_0_in); - fungible_asset::deposit(pool_data.token_1_reserve, token_1_in); - - event::emit( - AddLiquidityEvent { - user: user_address, - pool: object::object_address(&pool), - position_id, - liquidity: liquidity_delta, - amount_0, - amount_1, - }, - ) - } - - public fun remove_liquidity( - user: &signer, - pool: Object, - position_id: u64, - liquidity_delta: u128, - ): (FungibleAsset, FungibleAsset) - acquires LiquidityPool, PoolAccountCap { - emergency::assert_no_emergency(); - - let pool_data = pool_data_mut(&pool); - - assert!(pool_data.unlocked, E_LOCKED_POOL); - - let user_address = signer::address_of(user); - - let (amount_0_out, amount_1_out) = modify_position( - pool_data, - user_address, - position_id, - i128::new(liquidity_delta, true), - ); - - event::emit( - RemoveLiquidityEvent { - user: user_address, - pool: object::object_address(&pool), - position_id, - liquidity: liquidity_delta, - amount_0: amount_0_out, - amount_1: amount_1_out, - }, - ); - - let pool_account_signer = get_pool_account_signer(); - ( - fungible_asset::withdraw(&pool_account_signer, pool_data.token_0_reserve, amount_0_out), - fungible_asset::withdraw(&pool_account_signer, pool_data.token_1_reserve, amount_1_out), - ) - } - - public fun swap( - trader: &signer, - pool: Object, - zero_for_one: bool, - is_exact_in: bool, - specified_amount: u64, - sqrt_price_limit: u128, - ): (FungibleAsset, SwapReciept) - acquires LiquidityPool, PoolAccountCap { - emergency::assert_no_emergency(); - - assert!(specified_amount > 0, E_SWAP_AMOUNT_MUST_GREATER_THAN_ZERO); - - let pool_data = pool_data_mut(&pool); - - assert!(pool_data.unlocked, E_LOCKED_POOL); - // lock here and unlock in pay_swap function to guarantee that amount in reserves is correct, avoid error when - // the user swaps and then immediately modifies the pool (swap in opposite direction, remove liquidity, etc.) - // before paying the swap -> the token in reserves could not be enough to do those actions. - pool_data.unlocked = false; - - if (zero_for_one) { - assert!( - sqrt_price_limit < pool_data.current_sqrt_price - && sqrt_price_limit >= tick_math::min_sqrt_price(), - E_INVALID_LIMIT_SQRT_PRICE, - ); - } else { - assert!( - sqrt_price_limit > pool_data.current_sqrt_price - && sqrt_price_limit <= tick_math::max_sqrt_price(), - E_INVALID_LIMIT_SQRT_PRICE, - ) - }; - - let reward_growths_global = update_pool_reward_infos(pool_data); - - let tick_spacing = pool_data.tick_spacing; - let current_sqrt_price = pool_data.current_sqrt_price; - let current_tick = pool_data.current_tick; - let liquidity = pool_data.liquidity; - let remaining_amount = specified_amount; - let calculated_amount = 0; - - let protocol_fee_rate = config::protocol_fee_rate(); - let fee_scale = config::fee_scale(); - let total_protocol_fee_amount = 0; - let total_fee_amount = 0; - let fee_rate = get_fee_rate(signer::address_of(trader), pool_data); - let fee_growth_global_x64 = if (zero_for_one) { - pool_data.fee_growth_global_0_x64 - } else { - pool_data.fee_growth_global_1_x64 - }; - - while (remaining_amount > 0 && current_sqrt_price != sqrt_price_limit) { - let price_sqrt_start = current_sqrt_price; - - let (tick_next, is_initialized_tick) = tick_bitmap::get_next_initialized_tick_within_one_word( - &pool_data.tick_bitmap, - current_tick, - tick_spacing, - zero_for_one, - ); - - // ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds - if (tick_next < tick::min_tick()) { - tick_next = tick::min_tick(); - } else if (tick_next > tick::max_tick()) { - tick_next = tick::max_tick(); - }; - - let sqrt_price_next = tick_math::get_sqrt_price_at_tick(tick_next); - - let target_sqrt_price = if (zero_for_one) { - math128::max(sqrt_price_limit, sqrt_price_next) - } else { - math128::min(sqrt_price_limit, sqrt_price_next) - }; - let (sqrt_price, amount_in, amount_out, fee_amount) = swap_math::compute_swap_step( - current_sqrt_price, - target_sqrt_price, - liquidity, - remaining_amount, - is_exact_in, - fee_rate, - ); - current_sqrt_price = sqrt_price; - - if (is_exact_in) { - remaining_amount = remaining_amount - (amount_in + fee_amount); - calculated_amount = calculated_amount + amount_out; - } else { - remaining_amount = remaining_amount - amount_out; - calculated_amount = calculated_amount + (amount_in + fee_amount); - }; - - if (protocol_fee_rate > 0) { - let protocol_fee_amount = math64::mul_div(fee_amount, protocol_fee_rate, fee_scale); - total_protocol_fee_amount = total_protocol_fee_amount + protocol_fee_amount; - - fee_amount = fee_amount - protocol_fee_amount; - }; - total_fee_amount = total_fee_amount + fee_amount; - - if (liquidity != 0) { - fee_growth_global_x64 = fee_growth_global_x64 + ((fee_amount as u128) << 64) / liquidity; - }; - - // shift tick if we reached the next price - if (sqrt_price == sqrt_price_next) { - // if the tick is initialized, run the tick transition - if (is_initialized_tick) { - // TODO: calculate oracle - - let next_liquidity_net = cross_tick( - &mut pool_data.ticks, - tick_next, - if (zero_for_one) fee_growth_global_x64 else pool_data.fee_growth_global_0_x64, - if (zero_for_one) pool_data.fee_growth_global_1_x64 else fee_growth_global_x64, - &reward_growths_global, - ); - // if we're moving leftward, we interpret liquidityNet as the opposite sign - // safe because liquidityNet cannot be type(int128).min - if (zero_for_one) { - // next_liquidity_net = -next_liquidity_net - next_liquidity_net = i128::new( - i128::abs(&next_liquidity_net), - !i128::is_negative(&next_liquidity_net), - ); - }; - - liquidity = add_delta_liquidity(liquidity, &next_liquidity_net); - }; - - current_tick = if (zero_for_one) tick_next - 1 else tick_next; - } else if (current_sqrt_price != price_sqrt_start) { - current_tick = tick_math::get_tick_at_sqrt_price(sqrt_price); - } - }; - - if (current_tick != pool_data.current_tick) { - pool_data.current_tick = current_tick; - pool_data.current_sqrt_price = current_sqrt_price; - } else { - pool_data.current_sqrt_price = current_sqrt_price; - }; - - pool_data.liquidity = liquidity; - - if (zero_for_one) { - pool_data.fee_growth_global_0_x64 = fee_growth_global_x64; - pool_data.protocol_fee_amount_0 = pool_data.protocol_fee_amount_0 + total_protocol_fee_amount; - } else { - pool_data.fee_growth_global_1_x64 = fee_growth_global_x64; - pool_data.protocol_fee_amount_1 = pool_data.protocol_fee_amount_1 + total_protocol_fee_amount; - }; - - let (amount_in, amount_out) = if (is_exact_in) { - (specified_amount - remaining_amount, calculated_amount) - } else { - (calculated_amount, specified_amount - remaining_amount) - }; - - event::emit( - SwapEvent { - pool: object::object_address(&pool), - zero_for_one, - is_exact_in, - amount_in, - amount_out, - fee_amount: total_fee_amount, - sqrt_price_after: pool_data.current_sqrt_price, - liquidity_after: pool_data.liquidity, - tick_after: pool_data.current_tick, - } - ); - - // withdraw expected amount from reserves. - let pool_account_signer = get_pool_account_signer(); - if (zero_for_one) { - ( - fungible_asset::withdraw(&pool_account_signer, pool_data.token_1_reserve, amount_out), - SwapReciept { - pool, - token_metadata: fungible_asset::store_metadata(pool_data.token_0_reserve), - amount_in, - }, - ) - } else { - ( - fungible_asset::withdraw(&pool_account_signer, pool_data.token_0_reserve, amount_out), - SwapReciept { - pool, - token_metadata: fungible_asset::store_metadata(pool_data.token_1_reserve), - amount_in, - }, - ) - } - } - - public fun get_swap_receipt_amount(swap_receipt: &SwapReciept): u64 { - swap_receipt.amount_in - } - - public fun get_swap_receipt_token_metadata(swap_receipt: &SwapReciept): Object { - swap_receipt.token_metadata - } - - public fun pay_swap( - token_in: FungibleAsset, - reciept: SwapReciept, - ) acquires LiquidityPool { - let SwapReciept { - pool, - token_metadata, - amount_in, - } = reciept; - - assert!(token_metadata == fungible_asset::metadata_from_asset(&token_in), E_INVALID_PAY_SWAP_TOKEN); - assert!(fungible_asset::amount(&token_in) == amount_in, E_INVALID_PAY_SWAP_AMOUNT); - - let pool_data = pool_data_mut(&pool); - - if (token_metadata == fungible_asset::store_metadata(pool_data.token_0_reserve)) { - fungible_asset::deposit(pool_data.token_0_reserve, token_in); - } else { - fungible_asset::deposit(pool_data.token_1_reserve, token_in); - }; - - pool_data.unlocked = true; - } - - public fun collect_fee( - user: &signer, - pool: Object, - position_id: u64, - amount_0_requested: u64, - amount_1_requested: u64, - ): (FungibleAsset, FungibleAsset) - acquires LiquidityPool, PoolAccountCap { - emergency::assert_no_emergency(); - - let pool_data = pool_data_mut(&pool); - let user_address = signer::address_of(user); - let position = get_position_mut(&mut pool_data.positions, user_address, position_id); - - // only update fee if the position has liquidity to avoid unnecessary computation - if (position.liquidity > 0) { - let (fee_growth_inside_0_x64, fee_growth_inside_1_x64) = get_fee_growth_inside_tick( - &pool_data.ticks, - position.tick_lower, - position.tick_upper, - pool_data.current_tick, - pool_data.fee_growth_global_0_x64, - pool_data.fee_growth_global_1_x64, - ); - update_position_fee(position, fee_growth_inside_0_x64, fee_growth_inside_1_x64); - }; - - let amount_0 = math64::min(amount_0_requested, position.tokens_owed_0); - position.tokens_owed_0 = position.tokens_owed_0 - amount_0; - - let amount_1 = math64::min(amount_1_requested, position.tokens_owed_1); - position.tokens_owed_1 = position.tokens_owed_1 - amount_1; - - event::emit( - CollectFeeEvent { - user: user_address, - pool: object::object_address(&pool), - position_id, - amount_0, - amount_1, - }, - ); - - let pool_signer = get_pool_account_signer(); - ( - fungible_asset::withdraw(&pool_signer, pool_data.token_0_reserve, amount_0), - fungible_asset::withdraw(&pool_signer, pool_data.token_1_reserve, amount_1), - ) - } - - public fun collect_protocol_fee( - admin: &signer, - pool: Object, - amount_0_requested: u64, - amount_1_requested: u64, - ): (FungibleAsset, FungibleAsset) - acquires LiquidityPool, PoolAccountCap { - config::assert_pool_admin(admin); - - let pool_data = pool_data_mut(&pool); - - let amount_0 = math64::min(amount_0_requested, pool_data.protocol_fee_amount_0); - pool_data.protocol_fee_amount_0 = pool_data.protocol_fee_amount_0 - amount_0; - - let amount_1 = math64::min(amount_1_requested, pool_data.protocol_fee_amount_1); - pool_data.protocol_fee_amount_1 = pool_data.protocol_fee_amount_1 - amount_1; - - event::emit( - CollectProtocolFee { - admin: signer::address_of(admin), - pool: object::object_address(&pool), - amount_0, - amount_1, - }, - ); - - let pool_signer = get_pool_account_signer(); - ( - fungible_asset::withdraw(&pool_signer, pool_data.token_0_reserve, amount_0), - fungible_asset::withdraw(&pool_signer, pool_data.token_1_reserve, amount_1), - ) - } - - public fun update_reward_manager( - user: &signer, - pool: Object, - reward_index: u64, - new_manager: address, - ) acquires LiquidityPool { - let pool_data = pool_data_mut(&pool); - let reward_info = vector::borrow(&pool_data.reward_infos, reward_index); - assert!(reward_info.manager == signer::address_of(user), E_NOT_REWARD_MANAGER); - - let reward_info = vector::borrow_mut(&mut pool_data.reward_infos, reward_index); - reward_info.manager = new_manager; - - event::emit( - UpdateRewardManagerEvent { - pool: object::object_address(&pool), - reward_index, - manager: new_manager, - }, - ); - } - - public fun update_reward_emissions( - user: &signer, - pool: Object, - reward_index: u64, - emissions_per_second: u64, - ) acquires LiquidityPool { - let pool_data = pool_data_mut(&pool); - let reward_info = vector::borrow(&pool_data.reward_infos, reward_index); - assert!(reward_info.manager == signer::address_of(user), E_NOT_REWARD_MANAGER); - - update_pool_reward_infos(pool_data); - - let reward_info = vector::borrow_mut(&mut pool_data.reward_infos, reward_index); - reward_info.emissions_per_second = emissions_per_second; - - event::emit( - UpdateRewardEmissionsEvent { - pool: object::object_address(&pool), - reward_index, - manager: reward_info.manager, - emissions_per_second, - }, - ); - } - - public fun add_reward( - user: &signer, - pool: Object, - reward_index: u64, - token: FungibleAsset, - ) acquires LiquidityPool, PoolAccountCap { - let pool_data = pool_data_mut(&pool); - let reward_info = vector::borrow(&pool_data.reward_infos, reward_index); - assert!(reward_info.manager == signer::address_of(user), E_NOT_REWARD_MANAGER); - assert!(reward_info.token_metadata == fungible_asset::metadata_from_asset(&token), E_INVALID_REWARD_TOKEN); - - update_pool_reward_infos(pool_data); - - let reward_info = vector::borrow_mut(&mut pool_data.reward_infos, reward_index); - let added_amount = fungible_asset::amount(&token); - reward_info.remaining_reward = reward_info.remaining_reward + added_amount; - - let pool_acccount_address = get_pool_account_address(); - primary_fungible_store::deposit(pool_acccount_address, token); - - event::emit( - AddRewardEvent { - pool: object::object_address(&pool), - reward_index, - manager: reward_info.manager, - amount: added_amount, - }, - ); - } - - public fun remove_reward( - user: &signer, - pool: Object, - reward_index: u64, - amount: u64, - ): FungibleAsset - acquires LiquidityPool, PoolAccountCap { - let pool_data = pool_data_mut(&pool); - let reward_info = vector::borrow(&pool_data.reward_infos, reward_index); - assert!(reward_info.manager == signer::address_of(user), E_NOT_REWARD_MANAGER); - - update_pool_reward_infos(pool_data); - - let reward_info = vector::borrow_mut(&mut pool_data.reward_infos, reward_index); - let real_amount = math64::min(reward_info.remaining_reward, amount); - - let pool_signer = get_pool_account_signer(); - let removed_reward = primary_fungible_store::withdraw(&pool_signer, reward_info.token_metadata, real_amount); - - reward_info.remaining_reward = reward_info.remaining_reward - real_amount; - - event::emit( - RemoveRewardEvent { - pool: object::object_address(&pool), - reward_index, - manager: reward_info.manager, - amount: real_amount, - }, - ); - - removed_reward - } - - public fun collect_reward( - user: &signer, - pool: Object, - position_id: u64, - reward_index: u64, - amount_requested: u64, - ): FungibleAsset - acquires LiquidityPool, PoolAccountCap { - let pool_data = pool_data_mut(&pool); - let reward_growths_global = update_pool_reward_infos(pool_data); - - let user_address = signer::address_of(user); - let position = get_position_mut(&mut pool_data.positions, user_address, position_id); - - // only update position rewards if the position has liquidity to avoid unnecessary computation - if (position.liquidity > 0) { - let reward_growths_inside = get_reward_growths_inside( - &pool_data.ticks, - position.tick_lower, - position.tick_upper, - pool_data.current_tick, - &reward_growths_global, - ); - update_position_rewards(position, &reward_growths_inside); - }; - - let position_reward = vector::borrow_mut(&mut position.reward_infos, reward_index); - - let amount = math64::min(amount_requested, position_reward.amount_owed); - position_reward.amount_owed = position_reward.amount_owed - amount; - - event::emit( - CollectRewardEvent { - user: user_address, - pool: object::object_address(&pool), - position_id, - reward_index, - amount, - }, - ); - - let pool_signer = get_pool_account_signer(); - let pool_reward = vector::borrow(&pool_data.reward_infos, reward_index); - primary_fungible_store::withdraw(&pool_signer, pool_reward.token_metadata, amount) - } - - fun modify_position( - pool: &mut LiquidityPool, - owner: address, - position_id: u64, - liquidity_delta: I128, - ): (u64, u64) { - if (i128::is_zero(&liquidity_delta)) { - return (0, 0) - }; - - let position = update_position( - pool, - owner, - position_id, - liquidity_delta, - ); - - let (tick_lower, tick_upper) = (position.tick_lower, position.tick_upper); - - let amount0: u64 = 0; - let amount1: u64 = 0; - - if (pool.current_tick < tick_lower) { - amount0 = sqrt_price_math::get_amount_0_delta( - tick_math::get_sqrt_price_at_tick(tick_lower), - tick_math::get_sqrt_price_at_tick(tick_upper), - i128::abs(&liquidity_delta), - i128::is_positive(&liquidity_delta), - ); - } else if (pool.current_tick < tick_upper) { - // TODO: write oracle - - amount0 = sqrt_price_math::get_amount_0_delta( - pool.current_sqrt_price, - tick_math::get_sqrt_price_at_tick(tick_upper), - i128::abs(&liquidity_delta), - i128::is_positive(&liquidity_delta), - ); - amount1 = sqrt_price_math::get_amount_1_delta( - tick_math::get_sqrt_price_at_tick(tick_lower), - pool.current_sqrt_price, - i128::abs(&liquidity_delta), - i128::is_positive(&liquidity_delta), - ); - - pool.liquidity = add_delta_liquidity(pool.liquidity, &liquidity_delta); - } else { - amount1 = sqrt_price_math::get_amount_1_delta( - tick_math::get_sqrt_price_at_tick(tick_lower), - tick_math::get_sqrt_price_at_tick(tick_upper), - i128::abs(&liquidity_delta), - i128::is_positive(&liquidity_delta), - ); - }; - - (amount0, amount1) - } - - fun update_position( - pool: &mut LiquidityPool, - owner: address, - position_id: u64, - liquidity_delta: I128, - ): &Position { - let reward_growths_global = update_pool_reward_infos(pool); - - let position = get_position_mut(&mut pool.positions, owner, position_id); - - let flipped_tick_lower = false; - let flipped_tick_upper = false; - if (!i128::is_zero(&liquidity_delta)) { - flipped_tick_lower = update_tick( - &mut pool.ticks, - position.tick_lower, - pool.current_tick, - liquidity_delta, - pool.fee_growth_global_0_x64, - pool.fee_growth_global_1_x64, - false, - pool.max_liquidity_per_tick, - ); - flipped_tick_upper = update_tick( - &mut pool.ticks, - position.tick_upper, - pool.current_tick, - liquidity_delta, - pool.fee_growth_global_0_x64, - pool.fee_growth_global_1_x64, - true, - pool.max_liquidity_per_tick, - ); - - if (flipped_tick_lower) { - tick_bitmap::flip_tick(&mut pool.tick_bitmap, position.tick_lower, pool.tick_spacing); - }; - if (flipped_tick_upper) { - tick_bitmap::flip_tick(&mut pool.tick_bitmap, position.tick_upper, pool.tick_spacing); - }; - }; - - let (fee_growth_inside_0_x64, fee_growth_inside_1_x64) = - get_fee_growth_inside_tick( - &pool.ticks, - position.tick_lower, - position.tick_upper, - pool.current_tick, - pool.fee_growth_global_0_x64, - pool.fee_growth_global_1_x64, - ); - let reward_growths_inside = get_reward_growths_inside( - &pool.ticks, - position.tick_lower, - position.tick_upper, - pool.current_tick, - &reward_growths_global, - ); - update_position_fee(position, fee_growth_inside_0_x64, fee_growth_inside_1_x64); - update_position_rewards(position, &reward_growths_inside); - - position.liquidity = i128::as_u128(&i128::add(&liquidity_delta, &i128::new(position.liquidity, false))); - - // clear any tick data that is no longer needed - if (i128::is_negative(&liquidity_delta)) { - if (flipped_tick_lower) { - clear_tick(&mut pool.ticks, position.tick_lower); - }; - if (flipped_tick_upper) { - clear_tick(&mut pool.ticks, position.tick_upper); - }; - }; - - position - } - - fun update_position_fee( - position: &mut Position, - fee_growth_inside_0_x64: u128, - fee_growth_inside_1_x64: u128, - ) { - let tokens_owed_0 = math128::mul_div( - fee_growth_inside_0_x64 - position.fee_growth_inside_0_last_x64, - position.liquidity, - fixed_point::q64(), - ); - let tokens_owed_1 = math128::mul_div( - fee_growth_inside_1_x64 - position.fee_growth_inside_1_last_x64, - position.liquidity, - fixed_point::q64(), - ); - - position.fee_growth_inside_0_last_x64 = fee_growth_inside_0_x64; - position.fee_growth_inside_1_last_x64 = fee_growth_inside_1_x64; - - // overflow is acceptable, have to withdraw before you hit "maximum of uint64" fees - position.tokens_owed_0 = position.tokens_owed_0 + (tokens_owed_0 as u64); - position.tokens_owed_1 = position.tokens_owed_1 + (tokens_owed_1 as u64); - } - - fun update_tick( - ticks: &mut Table, - tick: u32, - current_tick: u32, - liquidity_delta: I128, - fee_growth_global_0_x64: u128, - fee_growth_global_1_x64: u128, - is_tick_upper: bool, - max_liquidity_per_tick: u128, - ): bool { - let tick_info = table::borrow_mut_with_default(ticks, tick, TickInfo { - liquditiy_gross: 0, - liquidity_net: i128::zero(), - fee_growth_outside_0_x64: 0, - fee_growth_outside_1_x64: 0, - reward_growths_outside: vector[], - initialized: false, - }); - - let liquidity_gross_before = tick_info.liquditiy_gross; - let liquidity_gross_after = i128::as_u128( - &i128::add(&liquidity_delta, &i128::new(liquidity_gross_before, false)) - ); - - assert!(liquidity_gross_after <= max_liquidity_per_tick, E_EXCEED_MAX_LIQUIDITY_PER_TICK); - - let flipped = (liquidity_gross_before == 0) != (liquidity_gross_after == 0); - if (liquidity_gross_before == 0) { - // by convention, we assume that all growth before a tick was initialized happened _below_ the tick - if (tick <= current_tick) { - tick_info.fee_growth_outside_0_x64 = fee_growth_global_0_x64; - tick_info.fee_growth_outside_1_x64 = fee_growth_global_1_x64; - - // TODO: calculate oracle - }; - - tick_info.initialized == true; - }; - - tick_info.liquditiy_gross = liquidity_gross_after; - - // when the lower (upper) tick is crossed left to right (right to left), liquidity must be added (removed) - if (is_tick_upper) { - tick_info.liquidity_net = i128::sub(&tick_info.liquidity_net, &liquidity_delta); - } else { - tick_info.liquidity_net = i128::add(&liquidity_delta, &tick_info.liquidity_net); - }; - - flipped - } - - fun get_fee_growth_inside_tick( - ticks: &Table, - tick_lower: u32, - tick_upper: u32, - current_tick: u32, - fee_growth_global_0_x64: u128, - fee_growth_global_1_x64: u128, - ): (u128, u128) { - let tick_lower_info = borrow_tick_or_empty(ticks, tick_lower); - let tick_upper_info = borrow_tick_or_empty(ticks, tick_upper); - - // calculate fee growth below - let fee_growth_below_0_x64: u128; - let fee_growth_below_1_x64: u128; - if (current_tick >= tick_lower) { - fee_growth_below_0_x64 = tick_lower_info.fee_growth_outside_0_x64; - fee_growth_below_1_x64 = tick_lower_info.fee_growth_outside_1_x64; - } else { - fee_growth_below_0_x64 = fee_growth_global_0_x64 - tick_lower_info.fee_growth_outside_0_x64; - fee_growth_below_1_x64 = fee_growth_global_1_x64 - tick_lower_info.fee_growth_outside_1_x64; - }; - - // calculate fee growth above - let fee_growth_above_0_x64: u128; - let fee_growth_above_1_x64: u128; - if (current_tick < tick_upper) { - fee_growth_above_0_x64 = tick_upper_info.fee_growth_outside_0_x64; - fee_growth_above_1_x64 = tick_upper_info.fee_growth_outside_1_x64; - } else { - fee_growth_above_0_x64 = fee_growth_global_0_x64 - tick_upper_info.fee_growth_outside_0_x64; - fee_growth_above_1_x64 = fee_growth_global_1_x64 - tick_upper_info.fee_growth_outside_1_x64; - }; - - let fee_growth_inside_0_x64 = fee_growth_global_0_x64 - fee_growth_below_0_x64 - fee_growth_above_0_x64; - let fee_growth_inside_1_x64 = fee_growth_global_1_x64 - fee_growth_below_1_x64 - fee_growth_above_1_x64; - - (fee_growth_inside_0_x64, fee_growth_inside_1_x64) - } - - fun cross_tick( - ticks: &mut Table, - tick: u32, - fee_growth_global_0_x64: u128, - fee_growth_global_1_x64: u128, - reward_growths_global: &vector, - ): I128 { - let tick_info = table::borrow_mut(ticks, tick); - tick_info.fee_growth_outside_0_x64 = fee_growth_global_0_x64 - tick_info.fee_growth_outside_0_x64; - tick_info.fee_growth_outside_1_x64 = fee_growth_global_1_x64 - tick_info.fee_growth_outside_1_x64; - - update_reward_growths(&mut tick_info.reward_growths_outside, reward_growths_global); - - tick_info.liquidity_net - } - - fun update_reward_growths( - reward_growths_outside: &mut vector, - reward_growths_global: &vector, - ) { - let reward_growths_outside_length = vector::length(reward_growths_outside); - for (i in 0..vector::length(reward_growths_global)) { - if (i >= reward_growths_outside_length) { - vector::push_back( - reward_growths_outside, - *vector::borrow(reward_growths_global, i), - ); - } else { - let reward_growth_outside = vector::borrow_mut(reward_growths_outside, i); - *reward_growth_outside = *vector::borrow(reward_growths_global, i) - *reward_growth_outside; - }; - }; - } - - fun sub_reward_growths( - reward_growths_global: &vector, - reward_growths_outside: &vector, - ): vector { - let result = vector[]; - - let reward_growths_outside_length = vector::length(reward_growths_outside); - for (i in 0..vector::length(reward_growths_global)) { - let reward_growth_outside = if (i >= reward_growths_outside_length) { - 0 - } else { - *vector::borrow(reward_growths_outside, i) - }; - - vector::push_back( - &mut result, - *vector::borrow(reward_growths_global, i) - reward_growth_outside, - ); - }; - - result - } - - public(friend) fun initialize_reward( - user: &signer, - pool: Object, - token_metadata: Object, - manager: address, - ) acquires LiquidityPool { - config::assert_reward_admin(user); - - let pool_data = pool_data_mut(&pool); - - assert!(vector::length(&pool_data.reward_infos) < MAX_REWARDS_PER_POOL, E_EXCEED_MAX_REWARDS_PER_POOL); - - let poolRewardInfo = PoolRewardInfo { - token_metadata, - remaining_reward: 0, - emissions_per_second: 0, - growth_global: 0, - manager, - }; - vector::push_back(&mut pool_data.reward_infos, poolRewardInfo); - - event::emit( - InitRewardEvent { - pool: object::object_address(&pool), - reward_index: vector::length(&pool_data.reward_infos) - 1, - manager, - }, - ); - } - - fun update_pool_reward_infos(pool: &mut LiquidityPool): vector { - let current_time = 0x1::timestamp::now_seconds(); - // This should never happen. - assert!(current_time >= pool.reward_last_updated_at_seconds, E_UNREACHABLE_CODE); - - let reward_infos = &mut pool.reward_infos; - - let reward_growths_global = 0x1::vector::empty(); - let elapsed_seconds = current_time - pool.reward_last_updated_at_seconds; - - for (i in 0..vector::length(reward_infos)) { - let reward_info = vector::borrow_mut(reward_infos, i); - if (pool.liquidity != 0 && elapsed_seconds != 0 - && reward_info.emissions_per_second != 0 && reward_info.remaining_reward != 0 - ) { - let emitted_reward = elapsed_seconds * reward_info.emissions_per_second; - emitted_reward = math64::min(emitted_reward, reward_info.remaining_reward); - - reward_info.remaining_reward = reward_info.remaining_reward - emitted_reward; - - let growth_reward = fixed_point::u64_to_x64_u128(emitted_reward) / pool.liquidity; - reward_info.growth_global = reward_info.growth_global + growth_reward; - }; - vector::push_back(&mut reward_growths_global, reward_info.growth_global); - }; - - pool.reward_last_updated_at_seconds = current_time; - - reward_growths_global - } - - fun get_pool_reward_infos(pool: &LiquidityPool): vector { - let current_time = 0x1::timestamp::now_seconds(); - // This should never happen. - assert!(current_time >= pool.reward_last_updated_at_seconds, E_UNREACHABLE_CODE); - - let reward_infos = &pool.reward_infos; - - let reward_growths_global = 0x1::vector::empty(); - let elapsed_seconds = current_time - pool.reward_last_updated_at_seconds; - - for (i in 0..vector::length(reward_infos)) { - let reward_info = vector::borrow(reward_infos, i); - let reward_growth_global = reward_info.growth_global; - if (pool.liquidity != 0 && elapsed_seconds != 0 - && reward_info.emissions_per_second != 0 && reward_info.remaining_reward != 0 - ) { - let emitted_reward = elapsed_seconds * reward_info.emissions_per_second; - emitted_reward = math64::min(emitted_reward, reward_info.remaining_reward); - - let growth_reward = fixed_point::u64_to_x64_u128(emitted_reward) / pool.liquidity; - reward_growth_global = reward_info.growth_global + growth_reward; - }; - vector::push_back(&mut reward_growths_global, reward_growth_global); - }; - - reward_growths_global - } - - fun get_reward_growths_inside( - ticks: &Table, - tick_lower: u32, - tick_upper: u32, - current_tick: u32, - reward_growths_global: &vector - ): (vector) { - let tick_lower_info = table::borrow(ticks, tick_lower); - let tick_upper_info = table::borrow(ticks, tick_upper); - - // calculate reward growth below - let reward_growths_below = if (current_tick >= tick_lower) { - tick_lower_info.reward_growths_outside - } else { - sub_reward_growths(reward_growths_global, &tick_lower_info.reward_growths_outside) - }; - - // calculate fee growth above - let reward_growths_above = if (current_tick < tick_upper) { - tick_upper_info.reward_growths_outside - } else { - sub_reward_growths(reward_growths_global, &tick_upper_info.reward_growths_outside) - }; - - sub_reward_growths( - &sub_reward_growths(reward_growths_global, &reward_growths_below), - &reward_growths_above, - ) - } - - fun update_position_rewards( - position: &mut Position, - reward_growths_inside: &vector, - ) { - for (i in 0..vector::length(reward_growths_inside)) { - let liquidity = position.liquidity; - let reward_growth_inside = *vector::borrow(reward_growths_inside, i); - - let position_reward = try_borrow_mut_reward_info(position, i); - let amount_owed = math128::mul_div( - reward_growth_inside - position_reward.reward_growth_inside_last, - liquidity, - fixed_point::q64() - ); - - position_reward.reward_growth_inside_last = reward_growth_inside; - // overflow is acceptable, have to withdraw before you hit "maximum of uint64" fees - position_reward.amount_owed = position_reward.amount_owed + (amount_owed as u64); - }; - } - - fun try_borrow_mut_reward_info(position: &mut Position, i: u64): &mut PositionRewardInfo { - let len = vector::length(&position.reward_infos); - if (i >= len) { - vector::push_back(&mut position.reward_infos, PositionRewardInfo { - reward_growth_inside_last: 0, - amount_owed: 0 - }); - }; - - vector::borrow_mut(&mut position.reward_infos, i) - } - - /// Returns the word and bit position of the tick within the bitmap. - fun tick_bitmap_position(tick: u32): (u16, u8) { - let word_position = ((tick >> 8) as u16); - let bit_position = ((tick % 256) as u8); - - (word_position, bit_position) - } - - fun clear_tick(ticks: &mut Table, tick: u32) { - table::remove(ticks, tick); - } - - fun assert_ticks(tick_lower: u32, tick_upper: u32, tick_spacing: u32) { - assert!(tick_lower < tick_upper, E_TICK_LOWER_MUST_LESS_THAN_TICK_UPPER); - assert!( - tick::is_spaced_tick(tick_lower, tick_spacing) && tick::is_spaced_tick(tick_upper, tick_spacing), - E_TICK_NOT_SPACED - ); - assert!(tick_upper <= tick::max_tick(), E_EXCEED_MAX_TICK); - } - - fun add_delta_liquidity(liquidity: u128, delta_liquidity: &I128): u128 { - let liqudity_after = if (i128::is_positive(delta_liquidity)) { - (liquidity as u256) + (i128::abs(delta_liquidity) as u256) - } else { - assert!(liquidity >= i128::abs(delta_liquidity), E_EXCEED_MAX_LIQUIDITY_PER_TICK); - (liquidity as u256) - (i128::abs(delta_liquidity) as u256) - }; - assert!(liqudity_after <= MAX_U128, 1); - - (liqudity_after as u128) - } - - fun get_position_mut( - positions: &mut Table, Position>, - owner: address, - position_id: u64, - ): &mut Position { - let position_key = get_position_key(&owner, position_id); - assert!(table::contains(positions, position_key), E_POSITION_NOT_EXIST); - - table::borrow_mut(positions, position_key) - } - - fun get_position_key( - owner: &address, - position_id: u64, - ): vector { - let position_key_raw_data = string::bytes( - &string_utils::format2(&b"{}-{}", *owner, position_id) - ); - aptos_hash::keccak256(*position_key_raw_data) - } - - fun get_fee_rate(trader: address, pool: &LiquidityPool): u64 { - config::get_trader_fee_rate(trader, pool.fee_rate) - } - - fun get_pool_account_address(): address acquires PoolAccountCap { - account::get_signer_capability_address(&borrow_global(@yuzuswap).signer_cap) - } - - fun get_pool_account_signer(): signer acquires PoolAccountCap { - account::create_signer_with_capability(&borrow_global(@yuzuswap).signer_cap) - } - - // Inline functions. - - inline fun pool_data_mut(pool: &Object): &mut LiquidityPool { - borrow_global_mut(object::object_address(pool)) - } - - inline fun pool_data(pool: &Object): &LiquidityPool { - borrow_global(object::object_address(pool)) - } - - inline fun get_pool_seed(token_0: Object, token_1: Object, fee: u64): vector { - let seed = vector[]; - vector::append(&mut seed, b"pool"); - vector::append(&mut seed, bcs::to_bytes(&object::object_address(&token_0))); - vector::append(&mut seed, bcs::to_bytes(&object::object_address(&token_1))); - vector::append(&mut seed, bcs::to_bytes(&fee)); - seed - } - - inline fun create_token_store(pool_signer: &signer, token: Object): Object { - let constructor_ref = &object::create_object_from_object(pool_signer); - fungible_asset::create_store(constructor_ref, token) - } - - inline fun borrow_tick_or_empty(ticks: &Table, tick: u32): &TickInfo { - if (table::contains(ticks, tick)) { - table::borrow(ticks, tick) - } else { - &TickInfo { - liquditiy_gross: 0, - liquidity_net: i128::zero(), - fee_growth_outside_0_x64: 0, - fee_growth_outside_1_x64: 0, - reward_growths_outside: vector[], - initialized: false, - } - } - } - - public(friend) fun get_pool_signer(pool: Object): signer acquires LiquidityPool { - object::generate_signer_for_extending(&pool_data(&pool).extend_ref) - } - - // View functions. - - #[view] - public fun get_pool( - token_x: Object, - token_y: Object, - fee: u64, - ): Object acquires PoolAccountCap { - object::address_to_object(get_pool_address(token_x, token_y, fee)) - } - - #[view] - public fun get_all_pools(): vector> acquires LiquidityPools { - borrow_global(@yuzuswap).all_pools - } - - #[view] - public fun count_pool(): u64 acquires LiquidityPools { - vector::length(&borrow_global(@yuzuswap).all_pools) - } - - #[view] - public fun get_pool_info( - pool: Object, - ): ( - Object, - Object, - u128, - u32, - u128, - u64, - u32, - ) - acquires LiquidityPool { - let pool_data = pool_data(&pool); - - ( - fungible_asset::store_metadata(pool_data.token_0_reserve), - fungible_asset::store_metadata(pool_data.token_1_reserve), - pool_data.current_sqrt_price, - pool_data.current_tick, - pool_data.liquidity, - pool_data.fee_rate, - pool_data.tick_spacing, - ) - } - - #[view] - public fun rewards_count( - pool: Object, - ): u64 - acquires LiquidityPool { - vector::length(&pool_data(&pool).reward_infos) - } - - struct TickView has copy, drop, store { - tick: u32, - liquidity_gross: u128, - liquidity_net: I128, - fee_growth_outside_0_x64: u128, - fee_growth_outside_1_x64: u128, - reward_growths_outside: vector, - } - - #[view] - public fun get_ticks( - pool: Object, - start_tick: u32, - limit: u32, - ): (vector) - acquires LiquidityPool { - let pool_data = pool_data(&pool); - - let tick_count = 0; - let ticks = vector[]; - let max_count = (((tick::max_tick() / pool_data.tick_spacing) >> 8) as u16); - - let tick_spacing = pool_data.tick_spacing; - let tick_adjustment = tick::tick_adjustment(tick_spacing); - let adjusted_start_tick = if (start_tick >= tick_adjustment) start_tick - tick_adjustment else 0; - let compessed_start_tick = (math64::ceil_div((adjusted_start_tick as u64), (tick_spacing as u64)) as u32); - - let i = ((compessed_start_tick >> 8) as u16); - let first_position_in_word = compessed_start_tick % 256; - while (i <= max_count && tick_count < limit) { - let word = *table::borrow_with_default(&pool_data.tick_bitmap, i, &0); - if (word == 0) { - i = i + 1; - continue - }; - - let mask: u256; - let j = first_position_in_word; - while (j < 256) { - mask = 1 << (j as u8); - - if (mask & word != 0) { - let tick: u32 = (((i as u32) << 8) + j) * pool_data.tick_spacing + tick_adjustment; - let tick_info = table::borrow(&pool_data.ticks, tick); - - vector::push_back(&mut ticks, TickView { - tick, - liquidity_gross: tick_info.liquditiy_gross, - liquidity_net: tick_info.liquidity_net, - fee_growth_outside_0_x64: tick_info.fee_growth_outside_0_x64, - fee_growth_outside_1_x64: tick_info.fee_growth_outside_1_x64, - reward_growths_outside: tick_info.reward_growths_outside, - }); - tick_count = tick_count + 1; - - if (tick_count >= limit) { - break - }; - }; - - j = j + 1; - }; - first_position_in_word = 0; - - i = i + 1; - }; - - return ticks - } - - #[view] - public fun get_pool_address( - token_x: Object, - token_y: Object, - fee: u64, - ): address acquires PoolAccountCap { - object::create_object_address(&get_pool_account_address(), get_pool_seed(token_x, token_y, fee)) - } - - #[view] - public fun get_tokens( - pool: Object, - ): (Object, Object) - acquires LiquidityPool { - ( - fungible_asset::store_metadata(pool_data(&pool).token_0_reserve), - fungible_asset::store_metadata(pool_data(&pool).token_1_reserve), - ) - } - - #[view] - public fun get_reserves_size(pool: Object): (u64, u64) acquires LiquidityPool { - let pool_data = pool_data_mut(&pool); - - let amount_0 = fungible_asset::balance(pool_data.token_0_reserve); - let amount_1 = fungible_asset::balance(pool_data.token_1_reserve); - - (amount_0, amount_1) - } - - // Struct for view purpose. - struct LiquidityPoolView has drop { - pool_addr: address, - token_0: address, - token_1: address, - token_0_decimals: u8, - token_1_decimals: u8, - token_0_reserve: u64, - token_1_reserve: u64, - current_tick: u32, - current_sqrt_price: u128, - liquidity: u128, - fee_growth_global_0_x64: u128, - fee_growth_global_1_x64: u128, - reward_infos: vector, - fee_rate: u64, - tick_spacing: u32, - } - - #[view] - public fun get_pool_view(pool: Object): LiquidityPoolView acquires LiquidityPool { - map_pool_view(&pool) - } - - #[view] - public fun get_pool_views( - offset: u64, - limit: u64, - ): vector - acquires LiquidityPool, LiquidityPools { - let pools = &borrow_global(@yuzuswap).all_pools; - - let pool_views = vector[]; - for (i in offset..math64::min(vector::length(pools), offset + limit)) { - let pool = vector::borrow(pools, i); - - vector::push_back(&mut pool_views, map_pool_view(pool)); - }; - - return pool_views - } - - fun map_pool_view(pool: &Object): LiquidityPoolView acquires LiquidityPool { - let pool_data = pool_data(pool); - - let token_0_metadata = fungible_asset::store_metadata(pool_data.token_0_reserve); - let token_1_metadata = fungible_asset::store_metadata(pool_data.token_1_reserve); - - LiquidityPoolView { - pool_addr: object::object_address(pool), - token_0: object::object_address( &token_0_metadata), - token_1: object::object_address( &token_1_metadata), - token_0_decimals: fungible_asset::decimals(token_0_metadata), - token_1_decimals: fungible_asset::decimals(token_1_metadata), - token_0_reserve: fungible_asset::balance(pool_data.token_0_reserve), - token_1_reserve: fungible_asset::balance(pool_data.token_1_reserve), - current_tick: pool_data.current_tick, - current_sqrt_price: pool_data.current_sqrt_price, - liquidity: pool_data.liquidity, - fee_growth_global_0_x64: pool_data.fee_growth_global_0_x64, - fee_growth_global_1_x64: pool_data.fee_growth_global_1_x64, - reward_infos: pool_data.reward_infos, - fee_rate: pool_data.fee_rate, - tick_spacing: pool_data.tick_spacing, - } - } - - #[view] - public fun get_position( - owner: address, - pool: Object, - position_id: u64, - ): Position - acquires LiquidityPool { - let position = table::borrow(&pool_data(&pool).positions, get_position_key(&owner, position_id)); - - Position { - id: position.id, - tick_lower: position.tick_lower, - tick_upper: position.tick_upper, - liquidity: position.liquidity, - fee_growth_inside_0_last_x64: position.fee_growth_inside_0_last_x64, - fee_growth_inside_1_last_x64: position.fee_growth_inside_1_last_x64, - tokens_owed_0: position.tokens_owed_0, - tokens_owed_1: position.tokens_owed_1, - reward_infos: position.reward_infos, - } - } - - #[view] - public fun get_position_with_pending_fees_and_rewards( - owner: address, - pool: Object, - position_id: u64, - ): Position acquires LiquidityPool { - let position = get_position(owner, pool, position_id); - let pool_data = pool_data(&pool); - - let position_view = Position { - id: position.id, - liquidity: position.liquidity, - tick_lower: position.tick_lower, - tick_upper: position.tick_upper, - fee_growth_inside_0_last_x64: position.fee_growth_inside_0_last_x64, - fee_growth_inside_1_last_x64: position.fee_growth_inside_1_last_x64, - tokens_owed_0: position.tokens_owed_0, - tokens_owed_1: position.tokens_owed_1, - reward_infos: position.reward_infos, - }; - - if (position.liquidity == 0) { - return position_view - }; - - let (fee_growth_inside_0_x64, fee_growth_inside_1_x64) = get_fee_growth_inside_tick( - &pool_data.ticks, - position.tick_lower, - position.tick_upper, - pool_data.current_tick, - pool_data.fee_growth_global_0_x64, - pool_data.fee_growth_global_1_x64, - ); - update_position_fee(&mut position_view, fee_growth_inside_0_x64, fee_growth_inside_1_x64); - - let reward_growths_global = get_pool_reward_infos(pool_data); - let reward_growths_inside = get_reward_growths_inside( - &pool_data.ticks, - position.tick_lower, - position.tick_upper, - pool_data.current_tick, - &reward_growths_global, - ); - update_position_rewards(&mut position_view, &reward_growths_inside); - - position_view - } - - #[view] - public fun get_position_info( - owner: address, - pool: Object, - position_id: u64, - ): (u128, u32, u32, u64, u64) acquires LiquidityPool { - let pool_data = pool_data_mut(&pool); - let position = get_position_mut(&mut pool_data.positions, owner, position_id); - - ( - position.liquidity, - position.tick_lower, - position.tick_upper, - position.tokens_owed_0, - position.tokens_owed_1, - ) - } - - #[view] - public fun quote_swap( - trader: address, - pool: Object, - zero_for_one: bool, - is_exact_in: bool, - specified_amount: u64, - sqrt_price_limit: u128, - ): (u64, u64, u64) acquires LiquidityPool { - assert!(specified_amount > 0, E_SWAP_AMOUNT_MUST_GREATER_THAN_ZERO); - - let pool_data = pool_data(&pool); - - if (zero_for_one) { - assert!( - sqrt_price_limit < pool_data.current_sqrt_price - && sqrt_price_limit >= tick_math::min_sqrt_price(), - E_INVALID_LIMIT_SQRT_PRICE, - ); - } else { - assert!( - sqrt_price_limit > pool_data.current_sqrt_price - && sqrt_price_limit <= tick_math::max_sqrt_price(), - E_INVALID_LIMIT_SQRT_PRICE, - ) - }; - - let tick_spacing = pool_data.tick_spacing; - let current_sqrt_price = pool_data.current_sqrt_price; - let current_tick = pool_data.current_tick; - let liquidity = pool_data.liquidity; - let remaining_amount = specified_amount; - let calculated_amount = 0; - let total_fee_amount = 0; - let fee_rate = get_fee_rate(trader, pool_data); - - while (remaining_amount > 0 && current_sqrt_price != sqrt_price_limit) { - let price_sqrt_start = current_sqrt_price; - - let (tick_next, is_initialized_tick) = tick_bitmap::get_next_initialized_tick_within_one_word( - &pool_data.tick_bitmap, - current_tick, - tick_spacing, - zero_for_one, - ); - - // ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds - if (tick_next < tick::min_tick()) { - tick_next = tick::min_tick(); - } else if (tick_next > tick::max_tick()) { - tick_next = tick::max_tick(); - }; - - let sqrt_price_next = tick_math::get_sqrt_price_at_tick(tick_next); - - let target_sqrt_price = if (zero_for_one) { - math128::max(sqrt_price_limit, sqrt_price_next) - } else { - math128::min(sqrt_price_limit, sqrt_price_next) - }; - let (sqrt_price, amount_in, amount_out, fee_amount) = swap_math::compute_swap_step( - current_sqrt_price, - target_sqrt_price, - liquidity, - remaining_amount, - is_exact_in, - fee_rate, - ); - current_sqrt_price = sqrt_price; - - if (is_exact_in) { - remaining_amount = remaining_amount - (amount_in + fee_amount); - calculated_amount = calculated_amount + amount_out; - } else { - remaining_amount = remaining_amount - amount_out; - calculated_amount = calculated_amount + (amount_in + fee_amount); - }; - - total_fee_amount = total_fee_amount + fee_amount; - - // shift tick if we reached the next price - if (sqrt_price == sqrt_price_next) { - if (is_initialized_tick) { - let next_liquidity_net = table::borrow(&pool_data.ticks, tick_next).liquidity_net; - - if (zero_for_one) { - // next_liquidity_net = -next_liquidity_net - next_liquidity_net = i128::new( - i128::abs(&next_liquidity_net), - !i128::is_negative(&next_liquidity_net), - ); - }; - - liquidity = add_delta_liquidity(liquidity, &next_liquidity_net); - }; - - current_tick = if (zero_for_one) tick_next - 1 else tick_next; - } else if (current_sqrt_price != price_sqrt_start) { - current_tick = tick_math::get_tick_at_sqrt_price(sqrt_price); - } - }; - - let (amount_in, amount_out) = if (is_exact_in) { - (specified_amount - remaining_amount, calculated_amount) - } else { - (calculated_amount, specified_amount - remaining_amount) - }; - - (amount_in, amount_out, total_fee_amount) - } - - // Tests. - - #[test_only] - friend yuzuswap::test_pool; - #[test_only] - friend yuzuswap::liquidity_pool_tests; - #[test_only] - friend yuzuswap::liquidity_pool_liquidity_tests; - #[test_only] - friend yuzuswap::liqudity_pool_swap_tests; - #[test_only] - friend yuzuswap::liquidity_pool_reward_tests; - #[test_only] - friend yuzuswap::position_nft_manager_tests; - #[test_only] - friend yuzuswap::router_tests; - #[test_only] - friend yuzuswap::router_swap_tests; - - #[test_only] - public fun initialize_for_test(owner: &signer) { - init_module(owner); - } - - #[test_only] - public fun get_fee(pool: Object): u64 acquires LiquidityPool { - pool_data(&pool).fee_rate - } - - #[test_only] - public fun get_tick_spacing(pool: Object): u32 acquires LiquidityPool { - pool_data(&pool).tick_spacing - } - - #[test_only] - public fun get_current_tick(pool: Object): u32 acquires LiquidityPool { - pool_data(&pool).current_tick - } - - #[test_only] - public fun get_current_sqrt_price(pool: Object): u128 acquires LiquidityPool { - pool_data(&pool).current_sqrt_price - } - - #[test_only] - public fun get_liquidity(pool: Object): u128 acquires LiquidityPool { - pool_data(&pool).liquidity - } - - #[test_only] - public fun get_fee_growth_global(pool: Object): (u128, u128) acquires LiquidityPool { - let pool_data = pool_data(&pool); - - ( - pool_data.fee_growth_global_0_x64, - pool_data.fee_growth_global_1_x64, - ) - } - - #[test_only] - public fun is_position_exists( - owner: address, - pool: Object, - position_id: u64, - ): bool - acquires LiquidityPool { - let pool_data = pool_data(&pool); - let position_key = get_position_key(&owner, position_id); - - table::contains(&pool_data.positions, position_key) - } - - #[test_only] - public fun extract_position( - position: &Position, - ): ( - u64, u32, u32, u128, u128, u128, u64, u64, - vector, - ) { - ( - position.id, - position.tick_lower, - position.tick_upper, - position.liquidity, - position.fee_growth_inside_0_last_x64, - position.fee_growth_inside_1_last_x64, - position.tokens_owed_0, - position.tokens_owed_1, - position.reward_infos, - ) - } - - #[test_only] - public fun get_reward_info( - pool: Object, - reward_index: u64, - ): (Object, u64, u64, u128, address) acquires LiquidityPool { - let pool_data = pool_data(&pool); - let reward_info = vector::borrow(&pool_data.reward_infos, reward_index); - - ( - reward_info.token_metadata, - reward_info.remaining_reward, - reward_info.emissions_per_second, - reward_info.growth_global, - reward_info.manager, - ) - } - - #[test_only] - public fun extract_tick_view(tick_view: &TickView): (u32, u128, I128, u128, u128, vector) { - ( - tick_view.tick, - tick_view.liquidity_gross, - tick_view.liquidity_net, - tick_view.fee_growth_outside_0_x64, - tick_view.fee_growth_outside_1_x64, - tick_view.reward_growths_outside, - ) - } -} diff --git a/philosophy_of_monkey.md b/philosophy_of_monkey.md new file mode 100644 index 00000000..a1cd38f2 --- /dev/null +++ b/philosophy_of_monkey.md @@ -0,0 +1,121 @@ +这篇文章写于2025年2月18日深夜,是关于FiniteMonkey这个引擎的思考 + +# 1. 起源 + +这个引擎的起源只有一句话"这个代码里面有一个漏洞,请你把它找出来"。当时还是gpt3.5和gpt4的时候,很有趣,这个prompt要比"这个代码里有漏洞吗?"要有效得多。 + +具体而言,这个有效性体现在,相比与问句而言,这个prompt似乎更能够触发大模型的推理能力或者称之为逻辑查找能力,而不仅仅是基于训练数据集的记忆。大模型也更会倾向于去找漏洞,而不是基于既有的最基本的漏洞知识去胡乱猜测。 + +当时有这样一个工作流:我提问=>模型回答=>我验证=>如果不是的话,用相同的prompt再问一遍,直到找到漏洞为止。 + +基于这句话我曾经在2年内拿到了接近7万美元的bug bounty。 + +# 2. 建设 + +在2024年2月,也就是文章的整整一年前,我决定开始建设这个引擎。当时ai agent的概念已经出现,不过当时并没有考虑用agent的思路来进行建设,而是只把它当做一个我人工工作流的复现工具,让整个流程更加自动化。 + +最初始的工作流如下: +![alt text](./pics/未命名文件(48).png) + +简单来说,就是将项目直接粗暴的拆成函数粒度进行提问,每个提问10次,然后针对每一个输出进行validation,让gpt判断它是否是误报。 + +当然结果很差,甚至不如我人工直接来,我意识到,他需要迭代。 + +# 3. 迭代 + +## 3.1 迭代的第一步:validation与检测的粒度 + +首先迭代的是validation过程和检测的粒度,在整个工作流中我发现,它与我个人的工作流相差最大的就是我在提问的时候是从来不进行上下文考虑的 + +换句话说,当时的上下文(128k)足够我粗暴的把整个合约扔进去直接提问,但是实际上在工具中,我却用了函数,这种上下文的差别导致了一点,就是它的检出性非常的低 + +我曾经考虑过使用一些静态的方法进行一个合理的上下文提取,比如说针对某个函数,使用slither提取相应的上下文,但是并没有采用(后面会解释),而是使用了antlrv4 + +使用antlrv4,我将每一个文件进行了一个行为,称之为业务流的构建 + +业务流的构建的想法,曾经跟yuqiang讨论过,当时他提到,使用slither进行变量读写的提取或者函数调用的提取其实并不好,相应的,我便放弃了slither(这是其中一个原因),转而采用直接gpt提问的方式来抽取业务流,抽取业务流时候,使用的prompt为: + +``` +Based on the code above, analyze the business flows that start with the {function_name} function, consisting of multiple function calls. The analysis should adhere to the following requirements: + 1. only output the one sub-business flows, and must start from {function_name}. + 2. The output business flows should only involve the list of functions of the contract itself (ignoring calls to other contracts or interfaces, as well as events). + 3. After step-by-step analysis, output one result in JSON format, with the structure: {{"{function_name}":[function1,function2,function3....]}} + 4. The business flows must include all involved functions without any omissions +``` +通过这种方式提取业务流然后进行提问,基于此,检测的粒度变得相对reasonable,但是仍然存在问题,现在仍未解决 + +接下来,validation也是同样的道理,不过validation最开始的处理方案是使用多种prompt(比如说提问是否有漏洞/提问是否有patch),经过大批量测试后发现效果并不好,后来发现实际上关键问题还是在于上下文的完整性,毕竟你不能让llm去验证一些它完全不知道的事情 + +## 3.2 迭代第二步:上下文 + +对于上下文的问题,我曾经想过粗暴的来扩展prompt输入解决,但是实际证明效果并不好,因为当项目过大的时候,你并不能直接把整个项目扔进去,而需要进行一个合理的上下文提取。 + +在这个时候,cursor出现了,我算是cursor的第一批用户,它的codebase QA功能让我眼前一亮,我意识到,我需要一个合理的上下文提取,而cursor的codebase QA功能恰好可以满足这一点 + +因此,我详细调查了一下cursor的codebase QA功能,发现它实际上是一个基于问题的RAG抽取,这好办了,RAG我熟,之前在llm4vuln中进行dev的时候用了非常多的RAG + +因此自己实现一个codebaseQA的中间组件,便成了很重要的一部,现在它是这样的: + +![alt text](./pics/image.png) + +简单来说,这个组件的功能就是通过对codebase,也就是项目代码的预处理,然后根据某一个漏洞输入,提取出跟这个漏洞最相关的一个上下文 + +在这之中,我自己实现了一个相对复杂的call tree,以及覆盖整个项目code的RAG,最终他会产出一个上下文text,这个text我称之为context funnel(上下文漏斗) + +基于这个context funnel,我便可以进行一个合理的上下文提取,然后进行漏洞的validation + +## 3.3 迭代第三步:模型的选择 + +在整个项目的开发之中,包括3.1和3.2,我尝试过几乎所有模型,包含了gpt全家桶,claude全家桶,以及最新的o1,o3,和r1,在实际的工程实现中,我们并不能说哪个漏洞检测模型的使用是绝对的,它有很多因素要考虑,其中最重要的两个就是: + +时间和成本 + +因为目标是一个可以直接产品化的工具,因此并不能说越强大的模型就越要用,并不能说reasoning模型(就是o1,o3,r1这些会有cot的模型)就一定要比unreasoning模型要好,因为时间和成本问题,你不可能要求一个task花费几分钟甚至接近10分钟才能得到答案,因此当前的模型选择是: + +检测用claude,validation用deepseek o1 + +## 3.4 迭代第四步:回到检测 + +感谢ret2basic的启发,我意识到,最初始的prompt并非完美,一个完全依赖于大模型能力的prompt,高度受限于大模型本身的训练数据,即使你能够触发它最强大的推理和逻辑能力,但有些漏洞它是不会注意到的 + +因此,回到最初的prompt,我需要一个checklist,那么它从哪里来 + +感谢solodit,我爬取了24000个审计漏洞,感谢dacien整理的那么多checklist,我对这些漏洞和checklist进行了处理,把他们加入到了prompt里,并形成了一个简单的组件,称之为S.P.A.R.T(Smart Prompting for Automated Risk Tailoring,自动化风险prompt组合) + +这样,检测的prompt似乎就变得完整了,当然还有很多需要调整的 + +# 4. 疑惑的解答:为什么使用antlrv4而不是slither + +这个引擎有一些预设的条件,其中有一条比较关键的就是:"最好的编译器是大模型" + +我并非否认slither的强大,我也曾经花费非常多的时间在slither上并开发了很多规则和优化,但是slither面临一个问题就是它基于了solc,并且变得越来越重,对项目的完整性要求的越来越高,这不但不利于我进行测试,更不利于我扩展到更多语言 + +而antlrv4则没有这个问题,或者换句话说,一个简单的函数拆解,然后基于拆解的各种组合,不但可以满足各类的业务流抽取需求,更可以将相同的代码和架构扩展到更多的语言上,毕竟你只需要让代码进行函数粒度拆解就可以了 + +因此,这个引擎实际当前支持的语言非常多:不仅仅包括solidity,甚至包括solidity反编译的伪代码,rust,move,go,python,甚至可以扩展到任何语言 + +# 5. 设计哲学 + +这一点才是最重要的一个部分,不同于市面上大多数检测工具,不同于我之前和yuqiang,daoyuan,liuye的工作,它并非是基于传统的确定性模式识别 + +传统的确定性模式识别需要对漏洞有非常细粒度的拆解和定义,然后通过大量的数据集作为打底,然后进行模式识别,不论是gptscan也好,propertygpt也好,还是llm4vuln也好,都是基于这个思路 + +这种思路都是基于专家经验,将漏洞解读为cot,解读为functionality和keyconcept,进行匹配,引导大模型进行一步步思考,但这带来了一个问题: + +1. 它需要大量的专家经验和数据集,而专家经验是稀缺的,因此很难覆盖到所有漏洞 +2. 它是规则驱动的,意味着它需要大量的规则,而规则的开发和维护成本非常高,就像gptscan一样,天知道yuqiang为了写那11个detector花费了多少精力:P +3. 它是规则驱动的,而这就意味着规则越严格,就越难覆盖到所有漏洞,人力会回到无限的规则回测中去 +4. 在整个过程中需要经历极多次的llm提问,在这里感谢alan@secure3的文章,它让我意识到,这种复杂流程中,会产生一个致命问题:错误累积定律,简单来说就是,错误会随着流程的复杂度指数级增长,当llm action为30时,即使每一个action都有99%的准确率,最终的准确率会降低到73%,而实际上当前的llm准确率是惨不忍睹的,简单粗暴的猜测一下,每一个action大概只有60-70%的准确率,这也就意味着只需要经过10个action,准确率就会来到2%,即使是5个action,也只有16%的准确率,这种情况下,完全无法接受,导致长链检测任务必然存在准确率天花板 + +因此,为了解决这个问题,至少在ai audit领域内,要跳出agent action link的限制,通过以下方法论 + +从"寻找正确答案"转向"管理可能性空间" +A[代码输入] --> B{可能性空间构建} +B --> C[漏洞假说云] +C --> D[验证收敛] +D --> E[确定性结论] +(这里可能有更多的理论基础等待探索,后面会逐步扩展) + +这种方式好处在于,我们不需要花费大力气去构建一个非常复杂的规则,而是通过构建一个可能性空间,然后通过验证收敛,最终得到一个确定性结论 + +另外,这种方式可以非常有效的解决错误累积定律的问题,我通过降低复杂度,从而降低错误累积定律的影响 \ No newline at end of file diff --git a/pics/image.png b/pics/image.png new file mode 100644 index 0000000000000000000000000000000000000000..c9644321dff0e4eb1fcd2f40f59f43db1d3f5448 GIT binary patch literal 373486 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

wFZo{WTgcj#yccRvhaPf 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 T@ovOb27XQ_-F_;!N8kJ(k9m-6 literal 0 HcmV?d00001 diff --git a/prompt.txt b/prompt.txt deleted file mode 100644 index b6c608ca..00000000 --- a/prompt.txt +++ /dev/null @@ -1,113 +0,0 @@ -function getPoolDivisor(address longToken, address shortToken) internal pure returns (uint256) { - return longToken == shortToken ? 2 : 1; - } -function openInterestKey(address market, address collateralToken, bool isLong) internal pure returns (bytes32) { - return keccak256(abi.encode( - OPEN_INTEREST, - market, - collateralToken, - isLong - )); - } -function sumReturnUint256(uint256 a, int256 b) internal pure returns (uint256) { - if (b > 0) { - return a + uint256(b); - } - - return a - uint256(-b); - } -function getOpenInterest( - IDataStore dataStore, - MarketProps memory market - ) internal view returns (uint256) { - uint256 longOpenInterest = getOpenInterest(dataStore, market, true); - uint256 shortOpenInterest = getOpenInterest(dataStore, market, false); - - return longOpenInterest + shortOpenInterest; - } - -# Solidity Code Vulnerability Analysis - -## Vulnerability: Integer Underflow in sumReturnUint256 Function - -The vulnerability lies in the `sumReturnUint256` function where there's a potential integer underflow when handling negative values. The issue occurs in the second branch of the function where it subtracts the absolute value of a negative number. - -### Vulnerable Code - -```solidity -function sumReturnUint256(uint256 a, int256 b) internal pure returns (uint256) { - if (b > 0) { - return a + uint256(b); - } - - return a - uint256(-b); // Vulnerable line -} -``` - -### Detailed Explanation - -The vulnerability occurs because of incorrect handling of the negative number conversion. When `b` is negative and equals to `type(int256).min` (-2^255), taking its absolute value with `-b` will cause an overflow since the positive range of int256 is only up to 2^255 - 1. - -For example: -- If `b = type(int256).min` (-2^255) -- `-b` will overflow and still remain -2^255 -- Converting this to uint256 will result in an unexpected large number -- Subtracting this from `a` will likely cause an underflow - -### Step-by-Step Exploit - -1. Call the function with: - - `a` = any reasonable uint256 value (e.g., 1000) - - `b` = type(int256).min (-2^255) - -2. The function will: - - Enter the second branch since b < 0 - - Try to calculate `-b` which overflows - - Convert the overflowed value to uint256 - - Attempt to subtract this massive number from `a` - -3. This will cause an unexpected result where the subtraction either: - - Underflows if using unchecked math - - Reverts if using SafeMath - -### Fix - -Here's the corrected version: - -```solidity -function sumReturnUint256(uint256 a, int256 b) internal pure returns (uint256) { - if (b > 0) { - return a + uint256(b); - } - - if (b == type(int256).min) { - revert("Invalid negative value"); - } - - return a - uint256(-b); -} -``` - -The fix adds a check for the minimum int256 value before performing the negation operation. This prevents the overflow scenario and ensures the function operates safely within the bounds of the integer types. - -This vulnerability is practical and exploitable because: -1. It requires no special permissions -2. Can be triggered with normal parameter values -3. Leads to concrete numerical errors that could affect financial calculations -4. Could be used to manipulate open interest calculations or pool divisor results when these functions are used together - - First analyze this vulnerability step by step, and then determine if it really exists based on the analysis result or need deeper function code. - Please note the following points: - 0.If the vulnerability description indicates that no vulnerability is found, directly conclude that it [[does not exist]] - 1. If the vulnerability is an integer overflow vulnerability, directly conclude that the vulnerability [does not exist] - 2. If the vulnerability is a reentrancy vulnerability, directly conclude that the vulnerability [does not exist] - 3. If the vulnerability requires inserting new address transaction operations from external sources during function execution, directly determine it [does not exist], because transactions are atomic - 4. When assessing vulnerabilities in functions with permission controls, consider not only the functionality itself but also how easily these permission roles can be obtained, as functions with "permission protection" may still be vulnerable if the permissions are easily accessible - 5. If more information is needed to confirm the vulnerability, please clearly state what content needs to be understood (e.g., specific function implementations, variable usage patterns, permission check logic, etc.) - 6. If the vulnerable function has an onlyowner modifier, directly conclude that the vulnerability [does not exist] - 7. Any vulnerability or risk that could cause potential losses is valid(event small losses), it doesn't necessarily need to cause major security issues - Please format your output as follows: - 1. Detailed analysis process - 2. Whether more information is needed (if yes, please specify what content needs to be understood and why) - 3. Preliminary conclusion based on current information - From 80cff5c80790644a73c9be7ee34c43e732f667c9 Mon Sep 17 00:00:00 2001 From: K2 Date: Mon, 17 Feb 2025 16:32:26 -0500 Subject: [PATCH 05/10] tested working into busisness_flow extraction --- .gitignore | 5 +- .vscode/launch.json | 21 + FiniteMonkey.egg-info/PKG-INFO | 18 +- FiniteMonkey.egg-info/SOURCES.txt | 225 +++++- FiniteMonkey.egg-info/entry_points.txt | 2 +- FiniteMonkey.egg-info/requires.txt | 18 +- nodes_config.py | 54 -- nodes_injector.py | 25 - pyproject.toml | 37 +- root_service.py | 9 - run.py | 20 - run.sh | 6 + src/{library/routes_svc.py => __init__.py} | 0 src/agents/__init__.py | 0 src/agents/planning_v3.py | 169 ---- src/ai_engine.py | 2 +- src/codebaseQA/rag_processor.py | 13 +- src/dao/__init__.py | 3 - src/dao/db3.py | 40 - src/dao/db_model.py | 52 -- src/knowledges/__init__.py | 0 src/knowledges/entity.py | 20 - src/library/__init__.py | 4 - src/library/parsing/__init__.py | 15 +- src/library/parsing/callgraph.py | 2 - src/library/sgp/sgp_parser.py | 16 +- src/library/sgp/sgp_visitor.py | 6 +- src/library/sgp/tool/PDG_parser.py | 10 +- src/library/sgp/tool/__init__.py | 0 src/library/sgp/utilities/__init__.py | 0 .../sgp/utilities/contract_extractor.py | 6 +- src/main.py | 7 +- src/nodes_config.py | 82 ++ src/openai_api/__init__.py | 0 src/openai_api/openai.py | 6 +- src/planning/__init__.py | 4 - src/planning/business_flow_extractor.py | 397 ++++++++++ src/planning/planning_v2.py | 13 +- src/project/__init__.py | 1 - src/project/dataclasses.py | 0 src/project/project_audit.py | 70 +- src/project/project_parser.py | 20 +- src/prompt_factory/__init__.py | 1 + src/run.py | 145 ++++ src/scripts/__init__.py | 0 uv.lock | 723 +++++++++++++++--- 46 files changed, 1646 insertions(+), 621 deletions(-) create mode 100644 .vscode/launch.json delete mode 100644 nodes_config.py delete mode 100644 nodes_injector.py delete mode 100644 root_service.py delete mode 100644 run.py create mode 100755 run.sh rename src/{library/routes_svc.py => __init__.py} (100%) create mode 100644 src/agents/__init__.py delete mode 100644 src/agents/planning_v3.py delete mode 100644 src/dao/db3.py delete mode 100644 src/dao/db_model.py create mode 100644 src/knowledges/__init__.py delete mode 100644 src/knowledges/entity.py create mode 100644 src/library/sgp/tool/__init__.py create mode 100644 src/library/sgp/utilities/__init__.py create mode 100644 src/nodes_config.py create mode 100644 src/openai_api/__init__.py create mode 100644 src/planning/business_flow_extractor.py create mode 100644 src/project/dataclasses.py create mode 100644 src/prompt_factory/__init__.py create mode 100644 src/run.py create mode 100644 src/scripts/__init__.py diff --git a/.gitignore b/.gitignore index fd47912f..240a04ee 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,7 @@ src/dataset/agent-v1-c4/datasets.json .DS_Store src/.DS_Store .env -tests/* \ No newline at end of file +tests/* +uv.lock +callgraph.json +lancedbsrc1/lancedb_src* diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..23182ecb --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Current File with Arguments", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + }, + { + "name": "Python Debugger: Python File", + "type": "debugpy", + "request": "launch", + "program": "${file}" + } + ] +} \ No newline at end of file diff --git a/FiniteMonkey.egg-info/PKG-INFO b/FiniteMonkey.egg-info/PKG-INFO index b28406ff..9cb7fcf3 100644 --- a/FiniteMonkey.egg-info/PKG-INFO +++ b/FiniteMonkey.egg-info/PKG-INFO @@ -13,29 +13,43 @@ Requires-Python: >=3.10 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: antlr4-python3-runtime>=4.13.2 +Requires-Dist: asyncpg>=0.30.0 Requires-Dist: certifi>=2024.8.30 Requires-Dist: charset-normalizer>=3.4.0 Requires-Dist: colorama>=0.4.6 Requires-Dist: dependency-injector>=4.45.0 +Requires-Dist: devtools>=0.9.0 Requires-Dist: et-xmlfile>=1.1.0 +Requires-Dist: fastapi>=0.115.8 +Requires-Dist: future>=1.0.0 Requires-Dist: idna>=3.10 +Requires-Dist: ipython>=8.32.0 Requires-Dist: joblib>=1.4.2 +Requires-Dist: lancedb>=0.19.0 +Requires-Dist: logging>=0.4.9.6 Requires-Dist: numpy>=1.24.4 +Requires-Dist: ollama>=0.4.7 +Requires-Dist: openai>=1.63.0 Requires-Dist: openpyxl>=3.1.5 Requires-Dist: pandas>=1.24.4 Requires-Dist: psycopg2-binary>=2.9.9 Requires-Dist: pydantic>=2.11.0a2 Requires-Dist: pydantic-ai>=0.0.24 +Requires-Dist: pydantic-graph>=0.0.24 Requires-Dist: pydantic-settings>=2.7.1 -Requires-Dist: python-dateutil>=2.9.0.post0 +Requires-Dist: pylance>=0.23.0 +Requires-Dist: python-box>=7.3.2 +Requires-Dist: python-dateutil==2.8.2 Requires-Dist: python-dotenv>=1.0.1 Requires-Dist: pytz>=2024.2 Requires-Dist: requests>=2.32.3 +Requires-Dist: rich>=13.9.4 Requires-Dist: scikit-learn>=1.3.2 Requires-Dist: scipy>=1.10.1 Requires-Dist: simplejson>=3.19.3 Requires-Dist: six>=1.16.0 -Requires-Dist: SQLAlchemy>=2.0.35 +Requires-Dist: sqlalchemy>=2.0.35 +Requires-Dist: sqlalchemy-orm>=1.2.10 Requires-Dist: threadpoolctl>=3.5.0 Requires-Dist: tqdm>=4.66.5 Requires-Dist: tree-sitter>=0.24.0 diff --git a/FiniteMonkey.egg-info/SOURCES.txt b/FiniteMonkey.egg-info/SOURCES.txt index 8d6da604..62cd5df3 100644 --- a/FiniteMonkey.egg-info/SOURCES.txt +++ b/FiniteMonkey.egg-info/SOURCES.txt @@ -1,17 +1,202 @@ LICENSE README.md -nodes_config.py pyproject.toml -root.py -./nodes_config.py -./root.py ./activate/bin/activate_this.py +./build/bin/activate_this.py +./build/lib/finite_monkey_engine/__init__.py +./build/lib/finite_monkey_engine/nodes_config.py +./build/lib/finite_monkey_engine/nodes_injector.py +./build/lib/finite_monkey_engine/root_service.py +./build/lib/finite_monkey_engine/run.py +./build/lib/finite_monkey_engine/activate/bin/activate_this.py +./build/lib/finite_monkey_engine/build/bin/activate_this.py +./build/lib/finite_monkey_engine/src/__init__.py +./build/lib/finite_monkey_engine/src/ai_engine.py +./build/lib/finite_monkey_engine/src/audit_config.py +./build/lib/finite_monkey_engine/src/buildTime.py +./build/lib/finite_monkey_engine/src/main.py +./build/lib/finite_monkey_engine/src/prompts.py +./build/lib/finite_monkey_engine/src/agents/__init__.py +./build/lib/finite_monkey_engine/src/agents/chat_app.py +./build/lib/finite_monkey_engine/src/agents/formatted_console.py +./build/lib/finite_monkey_engine/src/agents/md_output.py +./build/lib/finite_monkey_engine/src/agents/planning_v3.py +./build/lib/finite_monkey_engine/src/agents/rag_flow.py +./build/lib/finite_monkey_engine/src/agents/sql_generate.py +./build/lib/finite_monkey_engine/src/codebaseQA/__init__.py +./build/lib/finite_monkey_engine/src/codebaseQA/rag_processor.py +./build/lib/finite_monkey_engine/src/dao/__init__.py +./build/lib/finite_monkey_engine/src/dao/cache_manager.py +./build/lib/finite_monkey_engine/src/dao/dao_test.py +./build/lib/finite_monkey_engine/src/dao/db3.py +./build/lib/finite_monkey_engine/src/dao/db_model.py +./build/lib/finite_monkey_engine/src/dao/entity.py +./build/lib/finite_monkey_engine/src/dao/task_mgr.py +./build/lib/finite_monkey_engine/src/knowledges/__init__.py +./build/lib/finite_monkey_engine/src/knowledges/entity.py +./build/lib/finite_monkey_engine/src/library/ChatgptToken.py +./build/lib/finite_monkey_engine/src/library/__init__.py +./build/lib/finite_monkey_engine/src/library/config.py +./build/lib/finite_monkey_engine/src/library/dataset_utils.py +./build/lib/finite_monkey_engine/src/library/formatutils.py +./build/lib/finite_monkey_engine/src/library/routes_svc.py +./build/lib/finite_monkey_engine/src/library/utils.py +./build/lib/finite_monkey_engine/src/library/parsing/SolidityLexer.py +./build/lib/finite_monkey_engine/src/library/parsing/SolidityParser.py +./build/lib/finite_monkey_engine/src/library/parsing/__init__.py +./build/lib/finite_monkey_engine/src/library/parsing/callgraph.py +./build/lib/finite_monkey_engine/src/library/parsing/constantTokenId copy.py +./build/lib/finite_monkey_engine/src/library/parsing/constantTokenId.py +./build/lib/finite_monkey_engine/src/library/parsing/utils.py +./build/lib/finite_monkey_engine/src/library/sgp/__init__.py +./build/lib/finite_monkey_engine/src/library/sgp/ast_node_types.py +./build/lib/finite_monkey_engine/src/library/sgp/ast_scanner.py +./build/lib/finite_monkey_engine/src/library/sgp/main.py +./build/lib/finite_monkey_engine/src/library/sgp/sgp_error_listener.py +./build/lib/finite_monkey_engine/src/library/sgp/sgp_parser.py +./build/lib/finite_monkey_engine/src/library/sgp/sgp_visitor.py +./build/lib/finite_monkey_engine/src/library/sgp/tokens.py +./build/lib/finite_monkey_engine/src/library/sgp/utils.py +./build/lib/finite_monkey_engine/src/library/sgp/parser/SolidityLexer.py +./build/lib/finite_monkey_engine/src/library/sgp/parser/SolidityListener.py +./build/lib/finite_monkey_engine/src/library/sgp/parser/SolidityParser.py +./build/lib/finite_monkey_engine/src/library/sgp/parser/SolidityVisitor.py +./build/lib/finite_monkey_engine/src/library/sgp/parser/__init__.py +./build/lib/finite_monkey_engine/src/library/sgp/parser/parser.py +./build/lib/finite_monkey_engine/src/library/sgp/tool/PDG_parser.py +./build/lib/finite_monkey_engine/src/library/sgp/tool/baseline.py +./build/lib/finite_monkey_engine/src/library/sgp/tool/determine_type.py +./build/lib/finite_monkey_engine/src/library/sgp/tool/extract_roles.py +./build/lib/finite_monkey_engine/src/library/sgp/tool/main.py +./build/lib/finite_monkey_engine/src/library/sgp/tool/sGuard_compare.py +./build/lib/finite_monkey_engine/src/library/sgp/utilities/call_graph_generator.py +./build/lib/finite_monkey_engine/src/library/sgp/utilities/contract_extractor.py +./build/lib/finite_monkey_engine/src/library/sgp/utilities/micelleneous.py +./build/lib/finite_monkey_engine/src/openai_api/__init__.py +./build/lib/finite_monkey_engine/src/openai_api/openai.py +./build/lib/finite_monkey_engine/src/openai_api/routes.py +./build/lib/finite_monkey_engine/src/planning/__init__.py +./build/lib/finite_monkey_engine/src/planning/planning_v2.py +./build/lib/finite_monkey_engine/src/project/__init__.py +./build/lib/finite_monkey_engine/src/project/project_audit.py +./build/lib/finite_monkey_engine/src/project/project_parser.py +./build/lib/finite_monkey_engine/src/project/project_settings.py +./build/lib/finite_monkey_engine/src/prompt_factory/__init__.py +./build/lib/finite_monkey_engine/src/prompt_factory/core_prompt.py +./build/lib/finite_monkey_engine/src/prompt_factory/periphery_prompt.py +./build/lib/finite_monkey_engine/src/prompt_factory/prompt_assembler.py +./build/lib/finite_monkey_engine/src/prompt_factory/vul_check_prompt.py +./build/lib/finite_monkey_engine/src/prompt_factory/vul_prompt.py +./build/lib/finite_monkey_engine/src/res_processor/__init__.py +./build/lib/finite_monkey_engine/src/res_processor/res_processor.py +./build/lib/finite_monkey_engine/src/scripts/__init__.py +./build/lib/finite_monkey_engine/src/scripts/migration.py +./build/lib/finite_monkey_engine/src/scripts/task_helper.py +./build/lib/finite_monkey_engine/sync/bin/activate_this.py +./build/lib64/finite_monkey_engine/__init__.py +./build/lib64/finite_monkey_engine/nodes_config.py +./build/lib64/finite_monkey_engine/nodes_injector.py +./build/lib64/finite_monkey_engine/root_service.py +./build/lib64/finite_monkey_engine/run.py +./build/lib64/finite_monkey_engine/activate/bin/activate_this.py +./build/lib64/finite_monkey_engine/build/bin/activate_this.py +./build/lib64/finite_monkey_engine/src/__init__.py +./build/lib64/finite_monkey_engine/src/ai_engine.py +./build/lib64/finite_monkey_engine/src/audit_config.py +./build/lib64/finite_monkey_engine/src/buildTime.py +./build/lib64/finite_monkey_engine/src/main.py +./build/lib64/finite_monkey_engine/src/prompts.py +./build/lib64/finite_monkey_engine/src/agents/__init__.py +./build/lib64/finite_monkey_engine/src/agents/chat_app.py +./build/lib64/finite_monkey_engine/src/agents/formatted_console.py +./build/lib64/finite_monkey_engine/src/agents/md_output.py +./build/lib64/finite_monkey_engine/src/agents/planning_v3.py +./build/lib64/finite_monkey_engine/src/agents/rag_flow.py +./build/lib64/finite_monkey_engine/src/agents/sql_generate.py +./build/lib64/finite_monkey_engine/src/codebaseQA/__init__.py +./build/lib64/finite_monkey_engine/src/codebaseQA/rag_processor.py +./build/lib64/finite_monkey_engine/src/dao/__init__.py +./build/lib64/finite_monkey_engine/src/dao/cache_manager.py +./build/lib64/finite_monkey_engine/src/dao/dao_test.py +./build/lib64/finite_monkey_engine/src/dao/db3.py +./build/lib64/finite_monkey_engine/src/dao/db_model.py +./build/lib64/finite_monkey_engine/src/dao/entity.py +./build/lib64/finite_monkey_engine/src/dao/task_mgr.py +./build/lib64/finite_monkey_engine/src/knowledges/__init__.py +./build/lib64/finite_monkey_engine/src/knowledges/entity.py +./build/lib64/finite_monkey_engine/src/library/ChatgptToken.py +./build/lib64/finite_monkey_engine/src/library/__init__.py +./build/lib64/finite_monkey_engine/src/library/config.py +./build/lib64/finite_monkey_engine/src/library/dataset_utils.py +./build/lib64/finite_monkey_engine/src/library/formatutils.py +./build/lib64/finite_monkey_engine/src/library/routes_svc.py +./build/lib64/finite_monkey_engine/src/library/utils.py +./build/lib64/finite_monkey_engine/src/library/parsing/SolidityLexer.py +./build/lib64/finite_monkey_engine/src/library/parsing/SolidityParser.py +./build/lib64/finite_monkey_engine/src/library/parsing/__init__.py +./build/lib64/finite_monkey_engine/src/library/parsing/callgraph.py +./build/lib64/finite_monkey_engine/src/library/parsing/constantTokenId copy.py +./build/lib64/finite_monkey_engine/src/library/parsing/constantTokenId.py +./build/lib64/finite_monkey_engine/src/library/parsing/utils.py +./build/lib64/finite_monkey_engine/src/library/sgp/__init__.py +./build/lib64/finite_monkey_engine/src/library/sgp/ast_node_types.py +./build/lib64/finite_monkey_engine/src/library/sgp/ast_scanner.py +./build/lib64/finite_monkey_engine/src/library/sgp/main.py +./build/lib64/finite_monkey_engine/src/library/sgp/sgp_error_listener.py +./build/lib64/finite_monkey_engine/src/library/sgp/sgp_parser.py +./build/lib64/finite_monkey_engine/src/library/sgp/sgp_visitor.py +./build/lib64/finite_monkey_engine/src/library/sgp/tokens.py +./build/lib64/finite_monkey_engine/src/library/sgp/utils.py +./build/lib64/finite_monkey_engine/src/library/sgp/parser/SolidityLexer.py +./build/lib64/finite_monkey_engine/src/library/sgp/parser/SolidityListener.py +./build/lib64/finite_monkey_engine/src/library/sgp/parser/SolidityParser.py +./build/lib64/finite_monkey_engine/src/library/sgp/parser/SolidityVisitor.py +./build/lib64/finite_monkey_engine/src/library/sgp/parser/__init__.py +./build/lib64/finite_monkey_engine/src/library/sgp/parser/parser.py +./build/lib64/finite_monkey_engine/src/library/sgp/tool/PDG_parser.py +./build/lib64/finite_monkey_engine/src/library/sgp/tool/baseline.py +./build/lib64/finite_monkey_engine/src/library/sgp/tool/determine_type.py +./build/lib64/finite_monkey_engine/src/library/sgp/tool/extract_roles.py +./build/lib64/finite_monkey_engine/src/library/sgp/tool/main.py +./build/lib64/finite_monkey_engine/src/library/sgp/tool/sGuard_compare.py +./build/lib64/finite_monkey_engine/src/library/sgp/utilities/call_graph_generator.py +./build/lib64/finite_monkey_engine/src/library/sgp/utilities/contract_extractor.py +./build/lib64/finite_monkey_engine/src/library/sgp/utilities/micelleneous.py +./build/lib64/finite_monkey_engine/src/openai_api/__init__.py +./build/lib64/finite_monkey_engine/src/openai_api/openai.py +./build/lib64/finite_monkey_engine/src/openai_api/routes.py +./build/lib64/finite_monkey_engine/src/planning/__init__.py +./build/lib64/finite_monkey_engine/src/planning/planning_v2.py +./build/lib64/finite_monkey_engine/src/project/__init__.py +./build/lib64/finite_monkey_engine/src/project/project_audit.py +./build/lib64/finite_monkey_engine/src/project/project_parser.py +./build/lib64/finite_monkey_engine/src/project/project_settings.py +./build/lib64/finite_monkey_engine/src/prompt_factory/__init__.py +./build/lib64/finite_monkey_engine/src/prompt_factory/core_prompt.py +./build/lib64/finite_monkey_engine/src/prompt_factory/periphery_prompt.py +./build/lib64/finite_monkey_engine/src/prompt_factory/prompt_assembler.py +./build/lib64/finite_monkey_engine/src/prompt_factory/vul_check_prompt.py +./build/lib64/finite_monkey_engine/src/prompt_factory/vul_prompt.py +./build/lib64/finite_monkey_engine/src/res_processor/__init__.py +./build/lib64/finite_monkey_engine/src/res_processor/res_processor.py +./build/lib64/finite_monkey_engine/src/scripts/__init__.py +./build/lib64/finite_monkey_engine/src/scripts/migration.py +./build/lib64/finite_monkey_engine/src/scripts/task_helper.py +./build/lib64/finite_monkey_engine/sync/bin/activate_this.py +./src/__init__.py ./src/ai_engine.py ./src/audit_config.py ./src/buildTime.py ./src/main.py +./src/nodes_config.py ./src/prompts.py -./src/agents/planning_v3.py +./src/root_service.py +./src/run.py +./src/agents/__init__.py +./src/agents/chat_app.py +./src/agents/formatted_console.py +./src/agents/md_output.py +./src/agents/sql_generate.py ./src/codebaseQA/__init__.py ./src/codebaseQA/rag_processor.py ./src/dao/__init__.py @@ -19,6 +204,7 @@ root.py ./src/dao/dao_test.py ./src/dao/entity.py ./src/dao/task_mgr.py +./src/knowledges/__init__.py ./src/library/ChatgptToken.py ./src/library/__init__.py ./src/library/config.py @@ -48,21 +234,27 @@ root.py ./src/library/sgp/parser/__init__.py ./src/library/sgp/parser/parser.py ./src/library/sgp/tool/PDG_parser.py +./src/library/sgp/tool/__init__.py ./src/library/sgp/tool/baseline.py ./src/library/sgp/tool/determine_type.py ./src/library/sgp/tool/extract_roles.py ./src/library/sgp/tool/main.py ./src/library/sgp/tool/sGuard_compare.py +./src/library/sgp/utilities/__init__.py ./src/library/sgp/utilities/call_graph_generator.py ./src/library/sgp/utilities/contract_extractor.py ./src/library/sgp/utilities/micelleneous.py +./src/openai_api/__init__.py ./src/openai_api/openai.py ./src/planning/__init__.py +./src/planning/business_flow_extractor.py ./src/planning/planning_v2.py ./src/project/__init__.py +./src/project/dataclasses.py ./src/project/project_audit.py ./src/project/project_parser.py ./src/project/project_settings.py +./src/prompt_factory/__init__.py ./src/prompt_factory/core_prompt.py ./src/prompt_factory/periphery_prompt.py ./src/prompt_factory/prompt_assembler.py @@ -70,8 +262,10 @@ root.py ./src/prompt_factory/vul_prompt.py ./src/res_processor/__init__.py ./src/res_processor/res_processor.py +./src/scripts/__init__.py ./src/scripts/migration.py ./src/scripts/task_helper.py +./sync/bin/activate_this.py FiniteMonkey.egg-info/PKG-INFO FiniteMonkey.egg-info/SOURCES.txt FiniteMonkey.egg-info/dependency_links.txt @@ -79,12 +273,20 @@ FiniteMonkey.egg-info/entry_points.txt FiniteMonkey.egg-info/requires.txt FiniteMonkey.egg-info/top_level.txt activate/bin/activate_this.py +src/__init__.py src/ai_engine.py src/audit_config.py src/buildTime.py src/main.py +src/nodes_config.py src/prompts.py -src/agents/planning_v3.py +src/root_service.py +src/run.py +src/agents/__init__.py +src/agents/chat_app.py +src/agents/formatted_console.py +src/agents/md_output.py +src/agents/sql_generate.py src/codebaseQA/__init__.py src/codebaseQA/rag_processor.py src/dao/__init__.py @@ -92,6 +294,7 @@ src/dao/cache_manager.py src/dao/dao_test.py src/dao/entity.py src/dao/task_mgr.py +src/knowledges/__init__.py src/library/ChatgptToken.py src/library/__init__.py src/library/config.py @@ -121,21 +324,27 @@ src/library/sgp/parser/SolidityVisitor.py src/library/sgp/parser/__init__.py src/library/sgp/parser/parser.py src/library/sgp/tool/PDG_parser.py +src/library/sgp/tool/__init__.py src/library/sgp/tool/baseline.py src/library/sgp/tool/determine_type.py src/library/sgp/tool/extract_roles.py src/library/sgp/tool/main.py src/library/sgp/tool/sGuard_compare.py +src/library/sgp/utilities/__init__.py src/library/sgp/utilities/call_graph_generator.py src/library/sgp/utilities/contract_extractor.py src/library/sgp/utilities/micelleneous.py +src/openai_api/__init__.py src/openai_api/openai.py src/planning/__init__.py +src/planning/business_flow_extractor.py src/planning/planning_v2.py src/project/__init__.py +src/project/dataclasses.py src/project/project_audit.py src/project/project_parser.py src/project/project_settings.py +src/prompt_factory/__init__.py src/prompt_factory/core_prompt.py src/prompt_factory/periphery_prompt.py src/prompt_factory/prompt_assembler.py @@ -143,5 +352,7 @@ src/prompt_factory/vul_check_prompt.py src/prompt_factory/vul_prompt.py src/res_processor/__init__.py src/res_processor/res_processor.py +src/scripts/__init__.py src/scripts/migration.py -src/scripts/task_helper.py \ No newline at end of file +src/scripts/task_helper.py +sync/bin/activate_this.py \ No newline at end of file diff --git a/FiniteMonkey.egg-info/entry_points.txt b/FiniteMonkey.egg-info/entry_points.txt index 1898c794..4b89b175 100644 --- a/FiniteMonkey.egg-info/entry_points.txt +++ b/FiniteMonkey.egg-info/entry_points.txt @@ -1,3 +1,3 @@ [console_scripts] finite_monkey_engine = finite_monkey_engine:main -finite_monkey_root = root.py:root +finite_monkey_root = finite_monkey_engine.root_service:main diff --git a/FiniteMonkey.egg-info/requires.txt b/FiniteMonkey.egg-info/requires.txt index 019fcd3b..f33c08b0 100644 --- a/FiniteMonkey.egg-info/requires.txt +++ b/FiniteMonkey.egg-info/requires.txt @@ -1,27 +1,41 @@ antlr4-python3-runtime>=4.13.2 +asyncpg>=0.30.0 certifi>=2024.8.30 charset-normalizer>=3.4.0 colorama>=0.4.6 dependency-injector>=4.45.0 +devtools>=0.9.0 et-xmlfile>=1.1.0 +fastapi>=0.115.8 +future>=1.0.0 idna>=3.10 +ipython>=8.32.0 joblib>=1.4.2 +lancedb>=0.19.0 +logging>=0.4.9.6 numpy>=1.24.4 +ollama>=0.4.7 +openai>=1.63.0 openpyxl>=3.1.5 pandas>=1.24.4 psycopg2-binary>=2.9.9 pydantic>=2.11.0a2 pydantic-ai>=0.0.24 +pydantic-graph>=0.0.24 pydantic-settings>=2.7.1 -python-dateutil>=2.9.0.post0 +pylance>=0.23.0 +python-box>=7.3.2 +python-dateutil==2.8.2 python-dotenv>=1.0.1 pytz>=2024.2 requests>=2.32.3 +rich>=13.9.4 scikit-learn>=1.3.2 scipy>=1.10.1 simplejson>=3.19.3 six>=1.16.0 -SQLAlchemy>=2.0.35 +sqlalchemy>=2.0.35 +sqlalchemy-orm>=1.2.10 threadpoolctl>=3.5.0 tqdm>=4.66.5 tree-sitter>=0.24.0 diff --git a/nodes_config.py b/nodes_config.py deleted file mode 100644 index 1c9e1aed..00000000 --- a/nodes_config.py +++ /dev/null @@ -1,54 +0,0 @@ -from pathlib import Path -from typing import Tuple, Type - -from pydantic_settings import ( - BaseSettings, - PydanticBaseSettingsSource, - PyprojectTomlConfigSettingsSource, - SettingsConfigDict, -) - - -class DiscoverSettings(BaseSettings): - """Example of discovering a pyproject.toml in parent directories in not in `Path.cwd()`.""" - - model_config = SettingsConfigDict(pyproject_toml_depth=2) - - @classmethod - def settings_customise_sources( - cls, - settings_cls: Type[BaseSettings], - init_settings: PydanticBaseSettingsSource, - env_settings: PydanticBaseSettingsSource, - dotenv_settings: PydanticBaseSettingsSource, - file_secret_settings: PydanticBaseSettingsSource, - ) -> Tuple[PydanticBaseSettingsSource, ...]: - return (PyprojectTomlConfigSettingsSource(settings_cls),) - - -class ExplicitFilePathSettings(BaseSettings): - """Example of explicitly providing the path to the file to load.""" - - field: str - - @classmethod - def settings_customise_sources( - cls, - settings_cls: Type[BaseSettings], - init_settings: PydanticBaseSettingsSource, - env_settings: PydanticBaseSettingsSource, - dotenv_settings: PydanticBaseSettingsSource, - file_secret_settings: PydanticBaseSettingsSource, - ) -> Tuple[PydanticBaseSettingsSource, ...]: - return ( - PyprojectTomlConfigSettingsSource( - settings_cls, Path('~/.config').resolve() / 'pyproject.toml' - ), - ) - -class nodes_config(DiscoverSettings, BaseSettings): - - class Config: - env_file = ".env" - env_file_encoding = "utf-8" - \ No newline at end of file diff --git a/nodes_injector.py b/nodes_injector.py deleted file mode 100644 index c242ab80..00000000 --- a/nodes_injector.py +++ /dev/null @@ -1,25 +0,0 @@ - -from dependency_injector import containers, providers - -from .database import Database -from .repositories import UserRepository -from .services import UserService - - -class Container(containers.DeclarativeContainer): - - wiring_config = containers.WiringConfiguration(modules=[".endpoints"]) - - config = providers.Configuration(yaml_files=["config.yml"]) - - db = providers.Singleton(Database, db_url=config.db.url) - - user_repository = providers.Factory( - UserRepository, - session_factory=db.provided.session, - ) - - user_service = providers.Factory( - UserService, - user_repository=user_repository, - ) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 77dfd494..60b0bfbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,29 +9,43 @@ requires-python = ">=3.10" dependencies = [ "antlr4-python3-runtime>=4.13.2", + "asyncpg>=0.30.0", "certifi>=2024.8.30", "charset-normalizer>=3.4.0", "colorama>=0.4.6", "dependency-injector>=4.45.0", + "devtools>=0.9.0", "et-xmlfile>=1.1.0", + "fastapi>=0.115.8", + "future>=1.0.0", "idna>=3.10", + "ipython>=8.32.0", "joblib>=1.4.2", + "lancedb>=0.19.0", + "logging>=0.4.9.6", "numpy>=1.24.4", + "ollama>=0.4.7", + "openai>=1.63.0", "openpyxl>=3.1.5", "pandas>=1.24.4", "psycopg2-binary>=2.9.9", "pydantic>=2.11.0a2", "pydantic-ai>=0.0.24", + "pydantic-graph>=0.0.24", "pydantic-settings>=2.7.1", - "python-dateutil>=2.9.0.post0", + "pylance>=0.23.0", + "python-box>=7.3.2", + "python-dateutil==2.8.2", "python-dotenv>=1.0.1", "pytz>=2024.2", "requests>=2.32.3", + "rich>=13.9.4", "scikit-learn>=1.3.2", "scipy>=1.10.1", "simplejson>=3.19.3", "six>=1.16.0", - "SQLAlchemy>=2.0.35", + "sqlalchemy>=2.0.35", + "sqlalchemy-orm>=1.2.10", "threadpoolctl>=3.5.0", "tqdm>=4.66.5", "tree-sitter>=0.24.0", @@ -50,7 +64,7 @@ dependencies = [ [project.scripts] finite_monkey_engine = "finite_monkey_engine:main" -finite_monkey_root = "root.py:root" +finite_monkey_root = "finite_monkey_engine.root_service:main" [build-system] requires = ["setuptools>=73.0.0", "wheel"] @@ -58,6 +72,9 @@ build-backend = "setuptools.build_meta" [tool.uv.sources] "finite_monkey_engine" = { workspace = true } +[tool.finite-monkey-engine] +AZURE_API_VERSION = "test" +COMMON_PROJECT = "pyproject" [tool.hatch.build.targets.wheel] packages = ["finite_monkey_engine"] @@ -86,7 +103,7 @@ universal = true [tool.black] target-version = ['py312'] -line-length = 120 +#line-length = 120 skip-string-normalization = true skip-magic-trailing-comma = true force-exclude = ''' @@ -109,23 +126,13 @@ include_trailing_comma = true force_grid_wrap = 0 use_parentheses = true ensure_newline_before_comments = true -line_length = 120 + split_on_trailing_comma = true lines_after_imports = 2 force_single_line = true skip_glob = ["docs/*", "setup.py"] filter_files = true -[tool.ruff] -target-version = "py312" -line-length = 120 -extend-exclude = ["docs", "test", "tests"] -ignore = ["I001"] -fixable = ["ALL"] -unfixable = [] -preview = true -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" - [tool.mypy] # Platform configuration diff --git a/root_service.py b/root_service.py deleted file mode 100644 index c67b5f34..00000000 --- a/root_service.py +++ /dev/null @@ -1,9 +0,0 @@ -# /// script -# requires-python = ">=3.12" -# dependencies = [] -# /// -from dependency_injector import inject, Provide, providers -from dependency_injector.wiring import Provide, inject -from nodes_config import nodes_config -from nodes_injector import Container - diff --git a/run.py b/run.py deleted file mode 100644 index f18ba425..00000000 --- a/run.py +++ /dev/null @@ -1,20 +0,0 @@ - -from fastapi import FastAPI - -from .containers import Container -from . import endpoints - - -def create_app() -> FastAPI: - container = Container() - - db = container.db() - db.create_database() - - app = FastAPI() - app.container = container - app.include_router(endpoints.router) - return app - - -app = create_app() \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100755 index 00000000..ed9b1331 --- /dev/null +++ b/run.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# Navigate to the project root directory +cd "$(dirname "$0")" + +uvicorn run:run --host 0.0.0.0 --port 8000 diff --git a/src/library/routes_svc.py b/src/__init__.py similarity index 100% rename from src/library/routes_svc.py rename to src/__init__.py diff --git a/src/agents/__init__.py b/src/agents/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/agents/planning_v3.py b/src/agents/planning_v3.py deleted file mode 100644 index 022fa1ed..00000000 --- a/src/agents/planning_v3.py +++ /dev/null @@ -1,169 +0,0 @@ -import asyncio -import sys -from collections.abc import AsyncGenerator -from contextlib import asynccontextmanager -from dataclasses import dataclass -from datetime import date -from typing import Annotated, Any, Union - -import asyncpg -import logfire -from annotated_types import MinLen -from devtools import debug -from pydantic import BaseModel, Field -from typing_extensions import TypeAlias - -from pydantic_ai import Agent, ModelRetry, RunContext -from pydantic_ai.format_as_xml import format_as_xml - -# 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured -logfire.configure(send_to_logfire='if-token-present') -logfire.instrument_asyncpg() - -DB_SCHEMA = """ -CREATE TABLE records ( - created_at timestamptz, - start_timestamp timestamptz, - end_timestamp timestamptz, - trace_id text, - span_id text, - parent_span_id text, - level log_level, - span_name text, - message text, - attributes_json_schema text, - attributes jsonb, - tags text[], - is_exception boolean, - otel_status_message text, - service_name text -); -""" -SQL_EXAMPLES = [ - { - 'request': 'show me records where foobar is false', - 'response': "SELECT * FROM records WHERE attributes->>'foobar' = false", - }, - { - 'request': 'show me records where attributes include the key "foobar"', - 'response': "SELECT * FROM records WHERE attributes ? 'foobar'", - }, - { - 'request': 'show me records from yesterday', - 'response': "SELECT * FROM records WHERE start_timestamp::date > CURRENT_TIMESTAMP - INTERVAL '1 day'", - }, - { - 'request': 'show me error records with the tag "foobar"', - 'response': "SELECT * FROM records WHERE level = 'error' and 'foobar' = ANY(tags)", - }, -] - - -@dataclass -class Deps: - conn: asyncpg.Connection - - -class Success(BaseModel): - """Response when SQL could be successfully generated.""" - - sql_query: Annotated[str, MinLen(1)] - explanation: str = Field( - '', description='Explanation of the SQL query, as markdown' - ) - - -class InvalidRequest(BaseModel): - """Response the user input didn't include enough information to generate SQL.""" - - error_message: str - - -Response: TypeAlias = Union[Success, InvalidRequest] -agent: Agent[Deps, Response] = Agent( - 'google-gla:gemini-1.5-flash', - # Type ignore while we wait for PEP-0747, nonetheless unions will work fine everywhere else - result_type=Response, # type: ignore - deps_type=Deps, -) - - -@agent.system_prompt -async def system_prompt() -> str: - return f"""\ -Given the following PostgreSQL table of records, your job is to -write a SQL query that suits the user's request. - -Database schema: - -{DB_SCHEMA} - -today's date = {date.today()} - -{format_as_xml(SQL_EXAMPLES)} -""" - - -@agent.result_validator -async def validate_result(ctx: RunContext[Deps], result: Response) -> Response: - if isinstance(result, InvalidRequest): - return result - - # gemini often adds extraneous backslashes to SQL - result.sql_query = result.sql_query.replace('\\', '') - if not result.sql_query.upper().startswith('SELECT'): - raise ModelRetry('Please create a SELECT query') - - try: - await ctx.deps.conn.execute(f'EXPLAIN {result.sql_query}') - except asyncpg.exceptions.PostgresError as e: - raise ModelRetry(f'Invalid query: {e}') from e - else: - return result - - -async def main(): - if len(sys.argv) == 1: - prompt = 'show me logs from yesterday, with level "error"' - else: - prompt = sys.argv[1] - - async with database_connect( - 'postgresql://postgres:postgres@localhost:54320', 'pydantic_ai_sql_gen' - ) as conn: - deps = Deps(conn) - result = await agent.run(prompt, deps=deps) - debug(result.data) - - -# pyright: reportUnknownMemberType=false -# pyright: reportUnknownVariableType=false -@asynccontextmanager -async def database_connect(server_dsn: str, database: str) -> AsyncGenerator[Any, None]: - with logfire.span('check and create DB'): - conn = await asyncpg.connect(server_dsn) - try: - db_exists = await conn.fetchval( - 'SELECT 1 FROM pg_database WHERE datname = $1', database - ) - if not db_exists: - await conn.execute(f'CREATE DATABASE {database}') - finally: - await conn.close() - - conn = await asyncpg.connect(f'{server_dsn}/{database}') - try: - with logfire.span('create schema'): - async with conn.transaction(): - if not db_exists: - await conn.execute( - "CREATE TYPE log_level AS ENUM ('debug', 'info', 'warning', 'error', 'critical')" - ) - await conn.execute(DB_SCHEMA) - yield conn - finally: - await conn.close() - - -if __name__ == '__main__': - asyncio.run(main()) \ No newline at end of file diff --git a/src/ai_engine.py b/src/ai_engine.py index c5a0f8af..707086a7 100644 --- a/src/ai_engine.py +++ b/src/ai_engine.py @@ -16,8 +16,8 @@ from prompt_factory.prompt_assembler import PromptAssembler from prompt_factory.core_prompt import CorePrompt from openai_api.openai import * -class AiEngine(object): +class AiEngine(object): def __init__(self, planning, taskmgr,lancedb,lance_table_name,project_audit): # Step 1: 获取results self.planning = planning diff --git a/src/codebaseQA/rag_processor.py b/src/codebaseQA/rag_processor.py index ed77ae6a..15852c7b 100644 --- a/src/codebaseQA/rag_processor.py +++ b/src/codebaseQA/rag_processor.py @@ -8,13 +8,16 @@ from tqdm import tqdm from openai_api.openai import common_get_embedding +from project.project_audit import ProjectAudit class RAGProcessor: - def __init__(self, functions_to_check: List[Dict[str, Any]], db_path: str = "./lancedb", project_id:str=None): - os.makedirs(db_path, exist_ok=True) - - self.db = lancedb.connect(db_path) - self.table_name = f"lancedb_{project_id}" + def __init__(self, id:str=None, audit:ProjectAudit=None): + self.db_path: str = os.path.join(os.getcwd(),f"lancedb{id}") + self.audit:ProjectAudit = audit + os.makedirs(name=self.db_path, exist_ok=True) + functions_to_check: List[Dict[str, Any]] = audit.functions_to_check + self.db = lancedb.connect(self.db_path) + self.table_name = f"lancedb_{id}" # 创建schema self.schema = pa.schema([ diff --git a/src/dao/__init__.py b/src/dao/__init__.py index 5c427850..e69de29b 100644 --- a/src/dao/__init__.py +++ b/src/dao/__init__.py @@ -1,3 +0,0 @@ - -from .cache_manager import CacheManager -from .task_mgr import ProjectTaskMgr \ No newline at end of file diff --git a/src/dao/db3.py b/src/dao/db3.py deleted file mode 100644 index d416bd23..00000000 --- a/src/dao/db3.py +++ /dev/null @@ -1,40 +0,0 @@ - -from contextlib import contextmanager, AbstractContextManager -from typing import Callable -import logging - -from sqlalchemy import create_engine, orm -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import Session - -logger = logging.getLogger(__name__) - -Base = declarative_base() - - -class Database: - - def __init__(self, db_url: str) -> None: - self._engine = create_engine(db_url, echo=True) - self._session_factory = orm.scoped_session( - orm.sessionmaker( - autocommit=False, - autoflush=False, - bind=self._engine, - ), - ) - - def create_database(self) -> None: - Base.metadata.create_all(self._engine) - - @contextmanager - def session(self) -> Callable[..., AbstractContextManager[Session]]: - session: Session = self._session_factory() - try: - yield session - except Exception: - logger.exception("Session rollback because of exception") - session.rollback() - raise - finally: - session.close() \ No newline at end of file diff --git a/src/dao/db_model.py b/src/dao/db_model.py deleted file mode 100644 index 69bdd014..00000000 --- a/src/dao/db_model.py +++ /dev/null @@ -1,52 +0,0 @@ -from contextlib import AbstractContextManager -from typing import Callable, Iterator - -from sqlalchemy.orm import Session - -from .models import User - - -class UserRepository: - - def __init__(self, session_factory: Callable[..., AbstractContextManager[Session]]) -> None: - self.session_factory = session_factory - - def get_all(self) -> Iterator[User]: - with self.session_factory() as session: - return session.query(User).all() - - def get_by_id(self, user_id: int) -> User: - with self.session_factory() as session: - user = session.query(User).filter(User.id == user_id).first() - if not user: - raise UserNotFoundError(user_id) - return user - - def add(self, email: str, password: str, is_active: bool = True) -> User: - with self.session_factory() as session: - user = User(email=email, hashed_password=password, is_active=is_active) - session.add(user) - session.commit() - session.refresh(user) - return user - - def delete_by_id(self, user_id: int) -> None: - with self.session_factory() as session: - entity: User = session.query(User).filter(User.id == user_id).first() - if not entity: - raise UserNotFoundError(user_id) - session.delete(entity) - session.commit() - - -class NotFoundError(Exception): - - entity_name: str - - def __init__(self, entity_id): - super().__init__(f"{self.entity_name} not found, id: {entity_id}") - - -class UserNotFoundError(NotFoundError): - - entity_name: str = "User" \ No newline at end of file diff --git a/src/knowledges/__init__.py b/src/knowledges/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/knowledges/entity.py b/src/knowledges/entity.py deleted file mode 100644 index 94b2a99e..00000000 --- a/src/knowledges/entity.py +++ /dev/null @@ -1,20 +0,0 @@ -from sqlalchemy import Column, String, Boolean, Integer - -from .database import Base - -; User entity -class User(Base): - - __tablename__ = "users" - - id = Column(Integer, primary_key=True) - email = Column(String, unique=True) - hashed_password = Column(String) - is_active = Column(Boolean, default=True) - - def __repr__(self): - return f"" - \ No newline at end of file diff --git a/src/library/__init__.py b/src/library/__init__.py index 8ceee087..e69de29b 100644 --- a/src/library/__init__.py +++ b/src/library/__init__.py @@ -1,4 +0,0 @@ -import sys, os -sys.path.append(os.path.abspath(os.path.dirname(__file__))) - -from .parsing import * diff --git a/src/library/parsing/__init__.py b/src/library/parsing/__init__.py index 3c52448f..71f8e89c 100644 --- a/src/library/parsing/__init__.py +++ b/src/library/parsing/__init__.py @@ -1,14 +1 @@ -from antlr4 import CommonTokenStream, InputStream -from .SolidityLexer import SolidityLexer -from .SolidityParser import parseString -from .callgraph import CallGraph - -def parse(source:str): - lexer = SolidityLexer(InputStream(source)) - stream = CommonTokenStream(lexer) - parser = SolidityParser(stream) - return parser.sourceUnit() - -def get_tokens(source:str): - lexer = SolidityLexer(InputStream(source)) - return lexer.getAllTokens() +from .SolidityParser import parseString \ No newline at end of file diff --git a/src/library/parsing/callgraph.py b/src/library/parsing/callgraph.py index 68c4b241..e8175465 100644 --- a/src/library/parsing/callgraph.py +++ b/src/library/parsing/callgraph.py @@ -90,10 +90,8 @@ def __init__(self, root:str) -> None: self.root = root self.files = {} self.call_data = {} - self.__parse_all_files() self.__run_jar() - self.__clean() def get_rel_path(self, path:str)->str: diff --git a/src/library/sgp/sgp_parser.py b/src/library/sgp/sgp_parser.py index 771755f8..d2ba9ae3 100644 --- a/src/library/sgp/sgp_parser.py +++ b/src/library/sgp/sgp_parser.py @@ -4,15 +4,13 @@ import re from antlr4.CommonTokenStream import CommonTokenStream from antlr4.InputStream import InputStream as ANTLRInputStream - -from .parser.SolidityLexer import SolidityLexer -from .parser.SolidityParser import SolidityParser - -from .sgp_visitor import SGPVisitorOptions, SGPVisitor,SolidityInfoVisitor -from .sgp_error_listener import SGPErrorListener -from .ast_node_types import SourceUnit -from .tokens import build_token_list -from .utils import string_from_snake_to_camel_case +from library.sgp.parser.SolidityLexer import SolidityLexer +from library.sgp.parser.SolidityParser import SolidityParser +from library.sgp.sgp_visitor import SGPVisitorOptions, SGPVisitor,SolidityInfoVisitor +from library.sgp.sgp_error_listener import SGPErrorListener +from library.sgp.ast_node_types import SourceUnit +from library.sgp.tokens import build_token_list +from library.sgp.utils import string_from_snake_to_camel_case class ParserError(Exception): diff --git a/src/library/sgp/sgp_visitor.py b/src/library/sgp/sgp_visitor.py index da78b411..c3538b21 100644 --- a/src/library/sgp/sgp_visitor.py +++ b/src/library/sgp/sgp_visitor.py @@ -6,11 +6,11 @@ from antlr4.tree.Tree import ParseTree,ParseTreeVisitor from antlr4.error.Errors import RecognitionException -from sgp.utilities.contract_extractor import extract_contract_with_name +from library.sgp.utilities.contract_extractor import extract_contract_with_name -from .parser.SolidityParser import SolidityParser as SP -from .parser.SolidityVisitor import SolidityVisitor +from library.sgp.parser.SolidityParser import SolidityParser as SP +from library.sgp.parser.SolidityVisitor import SolidityVisitor from .ast_node_types import * diff --git a/src/library/sgp/tool/PDG_parser.py b/src/library/sgp/tool/PDG_parser.py index 12dfcf76..0b0449c5 100644 --- a/src/library/sgp/tool/PDG_parser.py +++ b/src/library/sgp/tool/PDG_parser.py @@ -2,9 +2,9 @@ import antlr4 from collections import defaultdict -from parser.SolidityLexer import SolidityLexer -from parser.SolidityParser import SolidityParser -from parser.SolidityListener import SolidityListener +from library.parsing.SolidityLexer import SolidityLexer +from library.parsing.SolidityParser import SolidityParser +from library.sgp.parser.SolidityListener import SolidityListener class FunctionListener(SolidityListener): def __init__(self): @@ -154,12 +154,12 @@ def enterFunctionCall(self, ctx:SolidityParser.FunctionCallContext): if self.current_function: # We are inside a function and we found a function call a = ctx.getText() - called_function = re.sub('\(.*\)$', '', ctx.getText()) # get the name of the called function + called_function = re.sub('(.*)$', '', ctx.getText()) # get the name of the called function self.functions[self.current_function]['calls'].add(called_function) self.call_graph[self.current_function].add(called_function) self.callers_graph[called_function].add(self.current_function) - def enterIdentifier(self, ctx:SolidityParser.Identifier): + def enterIdentifier(self, ctx:SolidityParser): if self.current_function: identifier = ctx.getText() if identifier and identifier in self.state_variables: diff --git a/src/library/sgp/tool/__init__.py b/src/library/sgp/tool/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/library/sgp/utilities/__init__.py b/src/library/sgp/utilities/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/library/sgp/utilities/contract_extractor.py b/src/library/sgp/utilities/contract_extractor.py index 1f15874d..e2f228e4 100644 --- a/src/library/sgp/utilities/contract_extractor.py +++ b/src/library/sgp/utilities/contract_extractor.py @@ -1,8 +1,8 @@ import re from antlr4 import * -from sgp.parser.SolidityLexer import SolidityLexer -from sgp.parser.SolidityParser import SolidityParser -from sgp.parser.SolidityListener import SolidityListener +from library.sgp.parser.SolidityLexer import SolidityLexer +from library.sgp.parser.SolidityParser import SolidityParser +from library.sgp.parser.SolidityListener import SolidityListener from colorama import Fore, init diff --git a/src/main.py b/src/main.py index a5cd6727..61341d65 100644 --- a/src/main.py +++ b/src/main.py @@ -10,15 +10,12 @@ from prompts import prompts from sqlalchemy import create_engine from dao import CacheManager, ProjectTaskMgr -import os import pandas as pd from openpyxl import Workbook,load_workbook from openpyxl.utils.dataframe import dataframe_to_rows from codebaseQA.rag_processor import RAGProcessor from res_processor.res_processor import ResProcessor -import dotenv -dotenv.load_dotenv() def scan_project(project, db_engine): # 1. parsing projects @@ -136,7 +133,7 @@ def generate_excel(output_path, project_id): if switch_production_or_test == 'test': start_time=time.time() db_url_from = os.environ.get("DATABASE_URL") - engine = create_engine(db_url_from) + engine = create_engine(db_url_from, echo=True) dataset_base = "./src/dataset/agent-v1-c4" projects = load_dataset(dataset_base) @@ -186,7 +183,7 @@ def generate_excel(output_path, project_id): # Database setup db_url_from = os.environ.get("DATABASE_URL") - engine = create_engine(db_url_from) + engine = create_engine(db_url_from, echo=True) # Load projects projects = load_dataset(dataset_base, args.id, folder_name) diff --git a/src/nodes_config.py b/src/nodes_config.py new file mode 100644 index 00000000..e7c0f9ac --- /dev/null +++ b/src/nodes_config.py @@ -0,0 +1,82 @@ +from typing import Tuple, Type +from griffe import DocstringStyle +from pydantic_settings import BaseSettings, CliPositionalArg, PydanticBaseSettingsSource, PyprojectTomlConfigSettingsSource, SettingsConfigDict +from typing import Tuple, Type + +from pydantic_settings import ( + BaseSettings, + PydanticBaseSettingsSource, + PyprojectTomlConfigSettingsSource, + SettingsConfigDict, +) +class Settings(BaseSettings): + model_config:SettingsConfigDict = SettingsConfigDict( + cli_parse_args=True, + cli_prog_name='finite-monkey-engine', + pyproject_toml_depth=0, + pyproject_toml_table_header=('tool', 'finite-monkey-engine'), + toml_file='pyproject.toml', + extra='ignore', + env_file ='.env', + strict=False + ) + #database_dsn:PostgresDsn = None + id:str + base_dir:str + src_dir:str + output:str="." + AZURE_OR_OPENAI:str="OPENAI" + AZURE_API_BASE:str="" + AZURE_API_KEY:str="" + AZURE_API_VERSION:str="" + AZURE_DEPLOYMENT_NAME:str="" + BUSINESS_FLOW_COUNT:str="10" + CLAUDE_MODEL:str="" + COMMON_PROJECT:str="" + COMMON_VUL:str="all" + CONFIRMATION_MODEL:str="" + DATABASE_SQLITE:str="" + DATABASE_SETTINGS_URL:str="" + DATABASE_URL:str='postgresql://postgres:1234@127.0.0.1:5432/postgres' + IGNORE_FOLDERS:str="test" + MAX_THREADS_OF_CONFIRMATION:str="8" + MAX_THREADS_OF_SCAN:str="8" + OPENAI_API_BASE:str="" + OPENAI_API_KEY:str="" + OPENAI_MODEL:str="" + OPTIMIZE:str="" + PRE_TRAIN_MODEL:str="" + SCAN_MODE:str="all" + SPECIFIC_PROJECT:str="" + SWITCH_BUSINESS_CODE:str="True" + SWITCH_FUNCTION_CODE:str="False" + + +class nodes_config(Settings): + model_config:SettingsConfigDict = SettingsConfigDict( + cli_parse_args=True, + cli_prog_name='finite-monkey-engine', + pyproject_toml_depth=0, + pyproject_toml_table_header=('tool', 'finite-monkey-engine'), + toml_file='pyproject.toml', + extra='ignore', + env_file ='.env', + env_ignore_empty=True, + strict=False, + ) + @classmethod + def settings_customise_sources( + cls, + settings_cls: Type[Settings], + env_settings: PydanticBaseSettingsSource, + init_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + + ) -> Tuple[PydanticBaseSettingsSource, ...]: + return ( + init_settings, + env_settings, + dotenv_settings, + PyprojectTomlConfigSettingsSource(settings_cls), + ) \ No newline at end of file diff --git a/src/openai_api/__init__.py b/src/openai_api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/openai_api/openai.py b/src/openai_api/openai.py index 13bab5b9..6eb8ead9 100644 --- a/src/openai_api/openai.py +++ b/src/openai_api/openai.py @@ -3,6 +3,8 @@ import numpy as np import requests from openai import OpenAI +from pydantic_ai import Agent +from pydantic_ai.models import * def azure_openai(prompt): # Azure OpenAI配置 @@ -112,7 +114,7 @@ def ask_openai_for_json(prompt): "Authorization": f"Bearer {api_key}" } data = { - "model": os.environ.get('OPENAI_MODEL'), + "model": os.environ.get('OPENAI_MODEL', 'hf.co/unsloth/DeepSeek-R1-Distill-Qwen-32B-GGUF:Q8_0'), "response_format": { "type": "json_object" }, "messages": [ { @@ -125,7 +127,7 @@ def ask_openai_for_json(prompt): } ] } - response = requests.post(f'https://{api_base}/v1/chat/completions', headers=headers, json=data) + response = requests.post(f'{api_base}/v1/chat/completions', headers=headers, json=data) if response.status_code != 200: print(response.text) response_josn = response.json() diff --git a/src/planning/__init__.py b/src/planning/__init__.py index 6e753106..e69de29b 100644 --- a/src/planning/__init__.py +++ b/src/planning/__init__.py @@ -1,4 +0,0 @@ -import os -import pathlib -from .planning_v2 import PlanningV2 - diff --git a/src/planning/business_flow_extractor.py b/src/planning/business_flow_extractor.py new file mode 100644 index 00000000..4d255b11 --- /dev/null +++ b/src/planning/business_flow_extractor.py @@ -0,0 +1,397 @@ +import json +import re +from typing import Any, Dict, Tuple, List, Optional +from collections import defaultdict +import logging + +# Local module imports +from dao.entity import Project_Task # Ensure usage or remove if unnecessary +from library.parsing import CallGraph # Replace with actual import path +from library.sgp.utilities.contract_extractor import ( + group_functions_by_contract, + check_function_if_public_or_external, + check_function_if_view_or_pure +) + +# Configure logger (if not already configured elsewhere) +logger = logging.getLogger(__name__) +if not logger.hasHandlers(): + logger.setLevel(logging.INFO) # Adjust as needed + handler = logging.StreamHandler() + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + + +class BusinessFlowExtractor: + # Centralized language patterns to avoid redundancy + LANGUAGE_PATTERNS = { + '.rust': lambda f: True, # No visibility filter for Rust + '.python': lambda f: True, # No visibility filter for Python + '.move': lambda f: f.get('visibility') == 'public', + '.fr': lambda f: f.get('visibility') == 'public', + '.java': lambda f: f.get('visibility') in ['public', 'protected'], + '.cairo': lambda f: f.get('visibility') == 'public', + '.tact': lambda f: f.get('visibility') == 'public', + '.func': lambda f: f.get('visibility') == 'public' + } + + def __init__(self, call_graph: CallGraph) -> None: + """ + Initialize the BusinessFlowExtractor with an instance of CallGraph. + + :param call_graph: An instance of the CallGraph class. + """ + self.call_graph = call_graph + + def get_all_business_flow( + self, + functions_to_check: List[str] + ) -> Tuple[ + Dict[str, Dict[str, Any]], + Dict[str, Dict[str, List[Tuple[int, int]]]], + Dict[str, Dict[str, str]] + ]: + """ + Extracts all business flows for a list of functions. + + :param functions_to_check: A list of function names to extract business flows for. + :return: + - all_business_flow: Dict[contract_name, Dict[function_name, business_flow_code]] + - all_business_flow_line: Dict[contract_name, Dict[function_name, List[Tuple[start_line, end_line]]]] + - all_business_flow_context: Dict[contract_name, Dict[function_name, extended_flow_code]] + """ + if not functions_to_check: + logger.warning("No functions provided for business flow extraction.") + return {}, {}, {} + + # Group functions by their respective contracts + grouped_functions = group_functions_by_contract(functions_to_check) + # Identify contexts for the functions + contexts = self.call_graph.identify_contexts(functions_to_check) + + # Initialize dictionaries to store business flows + all_business_flow: Dict[str, Dict[str, Any]] = defaultdict(dict) + all_business_flow_line: Dict[str, Dict[str, List[Tuple[int, int]]]] = defaultdict(dict) + all_business_flow_context: Dict[str, Dict[str, str]] = defaultdict(dict) + + logger.info(f"Grouped contract count: {len(grouped_functions)}") + + for contract_info in grouped_functions: + contract_name = contract_info.get('contract_name') + functions = contract_info.get('functions', []) + contract_code_without_comments = contract_info.get('contract_code_without_comment', '') + file_path = contract_info.get('file_path') # Assuming 'file_path' key exists + + if not contract_name: + logger.warning("Contract info missing 'contract_name'. Skipping.") + continue + + logger.info(f"Processing contract: {contract_name}") + + # Determine file extension and corresponding visibility filter + file_ext = self._get_file_extension(functions) + visibility_filter = self._get_visibility_filter(file_ext) + + # Filter public/external functions based on visibility + all_public_external_function_names = [ + self._extract_function_name(function.get('name', '')) + for function in functions + if visibility_filter(function) + ] + + logger.info(f"Public/External functions count in {contract_name}: {len(all_public_external_function_names)}") + + for function_name in all_public_external_function_names: + if not function_name: + logger.warning(f"Encountered empty function name in contract '{contract_name}'. Skipping.") + continue + + logger.debug(f"Processing function: {function_name}") + + # Special handling for Python contracts with a single public/external function + if "_python" in contract_name.lower() and len(all_public_external_function_names) == 1: + # Assuming downstream methods expect a dictionary, not a JSON string + business_flow_list = { + function_name: all_public_external_function_names + } + else: + try: + business_flow_list = self.ask_openai_for_business_flow(function_name, contract_code_without_comments) + except Exception as e: + logger.error(f"Error fetching business flow for {function_name}: {e}") + business_flow_list = {} + + if not business_flow_list: + logger.warning(f"No business flow data for function: {function_name}") + continue + + # Extract function lists from business_flow_list + try: + function_lists = business_flow_list.get("BusinessFlow", {}).get("flow", []) + function_lists = [fn for fn in function_lists if fn != function_name] + except AttributeError as e: + logger.error(f"Error processing business_flow_list for {function_name}: {e}") + continue + + logger.debug(f"Business flow list for {function_name}: {function_lists}") + + # Retrieve line information for each function in the flow + line_info_list = [] + for fn in function_lists: + if isinstance(fn, str) and fn != "-1": + func_struct = self.call_graph.get_function_detail(file=file_path, contract=contract_name, function=fn) + if func_struct: + line_info = (func_struct.get('start_line'), func_struct.get('end_line')) + line_info_list.append(line_info) + + # Extract and concatenate function contents + business_flow_code = self.extract_and_concatenate_functions_content(function_lists, contract_info) + + # Build extended flow code from contexts + extended_flow_code = self._build_extended_flow_code(contract_name, function_lists, contexts) + + # Assign to respective dictionaries + all_business_flow[contract_name][function_name] = business_flow_code + all_business_flow_line[contract_name][function_name] = line_info_list + all_business_flow_context[contract_name][function_name] = extended_flow_code.strip() + + return all_business_flow, all_business_flow_line, all_business_flow_context + + def _get_file_extension(self, functions: List[Dict[str, Any]]) -> Optional[str]: + """ + Determine the file extension based on the functions' relative file paths. + + :param functions: List of function dictionaries. + :return: File extension if found, else None. + """ + for func in functions: + file_path = func.get('relative_file_path', '') + for ext, filter_func in self.LANGUAGE_PATTERNS.items(): + if file_path.endswith(ext) and filter_func(func): + return ext + return None + + def _get_visibility_filter(self, file_ext: Optional[str]): + """ + Retrieve the visibility filter lambda based on the file extension. + + :param file_ext: File extension. + :return: A lambda function for visibility filtering. + """ + return self.LANGUAGE_PATTERNS.get(file_ext, lambda f: True) + + def _extract_function_name(self, full_name: str) -> str: + """ + Extract the function name from its full name. + + :param full_name: Full function name (e.g., "Contract.Function"). + :return: Function name (e.g., "Function"). + """ + if "." in full_name: + return full_name.split(".")[-1] + else: + logger.warning(f"Function name '{full_name}' does not contain a period. Returning as is.") + return full_name + + def _build_extended_flow_code( + self, + contract_name: str, + function_lists: List[str], + contexts: Dict[str, Dict[str, Any]] + ) -> str: + """ + Build the extended flow code by aggregating context content. + + :param contract_name: Name of the contract. + :param function_lists: List of function names involved in the business flow. + :param contexts: Contexts dictionary containing sub_calls and parent_calls. + :return: Concatenated extended flow code. + """ + extended_flow_parts = [] + for func in function_lists: + key = f"{contract_name}.{func}" + context = contexts.get(key, {}) + sub_calls = context.get("sub_calls", []) + parent_calls = context.get("parent_calls", []) + + combined_calls = sub_calls + parent_calls + if not combined_calls: + logger.debug(f"No sub_calls or parent_calls found for key '{key}'.") + continue + + context_content = "\n".join(call.get("content", "") for call in combined_calls if call.get("content")) + if context_content: + extended_flow_parts.append(context_content) + else: + logger.debug(f"No content found in sub_calls or parent_calls for key '{key}'.") + + extended_flow_code = "\n".join(extended_flow_parts) + return extended_flow_code.strip() + + def ask_openai_for_business_flow(self, function_name: str, contract_code: str) -> Dict[str, Any]: + """ + Interface with OpenAI to retrieve business flow for a given function. + + :param function_name: Name of the function. + :param contract_code: Source code of the contract. + :return: Parsed JSON response from OpenAI. + """ + prompt = f""" + Analyze the business flow for the function '{function_name}' in the following contract code. Identify all functions that are called by '{function_name}' and the sequence of these calls. Provide the output in the following JSON format: + + {{ + "BusinessFlow": {{ + "flow": ["{function_name}", "FunctionA", "FunctionB", "..."] + }} + }} + """ + + try: + logger.info(f"Asking OpenAI for business flow of function '{function_name}'.") + response = openai.Completion.create( + engine="text-davinci-003", # Replace with the desired engine + prompt=prompt, + max_tokens=500, + n=1, + stop=None, + temperature=0.5 + ) + response_text = response.choices[0].text.strip() + business_flow = json.loads(response_text) + logger.debug(f"Received business flow from OpenAI for function '{function_name}': {business_flow}") + return business_flow + except openai.error.OpenAIError as e: + logger.error(f"OpenAI API error while fetching business flow for '{function_name}': {e}") + return {} + except json.JSONDecodeError as e: + logger.error(f"JSON decoding error for function '{function_name}': {e}") + return {} + except Exception as e: + logger.error(f"Unexpected error while fetching business flow for '{function_name}': {e}") + return {} + + def extract_and_concatenate_functions_content( + self, + function_names: List[str], + contract_info: Dict[str, Any] + ) -> str: + """ + Extracts the content of functions based on a given function list and contract info, + and concatenates them into a single string. + + :param function_names: List of function names to extract. + :param contract_info: Information about the contract containing the functions. + :return: Concatenated source code of the specified functions. + """ + file_path = contract_info.get('file_path') + contract_name = contract_info.get('contract_name') + if not file_path or not contract_name: + logger.error("Contract information missing 'file_path' or 'contract_name'.") + return "" + + functions = contract_info.get('functions', []) + concatenated_code_parts = [] + + for func_name in function_names: + if not func_name: + logger.warning(f"Encountered empty function name in contract '{contract_name}'. Skipping.") + continue + + func_detail = self.call_graph.get_function_detail(file=file_path, contract=contract_name, function=func_name) + if func_detail: + func_src = self.call_graph.get_function_src(file=file_path, func=func_detail) + if func_src: + concatenated_code_parts.append(func_src) + else: + logger.warning(f"Source code for function '{func_name}' in contract '{contract_name}' is empty.") + else: + logger.warning(f"Function '{func_name}' not found in contract '{contract_name}'.") + + concatenated_code = "\n".join(concatenated_code_parts) + return concatenated_code.strip() + + def merge_and_sort_rulesets( + self, + high: List[Dict[str, Any]], + medium: List[Dict[str, Any]] + ) -> List[Dict[str, Any]]: + """ + Merge two rulesets based on sim_score and sort them in descending order. + + :param high: List of high-priority rules. + :param medium: List of medium-priority rules. + :return: Sorted combined ruleset. + """ + combined_ruleset = high + medium + sorted_ruleset = sorted(combined_ruleset, key=lambda x: x.get('sim_score', 0), reverse=True) + logger.debug(f"Merged and sorted ruleset with {len(sorted_ruleset)} rules.") + return sorted_ruleset + + def decode_business_flow_list_from_response(self, response: str) -> List[str]: + """ + Extracts unique function names from a JSON response. + + :param response: JSON string containing business flow information. + :return: A list of unique function names. + """ + unique_functions = set() + try: + json_obj = json.loads(response) + business_flow = json_obj.get("BusinessFlow", {}).get("flow", []) + for func in business_flow: + if isinstance(func, str): + func_name = func.split(".")[-1] if "." in func else func + unique_functions.add(func_name) + else: + logger.warning(f"Unexpected function format in business flow: {func}") + except json.JSONDecodeError as e: + logger.error(f"JSON decoding error: {e}") + except Exception as e: + logger.error(f"Unexpected error during business flow decoding: {e}") + return sorted(unique_functions) + + def search_business_flow( + self, + all_business_flow: Dict[str, Dict[str, Any]], + all_business_flow_line: Dict[str, Dict[str, Any]], + all_business_flow_context: Dict[str, Dict[str, Any]], + function_name: str, + contract_name: str + ) -> 'BusinessFlowResult': + """ + Search for the business flow code based on a function name and contract name. + + :param all_business_flow: The dictionary containing all business flows. + :param all_business_flow_line: The dictionary containing business flow lines. + :param all_business_flow_context: The dictionary containing business flow contexts. + :param function_name: The name of the function to search for. + :param contract_name: The name of the contract where the function is located. + :return: BusinessFlowResult containing (business_flow_code, business_flow_line, business_flow_context) + if found, otherwise (None, [], None). + """ + contract_flows = all_business_flow.get(contract_name) + contract_flows_line = all_business_flow_line.get(contract_name, {}) + contract_flows_context = all_business_flow_context.get(contract_name, {}) + + if not contract_flows: + logger.warning(f"Contract '{contract_name}' not found in all_business_flow.") + return BusinessFlowResult(None, [], None) + + business_flow_code = contract_flows.get(function_name) + if business_flow_code is None: + logger.warning(f"Function '{function_name}' not found in contract '{contract_name}'.") + return BusinessFlowResult(None, [], None) + + business_flow_line = contract_flows_line.get(function_name, []) + business_flow_context = contract_flows_context.get(function_name, "") + + return BusinessFlowResult(business_flow_code, business_flow_line, business_flow_context) + + +from typing import NamedTuple + +class BusinessFlowResult(NamedTuple): + business_flow_code: Optional[str] + business_flow_line: List[Tuple[int, int]] + business_flow_context: Optional[str] diff --git a/src/planning/planning_v2.py b/src/planning/planning_v2.py index 48070e23..b65a9663 100644 --- a/src/planning/planning_v2.py +++ b/src/planning/planning_v2.py @@ -8,16 +8,19 @@ import pickle from openai_api.openai import * import re +from project.project_audit import ProjectAudit +from dao.task_mgr import ProjectTaskMgr +from library.parsing.callgraph import CallGraph ''' 根据每个function 的 functionality embbeding 匹配结果 ''' class PlanningV2(object): - def __init__(self, project,taskmgr) -> None: - self.project = project - self.taskmgr=taskmgr - self.scan_list_for_larget_context=[] - + def __init__(self, project:ProjectAudit,taskmgr:ProjectTaskMgr) -> None: + self.project:ProjectAudit=project + self.taskmgr:ProjectTaskMgr=taskmgr + self.callgraph:CallGraph = project.cg + self.scan_list_for_larget_context:list=[] def ask_openai_for_business_flow(self,function_name,contract_code_without_comment): prompt=f""" diff --git a/src/project/__init__.py b/src/project/__init__.py index 6a2a50a3..e69de29b 100644 --- a/src/project/__init__.py +++ b/src/project/__init__.py @@ -1 +0,0 @@ -from .project_audit import ProjectAudit diff --git a/src/project/dataclasses.py b/src/project/dataclasses.py new file mode 100644 index 00000000..e69de29b diff --git a/src/project/project_audit.py b/src/project/project_audit.py index 4e91f8e9..4e9e7dfb 100644 --- a/src/project/project_audit.py +++ b/src/project/project_audit.py @@ -1,55 +1,65 @@ import csv -from .project_parser import parse_project, BaseProjectFilter +from typing import Any, Dict, Tuple, Union, List, Set +from os import path +from nodes_config import nodes_config +from library.parsing.callgraph import CallGraph +from .project_parser import parse_project, BaseProjectFilter import re from library.sgp.utilities.contract_extractor import extract_state_variables_from_code, extract_state_variables_from_code_move - +__all__ = ('ProjectAudit') class ProjectAudit(object): - def analyze_function_relationships(self, functions_to_check): + def analyze_function_relationships(self, functions_to_check: List[Dict]) -> Tuple[Dict[str, Dict[str, Set]], Dict[str, Dict]]: + # Construct a mapping and calling relationship dictionary from function name to function information # 构建函数名到函数信息的映射和调用关系字典 func_map = {} relationships = {'upstream': {}, 'downstream': {}} - + for idx, func in enumerate(functions_to_check): func_name = func['name'].split('.')[-1] + func['func_name'] = func_name func_map[func_name] = { 'index': idx, 'data': func } - + + # Analyze the calling relationship of each function # 分析每个函数的调用关系 - for func in functions_to_check: + for idx,func in enumerate(functions_to_check): func_name = func['name'].split('.')[-1] content = func['content'].lower() - + if func_name not in relationships['upstream']: relationships['upstream'][func_name] = set() if func_name not in relationships['downstream']: relationships['downstream'][func_name] = set() - + + # Check whether other functions call the current function # 检查其他函数是否调用了当前函数 for other_func in functions_to_check: if other_func == func: continue - + other_name = other_func['name'].split('.')[-1] other_content = other_func['content'].lower() - - # 如果其他函数调用了当前函数 + + # If other functions call the current function if re.search(r'\b' + re.escape(func_name.lower()) + r'\b', other_content): relationships['upstream'][func_name].add(other_name) + if other_name not in relationships['downstream']: relationships['downstream'][other_name] = set() relationships['downstream'][other_name].add(func_name) - - # 如果当前函数调用了其他函数 + + # If the current function calls other functions if re.search(r'\b' + re.escape(other_name.lower()) + r'\b', content): relationships['downstream'][func_name].add(other_name) + if other_name not in relationships['upstream']: relationships['upstream'][other_name] = set() relationships['upstream'][other_name].add(func_name) - - return relationships, func_map + return relationships, func_map + def build_call_tree(self, func_name, relationships, direction, func_map, visited=None): if visited is None: visited = set() @@ -74,7 +84,7 @@ def build_call_tree(self, func_name, relationships, direction, func_map, visited # 递归构建每个相关函数的调用树 for related_func in related_funcs: - child_tree = self.build_call_tree(related_func, relationships, direction, func_map, visited.copy()) + child_tree: None | dict[str, Any] = self.build_call_tree(related_func, relationships, direction, func_map, visited.copy()) if child_tree: node['children'].append(child_tree) @@ -98,26 +108,32 @@ def print_call_tree(self, node, level=0, prefix=''): new_prefix = prefix + (' ' if level == 0 else '│ ' if not is_last else ' ') self.print_call_tree(child, level + 1, new_prefix + ('└─' if is_last else '├─')) - def __init__(self, project_id, project_path, db_engine): - self.project_id = project_id - self.project_path = project_path - self.functions = [] - self.functions_to_check = [] - self.tasks = [] + #project_id, project_path, db_engine + def __init__(self, config:nodes_config) -> None: + self.config:nodes_config = config + self.project_id:str = config.id + self.project_path:str = config.base_dir + self.cg = CallGraph(root=path.join(config.base_dir, config.src_dir)) + + self.functions_to_check:list = [] + self.functions:list = [] + self.tasks:list = [] self.taskkeys = set() + - def parse(self, white_files, white_functions): + def parse(self, white_files, white_functions) -> None: parser_filter = BaseProjectFilter(white_files, white_functions) functions, functions_to_check = parse_project(self.project_path, parser_filter) self.functions = functions self.functions_to_check = functions_to_check - + relationships:Dict[str,Dict] + func_map:Dict # 分析函数关系 - relationships, func_map = self.analyze_function_relationships(functions_to_check) + relationships,func_map = self.analyze_function_relationships(functions_to_check) # 为每个函数构建并打印调用树 - call_trees = [] + call_trees:list = [] for func in functions_to_check: func_name = func['name'].split('.')[-1] # print(f"\nAnalyzing function: {func_name}") @@ -140,7 +156,7 @@ def parse(self, white_files, white_functions): 'state_variables': state_variables_text }) - self.call_trees = call_trees + self.call_trees:list = call_trees def get_function_names(self): return set([function['name'] for function in self.functions]) \ No newline at end of file diff --git a/src/project/project_parser.py b/src/project/project_parser.py index 2225efdc..31c27a3f 100644 --- a/src/project/project_parser.py +++ b/src/project/project_parser.py @@ -1,12 +1,13 @@ -from library.sgp.sgp_parser import get_antlr_parsing from library.parsing.callgraph import CallGraph import os import re - from library.sgp.utilities.contract_extractor import extract_state_variables_from_code from .project_settings import FILE_PARTIAL_WHITE_LIST, PATH_PARTIAL_WHITE_LIST, PATH_WHITE_LIST, OPENZEPPELIN_CONTRACTS,OPENZEPPELIN_FUNCTIONS +from library.sgp.sgp_parser import get_antlr_parsing +from dataclasses import * +from box import Box -class Function(dict): +class Function(Box): def __init__(self, file, contract, func): self.file = file self.contract = contract @@ -156,7 +157,6 @@ def filter_functions(self, function): def parse_project(project_path, project_filter = None): - if project_filter is None: project_filter = BaseProjectFilter([], []) @@ -184,9 +184,19 @@ def parse_project(project_path, project_filter = None): # fix func name fs = [] for func in functions: - name = func['name'][8:] # remove special_前缀,具体为啥我也忘了,似乎是为了考虑特定的function name + if func['name'][8:] != "tor": + name = func['name'][8:] # remove SPECIAL_ Prefix,I forgot the specific reason, it seems to be to consider a specificfunction name + else: + name = "constructor" func['name'] = "%s.%s" % (func['contract_name'], name) fs.append(func) + + + + # for func in functions: + # name = func['name'][8:] # remove special_前缀,具体为啥我也忘了,似乎是为了考虑特定的function name + # func['name'] = "%s.%s" % (func['contract_name'], name) + # fs.append(func) fs_filtered = fs[:] # 2. filter contract diff --git a/src/prompt_factory/__init__.py b/src/prompt_factory/__init__.py new file mode 100644 index 00000000..d365068f --- /dev/null +++ b/src/prompt_factory/__init__.py @@ -0,0 +1 @@ +print(f"running for {__file__}") \ No newline at end of file diff --git a/src/run.py b/src/run.py new file mode 100644 index 00000000..2a81af0b --- /dev/null +++ b/src/run.py @@ -0,0 +1,145 @@ +import asyncio +import os +from sqlalchemy import create_engine,Engine +from rich.console import Console, ConsoleOptions, RenderResult +from rich.live import Live +from rich.markdown import CodeBlock, Markdown +from rich.syntax import Syntax +from rich.text import Text +import logging + +from pydantic_ai import Agent +from pydantic_ai.models import KnownModelName +from pydantic_ai.models.openai import OpenAIModel + +from nodes_config import nodes_config + +logger = logging.getLogger(__name__) +if not logger.hasHandlers(): + logger.setLevel(logging.WARN) # Adjust as needed + handler = logging.StreamHandler() + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') # Adjust as needed + handler.setFormatter(formatter) + logger.addHandler(handler) + from dataclasses import dataclass + +from dao.task_mgr import ProjectTaskMgr +from codebaseQA.rag_processor import RAGProcessor +from res_processor.res_processor import ResProcessor +from project.project_audit import ProjectAudit +from planning.planning_v2 import PlanningV2 +from ai_engine import AiEngine + + +class Project(object): + def __init__(self, config:nodes_config) -> None: + self.id = config.id + self.output = config.output + self.path = config.base_dir + self.project = self.start_project(dataset_path=config.base_dir, code_path=config.src_dir) + self.functions: list[str] = [] + self.white_files = self.project["files"] + self.white_functions = self.project.get("functions", []) + self.files = self.project["files"] + + def __repr__(self) -> str: + return f"Project(id={self.id}, path={self.path})" + + def start_project(self, dataset_path, code_path): + self.AllFiles = False + self.AllMySource = True + srcs = f"{dataset_path}/{code_path}" + return { + "path": code_path, + "files": [ + os.path.relpath(os.path.join(root, file), dataset_path) + for root, _, files in os.walk(srcs) + for file in files + if file.endswith(".sol") + ], + "functions": [], + "base_path": dataset_path, + "AllFiles": False, + "AllMySource": True, + } + + +@dataclass +class Context: + def __init__(self, config:nodes_config, db_engine:Engine) -> None: + self.config:nodes_config= config + self.project:Project = Project(config) + self.id:str = config.id + self.path:str = self.project.path + self.db_engine:Engine = db_engine + self.all_files:bool = self.project.AllFiles + self.statefile:str = f"{config.id}-funcs.json" + self.output:str = config.output + self.tags:list[str] = [""] + logger.log(31,"Coontext: CallGraph") + #self.call_graph = CallGraph(root=self.path) + logger.log(31,"Context: ProjectAudit") + self.project_audit = ProjectAudit(config) + logger.log(31,"Contex: RAGProcesssor") + self.rag_processor = RAGProcessor(config.id,audit=self.project_audit) + + self.project_taskmgr = ProjectTaskMgr(project_id=self.id, engine=self.db_engine) + self.planning = PlanningV2( + project=self.project_audit, + taskmgr=self.project_taskmgr) + self.ai_engine:AiEngine = AiEngine(self.planning, self.project_taskmgr,self.rag_processor.db,"lancedb_"+config.id,self.project_audit) + + + +async def main(): + prettier_code_blocks() + console = Console() + prompt = '' + console.log(f'Asking: {prompt}...', style='cyan') + agent = Agent(OpenAIModel(model_name='dolphin3:8b-llama3.1-q8_0',base_url='http://127.0.0.1:11434/v1')) + + with Live('', console=console, vertical_overflow='visible') as live: + async with agent.run_stream(prompt) as result: + async for message in result.stream(): + live.update(Markdown(message)) + console.log(result.usage()) + + +def prettier_code_blocks(): + """Make rich code blocks prettier and easier to copy. + From https://github.com/samuelcolvin/aicli/blob/v0.8.0/samuelcolvin_aicli.py#L22 + """ + + class SimpleCodeBlock(CodeBlock): + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + code:str = str(self.text).rstrip() + yield Text(self.lexer_name, style='dim') + yield Syntax( + code, + self.lexer_name, + theme=self.theme, + background_color='default', + word_wrap=True, + ) + yield Text(f'/{self.lexer_name}', style='dim') + + Markdown.elements['fence'] = SimpleCodeBlock + + +if __name__ == '__main__': + config = nodes_config() + config.model_dump() + logger.log(31, "Main: Project") + logger.log(31, "Main: create_engine") + engine = create_engine(config.DATABASE_URL, echo=True) + context = Context(config, engine) + + logger.log(31,"starting project load/parse") + context.project_audit.parse(context.project.white_files, context.project.white_functions) + logger.log(31,f"saving state to {context.statefile}") + context.planning.do_planning() + context.ai_engine.do_scan() + context.ai_engine.check_function_vul() + asyncio.run(main()) \ No newline at end of file diff --git a/src/scripts/__init__.py b/src/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/uv.lock b/uv.lock index dac6bf00..d0fec490 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,5 @@ version = 1 +revision = 1 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.12'", @@ -60,6 +61,79 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 }, ] +[[package]] +name = "asttokens" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/1d/f03bcb60c4a3212e15f99a56085d93093a497718adf828d050b9d675da81/asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0", size = 62284 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/86/4736ac618d82a20d87d2f92ae19441ebc7ac9e7a581d7e58bbe79233b24a/asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24", size = 27764 }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233 }, +] + +[[package]] +name = "asyncpg" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/07/1650a8c30e3a5c625478fa8aafd89a8dd7d85999bf7169b16f54973ebf2c/asyncpg-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfb4dd5ae0699bad2b233672c8fc5ccbd9ad24b89afded02341786887e37927e", size = 673143 }, + { url = "https://files.pythonhosted.org/packages/a0/9a/568ff9b590d0954553c56806766914c149609b828c426c5118d4869111d3/asyncpg-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc1f62c792752a49f88b7e6f774c26077091b44caceb1983509edc18a2222ec0", size = 645035 }, + { url = "https://files.pythonhosted.org/packages/de/11/6f2fa6c902f341ca10403743701ea952bca896fc5b07cc1f4705d2bb0593/asyncpg-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3152fef2e265c9c24eec4ee3d22b4f4d2703d30614b0b6753e9ed4115c8a146f", size = 2912384 }, + { url = "https://files.pythonhosted.org/packages/83/83/44bd393919c504ffe4a82d0aed8ea0e55eb1571a1dea6a4922b723f0a03b/asyncpg-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7255812ac85099a0e1ffb81b10dc477b9973345793776b128a23e60148dd1af", size = 2947526 }, + { url = "https://files.pythonhosted.org/packages/08/85/e23dd3a2b55536eb0ded80c457b0693352262dc70426ef4d4a6fc994fa51/asyncpg-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:578445f09f45d1ad7abddbff2a3c7f7c291738fdae0abffbeb737d3fc3ab8b75", size = 2895390 }, + { url = "https://files.pythonhosted.org/packages/9b/26/fa96c8f4877d47dc6c1864fef5500b446522365da3d3d0ee89a5cce71a3f/asyncpg-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c42f6bb65a277ce4d93f3fba46b91a265631c8df7250592dd4f11f8b0152150f", size = 3015630 }, + { url = "https://files.pythonhosted.org/packages/34/00/814514eb9287614188a5179a8b6e588a3611ca47d41937af0f3a844b1b4b/asyncpg-0.30.0-cp310-cp310-win32.whl", hash = "sha256:aa403147d3e07a267ada2ae34dfc9324e67ccc4cdca35261c8c22792ba2b10cf", size = 568760 }, + { url = "https://files.pythonhosted.org/packages/f0/28/869a7a279400f8b06dd237266fdd7220bc5f7c975348fea5d1e6909588e9/asyncpg-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb622c94db4e13137c4c7f98834185049cc50ee01d8f657ef898b6407c7b9c50", size = 625764 }, + { url = "https://files.pythonhosted.org/packages/4c/0e/f5d708add0d0b97446c402db7e8dd4c4183c13edaabe8a8500b411e7b495/asyncpg-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e0511ad3dec5f6b4f7a9e063591d407eee66b88c14e2ea636f187da1dcfff6a", size = 674506 }, + { url = "https://files.pythonhosted.org/packages/6a/a0/67ec9a75cb24a1d99f97b8437c8d56da40e6f6bd23b04e2f4ea5d5ad82ac/asyncpg-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915aeb9f79316b43c3207363af12d0e6fd10776641a7de8a01212afd95bdf0ed", size = 645922 }, + { url = "https://files.pythonhosted.org/packages/5c/d9/a7584f24174bd86ff1053b14bb841f9e714380c672f61c906eb01d8ec433/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c198a00cce9506fcd0bf219a799f38ac7a237745e1d27f0e1f66d3707c84a5a", size = 3079565 }, + { url = "https://files.pythonhosted.org/packages/a0/d7/a4c0f9660e333114bdb04d1a9ac70db690dd4ae003f34f691139a5cbdae3/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3326e6d7381799e9735ca2ec9fd7be4d5fef5dcbc3cb555d8a463d8460607956", size = 3109962 }, + { url = "https://files.pythonhosted.org/packages/3c/21/199fd16b5a981b1575923cbb5d9cf916fdc936b377e0423099f209e7e73d/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51da377487e249e35bd0859661f6ee2b81db11ad1f4fc036194bc9cb2ead5056", size = 3064791 }, + { url = "https://files.pythonhosted.org/packages/77/52/0004809b3427534a0c9139c08c87b515f1c77a8376a50ae29f001e53962f/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc6d84136f9c4d24d358f3b02be4b6ba358abd09f80737d1ac7c444f36108454", size = 3188696 }, + { url = "https://files.pythonhosted.org/packages/52/cb/fbad941cd466117be58b774a3f1cc9ecc659af625f028b163b1e646a55fe/asyncpg-0.30.0-cp311-cp311-win32.whl", hash = "sha256:574156480df14f64c2d76450a3f3aaaf26105869cad3865041156b38459e935d", size = 567358 }, + { url = "https://files.pythonhosted.org/packages/3c/0a/0a32307cf166d50e1ad120d9b81a33a948a1a5463ebfa5a96cc5606c0863/asyncpg-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:3356637f0bd830407b5597317b3cb3571387ae52ddc3bca6233682be88bbbc1f", size = 629375 }, + { url = "https://files.pythonhosted.org/packages/4b/64/9d3e887bb7b01535fdbc45fbd5f0a8447539833b97ee69ecdbb7a79d0cb4/asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e", size = 673162 }, + { url = "https://files.pythonhosted.org/packages/6e/eb/8b236663f06984f212a087b3e849731f917ab80f84450e943900e8ca4052/asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a", size = 637025 }, + { url = "https://files.pythonhosted.org/packages/cc/57/2dc240bb263d58786cfaa60920779af6e8d32da63ab9ffc09f8312bd7a14/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3", size = 3496243 }, + { url = "https://files.pythonhosted.org/packages/f4/40/0ae9d061d278b10713ea9021ef6b703ec44698fe32178715a501ac696c6b/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737", size = 3575059 }, + { url = "https://files.pythonhosted.org/packages/c3/75/d6b895a35a2c6506952247640178e5f768eeb28b2e20299b6a6f1d743ba0/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a", size = 3473596 }, + { url = "https://files.pythonhosted.org/packages/c8/e7/3693392d3e168ab0aebb2d361431375bd22ffc7b4a586a0fc060d519fae7/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af", size = 3641632 }, + { url = "https://files.pythonhosted.org/packages/32/ea/15670cea95745bba3f0352341db55f506a820b21c619ee66b7d12ea7867d/asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e", size = 560186 }, + { url = "https://files.pythonhosted.org/packages/7e/6b/fe1fad5cee79ca5f5c27aed7bd95baee529c1bf8a387435c8ba4fe53d5c1/asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305", size = 621064 }, + { url = "https://files.pythonhosted.org/packages/3a/22/e20602e1218dc07692acf70d5b902be820168d6282e69ef0d3cb920dc36f/asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70", size = 670373 }, + { url = "https://files.pythonhosted.org/packages/3d/b3/0cf269a9d647852a95c06eb00b815d0b95a4eb4b55aa2d6ba680971733b9/asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3", size = 634745 }, + { url = "https://files.pythonhosted.org/packages/8e/6d/a4f31bf358ce8491d2a31bfe0d7bcf25269e80481e49de4d8616c4295a34/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33", size = 3512103 }, + { url = "https://files.pythonhosted.org/packages/96/19/139227a6e67f407b9c386cb594d9628c6c78c9024f26df87c912fabd4368/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4", size = 3592471 }, + { url = "https://files.pythonhosted.org/packages/67/e4/ab3ca38f628f53f0fd28d3ff20edff1c975dd1cb22482e0061916b4b9a74/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4", size = 3496253 }, + { url = "https://files.pythonhosted.org/packages/ef/5f/0bf65511d4eeac3a1f41c54034a492515a707c6edbc642174ae79034d3ba/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba", size = 3662720 }, + { url = "https://files.pythonhosted.org/packages/e7/31/1513d5a6412b98052c3ed9158d783b1e09d0910f51fbe0e05f56cc370bc4/asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590", size = 560404 }, + { url = "https://files.pythonhosted.org/packages/c8/a4/cec76b3389c4c5ff66301cd100fe88c318563ec8a520e0b2e792b5b84972/asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e", size = 621623 }, +] + +[[package]] +name = "cached-property" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/4b/3d870836119dbe9a5e3c9a61af8cc1a8b69d75aea564572e385882d5aefb/cached_property-2.0.1.tar.gz", hash = "sha256:484d617105e3ee0e4f1f58725e72a8ef9e93deee462222dbd51cd91230897641", size = 10574 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/0e/7d8225aab3bc1a0f5811f8e1b557aa034ac04bdf641925b30d3caf586b28/cached_property-2.0.1-py3-none-any.whl", hash = "sha256:f617d70ab1100b7bcf6e42228f9ddcb78c676ffa167278d9f730d1c2fba69ccb", size = 7428 }, +] + [[package]] name = "cachetools" version = "5.5.1" @@ -168,6 +242,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "decorator" +version = "5.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/0c/8d907af351aa16b42caae42f9d6aa37b900c67308052d10fdce809f8d952/decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", size = 35016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 }, +] + [[package]] name = "dependency-injector" version = "4.45.0" @@ -217,6 +300,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/61/8fb364abeb494a43001856a6433cd228544af9d2aa3fc57955b8c1b1095d/dependency_injector-4.45.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d2bdf7c6e4f7a74dedf4f0aea774eec4c3d6f4f909d411c769c9cd1346243518", size = 1718692 }, ] +[[package]] +name = "deprecation" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, +] + +[[package]] +name = "devtools" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/e2/a1e80801b195539f552a2a369691e09ecc27648f5c5c0c0cab0256f4f098/devtools-0.9.0.tar.gz", hash = "sha256:86ede6e0273e023db766344d14098228785b48a80f31716f28e8b9453d52fa1e", size = 68180 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/3a/741773833ee8f1d18d5ce377c3b062f0d63b951fb9e4bec25f994c7f4631/devtools-0.9.0-py3-none-any.whl", hash = "sha256:689cf4e7c75024237c42093ba19f4fa9cf15980269f02463aeab4d97d4b0a215", size = 15214 }, +] + [[package]] name = "distro" version = "1.9.0" @@ -253,6 +361,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, ] +[[package]] +name = "executing" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/e4/efb182548f9931b36b76fd9ba2236bfc5e6d342c905beff92e868afb5b5c/executing-0.8.2.tar.gz", hash = "sha256:c23bf42e9a7b9b212f185b1b2c3c91feb895963378887bb10e64a2e612ec0023", size = 481669 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/64/59e024b685666514cb20ffc2463a8b062df8e6c36efc5199cb422b728b78/executing-0.8.2-py2.py3-none-any.whl", hash = "sha256:32fc6077b103bd19e6494a72682d66d5763cf20a106d5aa7c5ccbea4e47b0df7", size = 16263 }, +] + +[[package]] +name = "fastapi" +version = "0.115.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/b2/5a5dc4affdb6661dea100324e19a7721d5dc524b464fe8e366c093fd7d87/fastapi-0.115.8.tar.gz", hash = "sha256:0ce9111231720190473e222cdf0f07f7206ad7e53ea02beb1d2dc36e2f0741e9", size = 295403 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/7d/2d6ce181d7a5f51dedb8c06206cbf0ec026a99bf145edd309f9e17c3282f/fastapi-0.115.8-py3-none-any.whl", hash = "sha256:753a96dd7e036b34eeef8babdfcfe3f28ff79648f86551eb36bfc1b0bf4a8cbf", size = 94814 }, +] + [[package]] name = "fastavro" version = "1.10.0" @@ -300,29 +431,43 @@ version = "0.0.1" source = { editable = "." } dependencies = [ { name = "antlr4-python3-runtime" }, + { name = "asyncpg" }, { name = "certifi" }, { name = "charset-normalizer" }, { name = "colorama" }, { name = "dependency-injector" }, + { name = "devtools" }, { name = "et-xmlfile" }, + { name = "fastapi" }, + { name = "future" }, { name = "idna" }, + { name = "ipython" }, { name = "joblib" }, + { name = "lancedb" }, + { name = "logging" }, { name = "numpy" }, + { name = "ollama" }, + { name = "openai" }, { name = "openpyxl" }, { name = "pandas" }, { name = "psycopg2-binary" }, { name = "pydantic" }, { name = "pydantic-ai" }, + { name = "pydantic-graph" }, { name = "pydantic-settings" }, + { name = "pylance" }, + { name = "python-box" }, { name = "python-dateutil" }, { name = "python-dotenv" }, { name = "pytz" }, { name = "requests" }, + { name = "rich" }, { name = "scikit-learn" }, { name = "scipy" }, { name = "simplejson" }, { name = "six" }, { name = "sqlalchemy" }, + { name = "sqlalchemy-orm" }, { name = "threadpoolctl" }, { name = "tqdm" }, { name = "tree-sitter" }, @@ -335,29 +480,43 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "antlr4-python3-runtime", specifier = ">=4.13.2" }, + { name = "asyncpg", specifier = ">=0.30.0" }, { name = "certifi", specifier = ">=2024.8.30" }, { name = "charset-normalizer", specifier = ">=3.4.0" }, { name = "colorama", specifier = ">=0.4.6" }, { name = "dependency-injector", specifier = ">=4.45.0" }, + { name = "devtools", specifier = ">=0.9.0" }, { name = "et-xmlfile", specifier = ">=1.1.0" }, + { name = "fastapi", specifier = ">=0.115.8" }, + { name = "future", specifier = ">=1.0.0" }, { name = "idna", specifier = ">=3.10" }, + { name = "ipython", specifier = ">=8.32.0" }, { name = "joblib", specifier = ">=1.4.2" }, + { name = "lancedb", specifier = ">=0.19.0" }, + { name = "logging", specifier = ">=0.4.9.6" }, { name = "numpy", specifier = ">=1.24.4" }, + { name = "ollama", specifier = ">=0.4.7" }, + { name = "openai", specifier = ">=1.63.0" }, { name = "openpyxl", specifier = ">=3.1.5" }, { name = "pandas", specifier = ">=1.24.4" }, { name = "psycopg2-binary", specifier = ">=2.9.9" }, { name = "pydantic", specifier = ">=2.11.0a2" }, { name = "pydantic-ai", specifier = ">=0.0.24" }, + { name = "pydantic-graph", specifier = ">=0.0.24" }, { name = "pydantic-settings", specifier = ">=2.7.1" }, - { name = "python-dateutil", specifier = ">=2.9.0.post0" }, + { name = "pylance", specifier = ">=0.23.0" }, + { name = "python-box", specifier = ">=7.3.2" }, + { name = "python-dateutil", specifier = "==2.8.2" }, { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "pytz", specifier = ">=2024.2" }, { name = "requests", specifier = ">=2.32.3" }, + { name = "rich", specifier = ">=13.9.4" }, { name = "scikit-learn", specifier = ">=1.3.2" }, { name = "scipy", specifier = ">=1.10.1" }, { name = "simplejson", specifier = ">=3.19.3" }, { name = "six", specifier = ">=1.16.0" }, { name = "sqlalchemy", specifier = ">=2.0.35" }, + { name = "sqlalchemy-orm", specifier = ">=1.2.10" }, { name = "threadpoolctl", specifier = ">=3.5.0" }, { name = "tqdm", specifier = ">=4.66.5" }, { name = "tree-sitter", specifier = ">=0.24.0" }, @@ -376,6 +535,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e2/94/758680531a00d06e471ef649e4ec2ed6bf185356a7f9fbfbb7368a40bd49/fsspec-2025.2.0-py3-none-any.whl", hash = "sha256:9de2ad9ce1f85e1931858535bc882543171d197001a0a5eb2ddc04f1781ab95b", size = 184484 }, ] +[[package]] +name = "future" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326 }, +] + [[package]] name = "google-auth" version = "2.38.0" @@ -543,6 +711,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] +[[package]] +name = "inflection" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/7e/691d061b7329bc8d54edbf0ec22fbfb2afe61facb681f9aaa9bff7a27d04/inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", size = 15091 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454 }, +] + +[[package]] +name = "ipython" +version = "8.32.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/80/4d2a072e0db7d250f134bc11676517299264ebe16d62a8619d49a78ced73/ipython-8.32.0.tar.gz", hash = "sha256:be2c91895b0b9ea7ba49d33b23e2040c352b33eb6a519cca7ce6e0c743444251", size = 5507441 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e1/f4474a7ecdb7745a820f6f6039dc43c66add40f1bcc66485607d93571af6/ipython-8.32.0-py3-none-any.whl", hash = "sha256:cae85b0c61eff1fc48b0a8002de5958b6528fa9c8defb1894da63f42613708aa", size = 825524 }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, +] + [[package]] name = "jiter" version = "0.8.2" @@ -620,6 +831,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/8a/d63959f4eff03893a00e6e63592e3a9f15b9266ed8e0275ab77f8c7dbc94/jsonpath_python-1.0.6-py3-none-any.whl", hash = "sha256:1e3b78df579f5efc23565293612decee04214609208a2335884b3ee3f786b575", size = 7552 }, ] +[[package]] +name = "lancedb" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecation" }, + { name = "overrides" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "pylance" }, + { name = "tqdm" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/ab/b9007906e66c432af21a22bd948483d1daf16bbfd5533c8ee76eafe1ee5d/lancedb-0.19.0-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:7efd53f10b4049d1254ed29420ef15f39d3d8fa3ae763b6d94f3f494ffe4fdb5", size = 28878834 }, + { url = "https://files.pythonhosted.org/packages/5e/32/13e73ae415e85879076b9709428541321dc756cc73de6f53d2abc5f359c0/lancedb-0.19.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:be9a970eb22fe8d3c8ca0e33a90ae16bafbe06b17031e7dfa595481ecc21e905", size = 26964436 }, + { url = "https://files.pythonhosted.org/packages/f5/18/989a19ddcf0ee783796291bf6da557ac81d2c28514d59d1d41cf5ee781fb/lancedb-0.19.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e4e7b651ee9a17d12bc22d04b530edf74e6872e38c9c3469c821b1b6ae2cf83", size = 30432010 }, + { url = "https://files.pythonhosted.org/packages/2d/77/cbf6cac7248da3f1457bff0d540bc4b43885406561f9701ad341fe6ba93d/lancedb-0.19.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa780699ad3da79b9bb4191da7a6f719d012bea84a0528ca090b89625d0686b4", size = 32857633 }, + { url = "https://files.pythonhosted.org/packages/53/2a/15fd4c3600476ecd1e36bfca06c80dc9e0ec0606bbf66155e07752d13fa9/lancedb-0.19.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ecc05ac68a7abcc4299dde821035275642492cbf136b0f59fa3101e20d3686f0", size = 30136673 }, + { url = "https://files.pythonhosted.org/packages/a2/43/b30338ccc61dfb95f3ee59918b61b0e545e67456ed89dc3155ac640e0b8e/lancedb-0.19.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:83bc158e7c543409d6eb90fee068a8621ebfdcd5ef2781639ff37fd1ec18ceda", size = 32335933 }, + { url = "https://files.pythonhosted.org/packages/4e/d0/2eb0d51aedc86b9bf27d214c9a4620eb4dfb78977dc1650fba30109ffb4c/lancedb-0.19.0-cp39-abi3-win_amd64.whl", hash = "sha256:1767b47427f6b0d9a1cdfb9fcd01ea18273050454af4f48dfa39e6aa886f7e06", size = 30000319 }, +] + [[package]] name = "logfire-api" version = "3.5.3" @@ -629,6 +862,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/21/7f18dc3154caa396fb2161f89c980882111537a4261dbc7a6b902cf9fc58/logfire_api-3.5.3-py3-none-any.whl", hash = "sha256:abecb0d2e43f900c58582c333f261c8f9b187e72933cf3f5aec93685666f86ae", size = 73821 }, ] +[[package]] +name = "logging" +version = "0.4.9.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/93/4b/979db9e44be09f71e85c9c8cfc42f258adfb7d93ce01deed2788b2948919/logging-0.4.9.6.tar.gz", hash = "sha256:26f6b50773f085042d301085bd1bf5d9f3735704db9f37c1ce6d8b85c38f2417", size = 96029 } + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + [[package]] name = "mistralai" version = "1.5.0" @@ -717,9 +989,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/17/7f/d322a4125405920401450118dbdc52e0384026bd669939484670ce8b2ab9/numpy-2.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4", size = 12839607 }, ] +[[package]] +name = "ollama" +version = "0.4.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/6d/dc77539c735bbed5d0c873fb029fb86aa9f0163df169b34152914331c369/ollama-0.4.7.tar.gz", hash = "sha256:891dcbe54f55397d82d289c459de0ea897e103b86a3f1fad0fdb1895922a75ff", size = 12843 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/83/c3ffac86906c10184c88c2e916460806b072a2cfe34cdcaf3a0c0e836d39/ollama-0.4.7-py3-none-any.whl", hash = "sha256:85505663cca67a83707be5fb3aeff0ea72e67846cea5985529d8eca4366564a1", size = 13210 }, +] + [[package]] name = "openai" -version = "1.63.0" +version = "1.63.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -731,9 +1016,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4f/32/2049e973a646801df425aecdf88c6504ca878bdb3951fe12076fc30f2977/openai-1.63.0.tar.gz", hash = "sha256:597d7a1b35b113e5a09fcb953bdb1eef44f404a39985f3d7573b3ab09221fd66", size = 356710 } +sdist = { url = "https://files.pythonhosted.org/packages/e6/1c/11b520deb71f9ea54ced3c52cd6a5f7131215deba63ad07f23982e328141/openai-1.63.2.tar.gz", hash = "sha256:aeabeec984a7d2957b4928ceaa339e2ead19c61cfcf35ae62b7c363368d26360", size = 356902 } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/a0/e1fe4e87218639fc0a0927da5266c2978eaa0e2eb5437479ee64a11535bb/openai-1.63.0-py3-none-any.whl", hash = "sha256:a664dfc78f0a05ca46c3e21f344f840cf6bf7174f13cfa9de214ed28bfca1dda", size = 472282 }, + { url = "https://files.pythonhosted.org/packages/15/64/db3462b358072387b8e93e6e6a38d3c741a17b4a84171ef01d6c85c63f25/openai-1.63.2-py3-none-any.whl", hash = "sha256:1f38b27b5a40814c2b7d8759ec78110df58c4a614c25f182809ca52b080ff4d4", size = 472282 }, ] [[package]] @@ -748,6 +1033,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 }, ] +[[package]] +name = "overrides" +version = "7.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, +] + [[package]] name = "packaging" version = "24.2" @@ -805,6 +1099,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, ] +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, +] + [[package]] name = "psycopg2-binary" version = "2.9.10" @@ -860,6 +1187,66 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 }, ] +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + +[[package]] +name = "pyarrow" +version = "19.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/01/fe1fd04744c2aa038e5a11c7a4adb3d62bce09798695e54f7274b5977134/pyarrow-19.0.0.tar.gz", hash = "sha256:8d47c691765cf497aaeed4954d226568563f1b3b74ff61139f2d77876717084b", size = 1129096 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/02/1ad80ffd3c558916858a49c83b6e494a9d93009bbebc603cf0cb8263bea7/pyarrow-19.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:c318eda14f6627966997a7d8c374a87d084a94e4e38e9abbe97395c215830e0c", size = 30686262 }, + { url = "https://files.pythonhosted.org/packages/1b/f0/adab5f142eb8203db8bfbd3a816816e37a85423ae684567e7f3555658315/pyarrow-19.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:62ef8360ff256e960f57ce0299090fb86423afed5e46f18f1225f960e05aae3d", size = 32100005 }, + { url = "https://files.pythonhosted.org/packages/94/8b/e674083610e5efc48d2f205c568d842cdfdf683d12f9ff0d546e38757722/pyarrow-19.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2795064647add0f16563e57e3d294dbfc067b723f0fd82ecd80af56dad15f503", size = 41144815 }, + { url = "https://files.pythonhosted.org/packages/d5/fb/2726241a792b7f8a58789e5a63d1be9a5a4059206318fd0ff9485a578952/pyarrow-19.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a218670b26fb1bc74796458d97bcab072765f9b524f95b2fccad70158feb8b17", size = 42180380 }, + { url = "https://files.pythonhosted.org/packages/7d/09/7aef12446d8e7002dfc07bb7bc71f594c1d5844ca78b364a49f07efb65b1/pyarrow-19.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:66732e39eaa2247996a6b04c8aa33e3503d351831424cdf8d2e9a0582ac54b34", size = 40515021 }, + { url = "https://files.pythonhosted.org/packages/31/55/f05fc5608cc96060c2b24de505324d641888bd62d4eed2fa1dacd872a1e1/pyarrow-19.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:e675a3ad4732b92d72e4d24009707e923cab76b0d088e5054914f11a797ebe44", size = 42067488 }, + { url = "https://files.pythonhosted.org/packages/f0/01/097653cec7a944c16313cb748a326771133c142034b252076bd84743b98d/pyarrow-19.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:f094742275586cdd6b1a03655ccff3b24b2610c3af76f810356c4c71d24a2a6c", size = 25276726 }, + { url = "https://files.pythonhosted.org/packages/82/42/fba3a35bef5833bf88ed35e6a810dc1781236e1d4f808d2df824a7d21819/pyarrow-19.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8e3a839bf36ec03b4315dc924d36dcde5444a50066f1c10f8290293c0427b46a", size = 30711936 }, + { url = "https://files.pythonhosted.org/packages/88/7a/0da93a3eaaf251a30e32f3221e874263cdcd366c2cd6b7c05293aad91152/pyarrow-19.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ce42275097512d9e4e4a39aade58ef2b3798a93aa3026566b7892177c266f735", size = 32133182 }, + { url = "https://files.pythonhosted.org/packages/2f/df/fe43b1c50d3100d0de53f988344118bc20362d0de005f8a407454fa565f8/pyarrow-19.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9348a0137568c45601b031a8d118275069435f151cbb77e6a08a27e8125f59d4", size = 41145489 }, + { url = "https://files.pythonhosted.org/packages/45/bb/6f73b41b342a0342f2516a02db4aa97a4f9569cc35482a5c288090140cd4/pyarrow-19.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0144a712d990d60f7f42b7a31f0acaccf4c1e43e957f7b1ad58150d6f639c1", size = 42177823 }, + { url = "https://files.pythonhosted.org/packages/23/7b/f038a96f421e453a71bd7a0f78d62b1b2ae9bcac06ed51179ca532e6a0a2/pyarrow-19.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2a1a109dfda558eb011e5f6385837daffd920d54ca00669f7a11132d0b1e6042", size = 40530609 }, + { url = "https://files.pythonhosted.org/packages/b8/39/a2a6714b471c000e6dd6af4495dce00d7d1332351b8e3170dfb9f91dad1f/pyarrow-19.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:be686bf625aa7b9bada18defb3a3ea3981c1099697239788ff111d87f04cd263", size = 42081534 }, + { url = "https://files.pythonhosted.org/packages/6c/a3/8396fb06ca05d807e89980c177be26617aad15211ece3184e0caa730b8a6/pyarrow-19.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:239ca66d9a05844bdf5af128861af525e14df3c9591bcc05bac25918e650d3a2", size = 25281090 }, + { url = "https://files.pythonhosted.org/packages/bc/2e/152885f5ef421e80dae68b9c133ab261934f93a6d5e16b61d79c0ed597fb/pyarrow-19.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:a7bbe7109ab6198688b7079cbad5a8c22de4d47c4880d8e4847520a83b0d1b68", size = 30667964 }, + { url = "https://files.pythonhosted.org/packages/80/c2/08bbee9a8610a47c9a1466845f405baf53a639ddd947c5133d8ba13544b6/pyarrow-19.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:4624c89d6f777c580e8732c27bb8e77fd1433b89707f17c04af7635dd9638351", size = 32125039 }, + { url = "https://files.pythonhosted.org/packages/d2/56/06994df823212f5688d3c8bf4294928b12c9be36681872853655724d28c6/pyarrow-19.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b6d3ce4288793350dc2d08d1e184fd70631ea22a4ff9ea5c4ff182130249d9b", size = 41140729 }, + { url = "https://files.pythonhosted.org/packages/94/65/38ad577c98140a9db71e9e1e594b6adb58a7478a5afec6456a8ca2df7f70/pyarrow-19.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:450a7d27e840e4d9a384b5c77199d489b401529e75a3b7a3799d4cd7957f2f9c", size = 42202267 }, + { url = "https://files.pythonhosted.org/packages/b6/1f/966b722251a7354114ccbb71cf1a83922023e69efd8945ebf628a851ec4c/pyarrow-19.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a08e2a8a039a3f72afb67a6668180f09fddaa38fe0d21f13212b4aba4b5d2451", size = 40505858 }, + { url = "https://files.pythonhosted.org/packages/3b/5e/6bc81aa7fc9affc7d1c03b912fbcc984ca56c2a18513684da267715dab7b/pyarrow-19.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f43f5aef2a13d4d56adadae5720d1fed4c1356c993eda8b59dace4b5983843c1", size = 42084973 }, + { url = "https://files.pythonhosted.org/packages/53/c3/2f56da818b6a4758cbd514957c67bd0f078ebffa5390ee2e2bf0f9e8defc/pyarrow-19.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:2f672f5364b2d7829ef7c94be199bb88bf5661dd485e21d2d37de12ccb78a136", size = 25241976 }, + { url = "https://files.pythonhosted.org/packages/f5/b9/ba07ed3dd6b6e4f379b78e9c47c50c8886e07862ab7fa6339ac38622d755/pyarrow-19.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:cf3bf0ce511b833f7bc5f5bb3127ba731e97222023a444b7359f3a22e2a3b463", size = 30651291 }, + { url = "https://files.pythonhosted.org/packages/ad/10/0d304243c8277035298a68a70807efb76199c6c929bb3363c92ac9be6a0d/pyarrow-19.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:4d8b0c0de0a73df1f1bf439af1b60f273d719d70648e898bc077547649bb8352", size = 32100461 }, + { url = "https://files.pythonhosted.org/packages/8a/61/bcfc5182e11831bca3f849945b9b106e09fd10ded773dff466658e972a45/pyarrow-19.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92aff08e23d281c69835e4a47b80569242a504095ef6a6223c1f6bb8883431d", size = 41132491 }, + { url = "https://files.pythonhosted.org/packages/8e/87/2915a29049ec352dc69a967fbcbd76b0180319233de0daf8bd368df37099/pyarrow-19.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3b78eff5968a1889a0f3bc81ca57e1e19b75f664d9c61a42a604bf9d8402aae", size = 42192529 }, + { url = "https://files.pythonhosted.org/packages/48/18/44e5542b2707a8afaf78b5b88c608f261871ae77787eac07b7c679ca6f0f/pyarrow-19.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:b34d3bde38eba66190b215bae441646330f8e9da05c29e4b5dd3e41bde701098", size = 40495363 }, + { url = "https://files.pythonhosted.org/packages/ba/d6/5096deb7599bbd20bc2768058fe23bc725b88eb41bee58303293583a2935/pyarrow-19.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5418d4d0fab3a0ed497bad21d17a7973aad336d66ad4932a3f5f7480d4ca0c04", size = 42074075 }, + { url = "https://files.pythonhosted.org/packages/2c/df/e3c839c04c284c9ec3d62b02a8c452b795d9b07b04079ab91ce33484d4c5/pyarrow-19.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e82c3d5e44e969c217827b780ed8faf7ac4c53f934ae9238872e749fa531f7c9", size = 25239803 }, + { url = "https://files.pythonhosted.org/packages/6a/d3/a6d4088e906c7b5d47792256212606d2ae679046dc750eee0ae167338e5c/pyarrow-19.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:f208c3b58a6df3b239e0bb130e13bc7487ed14f39a9ff357b6415e3f6339b560", size = 30695401 }, + { url = "https://files.pythonhosted.org/packages/94/25/70040fd0e397dd1b937f459eaeeec942a76027357491dca0ada09d1322af/pyarrow-19.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:c751c1c93955b7a84c06794df46f1cec93e18610dcd5ab7d08e89a81df70a849", size = 32104680 }, + { url = "https://files.pythonhosted.org/packages/4e/f9/92783290cc0d80ca16d34b0c126305bfacca4b87dd889c8f16c6ef2a8fd7/pyarrow-19.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b903afaa5df66d50fc38672ad095806443b05f202c792694f3a604ead7c6ea6e", size = 41076754 }, + { url = "https://files.pythonhosted.org/packages/05/46/2c9870f50a495c72e2b8982ae29a9b1680707ea936edc0de444cec48f875/pyarrow-19.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22a4bc0937856263df8b94f2f2781b33dd7f876f787ed746608e06902d691a5", size = 42163133 }, + { url = "https://files.pythonhosted.org/packages/7b/2f/437922b902549228fb15814e8a26105bff2787ece466a8d886eb6699efad/pyarrow-19.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:5e8a28b918e2e878c918f6d89137386c06fe577cd08d73a6be8dafb317dc2d73", size = 40452210 }, + { url = "https://files.pythonhosted.org/packages/36/ef/1d7975053af9d106da973bac142d0d4da71b7550a3576cc3e0b3f444d21a/pyarrow-19.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:29cd86c8001a94f768f79440bf83fee23963af5e7bc68ce3a7e5f120e17edf89", size = 42077618 }, +] + [[package]] name = "pyasn1" version = "0.6.1" @@ -1050,16 +1437,64 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/46/93416fdae86d40879714f72956ac14df9c7b76f7d41a4d68aa9f71a0028b/pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd", size = 29718 }, ] +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pylance" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pyarrow" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/44/baf24347317e4122a833db8c22e48d66259fced990acccc1857f6e603547/pylance-0.23.0-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:a77e2880cf4b9f4ea9f5bf5bba1ab9e93e28411fefd4de2127039c9a2fff2076", size = 35616653 }, + { url = "https://files.pythonhosted.org/packages/b7/fb/683256923be01b65b5c992992f5c976de5ddd9025ea20a835ba6cdc9fc88/pylance-0.23.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8736322e79e52df4a25ff87b3f93defa0e40ca70f2d335c696ba87b07cb5d1f5", size = 33035679 }, + { url = "https://files.pythonhosted.org/packages/f1/92/b07fccbf9951ce35d5a3b0989f463289fd0162a840c4f5bb8cb279a1a6d4/pylance-0.23.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e33f2601f085d03e485126fef9c67b6b6489e31c1bb5d1dcc489e1e0c508accb", size = 37052218 }, + { url = "https://files.pythonhosted.org/packages/dd/a8/348f5349f2e382b5d6147f3c4b5eac3fef17a5edcd6b234ff2a69c370e4b/pylance-0.23.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfa387fa1aa5bdb481f74d9fa3f65920944a77eb41018e38e6d25dd8ea25e79", size = 39066521 }, + { url = "https://files.pythonhosted.org/packages/19/be/71ad54973717ba24cdc6c4bf0af48a498718ae8051dab6a5a298c3adbfa0/pylance-0.23.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:95d3a66f29b0463431e85772057c56181f80f23151a33ae86722cdb1d1435be2", size = 36745305 }, + { url = "https://files.pythonhosted.org/packages/31/df/5aa7d20eebd69de7df7a64466516365ca70f3042937a5dd50738a0981a22/pylance-0.23.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1c0d648e1706caeaf55e66c26f1262e6efab63ef5ac05ce5c1e6a5a00d755c73", size = 38439865 }, + { url = "https://files.pythonhosted.org/packages/7f/9a/8bcbbaa357f23a8706b669b5880726c37ad4bb59df37359a4afd04319383/pylance-0.23.0-cp39-abi3-win_amd64.whl", hash = "sha256:e16db6e2fafdda1fa31cd8cd20f9afdaf6a6fbd4846bd90f65d6885a9e0e4917", size = 34658481 }, +] + +[[package]] +name = "python-box" +version = "7.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/f7/635eed8c500adf26208e86e985bbffb6ff039cd8950e3a4749ceca904218/python_box-7.3.2.tar.gz", hash = "sha256:028b9917129e67f311932d93347b8a4f1b500d7a5a2870ee3c035f4e7b19403b", size = 45771 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/ee/7d9a4bc3ac4e693018506fb21893f4ba253694fd31dd93f7195f57a6c8d0/python_box-7.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d136163294fd61a1554db7dd203f2e3035064798d30c17d67d948f0de5c572de", size = 1832073 }, + { url = "https://files.pythonhosted.org/packages/25/be/97c585206086f50175b72e31f092f0afb3de43013d040097c1febbc7a2ce/python_box-7.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d72e96547d8e2c2c333909826e9fae338d9a7e4cde07d5c6058cdd468432c0", size = 3954019 }, + { url = "https://files.pythonhosted.org/packages/34/6b/95d1f13ae2c2c72854f8e845385312693a67ba0b2c9499d57476d8394c61/python_box-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:3aa52e3b5cc50c80bb7ef4be3e41e81d095310f619454a7ffd61eef3209a6225", size = 1229788 }, + { url = "https://files.pythonhosted.org/packages/1e/3f/133619c00d8a9d4f86efd8626c0e4ec356c8b8dabac66da18dac5cfaf70c/python_box-7.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:32163b1cb151883de0da62b0cd3572610dc72ccf0762f2447baf1d2562e25bea", size = 1834122 }, + { url = "https://files.pythonhosted.org/packages/b7/52/51b6081562daa864847692536260200b337ccb4176d1e5f626ae48a7d493/python_box-7.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:064cb59b41e25aaf7dbd39efe53151a5f6797cc1cb3c68610f0f21a9d406d67e", size = 4305556 }, + { url = "https://files.pythonhosted.org/packages/d4/e2/6cdc8649381ae14def88c3e2e93d5b8b17a622a95896e0d1c92861270b7d/python_box-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:488f0fba9a6416c3334b602366dcd92825adb0811e07e03753dfcf0ed79cd6f7", size = 1232328 }, + { url = "https://files.pythonhosted.org/packages/45/68/0c2f289d8055d3e1b156ff258847f0e8f1010063e284cf5a612f09435575/python_box-7.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39009a2da5c20133718b24891a206592adbe09169856aedc450ad1600fc2e511", size = 1819681 }, + { url = "https://files.pythonhosted.org/packages/ce/5d/76b4d6d0e41edb676a229f032848a1ecea166890fa8d501513ea1a030f4d/python_box-7.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2a72e2f6fb97c7e472ff3272da207ecc615aa222e52e98352391428527c469", size = 4270424 }, + { url = "https://files.pythonhosted.org/packages/e4/6b/32484b2a3cd2fb5e5f56bfb53a4537d93a4d2014ccf7fc0c0017fa6f65e9/python_box-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9eead914b9fb7d98a1473f5027dcfe27d26b3a10ffa33b9ba22cf948a23cd280", size = 1211252 }, + { url = "https://files.pythonhosted.org/packages/2f/39/8bec609e93dbc5e0d3ea26cfb5af3ca78915f7a55ef5414713462fedeb59/python_box-7.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1dfc3b9b073f3d7cad1fa90de98eaaa684a494d0574bbc0666f74fa8307fd6b6", size = 1804675 }, + { url = "https://files.pythonhosted.org/packages/88/ae/baf3a8057d8129896a7e02619df43ea0d918fc5b2bb66eb6e2470595fbac/python_box-7.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca4685a7f764b5a71b6e08535ce2a96b7964bb63d8cb4df10f6bb7147b6c54b", size = 4265645 }, + { url = "https://files.pythonhosted.org/packages/43/90/72367e03033c11a5e82676ee389b572bf136647ff4e3081557392b37e1ad/python_box-7.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e143295f74d47a9ab24562ead2375c9be10629599b57f2e86717d3fff60f82a9", size = 1206740 }, + { url = "https://files.pythonhosted.org/packages/37/13/8a990c6e2b6cc12700dce16f3cb383324e6d9a30f604eca22a2fdf84c923/python_box-7.3.2-py3-none-any.whl", hash = "sha256:fd7d74d5a848623f93b5221fd9fb00b8c00ff0e130fa87f396277aa188659c92", size = 29479 }, +] + [[package]] name = "python-dateutil" -version = "2.9.0.post0" +version = "2.8.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +sdist = { url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", size = 357324 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, + { url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9", size = 247702 }, ] [[package]] @@ -1139,6 +1574,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, ] +[[package]] +name = "rich" +version = "13.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, +] + [[package]] name = "rsa" version = "4.9" @@ -1191,113 +1640,119 @@ wheels = [ [[package]] name = "scipy" -version = "1.15.1" +version = "1.15.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/c6/8eb0654ba0c7d0bb1bf67bf8fbace101a8e4f250f7722371105e8b6f68fc/scipy-1.15.1.tar.gz", hash = "sha256:033a75ddad1463970c96a88063a1df87ccfddd526437136b6ee81ff0312ebdf6", size = 59407493 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/53/b204ce5a4433f1864001b9d16f103b9c25f5002a602ae83585d0ea5f9c4a/scipy-1.15.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:c64ded12dcab08afff9e805a67ff4480f5e69993310e093434b10e85dc9d43e1", size = 41414518 }, - { url = "https://files.pythonhosted.org/packages/c7/fc/54ffa7a8847f7f303197a6ba65a66104724beba2e38f328135a78f0dc480/scipy-1.15.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5b190b935e7db569960b48840e5bef71dc513314cc4e79a1b7d14664f57fd4ff", size = 32519265 }, - { url = "https://files.pythonhosted.org/packages/f1/77/a98b8ba03d6f371dc31a38719affd53426d4665729dcffbed4afe296784a/scipy-1.15.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:4b17d4220df99bacb63065c76b0d1126d82bbf00167d1730019d2a30d6ae01ea", size = 24792859 }, - { url = "https://files.pythonhosted.org/packages/a7/78/70bb9f0df7444b18b108580934bfef774822e28fd34a68e5c263c7d2828a/scipy-1.15.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:63b9b6cd0333d0eb1a49de6f834e8aeaefe438df8f6372352084535ad095219e", size = 27886506 }, - { url = "https://files.pythonhosted.org/packages/14/a7/f40f6033e06de4176ddd6cc8c3ae9f10a226c3bca5d6b4ab883bc9914a14/scipy-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f151e9fb60fbf8e52426132f473221a49362091ce7a5e72f8aa41f8e0da4f25", size = 38375041 }, - { url = "https://files.pythonhosted.org/packages/17/03/390a1c5c61fd76b0fa4b3c5aa3bdd7e60f6c46f712924f1a9df5705ec046/scipy-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e10b1dd56ce92fba3e786007322542361984f8463c6d37f6f25935a5a6ef52", size = 40597556 }, - { url = "https://files.pythonhosted.org/packages/4e/70/fa95b3ae026b97eeca58204a90868802e5155ac71b9d7bdee92b68115dd3/scipy-1.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5dff14e75cdbcf07cdaa1c7707db6017d130f0af9ac41f6ce443a93318d6c6e0", size = 42938505 }, - { url = "https://files.pythonhosted.org/packages/d6/07/427859116bdd71847c898180f01802691f203c3e2455a1eb496130ff07c5/scipy-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:f82fcf4e5b377f819542fbc8541f7b5fbcf1c0017d0df0bc22c781bf60abc4d8", size = 43909663 }, - { url = "https://files.pythonhosted.org/packages/8e/2e/7b71312da9c2dabff53e7c9a9d08231bc34d9d8fdabe88a6f1155b44591c/scipy-1.15.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:5bd8d27d44e2c13d0c1124e6a556454f52cd3f704742985f6b09e75e163d20d2", size = 41424362 }, - { url = "https://files.pythonhosted.org/packages/81/8c/ab85f1aa1cc200c796532a385b6ebf6a81089747adc1da7482a062acc46c/scipy-1.15.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:be3deeb32844c27599347faa077b359584ba96664c5c79d71a354b80a0ad0ce0", size = 32535910 }, - { url = "https://files.pythonhosted.org/packages/3b/9c/6f4b787058daa8d8da21ddff881b4320e28de4704a65ec147adb50cb2230/scipy-1.15.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:5eb0ca35d4b08e95da99a9f9c400dc9f6c21c424298a0ba876fdc69c7afacedf", size = 24809398 }, - { url = "https://files.pythonhosted.org/packages/16/2b/949460a796df75fc7a1ee1becea202cf072edbe325ebe29f6d2029947aa7/scipy-1.15.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:74bb864ff7640dea310a1377d8567dc2cb7599c26a79ca852fc184cc851954ac", size = 27918045 }, - { url = "https://files.pythonhosted.org/packages/5f/36/67fe249dd7ccfcd2a38b25a640e3af7e59d9169c802478b6035ba91dfd6d/scipy-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:667f950bf8b7c3a23b4199db24cb9bf7512e27e86d0e3813f015b74ec2c6e3df", size = 38332074 }, - { url = "https://files.pythonhosted.org/packages/fc/da/452e1119e6f720df3feb588cce3c42c5e3d628d4bfd4aec097bd30b7de0c/scipy-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395be70220d1189756068b3173853029a013d8c8dd5fd3d1361d505b2aa58fa7", size = 40588469 }, - { url = "https://files.pythonhosted.org/packages/7f/71/5f94aceeac99a4941478af94fe9f459c6752d497035b6b0761a700f5f9ff/scipy-1.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce3a000cd28b4430426db2ca44d96636f701ed12e2b3ca1f2b1dd7abdd84b39a", size = 42965214 }, - { url = "https://files.pythonhosted.org/packages/af/25/caa430865749d504271757cafd24066d596217e83326155993980bc22f97/scipy-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:3fe1d95944f9cf6ba77aa28b82dd6bb2a5b52f2026beb39ecf05304b8392864b", size = 43896034 }, - { url = "https://files.pythonhosted.org/packages/d8/6e/a9c42d0d39e09ed7fd203d0ac17adfea759cba61ab457671fe66e523dbec/scipy-1.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c09aa9d90f3500ea4c9b393ee96f96b0ccb27f2f350d09a47f533293c78ea776", size = 41478318 }, - { url = "https://files.pythonhosted.org/packages/04/ee/e3e535c81828618878a7433992fecc92fa4df79393f31a8fea1d05615091/scipy-1.15.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0ac102ce99934b162914b1e4a6b94ca7da0f4058b6d6fd65b0cef330c0f3346f", size = 32596696 }, - { url = "https://files.pythonhosted.org/packages/c4/5e/b1b0124be8e76f87115f16b8915003eec4b7060298117715baf13f51942c/scipy-1.15.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:09c52320c42d7f5c7748b69e9f0389266fd4f82cf34c38485c14ee976cb8cb04", size = 24870366 }, - { url = "https://files.pythonhosted.org/packages/14/36/c00cb73eefda85946172c27913ab995c6ad4eee00fa4f007572e8c50cd51/scipy-1.15.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:cdde8414154054763b42b74fe8ce89d7f3d17a7ac5dd77204f0e142cdc9239e9", size = 28007461 }, - { url = "https://files.pythonhosted.org/packages/68/94/aff5c51b3799349a9d1e67a056772a0f8a47db371e83b498d43467806557/scipy-1.15.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c9d8fc81d6a3b6844235e6fd175ee1d4c060163905a2becce8e74cb0d7554ce", size = 38068174 }, - { url = "https://files.pythonhosted.org/packages/b0/3c/0de11ca154e24a57b579fb648151d901326d3102115bc4f9a7a86526ce54/scipy-1.15.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fb57b30f0017d4afa5fe5f5b150b8f807618819287c21cbe51130de7ccdaed2", size = 40249869 }, - { url = "https://files.pythonhosted.org/packages/15/09/472e8d0a6b33199d1bb95e49bedcabc0976c3724edd9b0ef7602ccacf41e/scipy-1.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:491d57fe89927fa1aafbe260f4cfa5ffa20ab9f1435025045a5315006a91b8f5", size = 42629068 }, - { url = "https://files.pythonhosted.org/packages/ff/ba/31c7a8131152822b3a2cdeba76398ffb404d81d640de98287d236da90c49/scipy-1.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:900f3fa3db87257510f011c292a5779eb627043dd89731b9c461cd16ef76ab3d", size = 43621992 }, - { url = "https://files.pythonhosted.org/packages/2b/bf/dd68965a4c5138a630eeed0baec9ae96e5d598887835bdde96cdd2fe4780/scipy-1.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:100193bb72fbff37dbd0bf14322314fc7cbe08b7ff3137f11a34d06dc0ee6b85", size = 41441136 }, - { url = "https://files.pythonhosted.org/packages/ef/5e/4928581312922d7e4d416d74c416a660addec4dd5ea185401df2269ba5a0/scipy-1.15.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:2114a08daec64980e4b4cbdf5bee90935af66d750146b1d2feb0d3ac30613692", size = 32533699 }, - { url = "https://files.pythonhosted.org/packages/32/90/03f99c43041852837686898c66767787cd41c5843d7a1509c39ffef683e9/scipy-1.15.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6b3e71893c6687fc5e29208d518900c24ea372a862854c9888368c0b267387ab", size = 24807289 }, - { url = "https://files.pythonhosted.org/packages/9d/52/bfe82b42ae112eaba1af2f3e556275b8727d55ac6e4932e7aef337a9d9d4/scipy-1.15.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:837299eec3d19b7e042923448d17d95a86e43941104d33f00da7e31a0f715d3c", size = 27929844 }, - { url = "https://files.pythonhosted.org/packages/f6/77/54ff610bad600462c313326acdb035783accc6a3d5f566d22757ad297564/scipy-1.15.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82add84e8a9fb12af5c2c1a3a3f1cb51849d27a580cb9e6bd66226195142be6e", size = 38031272 }, - { url = "https://files.pythonhosted.org/packages/f1/26/98585cbf04c7cf503d7eb0a1966df8a268154b5d923c5fe0c1ed13154c49/scipy-1.15.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070d10654f0cb6abd295bc96c12656f948e623ec5f9a4eab0ddb1466c000716e", size = 40210217 }, - { url = "https://files.pythonhosted.org/packages/fd/3f/3d2285eb6fece8bc5dbb2f9f94d61157d61d155e854fd5fea825b8218f12/scipy-1.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55cc79ce4085c702ac31e49b1e69b27ef41111f22beafb9b49fea67142b696c4", size = 42587785 }, - { url = "https://files.pythonhosted.org/packages/48/7d/5b5251984bf0160d6533695a74a5fddb1fa36edd6f26ffa8c871fbd4782a/scipy-1.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:c352c1b6d7cac452534517e022f8f7b8d139cd9f27e6fbd9f3cbd0bfd39f5bef", size = 43640439 }, - { url = "https://files.pythonhosted.org/packages/e7/b8/0e092f592d280496de52e152582030f8a270b194f87f890e1a97c5599b81/scipy-1.15.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0458839c9f873062db69a03de9a9765ae2e694352c76a16be44f93ea45c28d2b", size = 41619862 }, - { url = "https://files.pythonhosted.org/packages/f6/19/0b6e1173aba4db9e0b7aa27fe45019857fb90d6904038b83927cbe0a6c1d/scipy-1.15.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:af0b61c1de46d0565b4b39c6417373304c1d4f5220004058bdad3061c9fa8a95", size = 32610387 }, - { url = "https://files.pythonhosted.org/packages/e7/02/754aae3bd1fa0f2479ade3cfdf1732ecd6b05853f63eee6066a32684563a/scipy-1.15.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:71ba9a76c2390eca6e359be81a3e879614af3a71dfdabb96d1d7ab33da6f2364", size = 24883814 }, - { url = "https://files.pythonhosted.org/packages/1f/ac/d7906201604a2ea3b143bb0de51b3966f66441ba50b7dc182c4505b3edf9/scipy-1.15.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14eaa373c89eaf553be73c3affb11ec6c37493b7eaaf31cf9ac5dffae700c2e0", size = 27944865 }, - { url = "https://files.pythonhosted.org/packages/84/9d/8f539002b5e203723af6a6f513a45e0a7671e9dabeedb08f417ac17e4edc/scipy-1.15.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f735bc41bd1c792c96bc426dece66c8723283695f02df61dcc4d0a707a42fc54", size = 39883261 }, - { url = "https://files.pythonhosted.org/packages/97/c0/62fd3bab828bcccc9b864c5997645a3b86372a35941cdaf677565c25c98d/scipy-1.15.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2722a021a7929d21168830790202a75dbb20b468a8133c74a2c0230c72626b6c", size = 42093299 }, - { url = "https://files.pythonhosted.org/packages/e4/1f/5d46a8d94e9f6d2c913cbb109e57e7eed914de38ea99e2c4d69a9fc93140/scipy-1.15.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc7136626261ac1ed988dca56cfc4ab5180f75e0ee52e58f1e6aa74b5f3eacd5", size = 43181730 }, +sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/df/ef233fff6838fe6f7840d69b5ef9f20d2b5c912a8727b21ebf876cb15d54/scipy-1.15.2-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a2ec871edaa863e8213ea5df811cd600734f6400b4af272e1c011e69401218e9", size = 38692502 }, + { url = "https://files.pythonhosted.org/packages/5c/20/acdd4efb8a68b842968f7bc5611b1aeb819794508771ad104de418701422/scipy-1.15.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6f223753c6ea76983af380787611ae1291e3ceb23917393079dcc746ba60cfb5", size = 30085508 }, + { url = "https://files.pythonhosted.org/packages/42/55/39cf96ca7126f1e78ee72a6344ebdc6702fc47d037319ad93221063e6cf4/scipy-1.15.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ecf797d2d798cf7c838c6d98321061eb3e72a74710e6c40540f0e8087e3b499e", size = 22359166 }, + { url = "https://files.pythonhosted.org/packages/51/48/708d26a4ab8a1441536bf2dfcad1df0ca14a69f010fba3ccbdfc02df7185/scipy-1.15.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:9b18aa747da280664642997e65aab1dd19d0c3d17068a04b3fe34e2559196cb9", size = 25112047 }, + { url = "https://files.pythonhosted.org/packages/dd/65/f9c5755b995ad892020381b8ae11f16d18616208e388621dfacc11df6de6/scipy-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87994da02e73549dfecaed9e09a4f9d58a045a053865679aeb8d6d43747d4df3", size = 35536214 }, + { url = "https://files.pythonhosted.org/packages/de/3c/c96d904b9892beec978562f64d8cc43f9cca0842e65bd3cd1b7f7389b0ba/scipy-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69ea6e56d00977f355c0f84eba69877b6df084516c602d93a33812aa04d90a3d", size = 37646981 }, + { url = "https://files.pythonhosted.org/packages/3d/74/c2d8a24d18acdeae69ed02e132b9bc1bb67b7bee90feee1afe05a68f9d67/scipy-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:888307125ea0c4466287191e5606a2c910963405ce9671448ff9c81c53f85f58", size = 37230048 }, + { url = "https://files.pythonhosted.org/packages/42/19/0aa4ce80eca82d487987eff0bc754f014dec10d20de2f66754fa4ea70204/scipy-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9412f5e408b397ff5641080ed1e798623dbe1ec0d78e72c9eca8992976fa65aa", size = 40010322 }, + { url = "https://files.pythonhosted.org/packages/d0/d2/f0683b7e992be44d1475cc144d1f1eeae63c73a14f862974b4db64af635e/scipy-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:b5e025e903b4f166ea03b109bb241355b9c42c279ea694d8864d033727205e65", size = 41233385 }, + { url = "https://files.pythonhosted.org/packages/40/1f/bf0a5f338bda7c35c08b4ed0df797e7bafe8a78a97275e9f439aceb46193/scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4", size = 38703651 }, + { url = "https://files.pythonhosted.org/packages/de/54/db126aad3874601048c2c20ae3d8a433dbfd7ba8381551e6f62606d9bd8e/scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1", size = 30102038 }, + { url = "https://files.pythonhosted.org/packages/61/d8/84da3fffefb6c7d5a16968fe5b9f24c98606b165bb801bb0b8bc3985200f/scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971", size = 22375518 }, + { url = "https://files.pythonhosted.org/packages/44/78/25535a6e63d3b9c4c90147371aedb5d04c72f3aee3a34451f2dc27c0c07f/scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655", size = 25142523 }, + { url = "https://files.pythonhosted.org/packages/e0/22/4b4a26fe1cd9ed0bc2b2cb87b17d57e32ab72c346949eaf9288001f8aa8e/scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e", size = 35491547 }, + { url = "https://files.pythonhosted.org/packages/32/ea/564bacc26b676c06a00266a3f25fdfe91a9d9a2532ccea7ce6dd394541bc/scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0", size = 37634077 }, + { url = "https://files.pythonhosted.org/packages/43/c2/bfd4e60668897a303b0ffb7191e965a5da4056f0d98acfb6ba529678f0fb/scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40", size = 37231657 }, + { url = "https://files.pythonhosted.org/packages/4a/75/5f13050bf4f84c931bcab4f4e83c212a36876c3c2244475db34e4b5fe1a6/scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462", size = 40035857 }, + { url = "https://files.pythonhosted.org/packages/b9/8b/7ec1832b09dbc88f3db411f8cdd47db04505c4b72c99b11c920a8f0479c3/scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737", size = 41217654 }, + { url = "https://files.pythonhosted.org/packages/4b/5d/3c78815cbab499610f26b5bae6aed33e227225a9fa5290008a733a64f6fc/scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd", size = 38756184 }, + { url = "https://files.pythonhosted.org/packages/37/20/3d04eb066b471b6e171827548b9ddb3c21c6bbea72a4d84fc5989933910b/scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301", size = 30163558 }, + { url = "https://files.pythonhosted.org/packages/a4/98/e5c964526c929ef1f795d4c343b2ff98634ad2051bd2bbadfef9e772e413/scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93", size = 22437211 }, + { url = "https://files.pythonhosted.org/packages/1d/cd/1dc7371e29195ecbf5222f9afeedb210e0a75057d8afbd942aa6cf8c8eca/scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20", size = 25232260 }, + { url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095 }, + { url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371 }, + { url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390 }, + { url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276 }, + { url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317 }, + { url = "https://files.pythonhosted.org/packages/53/40/09319f6e0f276ea2754196185f95cd191cb852288440ce035d5c3a931ea2/scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf", size = 38717587 }, + { url = "https://files.pythonhosted.org/packages/fe/c3/2854f40ecd19585d65afaef601e5e1f8dbf6758b2f95b5ea93d38655a2c6/scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37", size = 30100266 }, + { url = "https://files.pythonhosted.org/packages/dd/b1/f9fe6e3c828cb5930b5fe74cb479de5f3d66d682fa8adb77249acaf545b8/scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d", size = 22373768 }, + { url = "https://files.pythonhosted.org/packages/15/9d/a60db8c795700414c3f681908a2b911e031e024d93214f2d23c6dae174ab/scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb", size = 25154719 }, + { url = "https://files.pythonhosted.org/packages/37/3b/9bda92a85cd93f19f9ed90ade84aa1e51657e29988317fabdd44544f1dd4/scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27", size = 35163195 }, + { url = "https://files.pythonhosted.org/packages/03/5a/fc34bf1aa14dc7c0e701691fa8685f3faec80e57d816615e3625f28feb43/scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0", size = 37255404 }, + { url = "https://files.pythonhosted.org/packages/4a/71/472eac45440cee134c8a180dbe4c01b3ec247e0338b7c759e6cd71f199a7/scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32", size = 36860011 }, + { url = "https://files.pythonhosted.org/packages/01/b3/21f890f4f42daf20e4d3aaa18182dddb9192771cd47445aaae2e318f6738/scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d", size = 39657406 }, + { url = "https://files.pythonhosted.org/packages/0d/76/77cf2ac1f2a9cc00c073d49e1e16244e389dd88e2490c91d84e1e3e4d126/scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f", size = 40961243 }, + { url = "https://files.pythonhosted.org/packages/4c/4b/a57f8ddcf48e129e6054fa9899a2a86d1fc6b07a0e15c7eebff7ca94533f/scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9", size = 38870286 }, + { url = "https://files.pythonhosted.org/packages/0c/43/c304d69a56c91ad5f188c0714f6a97b9c1fed93128c691148621274a3a68/scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f", size = 30141634 }, + { url = "https://files.pythonhosted.org/packages/44/1a/6c21b45d2548eb73be9b9bff421aaaa7e85e22c1f9b3bc44b23485dfce0a/scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6", size = 22415179 }, + { url = "https://files.pythonhosted.org/packages/74/4b/aefac4bba80ef815b64f55da06f62f92be5d03b467f2ce3668071799429a/scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af", size = 25126412 }, + { url = "https://files.pythonhosted.org/packages/b1/53/1cbb148e6e8f1660aacd9f0a9dfa2b05e9ff1cb54b4386fe868477972ac2/scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274", size = 34952867 }, + { url = "https://files.pythonhosted.org/packages/2c/23/e0eb7f31a9c13cf2dca083828b97992dd22f8184c6ce4fec5deec0c81fcf/scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776", size = 36890009 }, + { url = "https://files.pythonhosted.org/packages/03/f3/e699e19cabe96bbac5189c04aaa970718f0105cff03d458dc5e2b6bd1e8c/scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828", size = 36545159 }, + { url = "https://files.pythonhosted.org/packages/af/f5/ab3838e56fe5cc22383d6fcf2336e48c8fe33e944b9037fbf6cbdf5a11f8/scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28", size = 39136566 }, + { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705 }, ] [[package]] name = "simplejson" -version = "3.19.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3d/29/085111f19717f865eceaf0d4397bf3e76b08d60428b076b64e2a1903706d/simplejson-3.19.3.tar.gz", hash = "sha256:8e086896c36210ab6050f2f9f095a5f1e03c83fa0e7f296d6cba425411364680", size = 85237 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/24/260ad03435ce8ef2436031951134659c7161776ec3a78094b35b9375ceea/simplejson-3.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:50d8b742d74c449c4dcac570d08ce0f21f6a149d2d9cf7652dbf2ba9a1bc729a", size = 93660 }, - { url = "https://files.pythonhosted.org/packages/63/a1/dee207f357bcd6b106f2ca5129ee916c24993ba08b7dfbf9a37c22442ea9/simplejson-3.19.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd011fc3c1d88b779645495fdb8189fb318a26981eebcce14109460e062f209b", size = 75546 }, - { url = "https://files.pythonhosted.org/packages/80/7b/45ef1da43f54d209ce2ef59b7356cda13f810186c381f38ae23a4d2b1337/simplejson-3.19.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:637c4d4b81825c1f4d651e56210bd35b5604034b192b02d2d8f17f7ce8c18f42", size = 75602 }, - { url = "https://files.pythonhosted.org/packages/7f/4b/9a132382982f8127bc7ce5212a5585d83c174707c9dd698d0cb6a0d41882/simplejson-3.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f56eb03bc9e432bb81adc8ecff2486d39feb371abb442964ffb44f6db23b332", size = 138632 }, - { url = "https://files.pythonhosted.org/packages/76/37/012f5ad2f38afa28f8a6ad9da01dc0b64492ffbaf2a3f2f8a0e1fddf9c1d/simplejson-3.19.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef59a53be400c1fad2c914b8d74c9d42384fed5174f9321dd021b7017fd40270", size = 146740 }, - { url = "https://files.pythonhosted.org/packages/69/b3/89640bd676e26ea2315b5aaf80712a6fbbb4338e4caf872d91448502a19b/simplejson-3.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72e8abbc86fcac83629a030888b45fed3a404d54161118be52cb491cd6975d3e", size = 134440 }, - { url = "https://files.pythonhosted.org/packages/61/20/0035a288deaff05397d6cc0145b33f3dd2429b99cdc880de4c5eca41ca72/simplejson-3.19.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8efb03ca77bd7725dfacc9254df00d73e6f43013cf39bd37ef1a8ed0ebb5165", size = 137949 }, - { url = "https://files.pythonhosted.org/packages/5d/de/5b03fafe3003e32d179588953d38183af6c3747e95c7dcc668c4f9eb886a/simplejson-3.19.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:add8850db04b98507a8b62d248a326ecc8561e6d24336d1ca5c605bbfaab4cad", size = 139992 }, - { url = "https://files.pythonhosted.org/packages/d1/ce/e493116ff49fd215f7baa25195b8f684c91e65c153e2a57e04dc3f3a466b/simplejson-3.19.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fc3dc9fb413fc34c396f52f4c87de18d0bd5023804afa8ab5cc224deeb6a9900", size = 140320 }, - { url = "https://files.pythonhosted.org/packages/86/f3/a18b98a7a27548829f672754dd3940fb637a27981399838128d3e560087f/simplejson-3.19.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4dfa420bb9225dd33b6efdabde7c6a671b51150b9b1d9c4e5cd74d3b420b3fe1", size = 148625 }, - { url = "https://files.pythonhosted.org/packages/0f/55/d3da33ee3e708133da079b9d537693d7fef281e6f0d27921cc7e5b3ec523/simplejson-3.19.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7b5c472099b39b274dcde27f1113db8d818c9aa3ba8f78cbb8ad04a4c1ac2118", size = 141287 }, - { url = "https://files.pythonhosted.org/packages/17/e8/56184ab4d66bb64a6ff569f069b3796dfd943f9b961268fe0d403526fc17/simplejson-3.19.3-cp310-cp310-win32.whl", hash = "sha256:817abad79241ed4a507b3caf4d3f2be5079f39d35d4c550a061988986bffd2ec", size = 74143 }, - { url = "https://files.pythonhosted.org/packages/be/8f/a0089eff060f10a925f08b0a0f50854321484f1ac54b1895bbf4c9213dfe/simplejson-3.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:dd5b9b1783e14803e362a558680d88939e830db2466f3fa22df5c9319f8eea94", size = 75643 }, - { url = "https://files.pythonhosted.org/packages/8c/bb/9ee3959e6929d228cf669b3f13f0edd43c5261b6cd69598640748b19ca35/simplejson-3.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e88abff510dcff903a18d11c2a75f9964e768d99c8d147839913886144b2065e", size = 91930 }, - { url = "https://files.pythonhosted.org/packages/ac/ae/a06523928af3a6783e2638cd4f6035c3e32de1c1063d563d9060c8d2f1ad/simplejson-3.19.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:934a50a614fb831614db5dbfba35127ee277624dda4d15895c957d2f5d48610c", size = 74787 }, - { url = "https://files.pythonhosted.org/packages/c3/58/fea732e48a7540035fe46d39e6fd77679f5810311d31da8661ce7a18210a/simplejson-3.19.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:212fce86a22188b0c7f53533b0f693ea9605c1a0f02c84c475a30616f55a744d", size = 74612 }, - { url = "https://files.pythonhosted.org/packages/ab/4d/15718f20cb0e3875b8af9597d6bb3bfbcf1383834b82b6385ee9ac0b72a9/simplejson-3.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d9e8f836688a8fabe6a6b41b334aa550a6823f7b4ac3d3712fc0ad8655be9a8", size = 143550 }, - { url = "https://files.pythonhosted.org/packages/93/44/815a4343774760f7a82459c8f6a4d8268b4b6d23f81e7b922a5e2ca79171/simplejson-3.19.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23228037dc5d41c36666384062904d74409a62f52283d9858fa12f4c22cffad1", size = 153284 }, - { url = "https://files.pythonhosted.org/packages/9d/52/d3202d9bba95444090d1c98e43da3c10907875babf63ed3c134d1b9437e3/simplejson-3.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0791f64fed7d4abad639491f8a6b1ba56d3c604eb94b50f8697359b92d983f36", size = 141518 }, - { url = "https://files.pythonhosted.org/packages/b7/d4/850948bcbcfe0b4a6c69dfde10e245d3a1ea45252f16a1e2308a3b06b1da/simplejson-3.19.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f614581b61a26fbbba232a1391f6cee82bc26f2abbb6a0b44a9bba25c56a1c", size = 144688 }, - { url = "https://files.pythonhosted.org/packages/58/d2/b8dcb0a07d9cd54c47f9fe8733dbb83891d1efe4fc786d9dfc8781cc04f9/simplejson-3.19.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1df0aaf1cb787fdf34484ed4a1f0c545efd8811f6028623290fef1a53694e597", size = 144534 }, - { url = "https://files.pythonhosted.org/packages/a9/95/1e92d99039041f596e0923ec4f9153244acaf3830944dc69a7c11b23ceaa/simplejson-3.19.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:951095be8d4451a7182403354c22ec2de3e513e0cc40408b689af08d02611588", size = 146565 }, - { url = "https://files.pythonhosted.org/packages/21/04/c96aeb3a74031255e4cbcc0ca1b6ebfb5549902f0a065f06d65ce8447c0c/simplejson-3.19.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a954b30810988feeabde843e3263bf187697e0eb5037396276db3612434049b", size = 155014 }, - { url = "https://files.pythonhosted.org/packages/b7/41/e28a28593afc4a75d8999d057bfb7c73a103e35f927e66f4bb92571787ae/simplejson-3.19.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c40df31a75de98db2cdfead6074d4449cd009e79f54c1ebe5e5f1f153c68ad20", size = 148092 }, - { url = "https://files.pythonhosted.org/packages/2b/82/1c81a3af06f937afb6d2e9d74a465c0e0ae6db444d1bf2a436ea26de1965/simplejson-3.19.3-cp311-cp311-win32.whl", hash = "sha256:7e2a098c21ad8924076a12b6c178965d88a0ad75d1de67e1afa0a66878f277a5", size = 73942 }, - { url = "https://files.pythonhosted.org/packages/65/be/d8ab9717f471be3c114f16abd8be21d9a6a0a09b9b49177d93d64d3717d9/simplejson-3.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:c9bedebdc5fdad48af8783022bae307746d54006b783007d1d3c38e10872a2c6", size = 75469 }, - { url = "https://files.pythonhosted.org/packages/20/15/513fea93fafbdd4993eacfcb762965b2ff3d29e618c029e2956174d68c4b/simplejson-3.19.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:66a0399e21c2112acacfebf3d832ebe2884f823b1c7e6d1363f2944f1db31a99", size = 92921 }, - { url = "https://files.pythonhosted.org/packages/a4/4f/998a907ae1a6c104dc0ee48aa248c2478490152808d34d8e07af57f396c3/simplejson-3.19.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6ef9383c5e05f445be60f1735c1816163c874c0b1ede8bb4390aff2ced34f333", size = 75311 }, - { url = "https://files.pythonhosted.org/packages/db/44/acd6122201e927451869d45952b9ab1d3025cdb5e61548d286d08fbccc08/simplejson-3.19.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:42e5acf80d4d971238d4df97811286a044d720693092b20a56d5e56b7dcc5d09", size = 74964 }, - { url = "https://files.pythonhosted.org/packages/27/ca/d0a1e8f16e1bbdc0b8c6d88166f45f565ed7285f53928cfef3b6ce78f14d/simplejson-3.19.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0b0efc7279d768db7c74d3d07f0b5c81280d16ae3fb14e9081dc903e8360771", size = 150106 }, - { url = "https://files.pythonhosted.org/packages/63/59/0554b78cf26c98e2b9cae3f44723bd72c2394e2afec1a14eedc6211f7187/simplejson-3.19.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0552eb06e7234da892e1d02365cd2b7b2b1f8233aa5aabdb2981587b7cc92ea0", size = 158347 }, - { url = "https://files.pythonhosted.org/packages/b2/fe/9f30890352e431e8508cc569912d3322147d3e7e4f321e48c0adfcb4c97d/simplejson-3.19.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf6a3b9a7d7191471b464fe38f684df10eb491ec9ea454003edb45a011ab187", size = 148456 }, - { url = "https://files.pythonhosted.org/packages/37/e3/663a09542ee021d4131162f7a164cb2e7f04ef48433a67591738afbf12ea/simplejson-3.19.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7017329ca8d4dca94ad5e59f496e5fc77630aecfc39df381ffc1d37fb6b25832", size = 152190 }, - { url = "https://files.pythonhosted.org/packages/31/20/4e0c4d35e10ff6465003bec304316d822a559a1c38c66ef6892ca199c207/simplejson-3.19.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:67a20641afebf4cfbcff50061f07daad1eace6e7b31d7622b6fa2c40d43900ba", size = 149846 }, - { url = "https://files.pythonhosted.org/packages/08/7a/46e2e072cac3987cbb05946f25167f0ad2fe536748e7405953fd6661a486/simplejson-3.19.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:dd6a7dabcc4c32daf601bc45e01b79175dde4b52548becea4f9545b0a4428169", size = 151714 }, - { url = "https://files.pythonhosted.org/packages/7f/7d/dbeeac10eb61d5d8858d0bb51121a21050d281dc83af4c557f86da28746c/simplejson-3.19.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:08f9b443a94e72dd02c87098c96886d35790e79e46b24e67accafbf13b73d43b", size = 158777 }, - { url = "https://files.pythonhosted.org/packages/fc/8f/a98bdbb799c6a4a884b5823db31785a96ba895b4b0f4d8ac345d6fe98bbf/simplejson-3.19.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa97278ae6614346b5ca41a45a911f37a3261b57dbe4a00602048652c862c28b", size = 154230 }, - { url = "https://files.pythonhosted.org/packages/b1/db/852eebceb85f969ae40e06babed1a93d3bacb536f187d7a80ff5823a5979/simplejson-3.19.3-cp312-cp312-win32.whl", hash = "sha256:ef28c3b328d29b5e2756903aed888960bc5df39b4c2eab157ae212f70ed5bf74", size = 74002 }, - { url = "https://files.pythonhosted.org/packages/fe/68/9f0e5df0651cb79ef83cba1378765a00ee8038e6201cc82b8e7178a7778e/simplejson-3.19.3-cp312-cp312-win_amd64.whl", hash = "sha256:1e662336db50ad665777e6548b5076329a94a0c3d4a0472971c588b3ef27de3a", size = 75596 }, - { url = "https://files.pythonhosted.org/packages/93/3a/5896821ed543899fcb9c4256c7e71bb110048047349a00f42bc8b8fb379f/simplejson-3.19.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0959e6cb62e3994b5a40e31047ff97ef5c4138875fae31659bead691bed55896", size = 92931 }, - { url = "https://files.pythonhosted.org/packages/39/15/5d33d269440912ee40d856db0c8be2b91aba7a219690ab01f86cb0edd590/simplejson-3.19.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a7bfad839c624e139a4863007233a3f194e7c51551081f9789cba52e4da5167", size = 75318 }, - { url = "https://files.pythonhosted.org/packages/2a/8d/2e7483a2bf7ec53acf7e012bafbda79d7b34f90471dda8e424544a59d484/simplejson-3.19.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afab2f7f2486a866ff04d6d905e9386ca6a231379181a3838abce1f32fbdcc37", size = 74971 }, - { url = "https://files.pythonhosted.org/packages/4d/9d/9bdf34437c8834a7cf7246f85e9d5122e30579f512c10a0c2560e994294f/simplejson-3.19.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00313681015ac498e1736b304446ee6d1c72c5b287cd196996dad84369998f7", size = 150112 }, - { url = "https://files.pythonhosted.org/packages/a7/e2/1f2ae2d89eaf85f6163c82150180aae5eaa18085cfaf892f8a57d4c51cbd/simplejson-3.19.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d936ae682d5b878af9d9eb4d8bb1fdd5e41275c8eb59ceddb0aeed857bb264a2", size = 158354 }, - { url = "https://files.pythonhosted.org/packages/60/83/26f610adf234c8492b3f30501e12f2271e67790f946c6898fe0c58aefe99/simplejson-3.19.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c6657485393f2e9b8177c77a7634f13ebe70d5e6de150aae1677d91516ce6b", size = 148455 }, - { url = "https://files.pythonhosted.org/packages/b5/4b/109af50006af77133653c55b5b91b4bd2d579ff8254ce11216c0b75f911b/simplejson-3.19.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a6a750d3c7461b1c47cfc6bba8d9e57a455e7c5f80057d2a82f738040dd1129", size = 152191 }, - { url = "https://files.pythonhosted.org/packages/75/dc/108872a8825cbd99ae6f4334e0490ff1580367baf12198bcaf988f6820ba/simplejson-3.19.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ea7a4a998c87c5674a27089e022110a1a08a7753f21af3baf09efe9915c23c3c", size = 149954 }, - { url = "https://files.pythonhosted.org/packages/eb/be/deec1d947a5d0472276ab4a4d1a9378dc5ee27f3dc9e54d4f62ffbad7a08/simplejson-3.19.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6300680d83a399be2b8f3b0ef7ef90b35d2a29fe6e9c21438097e0938bbc1564", size = 151812 }, - { url = "https://files.pythonhosted.org/packages/e9/58/4ee130702d36b1551ef66e7587eefe56651f3669255bf748cd71691e2434/simplejson-3.19.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ab69f811a660c362651ae395eba8ce84f84c944cea0df5718ea0ba9d1e4e7252", size = 158880 }, - { url = "https://files.pythonhosted.org/packages/0f/e1/59cc6a371b60f89e3498d9f4c8109f6b7359094d453f5fe80b2677b777b0/simplejson-3.19.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:256e09d0f94d9c3d177d9e95fd27a68c875a4baa2046633df387b86b652f5747", size = 154344 }, - { url = "https://files.pythonhosted.org/packages/79/45/1b36044670016f5cb25ebd92497427d2d1711ecb454d00f71eb9a00b77cc/simplejson-3.19.3-cp313-cp313-win32.whl", hash = "sha256:2c78293470313aefa9cfc5e3f75ca0635721fb016fb1121c1c5b0cb8cc74712a", size = 74002 }, - { url = "https://files.pythonhosted.org/packages/e2/58/b06226e6b0612f2b1fa13d5273551da259f894566b1eef32249ddfdcce44/simplejson-3.19.3-cp313-cp313-win_amd64.whl", hash = "sha256:3bbcdc438dc1683b35f7a8dc100960c721f922f9ede8127f63bed7dfded4c64c", size = 75599 }, - { url = "https://files.pythonhosted.org/packages/0d/e7/f9fafbd4f39793a20cc52e77bbd766f7384312526d402c382928dc7667f6/simplejson-3.19.3-py3-none-any.whl", hash = "sha256:49cc4c7b940d43bd12bf87ec63f28cbc4964fc4e12c031cc8cd01650f43eb94e", size = 57004 }, +version = "3.20.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/92/51b417685abd96b31308b61b9acce7ec50d8e1de8fbc39a7fd4962c60689/simplejson-3.20.1.tar.gz", hash = "sha256:e64139b4ec4f1f24c142ff7dcafe55a22b811a74d86d66560c8815687143037d", size = 85591 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/c4/627214fb418cd4a17fb0230ff0b6c3bb4a85cbb48dd69c85dcc3b85df828/simplejson-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e580aa65d5f6c3bf41b9b4afe74be5d5ddba9576701c107c772d936ea2b5043a", size = 93790 }, + { url = "https://files.pythonhosted.org/packages/15/ca/56a6a2a33cbcf330c4d71af3f827c47e4e0ba791e78f2642f3d1ab02ff31/simplejson-3.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a586ce4f78cec11f22fe55c5bee0f067e803aab9bad3441afe2181693b5ebb5", size = 75707 }, + { url = "https://files.pythonhosted.org/packages/a9/c8/3d92b67e03a3b6207d97202669f9454ed700b35ade9bd4428265a078fb6c/simplejson-3.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74a1608f9e6e8c27a4008d70a54270868306d80ed48c9df7872f9f4b8ac87808", size = 75700 }, + { url = "https://files.pythonhosted.org/packages/74/30/20001219d6fdca4aaa3974c96dfb6955a766b4e2cc950505a5b51fd050b0/simplejson-3.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03db8cb64154189a92a7786209f24e391644f3a3fa335658be2df2af1960b8d8", size = 138672 }, + { url = "https://files.pythonhosted.org/packages/21/47/50157810876c2a7ebbd6e6346ec25eda841fe061fecaa02538a7742a3d2a/simplejson-3.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eea7e2b7d858f6fdfbf0fe3cb846d6bd8a45446865bc09960e51f3d473c2271b", size = 146616 }, + { url = "https://files.pythonhosted.org/packages/95/60/8c97cdc93096437b0aca2745aca63c880fe2315fd7f6a6ce6edbb344a2ae/simplejson-3.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e66712b17d8425bb7ff8968d4c7c7fd5a2dd7bd63728b28356223c000dd2f91f", size = 134344 }, + { url = "https://files.pythonhosted.org/packages/bb/9e/da184f0e9bb3a5d7ffcde713bd41b4fe46cca56b6f24d9bd155fac56805a/simplejson-3.20.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2cc4f6486f9f515b62f5831ff1888886619b84fc837de68f26d919ba7bbdcbc", size = 138017 }, + { url = "https://files.pythonhosted.org/packages/31/db/00d1a8d9b036db98f678c8a3c69ed17d2894d1768d7a00576e787ad3e546/simplejson-3.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a3c2df555ee4016148fa192e2b9cd9e60bc1d40769366134882685e90aee2a1e", size = 140118 }, + { url = "https://files.pythonhosted.org/packages/52/21/57fc47eab8c1c73390b933a5ba9271f08e3e1ec83162c580357f28f5b97c/simplejson-3.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:78520f04b7548a5e476b5396c0847e066f1e0a4c0c5e920da1ad65e95f410b11", size = 140314 }, + { url = "https://files.pythonhosted.org/packages/ad/cc/7cfd78d1e0fa5e57350b98cfe77353b6dfa13dce21afa4060e1019223852/simplejson-3.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f4bd49ecde87b0fe9f55cc971449a32832bca9910821f7072bbfae1155eaa007", size = 148544 }, + { url = "https://files.pythonhosted.org/packages/63/26/1c894a1c2bd95dc8be0cf5a2fa73b0d173105b6ca18c90cb981ff10443d0/simplejson-3.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7eaae2b88eb5da53caaffdfa50e2e12022553949b88c0df4f9a9663609373f72", size = 141172 }, + { url = "https://files.pythonhosted.org/packages/93/27/0717dccc10cd9988dbf1314def52ab32678a95a95328bb37cafacf499400/simplejson-3.20.1-cp310-cp310-win32.whl", hash = "sha256:e836fb88902799eac8debc2b642300748f4860a197fa3d9ea502112b6bb8e142", size = 74181 }, + { url = "https://files.pythonhosted.org/packages/5f/af/593f896573f306519332d4287b1ab8b7b888c239bbd5159f7054d7055c2d/simplejson-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a19b552b212fc3b5b96fc5ce92333d4a9ac0a800803e1f17ebb16dac4be5", size = 75738 }, + { url = "https://files.pythonhosted.org/packages/76/59/74bc90d1c051bc2432c96b34bd4e8036875ab58b4fcbe4d6a5a76985f853/simplejson-3.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:325b8c107253d3217e89d7b50c71015b5b31e2433e6c5bf38967b2f80630a8ca", size = 92132 }, + { url = "https://files.pythonhosted.org/packages/71/c7/1970916e0c51794fff89f76da2f632aaf0b259b87753c88a8c409623d3e1/simplejson-3.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88a7baa8211089b9e58d78fbc1b0b322103f3f3d459ff16f03a36cece0d0fcf0", size = 74956 }, + { url = "https://files.pythonhosted.org/packages/c8/0d/98cc5909180463f1d75fac7180de62d4cdb4e82c4fef276b9e591979372c/simplejson-3.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:299b1007b8101d50d95bc0db1bf5c38dc372e85b504cf77f596462083ee77e3f", size = 74772 }, + { url = "https://files.pythonhosted.org/packages/e1/94/a30a5211a90d67725a3e8fcc1c788189f2ae2ed2b96b63ed15d0b7f5d6bb/simplejson-3.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ec618ed65caab48e81e3ed29586236a8e57daef792f1f3bb59504a7e98cd10", size = 143575 }, + { url = "https://files.pythonhosted.org/packages/ee/08/cdb6821f1058eb5db46d252de69ff7e6c53f05f1bae6368fe20d5b51d37e/simplejson-3.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2cdead1d3197f0ff43373cf4730213420523ba48697743e135e26f3d179f38", size = 153241 }, + { url = "https://files.pythonhosted.org/packages/4c/2d/ca3caeea0bdc5efc5503d5f57a2dfb56804898fb196dfada121323ee0ccb/simplejson-3.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3466d2839fdc83e1af42e07b90bc8ff361c4e8796cd66722a40ba14e458faddd", size = 141500 }, + { url = "https://files.pythonhosted.org/packages/e1/33/d3e0779d5c58245e7370c98eb969275af6b7a4a5aec3b97cbf85f09ad328/simplejson-3.20.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d492ed8e92f3a9f9be829205f44b1d0a89af6582f0cf43e0d129fa477b93fe0c", size = 144757 }, + { url = "https://files.pythonhosted.org/packages/54/53/2d93128bb55861b2fa36c5944f38da51a0bc6d83e513afc6f7838440dd15/simplejson-3.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f924b485537b640dc69434565463fd6fc0c68c65a8c6e01a823dd26c9983cf79", size = 144409 }, + { url = "https://files.pythonhosted.org/packages/99/4c/dac310a98f897ad3435b4bdc836d92e78f09e38c5dbf28211ed21dc59fa2/simplejson-3.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e8eacf6a3491bf76ea91a8d46726368a6be0eb94993f60b8583550baae9439e", size = 146082 }, + { url = "https://files.pythonhosted.org/packages/ee/22/d7ba958cfed39827335b82656b1c46f89678faecda9a7677b47e87b48ee6/simplejson-3.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d04bf90b4cea7c22d8b19091633908f14a096caa301b24c2f3d85b5068fb8", size = 154339 }, + { url = "https://files.pythonhosted.org/packages/b8/c8/b072b741129406a7086a0799c6f5d13096231bf35fdd87a0cffa789687fc/simplejson-3.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:69dd28d4ce38390ea4aaf212902712c0fd1093dc4c1ff67e09687c3c3e15a749", size = 147915 }, + { url = "https://files.pythonhosted.org/packages/6c/46/8347e61e9cf3db5342a42f7fd30a81b4f5cf85977f916852d7674a540907/simplejson-3.20.1-cp311-cp311-win32.whl", hash = "sha256:dfe7a9da5fd2a3499436cd350f31539e0a6ded5da6b5b3d422df016444d65e43", size = 73972 }, + { url = "https://files.pythonhosted.org/packages/01/85/b52f24859237b4e9d523d5655796d911ba3d46e242eb1959c45b6af5aedd/simplejson-3.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:896a6c04d7861d507d800da7642479c3547060bf97419d9ef73d98ced8258766", size = 75595 }, + { url = "https://files.pythonhosted.org/packages/8d/eb/34c16a1ac9ba265d024dc977ad84e1659d931c0a700967c3e59a98ed7514/simplejson-3.20.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f31c4a3a7ab18467ee73a27f3e59158255d1520f3aad74315edde7a940f1be23", size = 93100 }, + { url = "https://files.pythonhosted.org/packages/41/fc/2c2c007d135894971e6814e7c0806936e5bade28f8db4dd7e2a58b50debd/simplejson-3.20.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:884e6183d16b725e113b83a6fc0230152ab6627d4d36cb05c89c2c5bccfa7bc6", size = 75464 }, + { url = "https://files.pythonhosted.org/packages/0f/05/2b5ecb33b776c34bb5cace5de5d7669f9b60e3ca13c113037b2ca86edfbd/simplejson-3.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03d7a426e416fe0d3337115f04164cd9427eb4256e843a6b8751cacf70abc832", size = 75112 }, + { url = "https://files.pythonhosted.org/packages/fe/36/1f3609a2792f06cd4b71030485f78e91eb09cfd57bebf3116bf2980a8bac/simplejson-3.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:000602141d0bddfcff60ea6a6e97d5e10c9db6b17fd2d6c66199fa481b6214bb", size = 150182 }, + { url = "https://files.pythonhosted.org/packages/2f/b0/053fbda38b8b602a77a4f7829def1b4f316cd8deb5440a6d3ee90790d2a4/simplejson-3.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af8377a8af78226e82e3a4349efdde59ffa421ae88be67e18cef915e4023a595", size = 158363 }, + { url = "https://files.pythonhosted.org/packages/d1/4b/2eb84ae867539a80822e92f9be4a7200dffba609275faf99b24141839110/simplejson-3.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15c7de4c88ab2fbcb8781a3b982ef883696736134e20b1210bca43fb42ff1acf", size = 148415 }, + { url = "https://files.pythonhosted.org/packages/e0/bd/400b0bd372a5666addf2540c7358bfc3841b9ce5cdbc5cc4ad2f61627ad8/simplejson-3.20.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:455a882ff3f97d810709f7b620007d4e0aca8da71d06fc5c18ba11daf1c4df49", size = 152213 }, + { url = "https://files.pythonhosted.org/packages/50/12/143f447bf6a827ee9472693768dc1a5eb96154f8feb140a88ce6973a3cfa/simplejson-3.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fc0f523ce923e7f38eb67804bc80e0a028c76d7868500aa3f59225574b5d0453", size = 150048 }, + { url = "https://files.pythonhosted.org/packages/5e/ea/dd9b3e8e8ed710a66f24a22c16a907c9b539b6f5f45fd8586bd5c231444e/simplejson-3.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76461ec929282dde4a08061071a47281ad939d0202dc4e63cdd135844e162fbc", size = 151668 }, + { url = "https://files.pythonhosted.org/packages/99/af/ee52a8045426a0c5b89d755a5a70cc821815ef3c333b56fbcad33c4435c0/simplejson-3.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19c2da8c043607bde4d4ef3a6b633e668a7d2e3d56f40a476a74c5ea71949f", size = 158840 }, + { url = "https://files.pythonhosted.org/packages/68/db/ab32869acea6b5de7d75fa0dac07a112ded795d41eaa7e66c7813b17be95/simplejson-3.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2578bedaedf6294415197b267d4ef678fea336dd78ee2a6d2f4b028e9d07be3", size = 154212 }, + { url = "https://files.pythonhosted.org/packages/fa/7a/e3132d454977d75a3bf9a6d541d730f76462ebf42a96fea2621498166f41/simplejson-3.20.1-cp312-cp312-win32.whl", hash = "sha256:339f407373325a36b7fd744b688ba5bae0666b5d340ec6d98aebc3014bf3d8ea", size = 74101 }, + { url = "https://files.pythonhosted.org/packages/bc/5d/4e243e937fa3560107c69f6f7c2eed8589163f5ed14324e864871daa2dd9/simplejson-3.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:627d4486a1ea7edf1f66bb044ace1ce6b4c1698acd1b05353c97ba4864ea2e17", size = 75736 }, + { url = "https://files.pythonhosted.org/packages/c4/03/0f453a27877cb5a5fff16a975925f4119102cc8552f52536b9a98ef0431e/simplejson-3.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:71e849e7ceb2178344998cbe5ade101f1b329460243c79c27fbfc51c0447a7c3", size = 93109 }, + { url = "https://files.pythonhosted.org/packages/74/1f/a729f4026850cabeaff23e134646c3f455e86925d2533463420635ae54de/simplejson-3.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b63fdbab29dc3868d6f009a59797cefaba315fd43cd32ddd998ee1da28e50e29", size = 75475 }, + { url = "https://files.pythonhosted.org/packages/e2/14/50a2713fee8ff1f8d655b1a14f4a0f1c0c7246768a1b3b3d12964a4ed5aa/simplejson-3.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1190f9a3ce644fd50ec277ac4a98c0517f532cfebdcc4bd975c0979a9f05e1fb", size = 75112 }, + { url = "https://files.pythonhosted.org/packages/45/86/ea9835abb646755140e2d482edc9bc1e91997ed19a59fd77ae4c6a0facea/simplejson-3.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1336ba7bcb722ad487cd265701ff0583c0bb6de638364ca947bb84ecc0015d1", size = 150245 }, + { url = "https://files.pythonhosted.org/packages/12/b4/53084809faede45da829fe571c65fbda8479d2a5b9c633f46b74124d56f5/simplejson-3.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e975aac6a5acd8b510eba58d5591e10a03e3d16c1cf8a8624ca177491f7230f0", size = 158465 }, + { url = "https://files.pythonhosted.org/packages/a9/7d/d56579468d1660b3841e1f21c14490d103e33cf911886b22652d6e9683ec/simplejson-3.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a6dd11ee282937ad749da6f3b8d87952ad585b26e5edfa10da3ae2536c73078", size = 148514 }, + { url = "https://files.pythonhosted.org/packages/19/e3/874b1cca3d3897b486d3afdccc475eb3a09815bf1015b01cf7fcb52a55f0/simplejson-3.20.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab980fcc446ab87ea0879edad41a5c28f2d86020014eb035cf5161e8de4474c6", size = 152262 }, + { url = "https://files.pythonhosted.org/packages/32/84/f0fdb3625292d945c2bd13a814584603aebdb38cfbe5fe9be6b46fe598c4/simplejson-3.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f5aee2a4cb6b146bd17333ac623610f069f34e8f31d2f4f0c1a2186e50c594f0", size = 150164 }, + { url = "https://files.pythonhosted.org/packages/95/51/6d625247224f01eaaeabace9aec75ac5603a42f8ebcce02c486fbda8b428/simplejson-3.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:652d8eecbb9a3b6461b21ec7cf11fd0acbab144e45e600c817ecf18e4580b99e", size = 151795 }, + { url = "https://files.pythonhosted.org/packages/7f/d9/bb921df6b35be8412f519e58e86d1060fddf3ad401b783e4862e0a74c4c1/simplejson-3.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8c09948f1a486a89251ee3a67c9f8c969b379f6ffff1a6064b41fea3bce0a112", size = 159027 }, + { url = "https://files.pythonhosted.org/packages/03/c5/5950605e4ad023a6621cf4c931b29fd3d2a9c1f36be937230bfc83d7271d/simplejson-3.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cbbd7b215ad4fc6f058b5dd4c26ee5c59f72e031dfda3ac183d7968a99e4ca3a", size = 154380 }, + { url = "https://files.pythonhosted.org/packages/66/ad/b74149557c5ec1e4e4d55758bda426f5d2ec0123cd01a53ae63b8de51fa3/simplejson-3.20.1-cp313-cp313-win32.whl", hash = "sha256:ae81e482476eaa088ef9d0120ae5345de924f23962c0c1e20abbdff597631f87", size = 74102 }, + { url = "https://files.pythonhosted.org/packages/db/a9/25282fdd24493e1022f30b7f5cdf804255c007218b2bfaa655bd7ad34b2d/simplejson-3.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:1b9fd15853b90aec3b1739f4471efbf1ac05066a2c7041bf8db821bb73cd2ddc", size = 75736 }, + { url = "https://files.pythonhosted.org/packages/4b/30/00f02a0a921556dd5a6db1ef2926a1bc7a8bbbfb1c49cfed68a275b8ab2b/simplejson-3.20.1-py3-none-any.whl", hash = "sha256:8a6c1bbac39fa4a79f83cbf1df6ccd8ff7069582a9fd8db1e52cea073bc2c697", size = 57121 }, ] [[package]] @@ -1363,6 +1818,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/e4/592120713a314621c692211eba034d09becaf6bc8848fabc1dc2a54d8c16/SQLAlchemy-2.0.38-py3-none-any.whl", hash = "sha256:63178c675d4c80def39f1febd625a6333f44c0ba269edd8a468b156394b27753", size = 1896347 }, ] +[[package]] +name = "sqlalchemy-orm" +version = "1.2.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cached-property" }, + { name = "inflection" }, + { name = "sqlalchemy" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c8/98/b0451ae949f8b16287965d4fc4a180fae50a78d9b186300e135ec22e1883/sqlalchemy-orm-1.2.10.tar.gz", hash = "sha256:7ab46d2a54a429d4fd384df9a37ad639dc87ff93be5205ed649c5ca4dad164bb", size = 21846 } + +[[package]] +name = "stack-data" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/be/af287da44f310088ea7c6b40f02d4f626a8903afbb678098ede01995b662/stack_data-0.5.1.tar.gz", hash = "sha256:95eb784942e861a3d80efd549ff9af6cf847d88343a12eb681d7157cfcb6e32b", size = 43435 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/dc/9367ef8074e2331706fbad14d749157341fbffd21339c43820e07664ec94/stack_data-0.5.1-py3-none-any.whl", hash = "sha256:5120731a18ba4c82cefcf84a945f6f3e62319ef413bfc210e32aca3a69310ba2", size = 24247 }, +] + +[[package]] +name = "starlette" +version = "0.45.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/fb/2984a686808b89a6781526129a4b51266f678b2d2b97ab2d325e56116df8/starlette-0.45.3.tar.gz", hash = "sha256:2cbcba2a75806f8a41c722141486f37c28e30a0921c5f6fe4346cb0dcee1302f", size = 2574076 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/61/f2b52e107b1fc8944b33ef56bf6ac4ebbe16d91b94d2b87ce013bf63fb84/starlette-0.45.3-py3-none-any.whl", hash = "sha256:dfb6d332576f136ec740296c7e8bb8c8a7125044e7c6da30744718880cdd059d", size = 71507 }, +] + [[package]] name = "threadpoolctl" version = "3.5.0" @@ -1409,6 +1902,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, ] +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + [[package]] name = "tree-sitter" version = "0.24.0" @@ -1511,3 +2013,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf wheels = [ { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, ] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] From 62592b079e0f8ea8e69c8b7eedca45090ed19d33 Mon Sep 17 00:00:00 2001 From: K2 Date: Mon, 17 Feb 2025 16:33:41 -0500 Subject: [PATCH 06/10] UV project management --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 60b0bfbe..0b901b23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ "joblib>=1.4.2", "lancedb>=0.19.0", "logging>=0.4.9.6", + "networkx>=3.4.2", "numpy>=1.24.4", "ollama>=0.4.7", "openai>=1.63.0", @@ -33,7 +34,9 @@ dependencies = [ "pydantic-ai>=0.0.24", "pydantic-graph>=0.0.24", "pydantic-settings>=2.7.1", + "pydot>=3.0.4", "pylance>=0.23.0", + "pyperclip>=1.9.0", "python-box>=7.3.2", "python-dateutil==2.8.2", "python-dotenv>=1.0.1", From bb0d2f2fd9407dfb1b56a55956fbf9dfc8ba74fa Mon Sep 17 00:00:00 2001 From: K2 Date: Tue, 18 Feb 2025 02:32:20 -0500 Subject: [PATCH 07/10] Missed file --- src/run.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/run.py b/src/run.py index 2a81af0b..e61b89d9 100644 --- a/src/run.py +++ b/src/run.py @@ -7,13 +7,19 @@ from rich.syntax import Syntax from rich.text import Text import logging - from pydantic_ai import Agent from pydantic_ai.models import KnownModelName from pydantic_ai.models.openai import OpenAIModel - from nodes_config import nodes_config +from openai import AsyncOpenAI +import asyncio + +# gets API Key from environment variable OPENAI_API_KEY +client = AsyncOpenAI() + + + logger = logging.getLogger(__name__) if not logger.hasHandlers(): logger.setLevel(logging.WARN) # Adjust as needed @@ -23,11 +29,11 @@ logger.addHandler(handler) from dataclasses import dataclass -from dao.task_mgr import ProjectTaskMgr -from codebaseQA.rag_processor import RAGProcessor +from dao.atask_mgr import AProjectTaskMgr +from codebaseQA.arag_processor import ARAGProcessor from res_processor.res_processor import ResProcessor -from project.project_audit import ProjectAudit -from planning.planning_v2 import PlanningV2 +from project.aproject_audit import AProjectAudit +from planning.aplanning_v2 import APlanningV2 from ai_engine import AiEngine @@ -80,6 +86,8 @@ def __init__(self, config:nodes_config, db_engine:Engine) -> None: #self.call_graph = CallGraph(root=self.path) logger.log(31,"Context: ProjectAudit") self.project_audit = ProjectAudit(config) + self.aproject_audit = AProjectAudit(config) + logger.log(31,"Contex: RAGProcesssor") self.rag_processor = RAGProcessor(config.id,audit=self.project_audit) @@ -128,6 +136,12 @@ def __rich_console__( Markdown.elements['fence'] = SimpleCodeBlock + + + + + + if __name__ == '__main__': config = nodes_config() config.model_dump() @@ -139,6 +153,7 @@ def __rich_console__( logger.log(31,"starting project load/parse") context.project_audit.parse(context.project.white_files, context.project.white_functions) logger.log(31,f"saving state to {context.statefile}") + context.planning.do_planning() context.ai_engine.do_scan() context.ai_engine.check_function_vul() From a9307398b3a6d5c0826c9c95cbec8c30bc333fc3 Mon Sep 17 00:00:00 2001 From: K2 Date: Tue, 18 Feb 2025 02:49:15 -0500 Subject: [PATCH 08/10] cleanup and skew --- .gitignore | 1 + .vscode/settings.json | 2 + FiniteMonkey.egg-info/PKG-INFO | 214 -- FiniteMonkey.egg-info/SOURCES.txt | 358 --- FiniteMonkey.egg-info/dependency_links.txt | 1 - FiniteMonkey.egg-info/entry_points.txt | 3 - FiniteMonkey.egg-info/requires.txt | 45 - FiniteMonkey.egg-info/top_level.txt | 1 - pyproject.toml | 3 +- src/agents/chat_app.html | 81 + src/agents/chat_app.py | 219 ++ src/agents/chat_app.ts | 0 src/agents/formatted_console.py | 79 + src/agents/md_output.py | 66 + src/codebaseQA/arag_processor.py | 100 + src/dao/aentity.py | 170 ++ src/dao/atask_mgr.py | 214 ++ .../sgp/utilities/contract_extractor.py | 4 +- src/openai_api/routes.py | 56 - src/planning/aplanning_v2.py | 556 +++++ src/project/aproject_audit.py | 154 ++ src/project/aproject_parser.py | 131 ++ src/res_processor/ares_processor.py | 149 ++ src/root_service.py | 22 + uv.lock | 2024 ----------------- 25 files changed, 1948 insertions(+), 2705 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 FiniteMonkey.egg-info/PKG-INFO delete mode 100644 FiniteMonkey.egg-info/SOURCES.txt delete mode 100644 FiniteMonkey.egg-info/dependency_links.txt delete mode 100644 FiniteMonkey.egg-info/entry_points.txt delete mode 100644 FiniteMonkey.egg-info/requires.txt delete mode 100644 FiniteMonkey.egg-info/top_level.txt create mode 100644 src/agents/chat_app.html create mode 100644 src/agents/chat_app.py create mode 100644 src/agents/chat_app.ts create mode 100644 src/agents/formatted_console.py create mode 100644 src/agents/md_output.py create mode 100644 src/codebaseQA/arag_processor.py create mode 100644 src/dao/aentity.py create mode 100644 src/dao/atask_mgr.py delete mode 100644 src/openai_api/routes.py create mode 100644 src/planning/aplanning_v2.py create mode 100644 src/project/aproject_audit.py create mode 100644 src/project/aproject_parser.py create mode 100644 src/res_processor/ares_processor.py create mode 100644 src/root_service.py delete mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index 240a04ee..79be880d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ tests/* uv.lock callgraph.json lancedbsrc1/lancedb_src* +*/FiniteMonkey.egg-info/* \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..7a73a41b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/FiniteMonkey.egg-info/PKG-INFO b/FiniteMonkey.egg-info/PKG-INFO deleted file mode 100644 index 9cb7fcf3..00000000 --- a/FiniteMonkey.egg-info/PKG-INFO +++ /dev/null @@ -1,214 +0,0 @@ -Metadata-Version: 2.2 -Name: FiniteMonkey -Version: 0.0.1 -Summary: Intelligent vulnerability mining engine. -Author: Xue Yue -License: Apache 2.0 -Project-URL: Homepage, https://github.com/BradMoonUESTC/finite-monkey-engine -Project-URL: Bug Reports, https://github.com/BradMoonUESTC/finite-monkey-engine/issues -Project-URL: Funding, https://github.com/BradMoonUESTC/finite-monkey-engine -Project-URL: Say Thanks!, https://github.com/BradMoonUESTC/finite-monkey-engine -Project-URL: Source, https://github.com/BradMoonUESTC/finite-monkey-engine -Requires-Python: >=3.10 -Description-Content-Type: text/markdown -License-File: LICENSE -Requires-Dist: antlr4-python3-runtime>=4.13.2 -Requires-Dist: asyncpg>=0.30.0 -Requires-Dist: certifi>=2024.8.30 -Requires-Dist: charset-normalizer>=3.4.0 -Requires-Dist: colorama>=0.4.6 -Requires-Dist: dependency-injector>=4.45.0 -Requires-Dist: devtools>=0.9.0 -Requires-Dist: et-xmlfile>=1.1.0 -Requires-Dist: fastapi>=0.115.8 -Requires-Dist: future>=1.0.0 -Requires-Dist: idna>=3.10 -Requires-Dist: ipython>=8.32.0 -Requires-Dist: joblib>=1.4.2 -Requires-Dist: lancedb>=0.19.0 -Requires-Dist: logging>=0.4.9.6 -Requires-Dist: numpy>=1.24.4 -Requires-Dist: ollama>=0.4.7 -Requires-Dist: openai>=1.63.0 -Requires-Dist: openpyxl>=3.1.5 -Requires-Dist: pandas>=1.24.4 -Requires-Dist: psycopg2-binary>=2.9.9 -Requires-Dist: pydantic>=2.11.0a2 -Requires-Dist: pydantic-ai>=0.0.24 -Requires-Dist: pydantic-graph>=0.0.24 -Requires-Dist: pydantic-settings>=2.7.1 -Requires-Dist: pylance>=0.23.0 -Requires-Dist: python-box>=7.3.2 -Requires-Dist: python-dateutil==2.8.2 -Requires-Dist: python-dotenv>=1.0.1 -Requires-Dist: pytz>=2024.2 -Requires-Dist: requests>=2.32.3 -Requires-Dist: rich>=13.9.4 -Requires-Dist: scikit-learn>=1.3.2 -Requires-Dist: scipy>=1.10.1 -Requires-Dist: simplejson>=3.19.3 -Requires-Dist: six>=1.16.0 -Requires-Dist: sqlalchemy>=2.0.35 -Requires-Dist: sqlalchemy-orm>=1.2.10 -Requires-Dist: threadpoolctl>=3.5.0 -Requires-Dist: tqdm>=4.66.5 -Requires-Dist: tree-sitter>=0.24.0 -Requires-Dist: tree-sitter-solidity>=1.2.11 -Requires-Dist: typing_extensions>=4.12.2 -Requires-Dist: tzdata>=2024.2 -Requires-Dist: urllib3>=2.2.3 - -# FiniteMonkey - -

- -

- -

- - - -

- -FiniteMonkey is an intelligent vulnerability mining engine based on large language models, requiring no pre-trained knowledge base or fine-tuning. Its core feature is using task-driven and prompt engineering approaches to guide models in vulnerability analysis through carefully designed prompts. - -## 🌟 Core Concepts - -- **Task-driven rather than problem-driven** -- **Prompt-driven rather than code-driven** -- **Focus on prompt design rather than model design** -- **Leverage "deception" and hallucination as key mechanisms** - -## 🏆 Achievements - -As of May 2024, this tool has helped discover over $60,000 worth of bug bounties. - -## 🚀 Latest Updates - -**2024.11.19**: Released version 1.0 - Validated LLM-based auditing and productization feasibility - -**Earlier Updates:** -- 2024.08.02: Project renamed to finite-monkey-engine -- 2024.08.01: Added Func, Tact language support -- 2024.07.23: Added Cairo, Move language support -- 2024.07.01: Updated license -- 2024.06.01: Added Python language support -- 2024.05.18: Improved false positive rate (~20%) -- 2024.05.16: Added cross-contract vulnerability confirmation -- 2024.04.29: Added basic Rust language support - -## 📋 Requirements - -- PostgreSQL database -- OpenAI API access -- Python environment - -## 🛠️ Installation & Configuration - -1. Place project in `src/dataset/agent-v1-c4` directory - -2. Configure project in `datasets.json`: -```json -{ - "StEverVault2": { - "path": "StEverVault", - "files": [], - "functions": [] - } -} -``` - -3. Create database using `src/db.sql` - -4. Configure `.env`: -```env -# Database connection -DATABASE_URL=postgresql://user:password@localhost:5432/dbname - -# API settings -OPENAI_API_BASE="api.example.com" -OPENAI_API_KEY=sk-your-api-key-here - -# Model settings -VUL_MODEL_ID=gpt-4-turbo -CLAUDE_MODEL=claude-3-5-sonnet-20240620 - -# Azure configuration -AZURE_API_KEY="your-azure-api-key" -AZURE_API_BASE="https://your-resource.openai.azure.com/" -AZURE_API_VERSION="2024-02-15-preview" -AZURE_DEPLOYMENT_NAME="your-deployment" - -# API selection -AZURE_OR_OPENAI="OPENAI" # Options: OPENAI, AZURE, CLAUDE - -# Scan parameters -BUSINESS_FLOW_COUNT=4 -SWITCH_FUNCTION_CODE=False -SWITCH_BUSINESS_CODE=True -``` - -## 🌈 Supported Languages - -- Solidity (.sol) -- Rust (.rs) -- Python (.py) -- Move (.move) -- Cairo (.cairo) -- Tact (.tact) -- Func (.fc) -- Java (.java) -- Pseudo-Solidity (.fr) - For scanning Solidity pseudocode - -## 📊 Scan Results Guide - -1. If interrupted due to network/API issues, resume scanning using the same project_id in main.py -3. Results include detailed annotations: - - Focus on entries marked "yes" in result column - - Filter "dont need In-project other contract" in category column - - Check specific code in business_flow_code column - - Find code location in name column - -## 🎯 Important Notes - -- Best suited for logic vulnerability mining in real projects -- Not recommended for academic vulnerability testing -- GPT-4-turbo recommended for best results -- Average scan time for medium-sized projects: 2-3 hours -- Estimated cost for 10 iterations on medium projects: $20-30 -- Current false positive rate: 30-65% (depends on project size) - -## 🔍 Technical Notes - -1. claude 3.5 sonnet in scanning provides better results with acceptable time cost, GPT-3 not fully tested -2. Deceptive prompt theory adaptable to any language with minor modifications -3. ANTLR AST parsing recommended for better code slicing results -4. Currently supports Solidity, plans to expand language support -5. DeepSeek-R1 is recommended for better confirmation results -## 🛡️ Scanning Features - -- Excels at code understanding and logic vulnerability detection -- Weaker at control flow vulnerability detection -- Designed for real projects, not academic test cases - -## 💡 Implementation Tips - -- Progress automatically saved per scan -- claude-3.5-sonnet offers best performance in scanning compared to other models -- deepseek-R1 offers best performance in confirmation compared to other models -- 10 iterations for medium projects take about 4 hours -- Results include detailed categorization - -## 📝 License - -Apache License 2.0 - -## 🤝 Contributing - -Pull Requests welcome! - ---- - -*Note: Project name inspired by [Large Language Monkeys paper](https://arxiv.org/abs/2407.21787v1)* - -Would you like me to explain or break down the code? diff --git a/FiniteMonkey.egg-info/SOURCES.txt b/FiniteMonkey.egg-info/SOURCES.txt deleted file mode 100644 index 62cd5df3..00000000 --- a/FiniteMonkey.egg-info/SOURCES.txt +++ /dev/null @@ -1,358 +0,0 @@ -LICENSE -README.md -pyproject.toml -./activate/bin/activate_this.py -./build/bin/activate_this.py -./build/lib/finite_monkey_engine/__init__.py -./build/lib/finite_monkey_engine/nodes_config.py -./build/lib/finite_monkey_engine/nodes_injector.py -./build/lib/finite_monkey_engine/root_service.py -./build/lib/finite_monkey_engine/run.py -./build/lib/finite_monkey_engine/activate/bin/activate_this.py -./build/lib/finite_monkey_engine/build/bin/activate_this.py -./build/lib/finite_monkey_engine/src/__init__.py -./build/lib/finite_monkey_engine/src/ai_engine.py -./build/lib/finite_monkey_engine/src/audit_config.py -./build/lib/finite_monkey_engine/src/buildTime.py -./build/lib/finite_monkey_engine/src/main.py -./build/lib/finite_monkey_engine/src/prompts.py -./build/lib/finite_monkey_engine/src/agents/__init__.py -./build/lib/finite_monkey_engine/src/agents/chat_app.py -./build/lib/finite_monkey_engine/src/agents/formatted_console.py -./build/lib/finite_monkey_engine/src/agents/md_output.py -./build/lib/finite_monkey_engine/src/agents/planning_v3.py -./build/lib/finite_monkey_engine/src/agents/rag_flow.py -./build/lib/finite_monkey_engine/src/agents/sql_generate.py -./build/lib/finite_monkey_engine/src/codebaseQA/__init__.py -./build/lib/finite_monkey_engine/src/codebaseQA/rag_processor.py -./build/lib/finite_monkey_engine/src/dao/__init__.py -./build/lib/finite_monkey_engine/src/dao/cache_manager.py -./build/lib/finite_monkey_engine/src/dao/dao_test.py -./build/lib/finite_monkey_engine/src/dao/db3.py -./build/lib/finite_monkey_engine/src/dao/db_model.py -./build/lib/finite_monkey_engine/src/dao/entity.py -./build/lib/finite_monkey_engine/src/dao/task_mgr.py -./build/lib/finite_monkey_engine/src/knowledges/__init__.py -./build/lib/finite_monkey_engine/src/knowledges/entity.py -./build/lib/finite_monkey_engine/src/library/ChatgptToken.py -./build/lib/finite_monkey_engine/src/library/__init__.py -./build/lib/finite_monkey_engine/src/library/config.py -./build/lib/finite_monkey_engine/src/library/dataset_utils.py -./build/lib/finite_monkey_engine/src/library/formatutils.py -./build/lib/finite_monkey_engine/src/library/routes_svc.py -./build/lib/finite_monkey_engine/src/library/utils.py -./build/lib/finite_monkey_engine/src/library/parsing/SolidityLexer.py -./build/lib/finite_monkey_engine/src/library/parsing/SolidityParser.py -./build/lib/finite_monkey_engine/src/library/parsing/__init__.py -./build/lib/finite_monkey_engine/src/library/parsing/callgraph.py -./build/lib/finite_monkey_engine/src/library/parsing/constantTokenId copy.py -./build/lib/finite_monkey_engine/src/library/parsing/constantTokenId.py -./build/lib/finite_monkey_engine/src/library/parsing/utils.py -./build/lib/finite_monkey_engine/src/library/sgp/__init__.py -./build/lib/finite_monkey_engine/src/library/sgp/ast_node_types.py -./build/lib/finite_monkey_engine/src/library/sgp/ast_scanner.py -./build/lib/finite_monkey_engine/src/library/sgp/main.py -./build/lib/finite_monkey_engine/src/library/sgp/sgp_error_listener.py -./build/lib/finite_monkey_engine/src/library/sgp/sgp_parser.py -./build/lib/finite_monkey_engine/src/library/sgp/sgp_visitor.py -./build/lib/finite_monkey_engine/src/library/sgp/tokens.py -./build/lib/finite_monkey_engine/src/library/sgp/utils.py -./build/lib/finite_monkey_engine/src/library/sgp/parser/SolidityLexer.py -./build/lib/finite_monkey_engine/src/library/sgp/parser/SolidityListener.py -./build/lib/finite_monkey_engine/src/library/sgp/parser/SolidityParser.py -./build/lib/finite_monkey_engine/src/library/sgp/parser/SolidityVisitor.py -./build/lib/finite_monkey_engine/src/library/sgp/parser/__init__.py -./build/lib/finite_monkey_engine/src/library/sgp/parser/parser.py -./build/lib/finite_monkey_engine/src/library/sgp/tool/PDG_parser.py -./build/lib/finite_monkey_engine/src/library/sgp/tool/baseline.py -./build/lib/finite_monkey_engine/src/library/sgp/tool/determine_type.py -./build/lib/finite_monkey_engine/src/library/sgp/tool/extract_roles.py -./build/lib/finite_monkey_engine/src/library/sgp/tool/main.py -./build/lib/finite_monkey_engine/src/library/sgp/tool/sGuard_compare.py -./build/lib/finite_monkey_engine/src/library/sgp/utilities/call_graph_generator.py -./build/lib/finite_monkey_engine/src/library/sgp/utilities/contract_extractor.py -./build/lib/finite_monkey_engine/src/library/sgp/utilities/micelleneous.py -./build/lib/finite_monkey_engine/src/openai_api/__init__.py -./build/lib/finite_monkey_engine/src/openai_api/openai.py -./build/lib/finite_monkey_engine/src/openai_api/routes.py -./build/lib/finite_monkey_engine/src/planning/__init__.py -./build/lib/finite_monkey_engine/src/planning/planning_v2.py -./build/lib/finite_monkey_engine/src/project/__init__.py -./build/lib/finite_monkey_engine/src/project/project_audit.py -./build/lib/finite_monkey_engine/src/project/project_parser.py -./build/lib/finite_monkey_engine/src/project/project_settings.py -./build/lib/finite_monkey_engine/src/prompt_factory/__init__.py -./build/lib/finite_monkey_engine/src/prompt_factory/core_prompt.py -./build/lib/finite_monkey_engine/src/prompt_factory/periphery_prompt.py -./build/lib/finite_monkey_engine/src/prompt_factory/prompt_assembler.py -./build/lib/finite_monkey_engine/src/prompt_factory/vul_check_prompt.py -./build/lib/finite_monkey_engine/src/prompt_factory/vul_prompt.py -./build/lib/finite_monkey_engine/src/res_processor/__init__.py -./build/lib/finite_monkey_engine/src/res_processor/res_processor.py -./build/lib/finite_monkey_engine/src/scripts/__init__.py -./build/lib/finite_monkey_engine/src/scripts/migration.py -./build/lib/finite_monkey_engine/src/scripts/task_helper.py -./build/lib/finite_monkey_engine/sync/bin/activate_this.py -./build/lib64/finite_monkey_engine/__init__.py -./build/lib64/finite_monkey_engine/nodes_config.py -./build/lib64/finite_monkey_engine/nodes_injector.py -./build/lib64/finite_monkey_engine/root_service.py -./build/lib64/finite_monkey_engine/run.py -./build/lib64/finite_monkey_engine/activate/bin/activate_this.py -./build/lib64/finite_monkey_engine/build/bin/activate_this.py -./build/lib64/finite_monkey_engine/src/__init__.py -./build/lib64/finite_monkey_engine/src/ai_engine.py -./build/lib64/finite_monkey_engine/src/audit_config.py -./build/lib64/finite_monkey_engine/src/buildTime.py -./build/lib64/finite_monkey_engine/src/main.py -./build/lib64/finite_monkey_engine/src/prompts.py -./build/lib64/finite_monkey_engine/src/agents/__init__.py -./build/lib64/finite_monkey_engine/src/agents/chat_app.py -./build/lib64/finite_monkey_engine/src/agents/formatted_console.py -./build/lib64/finite_monkey_engine/src/agents/md_output.py -./build/lib64/finite_monkey_engine/src/agents/planning_v3.py -./build/lib64/finite_monkey_engine/src/agents/rag_flow.py -./build/lib64/finite_monkey_engine/src/agents/sql_generate.py -./build/lib64/finite_monkey_engine/src/codebaseQA/__init__.py -./build/lib64/finite_monkey_engine/src/codebaseQA/rag_processor.py -./build/lib64/finite_monkey_engine/src/dao/__init__.py -./build/lib64/finite_monkey_engine/src/dao/cache_manager.py -./build/lib64/finite_monkey_engine/src/dao/dao_test.py -./build/lib64/finite_monkey_engine/src/dao/db3.py -./build/lib64/finite_monkey_engine/src/dao/db_model.py -./build/lib64/finite_monkey_engine/src/dao/entity.py -./build/lib64/finite_monkey_engine/src/dao/task_mgr.py -./build/lib64/finite_monkey_engine/src/knowledges/__init__.py -./build/lib64/finite_monkey_engine/src/knowledges/entity.py -./build/lib64/finite_monkey_engine/src/library/ChatgptToken.py -./build/lib64/finite_monkey_engine/src/library/__init__.py -./build/lib64/finite_monkey_engine/src/library/config.py -./build/lib64/finite_monkey_engine/src/library/dataset_utils.py -./build/lib64/finite_monkey_engine/src/library/formatutils.py -./build/lib64/finite_monkey_engine/src/library/routes_svc.py -./build/lib64/finite_monkey_engine/src/library/utils.py -./build/lib64/finite_monkey_engine/src/library/parsing/SolidityLexer.py -./build/lib64/finite_monkey_engine/src/library/parsing/SolidityParser.py -./build/lib64/finite_monkey_engine/src/library/parsing/__init__.py -./build/lib64/finite_monkey_engine/src/library/parsing/callgraph.py -./build/lib64/finite_monkey_engine/src/library/parsing/constantTokenId copy.py -./build/lib64/finite_monkey_engine/src/library/parsing/constantTokenId.py -./build/lib64/finite_monkey_engine/src/library/parsing/utils.py -./build/lib64/finite_monkey_engine/src/library/sgp/__init__.py -./build/lib64/finite_monkey_engine/src/library/sgp/ast_node_types.py -./build/lib64/finite_monkey_engine/src/library/sgp/ast_scanner.py -./build/lib64/finite_monkey_engine/src/library/sgp/main.py -./build/lib64/finite_monkey_engine/src/library/sgp/sgp_error_listener.py -./build/lib64/finite_monkey_engine/src/library/sgp/sgp_parser.py -./build/lib64/finite_monkey_engine/src/library/sgp/sgp_visitor.py -./build/lib64/finite_monkey_engine/src/library/sgp/tokens.py -./build/lib64/finite_monkey_engine/src/library/sgp/utils.py -./build/lib64/finite_monkey_engine/src/library/sgp/parser/SolidityLexer.py -./build/lib64/finite_monkey_engine/src/library/sgp/parser/SolidityListener.py -./build/lib64/finite_monkey_engine/src/library/sgp/parser/SolidityParser.py -./build/lib64/finite_monkey_engine/src/library/sgp/parser/SolidityVisitor.py -./build/lib64/finite_monkey_engine/src/library/sgp/parser/__init__.py -./build/lib64/finite_monkey_engine/src/library/sgp/parser/parser.py -./build/lib64/finite_monkey_engine/src/library/sgp/tool/PDG_parser.py -./build/lib64/finite_monkey_engine/src/library/sgp/tool/baseline.py -./build/lib64/finite_monkey_engine/src/library/sgp/tool/determine_type.py -./build/lib64/finite_monkey_engine/src/library/sgp/tool/extract_roles.py -./build/lib64/finite_monkey_engine/src/library/sgp/tool/main.py -./build/lib64/finite_monkey_engine/src/library/sgp/tool/sGuard_compare.py -./build/lib64/finite_monkey_engine/src/library/sgp/utilities/call_graph_generator.py -./build/lib64/finite_monkey_engine/src/library/sgp/utilities/contract_extractor.py -./build/lib64/finite_monkey_engine/src/library/sgp/utilities/micelleneous.py -./build/lib64/finite_monkey_engine/src/openai_api/__init__.py -./build/lib64/finite_monkey_engine/src/openai_api/openai.py -./build/lib64/finite_monkey_engine/src/openai_api/routes.py -./build/lib64/finite_monkey_engine/src/planning/__init__.py -./build/lib64/finite_monkey_engine/src/planning/planning_v2.py -./build/lib64/finite_monkey_engine/src/project/__init__.py -./build/lib64/finite_monkey_engine/src/project/project_audit.py -./build/lib64/finite_monkey_engine/src/project/project_parser.py -./build/lib64/finite_monkey_engine/src/project/project_settings.py -./build/lib64/finite_monkey_engine/src/prompt_factory/__init__.py -./build/lib64/finite_monkey_engine/src/prompt_factory/core_prompt.py -./build/lib64/finite_monkey_engine/src/prompt_factory/periphery_prompt.py -./build/lib64/finite_monkey_engine/src/prompt_factory/prompt_assembler.py -./build/lib64/finite_monkey_engine/src/prompt_factory/vul_check_prompt.py -./build/lib64/finite_monkey_engine/src/prompt_factory/vul_prompt.py -./build/lib64/finite_monkey_engine/src/res_processor/__init__.py -./build/lib64/finite_monkey_engine/src/res_processor/res_processor.py -./build/lib64/finite_monkey_engine/src/scripts/__init__.py -./build/lib64/finite_monkey_engine/src/scripts/migration.py -./build/lib64/finite_monkey_engine/src/scripts/task_helper.py -./build/lib64/finite_monkey_engine/sync/bin/activate_this.py -./src/__init__.py -./src/ai_engine.py -./src/audit_config.py -./src/buildTime.py -./src/main.py -./src/nodes_config.py -./src/prompts.py -./src/root_service.py -./src/run.py -./src/agents/__init__.py -./src/agents/chat_app.py -./src/agents/formatted_console.py -./src/agents/md_output.py -./src/agents/sql_generate.py -./src/codebaseQA/__init__.py -./src/codebaseQA/rag_processor.py -./src/dao/__init__.py -./src/dao/cache_manager.py -./src/dao/dao_test.py -./src/dao/entity.py -./src/dao/task_mgr.py -./src/knowledges/__init__.py -./src/library/ChatgptToken.py -./src/library/__init__.py -./src/library/config.py -./src/library/dataset_utils.py -./src/library/formatutils.py -./src/library/utils.py -./src/library/parsing/SolidityLexer.py -./src/library/parsing/SolidityParser.py -./src/library/parsing/__init__.py -./src/library/parsing/callgraph.py -./src/library/parsing/constantTokenId copy.py -./src/library/parsing/constantTokenId.py -./src/library/parsing/utils.py -./src/library/sgp/__init__.py -./src/library/sgp/ast_node_types.py -./src/library/sgp/ast_scanner.py -./src/library/sgp/main.py -./src/library/sgp/sgp_error_listener.py -./src/library/sgp/sgp_parser.py -./src/library/sgp/sgp_visitor.py -./src/library/sgp/tokens.py -./src/library/sgp/utils.py -./src/library/sgp/parser/SolidityLexer.py -./src/library/sgp/parser/SolidityListener.py -./src/library/sgp/parser/SolidityParser.py -./src/library/sgp/parser/SolidityVisitor.py -./src/library/sgp/parser/__init__.py -./src/library/sgp/parser/parser.py -./src/library/sgp/tool/PDG_parser.py -./src/library/sgp/tool/__init__.py -./src/library/sgp/tool/baseline.py -./src/library/sgp/tool/determine_type.py -./src/library/sgp/tool/extract_roles.py -./src/library/sgp/tool/main.py -./src/library/sgp/tool/sGuard_compare.py -./src/library/sgp/utilities/__init__.py -./src/library/sgp/utilities/call_graph_generator.py -./src/library/sgp/utilities/contract_extractor.py -./src/library/sgp/utilities/micelleneous.py -./src/openai_api/__init__.py -./src/openai_api/openai.py -./src/planning/__init__.py -./src/planning/business_flow_extractor.py -./src/planning/planning_v2.py -./src/project/__init__.py -./src/project/dataclasses.py -./src/project/project_audit.py -./src/project/project_parser.py -./src/project/project_settings.py -./src/prompt_factory/__init__.py -./src/prompt_factory/core_prompt.py -./src/prompt_factory/periphery_prompt.py -./src/prompt_factory/prompt_assembler.py -./src/prompt_factory/vul_check_prompt.py -./src/prompt_factory/vul_prompt.py -./src/res_processor/__init__.py -./src/res_processor/res_processor.py -./src/scripts/__init__.py -./src/scripts/migration.py -./src/scripts/task_helper.py -./sync/bin/activate_this.py -FiniteMonkey.egg-info/PKG-INFO -FiniteMonkey.egg-info/SOURCES.txt -FiniteMonkey.egg-info/dependency_links.txt -FiniteMonkey.egg-info/entry_points.txt -FiniteMonkey.egg-info/requires.txt -FiniteMonkey.egg-info/top_level.txt -activate/bin/activate_this.py -src/__init__.py -src/ai_engine.py -src/audit_config.py -src/buildTime.py -src/main.py -src/nodes_config.py -src/prompts.py -src/root_service.py -src/run.py -src/agents/__init__.py -src/agents/chat_app.py -src/agents/formatted_console.py -src/agents/md_output.py -src/agents/sql_generate.py -src/codebaseQA/__init__.py -src/codebaseQA/rag_processor.py -src/dao/__init__.py -src/dao/cache_manager.py -src/dao/dao_test.py -src/dao/entity.py -src/dao/task_mgr.py -src/knowledges/__init__.py -src/library/ChatgptToken.py -src/library/__init__.py -src/library/config.py -src/library/dataset_utils.py -src/library/formatutils.py -src/library/utils.py -src/library/parsing/SolidityLexer.py -src/library/parsing/SolidityParser.py -src/library/parsing/__init__.py -src/library/parsing/callgraph.py -src/library/parsing/constantTokenId copy.py -src/library/parsing/constantTokenId.py -src/library/parsing/utils.py -src/library/sgp/__init__.py -src/library/sgp/ast_node_types.py -src/library/sgp/ast_scanner.py -src/library/sgp/main.py -src/library/sgp/sgp_error_listener.py -src/library/sgp/sgp_parser.py -src/library/sgp/sgp_visitor.py -src/library/sgp/tokens.py -src/library/sgp/utils.py -src/library/sgp/parser/SolidityLexer.py -src/library/sgp/parser/SolidityListener.py -src/library/sgp/parser/SolidityParser.py -src/library/sgp/parser/SolidityVisitor.py -src/library/sgp/parser/__init__.py -src/library/sgp/parser/parser.py -src/library/sgp/tool/PDG_parser.py -src/library/sgp/tool/__init__.py -src/library/sgp/tool/baseline.py -src/library/sgp/tool/determine_type.py -src/library/sgp/tool/extract_roles.py -src/library/sgp/tool/main.py -src/library/sgp/tool/sGuard_compare.py -src/library/sgp/utilities/__init__.py -src/library/sgp/utilities/call_graph_generator.py -src/library/sgp/utilities/contract_extractor.py -src/library/sgp/utilities/micelleneous.py -src/openai_api/__init__.py -src/openai_api/openai.py -src/planning/__init__.py -src/planning/business_flow_extractor.py -src/planning/planning_v2.py -src/project/__init__.py -src/project/dataclasses.py -src/project/project_audit.py -src/project/project_parser.py -src/project/project_settings.py -src/prompt_factory/__init__.py -src/prompt_factory/core_prompt.py -src/prompt_factory/periphery_prompt.py -src/prompt_factory/prompt_assembler.py -src/prompt_factory/vul_check_prompt.py -src/prompt_factory/vul_prompt.py -src/res_processor/__init__.py -src/res_processor/res_processor.py -src/scripts/__init__.py -src/scripts/migration.py -src/scripts/task_helper.py -sync/bin/activate_this.py \ No newline at end of file diff --git a/FiniteMonkey.egg-info/dependency_links.txt b/FiniteMonkey.egg-info/dependency_links.txt deleted file mode 100644 index 8b137891..00000000 --- a/FiniteMonkey.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/FiniteMonkey.egg-info/entry_points.txt b/FiniteMonkey.egg-info/entry_points.txt deleted file mode 100644 index 4b89b175..00000000 --- a/FiniteMonkey.egg-info/entry_points.txt +++ /dev/null @@ -1,3 +0,0 @@ -[console_scripts] -finite_monkey_engine = finite_monkey_engine:main -finite_monkey_root = finite_monkey_engine.root_service:main diff --git a/FiniteMonkey.egg-info/requires.txt b/FiniteMonkey.egg-info/requires.txt deleted file mode 100644 index f33c08b0..00000000 --- a/FiniteMonkey.egg-info/requires.txt +++ /dev/null @@ -1,45 +0,0 @@ -antlr4-python3-runtime>=4.13.2 -asyncpg>=0.30.0 -certifi>=2024.8.30 -charset-normalizer>=3.4.0 -colorama>=0.4.6 -dependency-injector>=4.45.0 -devtools>=0.9.0 -et-xmlfile>=1.1.0 -fastapi>=0.115.8 -future>=1.0.0 -idna>=3.10 -ipython>=8.32.0 -joblib>=1.4.2 -lancedb>=0.19.0 -logging>=0.4.9.6 -numpy>=1.24.4 -ollama>=0.4.7 -openai>=1.63.0 -openpyxl>=3.1.5 -pandas>=1.24.4 -psycopg2-binary>=2.9.9 -pydantic>=2.11.0a2 -pydantic-ai>=0.0.24 -pydantic-graph>=0.0.24 -pydantic-settings>=2.7.1 -pylance>=0.23.0 -python-box>=7.3.2 -python-dateutil==2.8.2 -python-dotenv>=1.0.1 -pytz>=2024.2 -requests>=2.32.3 -rich>=13.9.4 -scikit-learn>=1.3.2 -scipy>=1.10.1 -simplejson>=3.19.3 -six>=1.16.0 -sqlalchemy>=2.0.35 -sqlalchemy-orm>=1.2.10 -threadpoolctl>=3.5.0 -tqdm>=4.66.5 -tree-sitter>=0.24.0 -tree-sitter-solidity>=1.2.11 -typing_extensions>=4.12.2 -tzdata>=2024.2 -urllib3>=2.2.3 diff --git a/FiniteMonkey.egg-info/top_level.txt b/FiniteMonkey.egg-info/top_level.txt deleted file mode 100644 index 2be4b43f..00000000 --- a/FiniteMonkey.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -finite_monkey_engine diff --git a/pyproject.toml b/pyproject.toml index 0b901b23..c8b48a5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ dependencies = [ "future>=1.0.0", "idna>=3.10", "ipython>=8.32.0", + "jiter>=0.8.2", "joblib>=1.4.2", "lancedb>=0.19.0", "logging>=0.4.9.6", @@ -47,7 +48,7 @@ dependencies = [ "scipy>=1.10.1", "simplejson>=3.19.3", "six>=1.16.0", - "sqlalchemy>=2.0.35", + "sqlalchemy[asyncpg]>=2.0.35", "sqlalchemy-orm>=1.2.10", "threadpoolctl>=3.5.0", "tqdm>=4.66.5", diff --git a/src/agents/chat_app.html b/src/agents/chat_app.html new file mode 100644 index 00000000..76b44c7b --- /dev/null +++ b/src/agents/chat_app.html @@ -0,0 +1,81 @@ + + + + + + Chat App + + + + +
+

Chat App

+

Ask me anything...

+
+
+
+
+
+ +
+ +
+
+
+ Error occurred, check the browser developer console for more information. +
+
+ + + + diff --git a/src/agents/chat_app.py b/src/agents/chat_app.py new file mode 100644 index 00000000..8e5043cb --- /dev/null +++ b/src/agents/chat_app.py @@ -0,0 +1,219 @@ +"""Simple chat app example build with FastAPI. + +Run with: + + uv run -m pydantic_ai_examples.chat_app +""" + +from __future__ import annotations as _annotations + +import asyncio +import json +import sqlite3 +from collections.abc import AsyncIterator +from concurrent.futures.thread import ThreadPoolExecutor +from contextlib import asynccontextmanager +from dataclasses import dataclass +from datetime import datetime, timezone +from functools import partial +from pathlib import Path +from typing import Annotated, Any, Callable, Literal, TypeVar + +import fastapi +from fastapi import Depends, Request +from fastapi.responses import FileResponse, Response, StreamingResponse +from typing_extensions import LiteralString, ParamSpec, TypedDict + +from pydantic_ai import Agent +from pydantic_ai.exceptions import UnexpectedModelBehavior +from pydantic_ai.messages import ( + ModelMessage, + ModelMessagesTypeAdapter, + ModelRequest, + ModelResponse, + TextPart, + UserPromptPart, +) + +agent = Agent('openai:gpt-4o') +THIS_DIR = Path(__file__).parent + + +@asynccontextmanager +async def lifespan(_app: fastapi.FastAPI): + async with Database.connect() as db: + yield {'db': db} + + +app = fastapi.FastAPI(lifespan=lifespan) + +@app.get('/') +async def index() -> FileResponse: + return FileResponse((THIS_DIR / 'chat_app.html'), media_type='text/html') + + +@app.get('/chat_app.ts') +async def main_ts() -> FileResponse: + """Get the raw typescript code, it's compiled in the browser, forgive me.""" + return FileResponse((THIS_DIR / 'chat_app.ts'), media_type='text/plain') + + +async def get_db(request: Request) -> Database: + return request.state.db + + +@app.get('/chat/') +async def get_chat(database: Database = Depends(get_db)) -> Response: + msgs = await database.get_messages() + return Response( + b'\n'.join(json.dumps(to_chat_message(m)).encode('utf-8') for m in msgs), + media_type='text/plain', + ) + + +class ChatMessage(TypedDict): + """Format of messages sent to the browser.""" + + role: Literal['user', 'model'] + timestamp: str + content: str + + +def to_chat_message(m: ModelMessage) -> ChatMessage: + first_part = m.parts[0] + if isinstance(m, ModelRequest): + if isinstance(first_part, UserPromptPart): + return { + 'role': 'user', + 'timestamp': first_part.timestamp.isoformat(), + 'content': first_part.content, + } + elif isinstance(m, ModelResponse): + if isinstance(first_part, TextPart): + return { + 'role': 'model', + 'timestamp': m.timestamp.isoformat(), + 'content': first_part.content, + } + raise UnexpectedModelBehavior(f'Unexpected message type for chat app: {m}') + + +@app.post('/chat/') +async def post_chat( + prompt: Annotated[str, fastapi.Form()], database: Database = Depends(get_db) +) -> StreamingResponse: + async def stream_messages(): + """Streams new line delimited JSON `Message`s to the client.""" + # stream the user prompt so that can be displayed straight away + yield ( + json.dumps( + { + 'role': 'user', + 'timestamp': datetime.now(tz=timezone.utc).isoformat(), + 'content': prompt, + } + ).encode('utf-8') + + b'\n' + ) + # get the chat history so far to pass as context to the agent + messages = await database.get_messages() + # run the agent with the user prompt and the chat history + async with agent.run_stream(prompt, message_history=messages) as result: + async for text in result.stream(debounce_by=0.01): + # text here is a `str` and the frontend wants + # JSON encoded ModelResponse, so we create one + m = ModelResponse(parts=[TextPart(text)], timestamp=result.timestamp()) + yield json.dumps(to_chat_message(m)).encode('utf-8') + b'\n' + + # add new messages (e.g. the user prompt and the agent response in this case) to the database + await database.add_messages(result.new_messages_json()) + + return StreamingResponse(stream_messages(), media_type='text/plain') + + +P = ParamSpec('P') +R = TypeVar('R') + + +@dataclass +class Database: + """Rudimentary database to store chat messages in SQLite. + + The SQLite standard library package is synchronous, so we + use a thread pool executor to run queries asynchronously. + """ + + con: sqlite3.Connection + _loop: asyncio.AbstractEventLoop + _executor: ThreadPoolExecutor + + @classmethod + @asynccontextmanager + async def connect( + cls, file: Path = THIS_DIR / '.chat_app_messages.sqlite' + ) -> AsyncIterator[Database]: + with logfire.span('connect to DB'): + loop = asyncio.get_event_loop() + executor = ThreadPoolExecutor(max_workers=1) + con = await loop.run_in_executor(executor, cls._connect, file) + slf = cls(con, loop, executor) + try: + yield slf + finally: + await slf._asyncify(con.close) + + @staticmethod + def _connect(file: Path) -> sqlite3.Connection: + con = sqlite3.connect(str(file)) + con = logfire.instrument_sqlite3(con) + cur = con.cursor() + cur.execute( + 'CREATE TABLE IF NOT EXISTS messages (id INT PRIMARY KEY, message_list TEXT);' + ) + con.commit() + return con + + async def add_messages(self, messages: bytes): + await self._asyncify( + self._execute, + 'INSERT INTO messages (message_list) VALUES (?);', + messages, + commit=True, + ) + await self._asyncify(self.con.commit) + + async def get_messages(self) -> list[ModelMessage]: + c = await self._asyncify( + self._execute, 'SELECT message_list FROM messages order by id' + ) + rows = await self._asyncify(c.fetchall) + messages: list[ModelMessage] = [] + for row in rows: + messages.extend(ModelMessagesTypeAdapter.validate_json(row[0])) + return messages + + def _execute( + self, sql: LiteralString, *args: Any, commit: bool = False + ) -> sqlite3.Cursor: + cur = self.con.cursor() + cur.execute(sql, args) + if commit: + self.con.commit() + return cur + + async def _asyncify( + self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs + ) -> R: + return await self._loop.run_in_executor( # type: ignore + self._executor, + partial(func, **kwargs), + *args, # type: ignore + ) + + +if __name__ == '__main__': + import uvicorn + + uvicorn.run( + 'pydantic_ai_examples.chat_app:app', reload=True, reload_dirs=[str(THIS_DIR)] + ) diff --git a/src/agents/chat_app.ts b/src/agents/chat_app.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/agents/formatted_console.py b/src/agents/formatted_console.py new file mode 100644 index 00000000..4ce731ae --- /dev/null +++ b/src/agents/formatted_console.py @@ -0,0 +1,79 @@ +from typing import Annotated + +from pydantic import Field, ValidationError +from rich.console import Console +from rich.live import Live +from rich.table import Table +from typing_extensions import NotRequired, TypedDict + +from pydantic_ai import Agent + +class Whale(TypedDict): + name: str + length: Annotated[ + float, Field(description='Average length of an adult whale in meters.') + ] + weight: NotRequired[ + Annotated[ + float, + Field(description='Average weight of an adult whale in kilograms.', ge=50), + ] + ] + ocean: NotRequired[str] + description: NotRequired[Annotated[str, Field(description='Short Description')]] + + +agent = Agent('openai:gpt-4', result_type=list[Whale]) + + +async def main(): + console = Console() + with Live('\n' * 36, console=console) as live: + console.print('Requesting data...', style='cyan') + async with agent.run_stream( + 'Generate me details of 5 species of Whale.' + ) as result: + console.print('Response:', style='green') + + async for message, last in result.stream_structured(debounce_by=0.01): + try: + whales = await result.validate_structured_result( + message, allow_partial=not last + ) + except ValidationError as exc: + if all( + e['type'] == 'missing' and e['loc'] == ('response',) + for e in exc.errors() + ): + continue + else: + raise + + table = Table( + title='Species of Whale', + caption='Streaming Structured responses from GPT-4', + width=120, + ) + table.add_column('ID', justify='right') + table.add_column('Name') + table.add_column('Avg. Length (m)', justify='right') + table.add_column('Avg. Weight (kg)', justify='right') + table.add_column('Ocean') + table.add_column('Description', justify='right') + + for wid, whale in enumerate(whales, start=1): + table.add_row( + str(wid), + whale['name'], + f'{whale["length"]:0.0f}', + f'{w:0.0f}' if (w := whale.get('weight')) else '…', + whale.get('ocean') or '…', + whale.get('description') or '…', + ) + live.update(table) + + +if __name__ == '__main__': + import asyncio + + asyncio.run(main()) diff --git a/src/agents/md_output.py b/src/agents/md_output.py new file mode 100644 index 00000000..a4c7c71d --- /dev/null +++ b/src/agents/md_output.py @@ -0,0 +1,66 @@ + +import asyncio +import os + +from rich.console import Console, ConsoleOptions, RenderResult +from rich.live import Live +from rich.markdown import CodeBlock, Markdown +from rich.syntax import Syntax +from rich.text import Text + +from pydantic_ai import Agent +from pydantic_ai.models import KnownModelName + +agent = Agent() + +# models to try, and the appropriate env var +models: list[tuple[KnownModelName, str]] = [ + ('google-gla:gemini-1.5-flash', 'GEMINI_API_KEY'), + ('openai:gpt-4o-mini', 'OPENAI_API_KEY'), + ('groq:llama-3.3-70b-versatile', 'GROQ_API_KEY'), +] + + +async def main(): + prettier_code_blocks() + console = Console() + prompt = 'Show me a short example of using Pydantic.' + console.log(f'Asking: {prompt}...', style='cyan') + for model, env_var in models: + if env_var in os.environ: + console.log(f'Using model: {model}') + with Live('', console=console, vertical_overflow='visible') as live: + async with agent.run_stream(prompt, model=model) as result: + async for message in result.stream(): + live.update(Markdown(message)) + console.log(result.usage()) + else: + console.log(f'{model} requires {env_var} to be set.') + + +def prettier_code_blocks(): + """Make rich code blocks prettier and easier to copy. + + From https://github.com/samuelcolvin/aicli/blob/v0.8.0/samuelcolvin_aicli.py#L22 + """ + + class SimpleCodeBlock(CodeBlock): + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + code = str(self.text).rstrip() + yield Text(self.lexer_name, style='dim') + yield Syntax( + code, + self.lexer_name, + theme=self.theme, + background_color='default', + word_wrap=True, + ) + yield Text(f'/{self.lexer_name}', style='dim') + + Markdown.elements['fence'] = SimpleCodeBlock + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/src/codebaseQA/arag_processor.py b/src/codebaseQA/arag_processor.py new file mode 100644 index 00000000..a0012ec7 --- /dev/null +++ b/src/codebaseQA/arag_processor.py @@ -0,0 +1,100 @@ +import lancedb +import os +import numpy as np +import requests +import pyarrow as pa +from typing import Coroutine, List, Dict, Any +from datetime import datetime +from tqdm.asyncio import tqdm + +from openai_api.openai import common_get_embedding +from project.aproject_audit import AProjectAudit + +class ARAGProcessor: + def __init__(self, id: str = None, audit: AProjectAudit = None): + self.db_path: str = os.path.join(os.getcwd(), f"Alancedb{id}") + self.audit:AProjectAudit = audit + os.makedirs(name=self.db_path, exist_ok=True) + functions_to_check: List[Dict[str, Any]] = audit.functions_to_check + self.db: Coroutine[Any, Any, lancedb.AsyncConnection] = lancedb.connect_async(self.db_path) + self.table_name = f"Alancedb_{id}" + + # 创建schema + self.schema = pa.schema([ + pa.field("id", pa.string()), + pa.field("name", pa.string()), + pa.field("content", pa.string()), + pa.field("start_line", pa.int32()), + pa.field("end_line", pa.int32()), + pa.field("file_path", pa.string()), + pa.field("embedding", pa.list_(pa.float32(), 3072)), + pa.field("modifiers", pa.list_(pa.string())), + pa.field("visibility", pa.string()), + pa.field("state_mutability", pa.string()) + ]) + + # 检查表是否存在且数据量匹配 + if self.table_exists() and self.check_data_count(len(functions_to_check)): + print(f"Table {self.table_name} already exists with correct data count. Skipping processing.") + return + + asyncio.run(self._create_database(functions_to_check)) + + async def table_exists(self) -> bool: + """检查表是否存在""" + try: + await self.db.open_table(self.table_name) + return True + except Exception: + return False + + async def check_data_count(self, expected_count: int) -> bool: + """检查表中的数据数量是否匹配""" + try: + table = await self.db.open_table(self.table_name) + actual_count = len(await table.to_lance()) + return actual_count == expected_count + except Exception: + return False + + def process_function(self, func: Dict[str, Any]) -> Dict[str, Any]: + return { + "id": f"{func['name']}_{func['start_line']}", + "name": func['name'], + "content": func['content'], + "start_line": func['start_line'], + "end_line": func['end_line'], + "file_path": func['relative_file_path'], + "embedding": common_get_embedding(func['content']), + "modifiers": func.get('modifiers', []), + "visibility": func.get('visibility', ''), + "state_mutability": func.get('stateMutability', '') + } + + async def _create_database(self, functions_to_check: List[Dict[str, Any]]) -> None: + print(f"Processing {len(functions_to_check)} functions...") + + # 创建表 + table = await self.db.create_table(self.table_name, schema=self.schema, mode="overwrite") + + # 逐条处理并添加数据 + for func in tqdm(functions_to_check, desc="Processing functions", unit="function"): + try: + processed_func = self.process_function(func) + # 将单条数据添加到表中 + await table.add([processed_func]) + except Exception as e: + print(f"Error processing function {func.get('name', 'unknown')}: {str(e)}") + continue + + print("Database creation completed!") + + async def search_similar_functions(self, query: str, k: int = 5) -> List[Dict[str, Any]]: + query_embedding = common_get_embedding(query) + table = await self.db.open_table(self.table_name) + return (await table.search(query_embedding).limit(k)).to_list() + + async def get_function_context(self, function_name: str) -> Dict[str, Any]: + table = await self.db.open_table(self.table_name) + results = (await table.filter(f"name = '{function_name}'")).to_list() + return results[0] if results else None \ No newline at end of file diff --git a/src/dao/aentity.py b/src/dao/aentity.py new file mode 100644 index 00000000..c6701747 --- /dev/null +++ b/src/dao/aentity.py @@ -0,0 +1,170 @@ +import random +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker, declarative_base +from library.utils import str_hash + +Base = declarative_base() + +class ACacheEntry(Base): + __tablename__ = 'prompt_cache2' + index = Column(String, primary_key=True) + key = Column(String) + value = Column(String) + +class AProject_Task(Base): + __tablename__ = 'project_tasks_amazing_prompt' + id = Column(Integer, autoincrement=True, primary_key=True) + key = Column(String, index=True) + project_id = Column(String, index=True) + name = Column(String) + content = Column(String) + keyword = Column(String) + business_type = Column(String) + sub_business_type = Column(String) + function_type = Column(String) + rule = Column(String) + result = Column(String) + result_gpt4 = Column(String) + score=Column(String) + category=Column(String) + contract_code=Column(String) + risklevel=Column(String) + similarity_with_rule=Column(String) + description = Column(String) + start_line=Column(String) + end_line=Column(String) + relative_file_path=Column(String) + absolute_file_path=Column(String) + recommendation=Column(String) + title=Column(String) + business_flow_code=Column(String) + business_flow_lines=Column(String) + business_flow_context=Column(String) + if_business_flow_scan=Column(String) + + fieldNames = ['name', 'content', 'keyword', 'business_type', 'sub_business_type', 'function_type', 'rule', 'result', 'result_gpt4','score','category','contract_code','risklevel','similarity_with_rule','description','start_line','end_line','relative_file_path','absolute_file_path','recommendation','title','business_flow_code','business_flow_lines','business_flow_context','if_business_flow_scan'] + + def __init__(self, project_id, name, content, keyword, business_type, sub_business_type, function_type, rule, result='', result_gpt4='',score='0.00',category='',contract_code='',risklevel='',similarity_with_rule='0.00',description='',start_line='',end_line='',relative_file_path='',absolute_file_path='',recommendation='',title='',business_flow_code='',business_flow_lines='',business_flow_context='',if_business_flow_scan='0'): + self.project_id = project_id + self.name = name + self.content = content + self.keyword = keyword + self.business_type = business_type + self.sub_business_type = sub_business_type + self.function_type = function_type + self.rule = rule + self.result = result + self.result_gpt4 = result_gpt4 + self.key = self.get_key() + self.score=score + self.category=category + self.contract_code=contract_code + self.risklevel=risklevel + self.similarity_with_rule=similarity_with_rule + self.description = description + self.start_line=start_line + self.end_line=end_line + self.relative_file_path=relative_file_path + self.absolute_file_path=absolute_file_path + self.recommendation=recommendation + self.title=title + self.business_flow_code=business_flow_code + self.business_flow_lines=business_flow_lines + self.business_flow_context=business_flow_context + self.if_business_flow_scan=if_business_flow_scan + + def as_dict(self): + return { + 'name': self.name, + 'content': self.content, + 'keyword': self.keyword, + 'business_type': self.business_type, + 'sub_business_type': self.sub_business_type, + 'function_type': self.function_type, + 'rule': self.rule, + 'result': self.result, + 'result_gpt4': self.result_gpt4, + 'score':self.score, + 'category':self.category, + 'contract_code':self.contract_code, + 'risklevel':self.risklevel, + 'similarity_with_rule':self.similarity_with_rule, + 'description': self.description, + 'start_line':self.start_line, + 'end_line':self.end_line, + 'relative_file_path':self.relative_file_path, + 'absolute_file_path':self.absolute_file_path, + 'recommendation':self.recommendation, + 'title':self.title, + 'business_flow_code':self.business_flow_code, + 'business_flow_lines':self.business_flow_lines, + 'business_flow_context':self.business_flow_context, + 'if_business_flow_scan':self.if_business_flow_scan + } + + def set_result(self, result, is_gpt4 = False): + if is_gpt4: + self.result_gpt4 = result + else: + self.result = result + + def get_result(self, is_gpt4 = False): + result = self.result + return None if result == '' else result + + def get_result_CN(self): + result = self.result_gpt4 + return None if result == '' else result + + def get_category(self): + result = self.category + return None if result == '' else result + + def get_key(self): + key = "/".join([self.name, self.content,self.keyword]) + # key = str(random.random()) + return str_hash(key) + + def get_similarity_with_rule(self): + result = self.similarity_with_rule + return None if result == '' else result + +# Create an async engine and sessionmaker +DATABASE_URL = "postgresql+asyncpg://user:password@localhost/dbname" +engine = create_async_engine(DATABASE_URL, echo=True) +AsyncSessionLocal = sessionmaker( + bind=engine, + class_=AsyncSession, + expire_on_commit=False, +) + +async def init_db(): + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + +# Example of using the asynchronous session +async def get_aproject_task_by_id(task_id: int): + async with AsyncSessionLocal() as session: + result = await session.execute(select(AProject_Task).where(AProject_Task.id == task_id)) + return result.scalars().first() + +# Example of inserting a new project task +async def create_aproject_task(project_id, name, content, keyword, business_type, sub_business_type, function_type, rule): + async with AsyncSessionLocal() as session: + new_task = AProject_Task( + project_id=project_id, + name=name, + content=content, + keyword=keyword, + business_type=business_type, + sub_business_type=sub_business_type, + function_type=function_type, + rule=rule + ) + session.add(new_task) + await session.commit() + return new_task + +# Initialize the database on startup +import asyncio +asyncio.run(init_db()) \ No newline at end of file diff --git a/src/dao/atask_mgr.py b/src/dao/atask_mgr.py new file mode 100644 index 00000000..d21cba09 --- /dev/null +++ b/src/dao/atask_mgr.py @@ -0,0 +1,214 @@ +import csv +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.ext.asyncio.engine import AsyncEngine +from sqlalchemy.orm import sessionmaker, declarative_base +from sqlalchemy.future import select +from sqlalchemy.exc import IntegrityError +from tqdm.asyncio import tqdm as tqdm_asyncio +from dao.aentity import AProject_Task +Base = declarative_base() + +class AProjectTaskMgr: + def __init__(self, project_id, engine_url): + self.project_id = project_id + # Create async engine + self.engine: AsyncEngine = create_async_engine(engine_url) + # Ensure table is created + Base.metadata.create_all(self.engine) + # Configure sessionmaker to use AsyncSession + self.Session = sessionmaker(bind=self.engine, class_=AsyncSession) + + async def _operate_in_session(self, func, *args, **kwargs): + """Generic function to handle operations within an async session.""" + async with self.Session() as session: + return await func(session, *args, **kwargs) + + async def add_tasks(self, tasks): + for task in tasks: + await self._operate_in_session(self._add_task, task) + + async def _add_task(self, session, task, commit=True): + try: + key = task.get_key() + # Uncomment if you need to check uniqueness + # ts = (await session.execute(select(Project_Task).filter_by(project_id=self.project_id, key=key))).scalars().all() + # if not ts: # Assuming get_key returns a unique identifier + session.add(task) + if commit: + await session.commit() + except IntegrityError as e: + await session.rollback() + + async def query_task_by_project_id(self, id): + return await self._operate_in_session(self._query_task_by_project_id, id) + + async def _query_task_by_project_id(self, session, id): + result = (await session.execute(select(AProject_Task).filter_by(project_id=id))).scalars().all() + return list(result) + + async def update_score(self, id, score): + await self._operate_in_session(self._update_score, id, score) + + async def _update_score(self, session, id, score): + await session.execute( + select(AProject_Task).filter_by(id=id).values(score=score) + ) + await session.commit() + + async def update_business_flow_context(self, id, context): + await self._operate_in_session(self._update_business_flow_context, id, context) + + async def _update_business_flow_context(self, session, id, context): + await session.execute( + select(AProject_Task).filter_by(id=id).values(business_flow_context=context) + ) + await session.commit() + + async def add_task( + self, + name, + content, + keyword, + business_type, + sub_business_type, + function_type, + rule, + result='', + result_gpt4='', + score='0.00', + category='', + contract_code='', + risklevel='', + similarity_with_rule='', + description='', + start_line='', + end_line='', + relative_file_path='', + absolute_file_path='', + recommendation='', + title='', + business_flow_code='', + business_flow_lines='', + business_flow_context='', + if_business_flow_scan='', + **kwargs + ): + task = AProject_Task( + self.project_id, name, content, keyword, business_type, sub_business_type, + function_type, rule, result, result_gpt4, score, category, contract_code, + risklevel, similarity_with_rule, description, start_line, end_line, + relative_file_path, absolute_file_path, recommendation, title, + business_flow_code, business_flow_lines, business_flow_context, + if_business_flow_scan + ) + await self._operate_in_session(self._add_task, task) + + async def get_task_list(self): + return await self._operate_in_session(self._get_task_list) + + async def _get_task_list(self, session): + result = (await session.execute(select(AProject_Task).filter_by(project_id=self.project_id))).scalars().all() + return list(result) + + async def get_task_list_by_id(self, id): + return await self._operate_in_session(self._get_task_list_by_id, id) + + async def _get_task_list_by_id(self, session, id): + result = (await session.execute(select(AProject_Task).filter_by(project_id=id))).scalars().all() + return list(result) + + async def update_result(self, id, result, result_gpt4, result_assumation): + await self._operate_in_session(self._update_result, id, result, result_gpt4, result_assumation) + + async def _update_result(self, session, id, result, result_gpt4, result_assumation): + await session.execute( + select(AProject_Task).filter_by(id=id).values( + result=result, + result_gpt4=result_gpt4, + category=result_assumation + ) + ) + await session.commit() + + async def update_similarity_generated_referenced_score(self, id, similarity_with_rule): + await self._operate_in_session(self._update_similarity_generated_referenced_score, id, similarity_with_rule) + + async def _update_similarity_generated_referenced_score(self, session, id, similarity_with_rule): + await session.execute( + select(AProject_Task).filter_by(id=id).values(similarity_with_rule=similarity_with_rule) + ) + await session.commit() + + async def update_description(self, id, description): + await self._operate_in_session(self._update_description, id, description) + + async def _update_description(self, session, id, description): + await session.execute( + select(AProject_Task).filter_by(id=id).values(description=description) + ) + await session.commit() + + async def update_recommendation(self, id, recommendation): + await self._operate_in_session(self._update_recommendation, id, recommendation) + + async def _update_recommendation(self, session, id, recommendation): + await session.execute( + select(AProject_Task).filter_by(id=id).values(recommendation=recommendation) + ) + await session.commit() + + async def update_title(self, id, title): + await self._operate_in_session(self._update_title, id, title) + + async def _update_title(self, session, id, title): + await session.execute( + select(AProject_Task).filter_by(id=id).values(title=title) + ) + await session.commit() + + async def import_file(self, filename): + reader = csv.DictReader(open(filename, 'r', encoding='utf-8')) + + processed = 0 + for row in tqdm_asyncio(list(reader), "import tasks"): + await self.add_task(**row) + processed += 1 + if processed % 10 == 0: + await self._operate_in_session(lambda s: s.commit()) + await self._operate_in_session(lambda s: s.commit()) + + def dump_file(self, filename): + writer = self.get_writer(filename) + + async def write_rows(): + ts = await self._operate_in_session(self._get_task_list) + for row in ts: + writer.writerow(row.as_dict()) + + # Run the asynchronous task within an event loop + import asyncio + asyncio.run(write_rows()) + + del writer + + def get_writer(self, filename): + file = open(filename, 'w', newline='', encoding='utf-8') + writer = csv.DictWriter(file, fieldnames=AProject_Task.fieldNames) + writer.writeheader() # write header + return writer + + def merge_results(self, function_rules): + rule_map = {} + for rule in function_rules: + keys = [ + rule['name'], + rule['content'], + rule['BusinessType'], + rule['Sub-BusinessType'], + rule['FunctionType'], + rule['KeySentence'] + ] + key = "/".join(keys) + rule_map[key] = rule + + return rule_map.values() \ No newline at end of file diff --git a/src/library/sgp/utilities/contract_extractor.py b/src/library/sgp/utilities/contract_extractor.py index e2f228e4..70031cc3 100644 --- a/src/library/sgp/utilities/contract_extractor.py +++ b/src/library/sgp/utilities/contract_extractor.py @@ -4,7 +4,7 @@ from library.sgp.parser.SolidityParser import SolidityParser from library.sgp.parser.SolidityListener import SolidityListener from colorama import Fore, init - +from os import path def extract_solc_version(filename): with open(filename, 'r') as file: @@ -332,7 +332,7 @@ def extract_function_from_solidity(function_name, solidity_file_path): match = function_pattern.search(contract_body) if match is None: - raise ValueError(f"No function found with name: {function_name} in contract: {contract_name}") + raise ValueError(f"No function found with name: {function_name} in contract: {path.basename(solidity_file_path)}") start = match.start() open_braces = 0 diff --git a/src/openai_api/routes.py b/src/openai_api/routes.py deleted file mode 100644 index 7cd471b9..00000000 --- a/src/openai_api/routes.py +++ /dev/null @@ -1,56 +0,0 @@ - -from fastapi import APIRouter, Depends, Response, status -from dependency_injector.wiring import inject, Provide - -from .containers import Container -from .services import UserService -from .repositories import NotFoundError - -router = APIRouter() - - -@router.get("/users") -@inject -def get_list( - user_service: UserService = Depends(Provide[Container.user_service]), -): - return user_service.get_users() - - -@router.get("/users/{user_id}") -@inject -def get_by_id( - user_id: int, - user_service: UserService = Depends(Provide[Container.user_service]), -): - try: - return user_service.get_user_by_id(user_id) - except NotFoundError: - return Response(status_code=status.HTTP_404_NOT_FOUND) - - -@router.post("/users", status_code=status.HTTP_201_CREATED) -@inject -def add( - user_service: UserService = Depends(Provide[Container.user_service]), -): - return user_service.create_user() - - -@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT) -@inject -def remove( - user_id: int, - user_service: UserService = Depends(Provide[Container.user_service]), -): - try: - user_service.delete_user_by_id(user_id) - except NotFoundError: - return Response(status_code=status.HTTP_404_NOT_FOUND) - else: - return Response(status_code=status.HTTP_204_NO_CONTENT) - - -@router.get("/status") -def get_status(): - return {"status": "OK"} \ No newline at end of file diff --git a/src/planning/aplanning_v2.py b/src/planning/aplanning_v2.py new file mode 100644 index 00000000..f601972d --- /dev/null +++ b/src/planning/aplanning_v2.py @@ -0,0 +1,556 @@ +import asyncio +import os +import json +import re +from pydantic_ai import Agent +from tqdm import tqdm +import random +from dao.aentity import AProject_Task +from dao.atask_mgr import AProjectTaskMgr +from planning.aplanning_v2 import ATaskManager + +# from pydantic_ai.agent import Agent +# from pydantic_ai.models.gemini import GeminiModel +from pydantic_ai.models.openai import OpenAIModel + +from src.ai_engine import ModelSettings +# from pydantic_ai.models.anthropic import AnthropicModel +# #from pydantic_ai.models..cohere import Model +# from pydantic_ai.models.function import FunctionModel +# from pydantic_ai.models.groq import GroqModel +# from pydantic_ai.models.mistral import MistralModel +# from pydantic_ai.models.vertexai import VertexAIModel + +# class Project_Task: +# def __init__(self, **kwargs): +# for key, value in kwargs.items(): +# setattr(self, key, value) + +# class TaskManager: +# async def get_task_list_by_id(self, project_id): +# # Simulate fetching tasks from a database asynchronously +# await asyncio.sleep(0.1) +# return [] + +# async def add_task_in_one(self, task): +# # Simulate adding a task to the database asynchronously +# await asyncio.sleep(0.1) + +# class Project: +# def __init__(self, project_id, functions_to_check): +# self.project_id = project_id +# self.functions_to_check = functions_to_check + +class APlanningV2: + def __init__(self, project): + self.project = project + self.taskmgr = AProjectTaskMgr() + self.scan_list_for_larget_context = [] + + async def get_all_business_flow(self, functions_to_check): + from library.sgp.utilities.contract_extractor import group_functions_by_contract + from library.sgp.utilities.contract_extractor import check_function_if_public_or_external + from library.sgp.utilities.contract_extractor import check_function_if_view_or_pure + + grouped_functions = group_functions_by_contract(functions_to_check) + contexts = self.identify_contexts(functions_to_check) + # 遍历grouped_functions,按每个合约代码进行业务流抽取 + all_business_flow = {} + all_business_flow_line={} + all_business_flow_context = {} + print("grouped contract count:",len(grouped_functions)) + + for contract_info in grouped_functions: + print("———————————————————————processing contract_info:",contract_info['contract_name'],"—————————————————————————") + contract_name = contract_info['contract_name'] + functions = contract_info['functions'] + contract_code_without_comments = contract_info['contract_code_without_comment'] # Assuming this is the correct key + + # 初始化合约名字典 + all_business_flow[contract_name] = {} + all_business_flow_line[contract_name]={} + all_business_flow_context[contract_name] = {} + + # New logic for determining function visibility + language_patterns = { + '.rust': lambda f: True, # No visibility filter for Rust + '.python': lambda f: True, # No visibility filter for Python + '.move': lambda f: f['visibility'] == 'public', + '.fr': lambda f: f['visibility'] == 'public', + '.java': lambda f: f['visibility'] in ['public', 'protected'], + '.cairo': lambda f: f['visibility'] == 'public', + '.tact': lambda f: f['visibility'] == 'public', + '.func': lambda f: f['visibility'] == 'public' + } + + def get_file_extension(funcs): + for func in funcs: + file_path = func['relative_file_path'] + for ext in language_patterns: + if file_path.endswith(ext): + return ext + return None + + file_ext = get_file_extension(functions) + visibility_filter = language_patterns.get(file_ext, lambda f: True) + + all_public_external_function_names = [ + function['name'].split(".")[1] for function in functions + if visibility_filter(function) + ] + + print("all_public_external_function_names count:",len(all_public_external_function_names)) + # if len(self.scan_list_for_larget_context)>0 and contract_name not in self.scan_list_for_larget_context: + # continue + # 有了函数名列表,有了contract_code_without_comments,可以进行业务流的GPT提问了 + print("-----------------asking openai for business flow-----------------") + for public_external_function_name in all_public_external_function_names: + # time.sleep(10) + print("***public_external_function_name***:",public_external_function_name) + if "_python" in str(contract_name) and len(all_public_external_function_names)==1: + key = all_public_external_function_names[0] + data = {key: all_public_external_function_names} + business_flow_list = json.dumps(data) + else: + try: + business_flow_list = self.ask_openai_for_business_flow(public_external_function_name, contract_code_without_comments) + except Exception as e: + business_flow_list=[] + if (not business_flow_list) or (len(business_flow_list)==0): + continue + # 返回一个list,这个list中包含着多条从public_external_function_name开始的业务流函数名 + try: + function_lists = self.extract_filtered_functions(business_flow_list) + # 判断function_lists中是否包含public_external_function_name,如果包含,则去掉 + if public_external_function_name in function_lists: + function_lists.remove(public_external_function_name) + except Exception as e: + print(e) + print("business_flow_list:",function_lists) + # 从functions_to_check中提取start_line和end_line行数 + # 然后将start_line和end_line行数对应的代码提取出来,放入all_business_flow_line + + def get_function_structure(functions, function_name): + for func in functions: + if func['name'] == function_name: + return func + return None + line_info_list = [] + for function in function_lists: + if str(function)=="-1": + continue + if isinstance(function, float): + continue + if contract_name is None: + print("contract_name is None") + function_name_to_search=contract_name+"."+function + function_structure=get_function_structure(functions, function_name_to_search) + if function_structure is not None: + start_line=function_structure['start_line'] + end_line=function_structure['end_line'] + line_info_list.append((start_line, end_line)) + + # 获取拼接后的业务流代码 + ask_business_flow_code = self.extract_and_concatenate_functions_content(function_lists, contract_info) + + # 在 contexts 中获取扩展后的业务流内容 + extended_flow_code = "" + for function in function_lists: + context = contexts.get(contract_name + "." + function, {}) + parent_calls = context.get("parent_calls", []) + sub_calls = context.get("sub_calls", []) + for call in parent_calls + sub_calls: + extended_flow_code += call["content"] + "\n" + all_business_flow_context[contract_name][public_external_function_name] = extended_flow_code.strip() + # 将结果存储为键值对,其中键是函数名,值是对应的业务流代码 + all_business_flow[contract_name][public_external_function_name] = ask_business_flow_code + all_business_flow_line[contract_name][public_external_function_name] = line_info_list + return all_business_flow,all_business_flow_line,all_business_flow_context + # 此时 all_business_flow 为一个字典,包含了每个合约及其对应的业务流 + + async def ask_openai_for_business_flow(self,function_name,contract_code_without_comment): + prompt=f""" + Based on the code above, analyze the business flows that start with the {function_name} function, consisting of multiple function calls. The analysis should adhere to the following requirements: + 1. only output the one sub-business flows, and must start from {function_name}. + 2. The output business flows should only involve the list of functions of the contract itself (ignoring calls to other contracts or interfaces, as well as events). + 3. After step-by-step analysis, output one result in JSON format, with the structure: {{"{function_name}":[function1,function2,function3....]}} + 4. The business flows must include all involved functions without any omissions + + """ + question=f""" + + {contract_code_without_comment} + \n + {prompt} + + """ + + oai = OpenAIModel(model_name="hf.co/unsloth/DeepSeek-R1-Distill-Qwen-14B-GGUF:Q8_0", base_url="http://127.0.0.1:11434/v1", api_key="k") + Agent(model=oai,model_settings=ModelSettings(max_tokens=32768), retries=3, result_type=json).run() + + + #return common_ask_for_json(question) + + async def extract_filtered_functions(self, json_string): + """ + Extracts function names from a JSON string. For function names and keys containing a period, + only the substring after the last period is included. The key is included as the first + element in the returned list, processed in the same way as the functions. + + :param json_string: A string representation of a JSON object. + :return: A list of the processed key followed by its corresponding filtered function names. + """ + # Load the JSON data into a Python dictionary + json_string=json_string.replace("```json",'"') + data = json.loads(json_string) + + # Initialize the result list + result_list = [] + + # Process each key-value pair in the dictionary + for key, functions in data.items(): + # Process the key in the same way as function names + result_list.append(key) + + # Extend the list with filtered function names + filtered_functions = [function for function in functions] + result_list.extend(filtered_functions) + + # Remove duplicates by converting to a set and back to a list + return list(set(result_list)) + async def extract_and_concatenate_functions_content(self,function_lists, contract_info): + """ + Extracts the content of functions based on a given function list and contract info, + and concatenates them into a single string. + + :param function_lists: A list of function names. + :param contract_info: A dictionary representing a single contract's information, including its functions. + :return: A string that concatenates all the function contents from the function list. + """ + concatenated_content = "" + + # Get the list of functions from the contract info + functions = contract_info.get("functions", []) + + # Create a dictionary for quick access to functions by name + function_dict = {str(function["name"]).split(".")[1]: function for function in functions} + + # Loop through each function name in the provided function list + for function_name in function_lists: + # Find the function content by name + function_content = function_dict.get(function_name, {}).get("content") + + # If function content is found, append it to the concatenated_content string + if function_content is not None: + concatenated_content += function_content + "\n" + + return concatenated_content.strip() + async def extract_results(self,text): + if text is None: + return [] + # 定义一个正则表达式来匹配包含关键字 "result" 的JSON对象 + regex = r'\{.*?\}' + + # 使用正则表达式查找所有匹配项 + matches = re.findall(regex, text) + + # 解析找到的每个匹配项 + json_objects = [] + for match in matches: + try: + json_obj = json.loads(match) + json_objects.append(json_obj) + except json.JSONDecodeError: + pass # 在这里可以处理JSON解析错误 + + return json_objects + # Function to merge two rulesets based on sim_score + async def merge_and_sort_rulesets(self,high, medium): + # Combine the two rulesets + # combined_ruleset = high # only high + combined_ruleset = high + medium + # Sort the combined ruleset based on sim_score in descending order + combined_ruleset.sort(key=lambda x: x['sim_score'], reverse=True) + return combined_ruleset + async def decode_business_flow_list_from_response(self, response): + # 正则表达式用于匹配形如 {xxxx:[]} 的结果 + pattern = r'({\s*\"[a-zA-Z0-9_]+\"\s*:\s*\[[^\]]*\]\s*})' + + # 使用正则表达式找到所有匹配项 + matches = re.findall(pattern, response) + + # 初始化一个集合用于去重 + unique_functions = set() + + # 遍历所有匹配项 + for match in matches: + # 尝试将匹配的字符串转换为JSON对象 + try: + json_obj = json.loads(match) + # 遍历JSON对象中的所有键(即函数名) + for key in json_obj: + # 将键(函数名)添加到集合中去重 + unique_functions.add(key) + # 遍历对应的值(即函数列表),并将它们也添加到集合中去重 + for function in json_obj[key]: + unique_functions.add(function) + except json.JSONDecodeError: + # 如果匹配的字符串不是有效的JSON格式,则忽略错误 + pass + + # 将集合转换为列表并返回 + return list(unique_functions) + async def identify_contexts(self, functions_to_check): + """ + Identify sub-calls and parent-calls for each function in functions_to_check, + only including calls that are not in the same contract. + Returns a dictionary with function names as keys and their sub-calls and parent-calls as values, + including the content of the sub-calls and parent-calls. + """ + contexts = {} + calls = {function["name"]: {"sub_calls": set(), "parent_calls": set()} for function in functions_to_check} + + for function in functions_to_check: + function_name = function["name"] + function_content = function["content"] + function_contract_name = function["contract_name"] + + for other_function in functions_to_check: + other_function_name = other_function["name"] + other_function_content = other_function["content"] + other_function_contract_name = other_function["contract_name"] + + # Check if the other function is not in the same contract + if function_contract_name != other_function_contract_name: + if function_name.split(".")[1] in other_function_content: + calls[function_name]["parent_calls"].add((other_function_name, other_function_content)) + + if other_function_name.split(".")[1] in function_content: + calls[function_name]["sub_calls"].add((other_function_name, other_function_content)) + + for function_name, call_data in calls.items(): + contexts[function_name] = { + "sub_calls": [{"name": name, "content": content} for name, content in call_data["sub_calls"]], + "parent_calls": [{"name": name, "content": content} for name, content in call_data["parent_calls"]] + } + + return contexts + + + async def search_business_flow(self, all_business_flow, all_business_flow_line, all_business_flow_context, function_name, contract_name): + """ + Search for the business flow code based on a function name and contract name. + + :param all_business_flow: The dictionary containing all business flows. + :param function_name: The name of the function to search for. + :param contract_name: The name of the contract where the function is located. + :return: The business flow code if found, or a message indicating it doesn't exist. + """ + # Check if the contract_name exists in the all_business_flow dictionary + if contract_name in all_business_flow: + # Check if the function_name exists within the nested dictionary for the contract + contract_flows = all_business_flow[contract_name] + contract_flows_line = all_business_flow_line[contract_name] + contract_flows_context = all_business_flow_context[contract_name] + if function_name in contract_flows: + # Return the business flow code for the function + return contract_flows[function_name], contract_flows_line[function_name], contract_flows_context[function_name] + else: + # Function name not found within the contract's business flows + return "not found", "", "" + else: + # Contract name not found in the all_business_flow dictionary + return "not found", "", "" + + async def common_ask_for_json(self, prompt): + # Simulate an asynchronous API call to get JSON response + await asyncio.sleep(0.1) + return '{"business_types": ["type1", "type2"]}' # Example JSON response + + + async def do_planning(self): + tasks = [] + print("Begin do planning...") + switch_function_code = eval(os.environ.get('SWITCH_FUNCTION_CODE', 'False')) + switch_business_code = eval(os.environ.get('SWITCH_BUSINESS_CODE', 'True')) + tasks = await self.taskmgr.get_task_list_by_id(self.project.project_id) + if len(tasks) > 0: + return + + # Filter all "test" function + functions_to_check = [f for f in self.project.functions_to_check if "test" not in f['name']] + self.project.functions_to_check = functions_to_check + + if switch_business_code: + all_business_flow, all_business_flow_line, all_business_flow_context = await self.get_all_business_flow(self.project.functions_to_check) + + # Process each function with optimized threshold + for function in tqdm(self.project.functions_to_check, desc="Finding project rules"): + name = function['name'] + content = function['content'] + contract_code = function['contract_code'] + contract_name = function['contract_name'] + + print(f"————————Processing function: {name}————————") + + if switch_business_code: + business_flow_code, line_info_list, other_contract_context = await self.search_business_flow(all_business_flow, all_business_flow_line, all_business_flow_context, name.split(".")[1], contract_name) + print(f"[DEBUG] 获取到的业务流代码长度: {len(business_flow_code) if business_flow_code else 0}") + print(f"[DEBUG] 获取到的其他合约上下文长度: {len(other_contract_context) if other_contract_context else 0}") + + type_check_prompt = '''分析以下智能合约代码,判断它属于哪些业务类型。可能的类型包括: + chainlink, dao, inline assembly, lending, liquidation, liquidity manager, signature, slippage, univ3, other + + 请以JSON格式返回结果,格式为:{{"business_types": ["type1", "type2"]}} + + 代码: + {0} + ''' + + try: + formatted_prompt = type_check_prompt.format(business_flow_code + "\n" + other_contract_context + "\n" + content) + type_response = await self.common_ask_for_json(formatted_prompt) + print(f"[DEBUG] Claude返回的响应: {type_response}") + + # More rigorous response cleaning + cleaned_response = type_response.strip() + cleaned_response = cleaned_response.replace("```json", "").replace("```", "") + cleaned_response = cleaned_response.replace("\n", "").replace(" ", "") + cleaned_response = cleaned_response.strip() + + # Ensure response is valid JSON format + if not cleaned_response.startswith("{"): + cleaned_response = "{" + cleaned_response + if not cleaned_response.endswith("}"): + cleaned_response = cleaned_response + "}" + + print(f"[DEBUG] 清理后的响应: {cleaned_response}") + + type_data = json.loads(cleaned_response) + business_type = type_data.get('business_types', ['other']) + print(f"[DEBUG] 解析出的业务类型: {business_type}") + + # Defensive logic: Ensure business_type is list type + if not isinstance(business_type, list): + business_type = [str(business_type)] + + # Handle 'other' case + if 'other' in business_type and len(business_type) > 1: + business_type.remove('other') + + # Ensure list is not empty + if not business_type: + business_type = ['other'] + + business_type_str = ','.join(str(bt) for bt in business_type) + print(f"[DEBUG] 最终的业务类型字符串: {business_type_str}") + + except json.JSONDecodeError as e: + print(f"[ERROR] JSON解析失败: {str(e)}") + print(f"[ERROR] 原始响应: {type_response}") + business_type = ['other'] + business_type_str = 'other' + except Exception as e: + print(f"[ERROR] 处理业务类型时发生错误: {str(e)}") + business_type = ['other'] + business_type_str = 'other' + + if business_flow_code != "not found": + for i in range(int(os.environ.get('BUSINESS_FLOW_COUNT', 1))): + task = AProject_Task( + project_id=self.project.project_id, + name=name, + content=content, + keyword=str(random.random()), + business_type='', + sub_business_type='', + function_type='', + rule='', + result='', + result_gpt4='', + score='', + category='', + contract_code=contract_code, + risklevel='', + similarity_with_rule='', + description='', + start_line=function['start_line'], + end_line=function['end_line'], + relative_file_path=function['relative_file_path'], + absolute_file_path=function['absolute_file_path'], + recommendation=business_type_str, # Save converted string + title='', + business_flow_code=str(business_flow_code) + "\n" + str(content), + business_flow_lines=line_info_list, + business_flow_context=other_contract_context, + if_business_flow_scan=1 # Indicating scanned using business flow code + ) + await self.taskmgr.add_task_in_one(task) + tasks.append(task) + + if switch_function_code: + for i in range(int(os.environ.get('BUSINESS_FLOW_COUNT', 1))): + task = AProject_Task( + project_id=self.project.project_id, + name=name, + content=content, + keyword=str(random.random()), + business_type='', + sub_business_type='', + function_type='', + rule='', + result='', + result_gpt4='', + score='', + category='', + contract_code=contract_code, + risklevel='', + similarity_with_rule='', + description='', + start_line=function['start_line'], + end_line=function['end_line'], + relative_file_path=function['relative_file_path'], + absolute_file_path=function['absolute_file_path'], + recommendation='', + title='', + business_flow_code='', + business_flow_lines='', + business_flow_context='', + if_business_flow_scan=0 # Indicating scanned using function code + ) + await self.taskmgr.add_task_in_one(task) + tasks.append(task) + + return tasks + +# # Example usage +# async def main(): +# functions_to_check = [ +# { +# 'name': 'function1', +# 'content': 'some content', +# 'contract_code': 'some_contract_code', +# 'contract_name': 'some_contract_name', +# 'start_line': 1, +# 'end_line': 10, +# 'relative_file_path': 'path/to/file', +# 'absolute_file_path': '/absolute/path/to/file' +# }, +# { +# 'name': 'function2.test', +# 'content': 'some test content', +# 'contract_code': 'some_contract_code', +# 'contract_name': 'some_contract_name', +# 'start_line': 1, +# 'end_line': 10, +# 'relative_file_path': 'path/to/file', +# 'absolute_file_path': '/absolute/path/to/file' +# } +# ] +# project = AProject(project_id=1, functions_to_check=functions_to_check) +# aplanning = APlanningV2(project) +# tasks = await aplanning.do_planning() +# print(f"Tasks created: {len(tasks)}") + +# if __name__ == "__main__": +# asyncio.run(main()) \ No newline at end of file diff --git a/src/project/aproject_audit.py b/src/project/aproject_audit.py new file mode 100644 index 00000000..9737bb06 --- /dev/null +++ b/src/project/aproject_audit.py @@ -0,0 +1,154 @@ +import csv +from typing import Any, Dict, Optional, Tuple, Union, List, Set +from os import path +import asyncio +from nodes_config import nodes_config +from library.parsing.callgraph import CallGraph +from aproject_parser import aparse_project, ABaseProjectFilter +import re +from library.sgp.utilities.contract_extractor import extract_state_variables_from_code, extract_state_variables_from_code_move + +__all__ = ('ProjectAudit') + +class AProjectAudit(object): + def __init__(self, config: nodes_config) -> None: + self.config: nodes_config = config + self.project_id: str = config.id + self.project_path: str = config.base_dir + self.cg = CallGraph(root=path.join(config.base_dir, config.src_dir)) + + self.functions_to_check: list = [] + self.functions: list = [] + self.tasks: list = [] + self.taskkeys: set = set() + + async def analyze_function_relationships(self, functions_to_check: List[Dict]) -> Tuple[Dict[str, Dict[str, Set]], Dict[str, Dict]]: + # Construct a mapping and calling relationship dictionary from function name to function information + func_map = {} + relationships = {'upstream': {}, 'downstream': {}} + for idx, func in enumerate(functions_to_check): + func_name = func['name'].split('.')[-1] + func['func_name'] = func_name + func_map[func_name] = { + 'index': idx, + 'data': func + } + + # Analyze the calling relationship of each function + for idx, func in enumerate(functions_to_check): + func_name = func['name'].split('.')[-1] + content = func['content'].lower() + + if func_name not in relationships['upstream']: + relationships['upstream'][func_name] = set() + if func_name not in relationships['downstream']: + relationships['downstream'][func_name] = set() + + # Check whether other functions call the current function + for other_func in functions_to_check: + if other_func == func: + continue + other_name = other_func['name'].split('.')[-1] + other_content = other_func['content'].lower() + + # If other functions call the current function + if re.search(r'\b' + re.escape(func_name.lower()) + r'\b', other_content): + relationships['upstream'][func_name].add(other_name) + if other_name not in relationships['downstream']: + relationships['downstream'][other_name] = set() + relationships['downstream'][other_name].add(func_name) + + # If the current function calls other functions + if re.search(r'\b' + re.escape(other_name.lower()) + r'\b', content): + relationships['downstream'][func_name].add(other_name) + if other_name not in relationships['upstream']: + relationships['upstream'][other_name] = set() + relationships['upstream'][other_name].add(func_name) + + return relationships, func_map + + async def build_call_tree(self, func_name: str, relationships: Dict[str, Dict[str, Set]], direction: str, func_map: Dict[str, Dict], visited: Optional[Set[str]] = None) -> Optional[Dict[str, Any]]: + if visited is None: + visited = set() + + if func_name in visited: + return None + + visited.add(func_name) + + # 获取函数完整信息 + func_info = func_map.get(func_name, {'index': -1, 'data': None}) + node = { + 'name': func_name, + 'index': func_info['index'], + 'function_data': func_info['data'], # 包含完整的函数信息 + 'children': [] + } + + # 获取该方向上的所有直接调用 + related_funcs = relationships[direction].get(func_name, set()) + + # 递归构建每个相关函数的调用树 + for related_func in related_funcs: + child_tree: Optional[Dict[str, Any]] = await self.build_call_tree(related_func, relationships, direction, func_map, visited.copy()) + if child_tree: + node['children'].append(child_tree) + + return node + + def print_call_tree(self, node: Dict[str, Any], level: int = 0, prefix: str = ''): + if not node: + return + + # 打印当前节点的基本信息 + func_data = node['function_data'] + if func_data: + print(f"{prefix}{'└─' if level > 0 else ''}{node['name']} (index: {node['index']}, " + f"lines: {func_data['start_line']}-{func_data['end_line']})") + else: + print(f"{prefix}{'└─' if level > 0 else ''}{node['name']} (index: {node['index']})") + + # 打印子节点 + for i, child in enumerate(node['children']): + is_last = i == len(node['children']) - 1 + new_prefix = prefix + (' ' if level == 0 else '│ ' if not is_last else ' ') + self.print_call_tree(child, level + 1, new_prefix + ('└─' if is_last else '├─')) + + async def parse(self, white_files: List[str], white_functions: List[str]) -> None: + parser_filter = ABaseProjectFilter(white_files, white_functions) + functions, functions_to_check = await parse_project(self.project_path, parser_filter) + self.functions = functions + self.functions_to_check = functions_to_check + + relationships: Dict[str, Dict] + func_map: Dict + + # 分析函数关系 + relationships, func_map = await self.analyze_function_relationships(functions_to_check) + + # 为每个函数构建并打印调用树 + call_trees: List[Dict] = [] + for func in functions_to_check: + func_name = func['name'].split('.')[-1] + + upstream_tree = await self.build_call_tree(func_name, relationships, 'upstream', func_map) + downstream_tree = await self.build_call_tree(func_name, relationships, 'downstream', func_map) + + state_variables: List[str] = [] + if func['relative_file_path'].endswith('.move'): + state_variables = extract_state_variables_from_code_move(func['contract_code'], func['relative_file_path']) + if func['relative_file_path'].endswith('.sol') or func['relative_file_path'].endswith('.fr'): + state_variables = extract_state_variables_from_code(func['contract_code']) + + state_variables_text = '\n'.join(state_variables) if state_variables else '' + call_trees.append({ + 'function': func_name, + 'upstream_tree': upstream_tree, + 'downstream_tree': downstream_tree, + 'state_variables': state_variables_text + }) + + self.call_trees: List[Dict] = call_trees + + def get_function_names(self) -> Set[str]: + return set([function['name'] for function in self.functions]) \ No newline at end of file diff --git a/src/project/aproject_parser.py b/src/project/aproject_parser.py new file mode 100644 index 00000000..327c84f9 --- /dev/null +++ b/src/project/aproject_parser.py @@ -0,0 +1,131 @@ +import os +from typing import Any, Dict, Tuple, Union, List, Set +from os import path +import asyncio + +async def parse_project_async(project_path: str, project_filter=None): + if project_filter is None: + project_filter = ABaseProjectFilter([], []) + + ignore_folders = set() + if os.environ.get('IGNORE_FOLDERS'): + ignore_folders = set(os.environ.get('IGNORE_FOLDERS').split(',')) + ignore_folders.add('.git') + all_results = [] + + async def walk_and_parse(dirpath: str, dirs: List[str], files: List[str]): + dirs[:] = [d for d in dirs if d not in ignore_folders] + for file in files: + to_scan = not await project_filter.filter_file_async(dirpath, file) + sol_file = os.path.join(dirpath, file) # relative path + absolute_path = os.path.abspath(sol_file) # absolute path + print("parsing file: ", sol_file, " " if to_scan else "[skipped]") + + if to_scan: + results = await get_antlr_parsing_async(sol_file) + for result in results: + result['relative_file_path'] = sol_file + result['absolute_file_path'] = absolute_path + all_results.extend(results) + + tasks = [] + for dirpath, dirs, files in os.walk(project_path): + tasks.append(walk_and_parse(dirpath, dirs, files)) + + await asyncio.gather(*tasks) + + functions = [result for result in all_results if result['type'] == 'FunctionDefinition'] + # fix func name + fs = [] + for func in functions: + if func['name'][8:] != "tor": + name = func['name'][8:] # remove SPECIAL_ Prefix, I forgot the specific reason, it seems to be to consider a specific function name + else: + name = "constructor" + func['name'] = "%s.%s" % (func['contract_name'], name) + fs.append(func) + + fs_filtered = fs[:] + # 2. filter contract + fs_filtered = [func for func in fs_filtered if not await project_filter.filter_contract_async(func)] + + # 3. filter functions + fs_filtered = [func for func in fs_filtered if not await project_filter.filter_functions_async(func)] + + return fs, fs_filtered + +class ABaseProjectFilter(object): + def __init__(self, white_files=[], white_functions=[]): + self.white_files = white_files + self.white_functions = white_functions + + async def filter_file_async(self, path: str, filename: str) -> bool: + # 检查文件后缀 + valid_extensions = ('.sol', '.rs', '.py', '.move', '.cairo', '.tact', '.fc', '.fr', '.java') + if not any(filename.endswith(ext) for ext in valid_extensions) or filename.endswith('.t.sol'): + return True + + # 如果白名单不为空,检查文件是否在白名单中 + if len(self.white_files) > 0: + return not any(os.path.basename(filename) in white_file for white_file in self.white_files) + + return False + + async def filter_contract_async(self, function: Dict[str, Any]) -> bool: + # rust情况下,不进行筛选 + if '_rust' in function["name"]: + return False + if '_python' in function["name"]: + return False + if '_move' in function["name"]: + return False + if '_cairo' in function["name"]: + return False + if '_tact' in function["name"]: + return False + if '_func' in function["name"]: + return False + if '_fa' in function["name"]: + return False + + # solidity情况下,进行筛选 + if str(function["contract_name"]).startswith("I") and function["contract_name"][1].isupper(): + print("function ", function['name'], " skipped for interface contract") + return True + if "test" in str(function["name"]).lower(): + print("function ", function['name'], " skipped for test function") + return True + + if "function init" in str(function["content"]).lower() or "function initialize" in str(function["content"]).lower() or "constructor(" in str(function["content"]).lower() or "receive()" in str(function["content"]).lower() or "fallback()" in str(function["content"]).lower(): + print("function ", function['name'], " skipped for constructor") + return True + + return False + + async def filter_functions_async(self, function: Dict[str, Any]) -> bool: + # Step 3: function 筛选 ( 白名单检查 ) + if len(self.white_functions) == 0: + return False + return function['name'] not in self.white_functions + +async def get_antlr_parsing_async(sol_file: str) -> List[Dict[str, Any]]: + # Simulate asynchronous parsing process + await asyncio.sleep(1) # Placeholder for actual async operation + return [{"type": "FunctionDefinition", "contract_name": "example", "name": "exampleFunc"}] # TODO!! + +async def main(): + from library.dataset_utils import load_dataset_async + dataset_base = "../../dataset/agent-v1-c4" + projects = await load_dataset_async(dataset_base) + project = projects['whalefall'] + + project_path = os.path.join(project['base_path'], project['path']) + white_files, white_functions = project.get('files', []), project.get('functions', []) + + parser_filter = ABaseProjectFilter(white_files, white_functions) + functions, functions_to_check = await parse_project_async(project_path, parser_filter) + + print(functions_to_check) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/src/res_processor/ares_processor.py b/src/res_processor/ares_processor.py new file mode 100644 index 00000000..45fb804c --- /dev/null +++ b/src/res_processor/ares_processor.py @@ -0,0 +1,149 @@ +import pandas as pd +from tqdm import tqdm +import json +from openai_api.openai import ask_claude, common_ask_for_json +import asyncio +import aiohttp + +class ResProcessor: + def __init__(self, df): + self.df = df + + async def process(self): + self.df['flow_code_len'] = self.df['业务流程代码'].str.len() + grouped = list(self.df.groupby('业务流程代码')) + tasks = [] + + for flow_code, group in grouped: + task = asyncio.create_task(self._process_group(flow_code, group)) + tasks.append(task) + + processed_results = await asyncio.gather(*tasks) + new_df = pd.DataFrame(processed_results) + + if 'flow_code_len' in new_df.columns: + new_df = new_df.drop('flow_code_len', axis=1) + + original_columns = [col for col in self.df.columns if col != 'flow_code_len'] + new_df = new_df[original_columns] + + return new_df + + async def _process_group(self, flow_code, group): + if len(group) <= 1: + return await self._process_single_vulnerability(group.iloc[0]) + return await self._merge_vulnerabilities(group) + + async def _process_single_vulnerability(self, row): + translate_prompt = f"""请对以下漏洞描述翻译,用中文输出 +原漏洞描述: +{row['漏洞结果']} +""" + translated_description = await ask_claude(translate_prompt) + return { + '漏洞结果': translated_description, + 'ID': row['ID'], + '项目名称': row['项目名称'], + '合同编号': row['合同编号'], + 'UUID': row['UUID'], + '函数名称': row['函数名称'], + '函数代码': row['函数代码'], + '开始行': row['开始行'], + '结束行': row['结束行'], + '相对路径': row['相对路径'], + '绝对路径': row['绝对路径'], + '业务流程代码': row['业务流程代码'], + '业务流程行': row['业务流程行'], + '业务流程上下文': row['业务流程上下文'], + '确认结果': row['确认结果'], + '确认细节': row['确认细节'] + } + + async def _merge_vulnerabilities(self, group): + base_info = group.iloc[0].copy() + merge_prompt = """ +合并一下这几个漏洞,将相同或本质相似漏洞合并到一起,用中文输出,如果存在多个不同漏洞,则分开描述, +但要保证如下:1. 合并的结果相比于原来的漏洞结果不能有任何信息或漏洞缺失,且合并的相同或本质相似的漏洞描述要全面不能有遗漏; +2. 每个合并后的漏洞描述必须非常详细,不能少于600个字用原来的原文翻译来描述漏洞,不能有任何表述遗漏,否则你会受到惩罚, +输出格式如下,必须严格遵循格式输出 +{ +"merged_vulnerabilities": [ +{"description": "合并后的漏洞1描述"}, +{"description": "合并后的漏洞2描述"}, +... +] +} +""" + for _, row in group.iterrows(): + merge_prompt += f"漏洞结果:{row['漏洞结果']}\n" + merge_prompt += "---\n" + + merged_result = await common_ask_for_json(merge_prompt) + try: + merged_data = json.loads(merged_result) + vulnerabilities = merged_data.get('merged_vulnerabilities', []) + if not vulnerabilities: + return { + '漏洞结果': merged_result, + 'ID': base_info['ID'], + '项目名称': base_info['项目名称'], + '合同编号': base_info['合同编号'], + 'UUID': base_info['UUID'], + '函数名称': base_info['函数名称'], + '函数代码': row['函数代码'], + '开始行': min(group['开始行']), + '结束行': max(group['结束行']), + '相对路径': base_info['相对路径'], + '绝对路径': base_info['绝对路径'], + '业务流程代码': base_info['业务流程代码'], + '业务流程行': base_info['业务流程行'], + '业务流程上下文': base_info['业务流程上下文'], + '确认结果': base_info['确认结果'], + '确认细节': base_info['确认细节'] + } + results = [] + for vuln in vulnerabilities: + results.append({ + '漏洞结果': vuln['description'], + 'ID': base_info['ID'], + '项目名称': base_info['项目名称'], + '合同编号': base_info['合同编号'], + 'UUID': base_info['UUID'], + '函数名称': base_info['函数名称'], + '函数代码': row['函数代码'], + '开始行': min(group['开始行']), + '结束行': max(group['结束行']), + '相对路径': base_info['相对路径'], + '绝对路径': base_info['绝对路径'], + '业务流程代码': base_info['业务流程代码'], + '业务流程行': base_info['业务流程行'], + '业务流程上下文': base_info['业务流程上下文'], + '确认结果': base_info['确认结果'], + '确认细节': base_info['确认细节'] + }) + return results + except json.JSONDecodeError: + # Fallback in case of JSON parsing error + return { + '漏洞结果': merged_result, + 'ID': base_info['ID'], + '项目名称': base_info['项目名称'], + '合同编号': base_info['合同编号'], + 'UUID': base_info['UUID'], + '函数名称': base_info['函数名称'], + '函数代码': row['函数代码'], + '开始行': min(group['开始行']), + '结束行': max(group['结束行']), + '相对路径': base_info['相对路径'], + '绝对路径': base_info['绝对路径'], + '业务流程代码': base_info['业务流程代码'], + '业务流程行': base_info['业务流程行'], + '业务流程上下文': base_info['业务流程上下文'], + '确认结果': base_info['确认结果'], + '确认细节': base_info['确认细节'] + } + + def _clean_text(self, text): + if pd.isna(text): + return '' + return str(text).strip() \ No newline at end of file diff --git a/src/root_service.py b/src/root_service.py new file mode 100644 index 00000000..08ffe858 --- /dev/null +++ b/src/root_service.py @@ -0,0 +1,22 @@ +import os +import subprocess + + + +def main(): + # Set the PYTHONPATH environment variable + project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + python_path = os.environ.get('PYTHONPATH', '') + print(python_path) + if project_root not in python_path.split(os.pathsep): + new_python_path = f"{project_root}:{python_path}" + os.environ['PYTHONPATH'] = new_python_path + print(f"Updated PYTHONPATH: {new_python_path}") + else: + print("PYTHONPATH is already set correctly.") + print(new_python_path) + from run import run + subprocess.run("run.py") + +if __name__ == "__main__": + main() diff --git a/uv.lock b/uv.lock deleted file mode 100644 index d0fec490..00000000 --- a/uv.lock +++ /dev/null @@ -1,2024 +0,0 @@ -version = 1 -revision = 1 -requires-python = ">=3.10" -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version < '3.11'", -] - -[options] -prerelease-mode = "allow" - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, -] - -[[package]] -name = "anthropic" -version = "0.45.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/15/74/2b2485fc120da834c0c5be07462541ec082e9fa8851d845f2587e480535a/anthropic-0.45.2.tar.gz", hash = "sha256:32a18b9ecd12c91b2be4cae6ca2ab46a06937b5aa01b21308d97a6d29794fb5e", size = 200901 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/74/86/e81814e542d1eaeec84d2312bec93a99b9ef1d78d9bfae1fc5dd74abdf15/anthropic-0.45.2-py3-none-any.whl", hash = "sha256:ecd746f7274451dfcb7e1180571ead624c7e1195d1d46cb7c70143d2aedb4d35", size = 222797 }, -] - -[[package]] -name = "antlr4-python3-runtime" -version = "4.13.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/33/5f/2cdf6f7aca3b20d3f316e9f505292e1f256a32089bd702034c29ebde6242/antlr4_python3_runtime-4.13.2.tar.gz", hash = "sha256:909b647e1d2fc2b70180ac586df3933e38919c85f98ccc656a96cd3f25ef3916", size = 117467 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/03/a851e84fcbb85214dc637b6378121ef9a0dd61b4c65264675d8a5c9b1ae7/antlr4_python3_runtime-4.13.2-py3-none-any.whl", hash = "sha256:fe3835eb8d33daece0e799090eda89719dbccee7aa39ef94eed3818cafa5a7e8", size = 144462 }, -] - -[[package]] -name = "anyio" -version = "4.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "sniffio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/73/199a98fc2dae33535d6b8e8e6ec01f8c1d76c9adb096c6b7d64823038cde/anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", size = 181126 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 }, -] - -[[package]] -name = "asttokens" -version = "2.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/45/1d/f03bcb60c4a3212e15f99a56085d93093a497718adf828d050b9d675da81/asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0", size = 62284 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/86/4736ac618d82a20d87d2f92ae19441ebc7ac9e7a581d7e58bbe79233b24a/asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24", size = 27764 }, -] - -[[package]] -name = "async-timeout" -version = "5.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233 }, -] - -[[package]] -name = "asyncpg" -version = "0.30.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "async-timeout", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/07/1650a8c30e3a5c625478fa8aafd89a8dd7d85999bf7169b16f54973ebf2c/asyncpg-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfb4dd5ae0699bad2b233672c8fc5ccbd9ad24b89afded02341786887e37927e", size = 673143 }, - { url = "https://files.pythonhosted.org/packages/a0/9a/568ff9b590d0954553c56806766914c149609b828c426c5118d4869111d3/asyncpg-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc1f62c792752a49f88b7e6f774c26077091b44caceb1983509edc18a2222ec0", size = 645035 }, - { url = "https://files.pythonhosted.org/packages/de/11/6f2fa6c902f341ca10403743701ea952bca896fc5b07cc1f4705d2bb0593/asyncpg-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3152fef2e265c9c24eec4ee3d22b4f4d2703d30614b0b6753e9ed4115c8a146f", size = 2912384 }, - { url = "https://files.pythonhosted.org/packages/83/83/44bd393919c504ffe4a82d0aed8ea0e55eb1571a1dea6a4922b723f0a03b/asyncpg-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7255812ac85099a0e1ffb81b10dc477b9973345793776b128a23e60148dd1af", size = 2947526 }, - { url = "https://files.pythonhosted.org/packages/08/85/e23dd3a2b55536eb0ded80c457b0693352262dc70426ef4d4a6fc994fa51/asyncpg-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:578445f09f45d1ad7abddbff2a3c7f7c291738fdae0abffbeb737d3fc3ab8b75", size = 2895390 }, - { url = "https://files.pythonhosted.org/packages/9b/26/fa96c8f4877d47dc6c1864fef5500b446522365da3d3d0ee89a5cce71a3f/asyncpg-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c42f6bb65a277ce4d93f3fba46b91a265631c8df7250592dd4f11f8b0152150f", size = 3015630 }, - { url = "https://files.pythonhosted.org/packages/34/00/814514eb9287614188a5179a8b6e588a3611ca47d41937af0f3a844b1b4b/asyncpg-0.30.0-cp310-cp310-win32.whl", hash = "sha256:aa403147d3e07a267ada2ae34dfc9324e67ccc4cdca35261c8c22792ba2b10cf", size = 568760 }, - { url = "https://files.pythonhosted.org/packages/f0/28/869a7a279400f8b06dd237266fdd7220bc5f7c975348fea5d1e6909588e9/asyncpg-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb622c94db4e13137c4c7f98834185049cc50ee01d8f657ef898b6407c7b9c50", size = 625764 }, - { url = "https://files.pythonhosted.org/packages/4c/0e/f5d708add0d0b97446c402db7e8dd4c4183c13edaabe8a8500b411e7b495/asyncpg-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e0511ad3dec5f6b4f7a9e063591d407eee66b88c14e2ea636f187da1dcfff6a", size = 674506 }, - { url = "https://files.pythonhosted.org/packages/6a/a0/67ec9a75cb24a1d99f97b8437c8d56da40e6f6bd23b04e2f4ea5d5ad82ac/asyncpg-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915aeb9f79316b43c3207363af12d0e6fd10776641a7de8a01212afd95bdf0ed", size = 645922 }, - { url = "https://files.pythonhosted.org/packages/5c/d9/a7584f24174bd86ff1053b14bb841f9e714380c672f61c906eb01d8ec433/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c198a00cce9506fcd0bf219a799f38ac7a237745e1d27f0e1f66d3707c84a5a", size = 3079565 }, - { url = "https://files.pythonhosted.org/packages/a0/d7/a4c0f9660e333114bdb04d1a9ac70db690dd4ae003f34f691139a5cbdae3/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3326e6d7381799e9735ca2ec9fd7be4d5fef5dcbc3cb555d8a463d8460607956", size = 3109962 }, - { url = "https://files.pythonhosted.org/packages/3c/21/199fd16b5a981b1575923cbb5d9cf916fdc936b377e0423099f209e7e73d/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51da377487e249e35bd0859661f6ee2b81db11ad1f4fc036194bc9cb2ead5056", size = 3064791 }, - { url = "https://files.pythonhosted.org/packages/77/52/0004809b3427534a0c9139c08c87b515f1c77a8376a50ae29f001e53962f/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc6d84136f9c4d24d358f3b02be4b6ba358abd09f80737d1ac7c444f36108454", size = 3188696 }, - { url = "https://files.pythonhosted.org/packages/52/cb/fbad941cd466117be58b774a3f1cc9ecc659af625f028b163b1e646a55fe/asyncpg-0.30.0-cp311-cp311-win32.whl", hash = "sha256:574156480df14f64c2d76450a3f3aaaf26105869cad3865041156b38459e935d", size = 567358 }, - { url = "https://files.pythonhosted.org/packages/3c/0a/0a32307cf166d50e1ad120d9b81a33a948a1a5463ebfa5a96cc5606c0863/asyncpg-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:3356637f0bd830407b5597317b3cb3571387ae52ddc3bca6233682be88bbbc1f", size = 629375 }, - { url = "https://files.pythonhosted.org/packages/4b/64/9d3e887bb7b01535fdbc45fbd5f0a8447539833b97ee69ecdbb7a79d0cb4/asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e", size = 673162 }, - { url = "https://files.pythonhosted.org/packages/6e/eb/8b236663f06984f212a087b3e849731f917ab80f84450e943900e8ca4052/asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a", size = 637025 }, - { url = "https://files.pythonhosted.org/packages/cc/57/2dc240bb263d58786cfaa60920779af6e8d32da63ab9ffc09f8312bd7a14/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3", size = 3496243 }, - { url = "https://files.pythonhosted.org/packages/f4/40/0ae9d061d278b10713ea9021ef6b703ec44698fe32178715a501ac696c6b/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737", size = 3575059 }, - { url = "https://files.pythonhosted.org/packages/c3/75/d6b895a35a2c6506952247640178e5f768eeb28b2e20299b6a6f1d743ba0/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a", size = 3473596 }, - { url = "https://files.pythonhosted.org/packages/c8/e7/3693392d3e168ab0aebb2d361431375bd22ffc7b4a586a0fc060d519fae7/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af", size = 3641632 }, - { url = "https://files.pythonhosted.org/packages/32/ea/15670cea95745bba3f0352341db55f506a820b21c619ee66b7d12ea7867d/asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e", size = 560186 }, - { url = "https://files.pythonhosted.org/packages/7e/6b/fe1fad5cee79ca5f5c27aed7bd95baee529c1bf8a387435c8ba4fe53d5c1/asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305", size = 621064 }, - { url = "https://files.pythonhosted.org/packages/3a/22/e20602e1218dc07692acf70d5b902be820168d6282e69ef0d3cb920dc36f/asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70", size = 670373 }, - { url = "https://files.pythonhosted.org/packages/3d/b3/0cf269a9d647852a95c06eb00b815d0b95a4eb4b55aa2d6ba680971733b9/asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3", size = 634745 }, - { url = "https://files.pythonhosted.org/packages/8e/6d/a4f31bf358ce8491d2a31bfe0d7bcf25269e80481e49de4d8616c4295a34/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33", size = 3512103 }, - { url = "https://files.pythonhosted.org/packages/96/19/139227a6e67f407b9c386cb594d9628c6c78c9024f26df87c912fabd4368/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4", size = 3592471 }, - { url = "https://files.pythonhosted.org/packages/67/e4/ab3ca38f628f53f0fd28d3ff20edff1c975dd1cb22482e0061916b4b9a74/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4", size = 3496253 }, - { url = "https://files.pythonhosted.org/packages/ef/5f/0bf65511d4eeac3a1f41c54034a492515a707c6edbc642174ae79034d3ba/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba", size = 3662720 }, - { url = "https://files.pythonhosted.org/packages/e7/31/1513d5a6412b98052c3ed9158d783b1e09d0910f51fbe0e05f56cc370bc4/asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590", size = 560404 }, - { url = "https://files.pythonhosted.org/packages/c8/a4/cec76b3389c4c5ff66301cd100fe88c318563ec8a520e0b2e792b5b84972/asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e", size = 621623 }, -] - -[[package]] -name = "cached-property" -version = "2.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/4b/3d870836119dbe9a5e3c9a61af8cc1a8b69d75aea564572e385882d5aefb/cached_property-2.0.1.tar.gz", hash = "sha256:484d617105e3ee0e4f1f58725e72a8ef9e93deee462222dbd51cd91230897641", size = 10574 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/0e/7d8225aab3bc1a0f5811f8e1b557aa034ac04bdf641925b30d3caf586b28/cached_property-2.0.1-py3-none-any.whl", hash = "sha256:f617d70ab1100b7bcf6e42228f9ddcb78c676ffa167278d9f730d1c2fba69ccb", size = 7428 }, -] - -[[package]] -name = "cachetools" -version = "5.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d9/74/57df1ab0ce6bc5f6fa868e08de20df8ac58f9c44330c7671ad922d2bbeae/cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95", size = 28044 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/4e/de4ff18bcf55857ba18d3a4bd48c8a9fde6bb0980c9d20b263f05387fd88/cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb", size = 9530 }, -] - -[[package]] -name = "certifi" -version = "2025.1.31" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, - { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, - { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, - { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, - { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, - { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, - { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, - { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, - { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, - { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, - { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, - { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, - { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, - { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, - { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, - { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, - { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, - { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, - { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, - { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, - { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, - { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, - { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, - { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, -] - -[[package]] -name = "cohere" -version = "5.13.12" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fastavro" }, - { name = "httpx" }, - { name = "httpx-sse" }, - { name = "pydantic" }, - { name = "pydantic-core" }, - { name = "requests" }, - { name = "tokenizers" }, - { name = "types-requests" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f4/48/795c53b25b08ec353cc4f48dc5c199ac4615b3c331e716ac50c7cb07034c/cohere-5.13.12.tar.gz", hash = "sha256:97bb9ac107e580780b941acbabd3aa5e71960e6835398292c46aaa8a0a4cab88", size = 132860 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/c6/cffec9284d9713d28c6235a653a9a34c49b0f880f00cfa002252cdb8d033/cohere-5.13.12-py3-none-any.whl", hash = "sha256:2a043591a3e5280b47716a6b311e4c7f58e799364113a9cb81b50cd4f6c95f7e", size = 252856 }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, -] - -[[package]] -name = "decorator" -version = "5.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/0c/8d907af351aa16b42caae42f9d6aa37b900c67308052d10fdce809f8d952/decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", size = 35016 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 }, -] - -[[package]] -name = "dependency-injector" -version = "4.45.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/32/beb8a8aed8a07273bbc2afc6e823d2d70e5f898ed9a8cc413c45aec5642a/dependency_injector-4.45.0.tar.gz", hash = "sha256:7effdb9e45f5c2813e2fd9dc2ef2c446dd59716f66f9c772ab27b9ed16efc894", size = 1117514 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/20/b330400537783031a9e0d09865a2f47b3711f4cca1d13617ef3ebd9bc38f/dependency_injector-4.45.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7ec03cd54b79b5fc8df46ae821392358a24dc371bda01a2a68917ab0e559a96b", size = 1853439 }, - { url = "https://files.pythonhosted.org/packages/5f/fd/8708ab921876ee71fcb94778f99147251461b8772f95a17f182501241be8/dependency_injector-4.45.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:433883ccdfa353e6c8c2e20ba6eb08f77149a7e15f816f05be65bb791b86e5fe", size = 6522374 }, - { url = "https://files.pythonhosted.org/packages/df/ef/e30175362f9d601343113107a853bb1f6399d64e961004adb27e2f88a1e6/dependency_injector-4.45.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:892c40f800e19732e9a10d595237bfbe09637e07a98b55c8b0526ac7f8f7400b", size = 6571378 }, - { url = "https://files.pythonhosted.org/packages/90/97/b5b0f0ee0f7d99e0e5dc50380f109ee5b8e3180c07cf9702e4d7b80e8241/dependency_injector-4.45.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cedd4f87795b9a9712bf2b6786f0d37e8848418a6b4a59cb185fd1c3866dd888", size = 6146970 }, - { url = "https://files.pythonhosted.org/packages/ac/fc/4ef2836be45fce69d90fb0a7c6e818b52a90ae9e9aef318e1598a6abbd2a/dependency_injector-4.45.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:db61b15cfa7fc679b22abb216bbef7888cc332a3f1f0279d6a621cf899661a80", size = 6127692 }, - { url = "https://files.pythonhosted.org/packages/51/ce/b104a5f68a413be723f002b53f05d2032a417f6cad0ed4783eff48ddb7ff/dependency_injector-4.45.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:75d0f8c748efe5e1f8a0970742483b38687bfe460496a4b7e344440798d0fa99", size = 5991031 }, - { url = "https://files.pythonhosted.org/packages/1a/bc/2dbb1e5ca2c450642947d1243e7dea138adc281ad834ef851d7538306a2f/dependency_injector-4.45.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21cfd5d2f26cca02bd897069b4460be1cbac1b07875a33885a84f9aa165cead5", size = 6339597 }, - { url = "https://files.pythonhosted.org/packages/de/89/9cefb635be10244b54b57c546b93746dac19cbceb848c3f02cc50f089c10/dependency_injector-4.45.0-cp310-cp310-win32.whl", hash = "sha256:cebd7fa8a9bbfed3af38c6f98763caadf0b3c63ade142c3eefb41788247cb5d7", size = 1664866 }, - { url = "https://files.pythonhosted.org/packages/d8/25/b791d2d6a313cfb673fc2fab3b92d20870682c945023232f147f779a9bc3/dependency_injector-4.45.0-cp310-cp310-win_amd64.whl", hash = "sha256:f349e3ceb1913eba37489cab82831e9d46d9008e59c6560cfefe0c57dc1168b0", size = 1798287 }, - { url = "https://files.pythonhosted.org/packages/54/f1/8151f38a1b62651ebf089974c8c393d81c2901f2fce44eeb0b9828edcca4/dependency_injector-4.45.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a76ab1e114402f284c71039391826d9e43083169d88568ede109ff4fc85de1e", size = 1859197 }, - { url = "https://files.pythonhosted.org/packages/97/da/4013b6ea6b358cb60f1aca64b80368a4d2ddcb5e1c14d197eabb75049d46/dependency_injector-4.45.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4aefc511be8ba97d823a71fb52a3b0ccaa7f9387cb51444862ebcb919822cae", size = 7069850 }, - { url = "https://files.pythonhosted.org/packages/7f/10/9f299641f1c03a2b202c988261416531ffc1f816710f0bd9aa4971f680ea/dependency_injector-4.45.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a013c93a870a3a1ce9829dd272bd02b194ea286df48dfb9d059cf34526738b8", size = 7106114 }, - { url = "https://files.pythonhosted.org/packages/6d/58/3aefb686a5dc25ab2f376904ac37e7a04c5b9054bfd57ce8f57121129355/dependency_injector-4.45.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1641f25faf543df14b2395a3cea51bd0610bc7b2c1cb6ed1c13ec4bec0063e2", size = 6636391 }, - { url = "https://files.pythonhosted.org/packages/b6/08/66bcefa816e09deb771233e5f095721c2e061ed9406383f6ebad31379dfc/dependency_injector-4.45.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97dc40a527e488df23e9fb5b33a85f9176b730449ad2e498d6cc556dcad540ae", size = 6718172 }, - { url = "https://files.pythonhosted.org/packages/ca/62/ed699aa2d4db0d44a8f91fb730d705c6da696b63711c82d3c2be91567c00/dependency_injector-4.45.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9396c71f25744fd0ded9b171fee05ea3ca13a87c12512ddd8b7e4330f70647eb", size = 6538136 }, - { url = "https://files.pythonhosted.org/packages/ea/ec/ed00a79e89984edde7d16313c9379c9c4eea8d9d5e0d5a2071e940f33236/dependency_injector-4.45.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d98a490f8a2decf40ca2aa69d0559d01dd96131422fa2086626fd5949837335a", size = 6897940 }, - { url = "https://files.pythonhosted.org/packages/f5/72/9ced97efda3ed48e803b942482b4c195caaebd09504604ff3b1d662b3c73/dependency_injector-4.45.0-cp311-cp311-win32.whl", hash = "sha256:4cec41b8dd678555ea025ad320e6354cad92b18b6433f8cd0b423b02989668c3", size = 1665406 }, - { url = "https://files.pythonhosted.org/packages/e1/af/1690e57fae9eaf8ec285f0de409d13566759059cc5d0f6fdc42857431f95/dependency_injector-4.45.0-cp311-cp311-win_amd64.whl", hash = "sha256:5eb99c2ea049ac1065a0b4cf06166f70fac697049ddd342b88cb6a264f106faf", size = 1800647 }, - { url = "https://files.pythonhosted.org/packages/52/9f/57324d953301136b5f709e807532183c82ee15a9982d7da8972c0223d8ae/dependency_injector-4.45.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2144948b5218f480ca94c11e848762aa305539ec67406ccecfbcb556d06a61dd", size = 1865841 }, - { url = "https://files.pythonhosted.org/packages/fa/7d/02ec92c0cef097047867712d702f7edefcb566f681dac87fdd1e121f3ee2/dependency_injector-4.45.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a0c665ee9bbb9434497a9b97bbb2018a9c427c0d014bff77dda2e0586c9020", size = 6818546 }, - { url = "https://files.pythonhosted.org/packages/84/0e/abea9847d5a6bc3ecb4b0057842661e895c02524503bb41963f0b6b3d033/dependency_injector-4.45.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:403f44e2073986138dd95f4a8ac864453524b18385c44b838cba8a2f7f742f2e", size = 6946584 }, - { url = "https://files.pythonhosted.org/packages/00/9a/87155cc98e2afda2deb9f81309698f756f4ab6e9f8522ce386e65dc02fb8/dependency_injector-4.45.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2a38cf7826ff253d30958c35bc1e121af292f0f12fb5ffd1ad3ea5377b63e3b", size = 6400998 }, - { url = "https://files.pythonhosted.org/packages/31/fd/60262f2d61d0b049107f5e3bca8f6f3321858bac3190f143e61c0dcbca29/dependency_injector-4.45.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2b18413b2f965793f8e401996a6cc01e0aa0336568805fc822285cdc72cfb7d9", size = 6493477 }, - { url = "https://files.pythonhosted.org/packages/8f/dc/ac111c2e17f764c9584a99be2310c9593195fe694e6db905d8c71f22ce09/dependency_injector-4.45.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ed3e60296b3b9d29c3eb57cb44a82d7627da1a7eac68ac97fbf9bbd9e2a5cb99", size = 6317674 }, - { url = "https://files.pythonhosted.org/packages/1b/26/6546f8acc6d3da46b3adab2fa790ffa6739c236604533f4c05e66e3a7fcd/dependency_injector-4.45.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd1afbbf03ed97007f6d4cb11944776e79207d9d7634ba5c4f897f4802b382ae", size = 6724126 }, - { url = "https://files.pythonhosted.org/packages/31/c2/5580c731fd550327d9f66ff62824cb04b4a0530b5ca6997f5dd2905b383f/dependency_injector-4.45.0-cp312-cp312-win32.whl", hash = "sha256:2bfca5875aedb5a0995a4b2e5a928aef5791a58f4280d0c3003cb0720385f546", size = 1660532 }, - { url = "https://files.pythonhosted.org/packages/52/4d/185a604a96d766f7bb8ea1017ad66a136c78593d15a294163ac258778e4a/dependency_injector-4.45.0-cp312-cp312-win_amd64.whl", hash = "sha256:5510a68445741fce2af071fd7fda177c4249f2b576d2b2b2b5af23083e4208e8", size = 1776903 }, - { url = "https://files.pythonhosted.org/packages/fa/41/e8dc964575ef0f6e7abb90942be9e6f265d770616518216368165d3079d7/dependency_injector-4.45.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16c17b17d1974a288b16f0d9b4eda7fae892dc2823ff1d95a93207723b681b9a", size = 1856812 }, - { url = "https://files.pythonhosted.org/packages/30/e3/ccfe9ad88d7ee197a12954290697a8c503a0f8f049cd1e4e81f294861053/dependency_injector-4.45.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1cfd84779e718974b8d40eefc2d664ba69c142ca676ed97f487de05e733e9e", size = 6819924 }, - { url = "https://files.pythonhosted.org/packages/85/e7/c3e392f005ba3072d4c6e7a5ff60391648df0f0664ad25b3570f3283c270/dependency_injector-4.45.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b03382b9423f008bf307d4dee7e56ea1e070fc0e23481e4e327b01b9b463a9d", size = 6957754 }, - { url = "https://files.pythonhosted.org/packages/d4/87/c6907e001a0504678c5e5ed62bc6c5c86d1668302fccd5c159d2542bef58/dependency_injector-4.45.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcba70e44a3d6436196f1ffebad0b02f0a72a7c8014d85617f21bd8535434bf4", size = 6406441 }, - { url = "https://files.pythonhosted.org/packages/90/a7/2b003dc0e9e8624e3948289f7764aa87b59cbff392ca06c7f41d28be43b6/dependency_injector-4.45.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73d52ba9efdbb6a1c0ba49d9c68be9cedb0abfa0e6fbdfb942371c642cb30541", size = 6558807 }, - { url = "https://files.pythonhosted.org/packages/d1/a6/5b8f21957af82488ce2e70e321c45c3ba3707004c211c0e6b261e1c77afc/dependency_injector-4.45.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9ebcefa9218941337466cf0985bdcd5d38cf84ab1d3a4d7a3cc91845e38c0d1e", size = 6354444 }, - { url = "https://files.pythonhosted.org/packages/dd/2f/bae5beaf6ac9c5464e6016d0f94d10898c192b4528e2978a2ada44af0c21/dependency_injector-4.45.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d785dacb136479b0536e8b05f83a7375f09404d63eabd1be9b694eca6defea4", size = 6762151 }, - { url = "https://files.pythonhosted.org/packages/13/6f/068b17dc76765e5344c72b85d46711a596eca1d4ee441bac7b7275dadc94/dependency_injector-4.45.0-cp313-cp313-win32.whl", hash = "sha256:3df40ff15bd31058b24cd79781b1dc2f31e0c157ee2067566dfd7a92191969e9", size = 1660060 }, - { url = "https://files.pythonhosted.org/packages/53/d3/890c4be056256e56e97264e72b376ecad273cfa8c8dc70ce5eb623f06472/dependency_injector-4.45.0-cp313-cp313-win_amd64.whl", hash = "sha256:f018f634edf57d4b4214e007eaa2432d2410f304b170f5684bd3fdbffd244b1f", size = 1775930 }, - { url = "https://files.pythonhosted.org/packages/02/8e/915f9b6b7105a2a55933b808a2b4c2d2bd73ac2ccf320d4ebd3d2729c2a2/dependency_injector-4.45.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f2cd876d9726db63df33ecbfb097a06de0306ddae5861c6ba71363606184428e", size = 1698714 }, - { url = "https://files.pythonhosted.org/packages/b4/4f/08c59f93522429944c941f49de8d2ae99c352f84a0825bb7d580d057c522/dependency_injector-4.45.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35baac99140143aed5f7dbf098c523fc534884063ccacc3f51aaccbacc7f9f3", size = 1834874 }, - { url = "https://files.pythonhosted.org/packages/a3/aa/0ed6ae263af7e3b3f12ca69c63be13a42863b954e8e73cb66d4a6d29d4f9/dependency_injector-4.45.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c76c944e4732d72787f99b18dd5b3bcb012427efe8373d8b895f9774880f0cc", size = 1853759 }, - { url = "https://files.pythonhosted.org/packages/50/08/5c8c8bf937e84d0b020eea06811ac5c7b23e05333a5f3f3925dc23b8f6a6/dependency_injector-4.45.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beabb619969c1c87085bcb228b10e2f8751bf7cca66d77dac99c85c3b7a29847", size = 1872820 }, - { url = "https://files.pythonhosted.org/packages/cc/61/8fb364abeb494a43001856a6433cd228544af9d2aa3fc57955b8c1b1095d/dependency_injector-4.45.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d2bdf7c6e4f7a74dedf4f0aea774eec4c3d6f4f909d411c769c9cd1346243518", size = 1718692 }, -] - -[[package]] -name = "deprecation" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, -] - -[[package]] -name = "devtools" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asttokens" }, - { name = "executing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2d/e2/a1e80801b195539f552a2a369691e09ecc27648f5c5c0c0cab0256f4f098/devtools-0.9.0.tar.gz", hash = "sha256:86ede6e0273e023db766344d14098228785b48a80f31716f28e8b9453d52fa1e", size = 68180 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/3a/741773833ee8f1d18d5ce377c3b062f0d63b951fb9e4bec25f994c7f4631/devtools-0.9.0-py3-none-any.whl", hash = "sha256:689cf4e7c75024237c42093ba19f4fa9cf15980269f02463aeab4d97d4b0a215", size = 15214 }, -] - -[[package]] -name = "distro" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, -] - -[[package]] -name = "et-xmlfile" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059 }, -] - -[[package]] -name = "eval-type-backport" -version = "0.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/ea/8b0ac4469d4c347c6a385ff09dc3c048c2d021696664e26c7ee6791631b5/eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1", size = 9079 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830 }, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, -] - -[[package]] -name = "executing" -version = "0.8.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/e4/efb182548f9931b36b76fd9ba2236bfc5e6d342c905beff92e868afb5b5c/executing-0.8.2.tar.gz", hash = "sha256:c23bf42e9a7b9b212f185b1b2c3c91feb895963378887bb10e64a2e612ec0023", size = 481669 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/64/59e024b685666514cb20ffc2463a8b062df8e6c36efc5199cb422b728b78/executing-0.8.2-py2.py3-none-any.whl", hash = "sha256:32fc6077b103bd19e6494a72682d66d5763cf20a106d5aa7c5ccbea4e47b0df7", size = 16263 }, -] - -[[package]] -name = "fastapi" -version = "0.115.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "starlette" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a2/b2/5a5dc4affdb6661dea100324e19a7721d5dc524b464fe8e366c093fd7d87/fastapi-0.115.8.tar.gz", hash = "sha256:0ce9111231720190473e222cdf0f07f7206ad7e53ea02beb1d2dc36e2f0741e9", size = 295403 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/7d/2d6ce181d7a5f51dedb8c06206cbf0ec026a99bf145edd309f9e17c3282f/fastapi-0.115.8-py3-none-any.whl", hash = "sha256:753a96dd7e036b34eeef8babdfcfe3f28ff79648f86551eb36bfc1b0bf4a8cbf", size = 94814 }, -] - -[[package]] -name = "fastavro" -version = "1.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/67/7121d2221e998706cac00fa779ec44c1c943cb65e8a7ed1bd57d78d93f2c/fastavro-1.10.0.tar.gz", hash = "sha256:47bf41ac6d52cdfe4a3da88c75a802321321b37b663a900d12765101a5d6886f", size = 987970 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/e9/f5813450d672f500c4794a39a7cfea99316cb63d5ea11f215e320ea5243b/fastavro-1.10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1a9fe0672d2caf0fe54e3be659b13de3cad25a267f2073d6f4b9f8862acc31eb", size = 1037355 }, - { url = "https://files.pythonhosted.org/packages/6a/41/3f120f72e65f0c80e9bc4f855ac1c9578c8c0e2cdac4d4d4da1f91ca73b9/fastavro-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86dd0410770e0c99363788f0584523709d85e57bb457372ec5c285a482c17fe6", size = 3024739 }, - { url = "https://files.pythonhosted.org/packages/e1/e3/7d9b019158498b45c383e696ba8733b01535337136e9402b0487afeb92b6/fastavro-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:190e80dc7d77d03a6a8597a026146b32a0bbe45e3487ab4904dc8c1bebecb26d", size = 3074020 }, - { url = "https://files.pythonhosted.org/packages/36/31/7ede5629e66eeb71c234d17a799000e737fe0ffd71ef9e1d57a3510def46/fastavro-1.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bf570d63be9155c3fdc415f60a49c171548334b70fff0679a184b69c29b6bc61", size = 2968623 }, - { url = "https://files.pythonhosted.org/packages/10/13/d215411ff5d5de23d6ed62a31eb7f7fa53941681d86bcd5c6388a0918fc3/fastavro-1.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e07abb6798e95dccecaec316265e35a018b523d1f3944ad396d0a93cb95e0a08", size = 3122217 }, - { url = "https://files.pythonhosted.org/packages/6a/1d/7a54fac3f90f0dc120b92f244067976831e393789d3b78c08f2b035ccb19/fastavro-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:37203097ed11d0b8fd3c004904748777d730cafd26e278167ea602eebdef8eb2", size = 497256 }, - { url = "https://files.pythonhosted.org/packages/ac/bf/e7e8e0f841e608dc6f78c746ef2d971fb1f6fe8a9a428d0731ef0abf8b59/fastavro-1.10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d183c075f527ab695a27ae75f210d4a86bce660cda2f85ae84d5606efc15ef50", size = 1040292 }, - { url = "https://files.pythonhosted.org/packages/3a/96/43a65881f061bc5ec6dcf39e59f639a7344e822d4caadae748d076aaf4d0/fastavro-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7a95a2c0639bffd7c079b59e9a796bfc3a9acd78acff7088f7c54ade24e4a77", size = 3312624 }, - { url = "https://files.pythonhosted.org/packages/c8/45/dba0cc08cf42500dd0f1e552e0fefe1cd81c47099d99277828a1081cbd87/fastavro-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a678153b5da1b024a32ec3f611b2e7afd24deac588cb51dd1b0019935191a6d", size = 3334284 }, - { url = "https://files.pythonhosted.org/packages/76/e3/3d9b0824e2e2da56e6a435a70a4db7ed801136daa451577a819bbedc6cf8/fastavro-1.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67a597a5cfea4dddcf8b49eaf8c2b5ffee7fda15b578849185bc690ec0cd0d8f", size = 3283647 }, - { url = "https://files.pythonhosted.org/packages/a1/dc/83d985f8212194e8283ebae86491fccde8710fd81d81ef8659e5373f4f1b/fastavro-1.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fd689724760b17f69565d8a4e7785ed79becd451d1c99263c40cb2d6491f1d4", size = 3419520 }, - { url = "https://files.pythonhosted.org/packages/fd/7f/21711a9ec9937c84406e0773ba3fc6f8d66389a364da46618706f9c37d30/fastavro-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f949d463f9ac4221128a51e4e34e2562f401e5925adcadfd28637a73df6c2d8", size = 499750 }, - { url = "https://files.pythonhosted.org/packages/9c/a4/8e69c0a5cd121e5d476237de1bde5a7947f791ae45768ae52ed0d3ea8d18/fastavro-1.10.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cfe57cb0d72f304bd0dcc5a3208ca6a7363a9ae76f3073307d095c9d053b29d4", size = 1036343 }, - { url = "https://files.pythonhosted.org/packages/1e/01/aa219e2b33e5873d27b867ec0fad9f35f23d461114e1135a7e46c06786d2/fastavro-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e517440c824cb65fb29d3e3903a9406f4d7c75490cef47e55c4c82cdc66270", size = 3263368 }, - { url = "https://files.pythonhosted.org/packages/a7/ba/1766e2d7d95df2e95e9e9a089dc7a537c0616720b053a111a918fa7ee6b6/fastavro-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:203c17d44cadde76e8eecb30f2d1b4f33eb478877552d71f049265dc6f2ecd10", size = 3328933 }, - { url = "https://files.pythonhosted.org/packages/2e/40/26e56696b9696ab4fbba25a96b8037ca3f9fd8a8cc55b4b36400ef023e49/fastavro-1.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6575be7f2b5f94023b5a4e766b0251924945ad55e9a96672dc523656d17fe251", size = 3258045 }, - { url = "https://files.pythonhosted.org/packages/4e/bc/2f6c92c06c5363372abe828bccdd95762f2c1983b261509f94189c38c8a1/fastavro-1.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe471deb675ed2f01ee2aac958fbf8ebb13ea00fa4ce7f87e57710a0bc592208", size = 3418001 }, - { url = "https://files.pythonhosted.org/packages/0c/ce/cfd16546c04ebbca1be80873b533c788cec76f7bfac231bfac6786047572/fastavro-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:567ff515f2a5d26d9674b31c95477f3e6022ec206124c62169bc2ffaf0889089", size = 487855 }, - { url = "https://files.pythonhosted.org/packages/c9/c4/163cf154cc694c2dccc70cd6796db6214ac668a1260bf0310401dad188dc/fastavro-1.10.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:82263af0adfddb39c85f9517d736e1e940fe506dfcc35bc9ab9f85e0fa9236d8", size = 1022741 }, - { url = "https://files.pythonhosted.org/packages/38/01/a24598f5f31b8582a92fe9c41bf91caeed50d5b5eaa7576e6f8b23cb488d/fastavro-1.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:566c193109ff0ff84f1072a165b7106c4f96050078a4e6ac7391f81ca1ef3efa", size = 3237421 }, - { url = "https://files.pythonhosted.org/packages/a7/bf/08bcf65cfb7feb0e5b1329fafeb4a9b95b7b5ec723ba58c7dbd0d04ded34/fastavro-1.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e400d2e55d068404d9fea7c5021f8b999c6f9d9afa1d1f3652ec92c105ffcbdd", size = 3300222 }, - { url = "https://files.pythonhosted.org/packages/53/4d/a6c25f3166328f8306ec2e6be1123ed78a55b8ab774a43a661124508881f/fastavro-1.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9b8227497f71565270f9249fc9af32a93644ca683a0167cfe66d203845c3a038", size = 3233276 }, - { url = "https://files.pythonhosted.org/packages/47/1c/b2b2ce2bf866a248ae23e96a87b3b8369427ff79be9112073039bee1d245/fastavro-1.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e62d04c65461b30ac6d314e4197ad666371e97ae8cb2c16f971d802f6c7f514", size = 3388936 }, - { url = "https://files.pythonhosted.org/packages/1f/2c/43927e22a2d57587b3aa09765098a6d833246b672d34c10c5f135414745a/fastavro-1.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:86baf8c9740ab570d0d4d18517da71626fe9be4d1142bea684db52bd5adb078f", size = 483967 }, -] - -[[package]] -name = "filelock" -version = "3.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 }, -] - -[[package]] -name = "finitemonkey" -version = "0.0.1" -source = { editable = "." } -dependencies = [ - { name = "antlr4-python3-runtime" }, - { name = "asyncpg" }, - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "colorama" }, - { name = "dependency-injector" }, - { name = "devtools" }, - { name = "et-xmlfile" }, - { name = "fastapi" }, - { name = "future" }, - { name = "idna" }, - { name = "ipython" }, - { name = "joblib" }, - { name = "lancedb" }, - { name = "logging" }, - { name = "numpy" }, - { name = "ollama" }, - { name = "openai" }, - { name = "openpyxl" }, - { name = "pandas" }, - { name = "psycopg2-binary" }, - { name = "pydantic" }, - { name = "pydantic-ai" }, - { name = "pydantic-graph" }, - { name = "pydantic-settings" }, - { name = "pylance" }, - { name = "python-box" }, - { name = "python-dateutil" }, - { name = "python-dotenv" }, - { name = "pytz" }, - { name = "requests" }, - { name = "rich" }, - { name = "scikit-learn" }, - { name = "scipy" }, - { name = "simplejson" }, - { name = "six" }, - { name = "sqlalchemy" }, - { name = "sqlalchemy-orm" }, - { name = "threadpoolctl" }, - { name = "tqdm" }, - { name = "tree-sitter" }, - { name = "tree-sitter-solidity" }, - { name = "typing-extensions" }, - { name = "tzdata" }, - { name = "urllib3" }, -] - -[package.metadata] -requires-dist = [ - { name = "antlr4-python3-runtime", specifier = ">=4.13.2" }, - { name = "asyncpg", specifier = ">=0.30.0" }, - { name = "certifi", specifier = ">=2024.8.30" }, - { name = "charset-normalizer", specifier = ">=3.4.0" }, - { name = "colorama", specifier = ">=0.4.6" }, - { name = "dependency-injector", specifier = ">=4.45.0" }, - { name = "devtools", specifier = ">=0.9.0" }, - { name = "et-xmlfile", specifier = ">=1.1.0" }, - { name = "fastapi", specifier = ">=0.115.8" }, - { name = "future", specifier = ">=1.0.0" }, - { name = "idna", specifier = ">=3.10" }, - { name = "ipython", specifier = ">=8.32.0" }, - { name = "joblib", specifier = ">=1.4.2" }, - { name = "lancedb", specifier = ">=0.19.0" }, - { name = "logging", specifier = ">=0.4.9.6" }, - { name = "numpy", specifier = ">=1.24.4" }, - { name = "ollama", specifier = ">=0.4.7" }, - { name = "openai", specifier = ">=1.63.0" }, - { name = "openpyxl", specifier = ">=3.1.5" }, - { name = "pandas", specifier = ">=1.24.4" }, - { name = "psycopg2-binary", specifier = ">=2.9.9" }, - { name = "pydantic", specifier = ">=2.11.0a2" }, - { name = "pydantic-ai", specifier = ">=0.0.24" }, - { name = "pydantic-graph", specifier = ">=0.0.24" }, - { name = "pydantic-settings", specifier = ">=2.7.1" }, - { name = "pylance", specifier = ">=0.23.0" }, - { name = "python-box", specifier = ">=7.3.2" }, - { name = "python-dateutil", specifier = "==2.8.2" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "pytz", specifier = ">=2024.2" }, - { name = "requests", specifier = ">=2.32.3" }, - { name = "rich", specifier = ">=13.9.4" }, - { name = "scikit-learn", specifier = ">=1.3.2" }, - { name = "scipy", specifier = ">=1.10.1" }, - { name = "simplejson", specifier = ">=3.19.3" }, - { name = "six", specifier = ">=1.16.0" }, - { name = "sqlalchemy", specifier = ">=2.0.35" }, - { name = "sqlalchemy-orm", specifier = ">=1.2.10" }, - { name = "threadpoolctl", specifier = ">=3.5.0" }, - { name = "tqdm", specifier = ">=4.66.5" }, - { name = "tree-sitter", specifier = ">=0.24.0" }, - { name = "tree-sitter-solidity", specifier = ">=1.2.11" }, - { name = "typing-extensions", specifier = ">=4.12.2" }, - { name = "tzdata", specifier = ">=2024.2" }, - { name = "urllib3", specifier = ">=2.2.3" }, -] - -[[package]] -name = "fsspec" -version = "2025.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/79/68612ed99700e6413de42895aa725463e821a6b3be75c87fcce1b4af4c70/fsspec-2025.2.0.tar.gz", hash = "sha256:1c24b16eaa0a1798afa0337aa0db9b256718ab2a89c425371f5628d22c3b6afd", size = 292283 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/94/758680531a00d06e471ef649e4ec2ed6bf185356a7f9fbfbb7368a40bd49/fsspec-2025.2.0-py3-none-any.whl", hash = "sha256:9de2ad9ce1f85e1931858535bc882543171d197001a0a5eb2ddc04f1781ab95b", size = 184484 }, -] - -[[package]] -name = "future" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326 }, -] - -[[package]] -name = "google-auth" -version = "2.38.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cachetools" }, - { name = "pyasn1-modules" }, - { name = "rsa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c6/eb/d504ba1daf190af6b204a9d4714d457462b486043744901a6eeea711f913/google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4", size = 270866 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/47/603554949a37bca5b7f894d51896a9c534b9eab808e2520a748e081669d0/google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a", size = 210770 }, -] - -[[package]] -name = "greenlet" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/90/5234a78dc0ef6496a6eb97b67a42a8e96742a56f7dc808cb954a85390448/greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", size = 271235 }, - { url = "https://files.pythonhosted.org/packages/7c/16/cd631fa0ab7d06ef06387135b7549fdcc77d8d859ed770a0d28e47b20972/greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", size = 637168 }, - { url = "https://files.pythonhosted.org/packages/2f/b1/aed39043a6fec33c284a2c9abd63ce191f4f1a07319340ffc04d2ed3256f/greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", size = 648826 }, - { url = "https://files.pythonhosted.org/packages/76/25/40e0112f7f3ebe54e8e8ed91b2b9f970805143efef16d043dfc15e70f44b/greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", size = 644443 }, - { url = "https://files.pythonhosted.org/packages/fb/2f/3850b867a9af519794784a7eeed1dd5bc68ffbcc5b28cef703711025fd0a/greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", size = 643295 }, - { url = "https://files.pythonhosted.org/packages/cf/69/79e4d63b9387b48939096e25115b8af7cd8a90397a304f92436bcb21f5b2/greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", size = 599544 }, - { url = "https://files.pythonhosted.org/packages/46/1d/44dbcb0e6c323bd6f71b8c2f4233766a5faf4b8948873225d34a0b7efa71/greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", size = 1125456 }, - { url = "https://files.pythonhosted.org/packages/e0/1d/a305dce121838d0278cee39d5bb268c657f10a5363ae4b726848f833f1bb/greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", size = 1149111 }, - { url = "https://files.pythonhosted.org/packages/96/28/d62835fb33fb5652f2e98d34c44ad1a0feacc8b1d3f1aecab035f51f267d/greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", size = 298392 }, - { url = "https://files.pythonhosted.org/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", size = 272479 }, - { url = "https://files.pythonhosted.org/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", size = 640404 }, - { url = "https://files.pythonhosted.org/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", size = 652813 }, - { url = "https://files.pythonhosted.org/packages/49/93/d5f93c84241acdea15a8fd329362c2c71c79e1a507c3f142a5d67ea435ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", size = 648517 }, - { url = "https://files.pythonhosted.org/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", size = 647831 }, - { url = "https://files.pythonhosted.org/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", size = 602413 }, - { url = "https://files.pythonhosted.org/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", size = 1129619 }, - { url = "https://files.pythonhosted.org/packages/f4/fb/201e1b932e584066e0f0658b538e73c459b34d44b4bd4034f682423bc801/greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", size = 1155198 }, - { url = "https://files.pythonhosted.org/packages/12/da/b9ed5e310bb8b89661b80cbcd4db5a067903bbcd7fc854923f5ebb4144f0/greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", size = 298930 }, - { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260 }, - { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064 }, - { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420 }, - { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035 }, - { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105 }, - { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077 }, - { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975 }, - { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955 }, - { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655 }, - { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990 }, - { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175 }, - { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425 }, - { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736 }, - { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347 }, - { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583 }, - { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039 }, - { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716 }, - { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490 }, - { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731 }, - { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304 }, - { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537 }, - { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506 }, - { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753 }, - { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731 }, - { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112 }, -] - -[[package]] -name = "griffe" -version = "1.5.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/59/80/13b6456bfbf8bc854875e58d3a3bad297ee19ebdd693ce62a10fab007e7a/griffe-1.5.7.tar.gz", hash = "sha256:465238c86deaf1137761f700fb343edd8ffc846d72f6de43c3c345ccdfbebe92", size = 391503 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/67/b43330ed76f96be098c165338d47ccb952964ed77ba1d075247fbdf05c04/griffe-1.5.7-py3-none-any.whl", hash = "sha256:4af8ec834b64de954d447c7b6672426bb145e71605c74a4e22d510cc79fe7d8b", size = 128294 }, -] - -[[package]] -name = "groq" -version = "0.18.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/40/8c/e72c164474a88dfed6c7327ad53cb87ff11566b74b3a76d41dc7b94fc51c/groq-0.18.0.tar.gz", hash = "sha256:8e2ccfea406d68b3525af4b7c0e321fcb3d2a73fc60bb70b4156e6cd88c72f03", size = 117322 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/6c/5a53d632b44ef7655ac8d9b34432e13160917f9307c94b1467efd34e336e/groq-0.18.0-py3-none-any.whl", hash = "sha256:81d5ac00057a45d8ce559d23ab5d3b3893011d1f12c35187ab35a9182d826ea6", size = 121911 }, -] - -[[package]] -name = "h11" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, -] - -[[package]] -name = "httpcore" -version = "1.0.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, -] - -[[package]] -name = "httpx-sse" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, -] - -[[package]] -name = "huggingface-hub" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/ce/a734204aaae6c35a22f9956ebcd8d8708ae5b842e15d6f42bd6f49e634a4/huggingface_hub-0.28.1.tar.gz", hash = "sha256:893471090c98e3b6efbdfdacafe4052b20b84d59866fb6f54c33d9af18c303ae", size = 387074 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/da/6c2bea5327b640920267d3bf2c9fc114cfbd0a5de234d81cda80cc9e33c8/huggingface_hub-0.28.1-py3-none-any.whl", hash = "sha256:aa6b9a3ffdae939b72c464dbb0d7f99f56e649b55c3d52406f49e0a5a620c0a7", size = 464068 }, -] - -[[package]] -name = "idna" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, -] - -[[package]] -name = "inflection" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/7e/691d061b7329bc8d54edbf0ec22fbfb2afe61facb681f9aaa9bff7a27d04/inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", size = 15091 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454 }, -] - -[[package]] -name = "ipython" -version = "8.32.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "decorator" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "jedi" }, - { name = "matplotlib-inline" }, - { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "stack-data" }, - { name = "traitlets" }, - { name = "typing-extensions", marker = "python_full_version < '3.12'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/36/80/4d2a072e0db7d250f134bc11676517299264ebe16d62a8619d49a78ced73/ipython-8.32.0.tar.gz", hash = "sha256:be2c91895b0b9ea7ba49d33b23e2040c352b33eb6a519cca7ce6e0c743444251", size = 5507441 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/e1/f4474a7ecdb7745a820f6f6039dc43c66add40f1bcc66485607d93571af6/ipython-8.32.0-py3-none-any.whl", hash = "sha256:cae85b0c61eff1fc48b0a8002de5958b6528fa9c8defb1894da63f42613708aa", size = 825524 }, -] - -[[package]] -name = "jedi" -version = "0.19.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "parso" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, -] - -[[package]] -name = "jiter" -version = "0.8.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/70/90bc7bd3932e651486861df5c8ffea4ca7c77d28e8532ddefe2abc561a53/jiter-0.8.2.tar.gz", hash = "sha256:cd73d3e740666d0e639f678adb176fad25c1bcbdae88d8d7b857e1783bb4212d", size = 163007 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/f3/8c11e0e87bd5934c414f9b1cfae3cbfd4a938d4669d57cb427e1c4d11a7f/jiter-0.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ca8577f6a413abe29b079bc30f907894d7eb07a865c4df69475e868d73e71c7b", size = 303381 }, - { url = "https://files.pythonhosted.org/packages/ea/28/4cd3f0bcbf40e946bc6a62a82c951afc386a25673d3d8d5ee461f1559bbe/jiter-0.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b25bd626bde7fb51534190c7e3cb97cee89ee76b76d7585580e22f34f5e3f393", size = 311718 }, - { url = "https://files.pythonhosted.org/packages/0d/17/57acab00507e60bd954eaec0837d9d7b119b4117ff49b8a62f2b646f32ed/jiter-0.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c826a221851a8dc028eb6d7d6429ba03184fa3c7e83ae01cd6d3bd1d4bd17d", size = 335465 }, - { url = "https://files.pythonhosted.org/packages/74/b9/1a3ddd2bc95ae17c815b021521020f40c60b32137730126bada962ef32b4/jiter-0.8.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d35c864c2dff13dfd79fb070fc4fc6235d7b9b359efe340e1261deb21b9fcb66", size = 355570 }, - { url = "https://files.pythonhosted.org/packages/78/69/6d29e2296a934199a7d0dde673ecccf98c9c8db44caf0248b3f2b65483cb/jiter-0.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f557c55bc2b7676e74d39d19bcb8775ca295c7a028246175d6a8b431e70835e5", size = 381383 }, - { url = "https://files.pythonhosted.org/packages/22/d7/fbc4c3fb1bf65f9be22a32759b539f88e897aeb13fe84ab0266e4423487a/jiter-0.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:580ccf358539153db147e40751a0b41688a5ceb275e6f3e93d91c9467f42b2e3", size = 390454 }, - { url = "https://files.pythonhosted.org/packages/4d/a0/3993cda2e267fe679b45d0bcc2cef0b4504b0aa810659cdae9737d6bace9/jiter-0.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af102d3372e917cffce49b521e4c32c497515119dc7bd8a75665e90a718bbf08", size = 345039 }, - { url = "https://files.pythonhosted.org/packages/b9/ef/69c18562b4c09ce88fab5df1dcaf643f6b1a8b970b65216e7221169b81c4/jiter-0.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cadcc978f82397d515bb2683fc0d50103acff2a180552654bb92d6045dec2c49", size = 376200 }, - { url = "https://files.pythonhosted.org/packages/4d/17/0b5a8de46a6ab4d836f70934036278b49b8530c292b29dde3483326d4555/jiter-0.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba5bdf56969cad2019d4e8ffd3f879b5fdc792624129741d3d83fc832fef8c7d", size = 511158 }, - { url = "https://files.pythonhosted.org/packages/6c/b2/c401a0a2554b36c9e6d6e4876b43790d75139cf3936f0222e675cbc23451/jiter-0.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3b94a33a241bee9e34b8481cdcaa3d5c2116f575e0226e421bed3f7a6ea71cff", size = 503956 }, - { url = "https://files.pythonhosted.org/packages/d4/02/a0291ed7d72c0ac130f172354ee3cf0b2556b69584de391463a8ee534f40/jiter-0.8.2-cp310-cp310-win32.whl", hash = "sha256:6e5337bf454abddd91bd048ce0dca5134056fc99ca0205258766db35d0a2ea43", size = 202846 }, - { url = "https://files.pythonhosted.org/packages/ad/20/8c988831ae4bf437e29f1671e198fc99ba8fe49f2895f23789acad1d1811/jiter-0.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:4a9220497ca0cb1fe94e3f334f65b9b5102a0b8147646118f020d8ce1de70105", size = 204414 }, - { url = "https://files.pythonhosted.org/packages/cb/b0/c1a7caa7f9dc5f1f6cfa08722867790fe2d3645d6e7170ca280e6e52d163/jiter-0.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2dd61c5afc88a4fda7d8b2cf03ae5947c6ac7516d32b7a15bf4b49569a5c076b", size = 303666 }, - { url = "https://files.pythonhosted.org/packages/f5/97/0468bc9eeae43079aaa5feb9267964e496bf13133d469cfdc135498f8dd0/jiter-0.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a6c710d657c8d1d2adbbb5c0b0c6bfcec28fd35bd6b5f016395f9ac43e878a15", size = 311934 }, - { url = "https://files.pythonhosted.org/packages/e5/69/64058e18263d9a5f1e10f90c436853616d5f047d997c37c7b2df11b085ec/jiter-0.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9584de0cd306072635fe4b89742bf26feae858a0683b399ad0c2509011b9dc0", size = 335506 }, - { url = "https://files.pythonhosted.org/packages/9d/14/b747f9a77b8c0542141d77ca1e2a7523e854754af2c339ac89a8b66527d6/jiter-0.8.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a90a923338531b7970abb063cfc087eebae6ef8ec8139762007188f6bc69a9f", size = 355849 }, - { url = "https://files.pythonhosted.org/packages/53/e2/98a08161db7cc9d0e39bc385415890928ff09709034982f48eccfca40733/jiter-0.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21974d246ed0181558087cd9f76e84e8321091ebfb3a93d4c341479a736f099", size = 381700 }, - { url = "https://files.pythonhosted.org/packages/7a/38/1674672954d35bce3b1c9af99d5849f9256ac8f5b672e020ac7821581206/jiter-0.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32475a42b2ea7b344069dc1e81445cfc00b9d0e3ca837f0523072432332e9f74", size = 389710 }, - { url = "https://files.pythonhosted.org/packages/f8/9b/92f9da9a9e107d019bcf883cd9125fa1690079f323f5a9d5c6986eeec3c0/jiter-0.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9931fd36ee513c26b5bf08c940b0ac875de175341cbdd4fa3be109f0492586", size = 345553 }, - { url = "https://files.pythonhosted.org/packages/44/a6/6d030003394e9659cd0d7136bbeabd82e869849ceccddc34d40abbbbb269/jiter-0.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0820f4a3a59ddced7fce696d86a096d5cc48d32a4183483a17671a61edfddc", size = 376388 }, - { url = "https://files.pythonhosted.org/packages/ad/8d/87b09e648e4aca5f9af89e3ab3cfb93db2d1e633b2f2931ede8dabd9b19a/jiter-0.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ffc86ae5e3e6a93765d49d1ab47b6075a9c978a2b3b80f0f32628f39caa0c88", size = 511226 }, - { url = "https://files.pythonhosted.org/packages/77/95/8008ebe4cdc82eac1c97864a8042ca7e383ed67e0ec17bfd03797045c727/jiter-0.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5127dc1abd809431172bc3fbe8168d6b90556a30bb10acd5ded41c3cfd6f43b6", size = 504134 }, - { url = "https://files.pythonhosted.org/packages/26/0d/3056a74de13e8b2562e4d526de6dac2f65d91ace63a8234deb9284a1d24d/jiter-0.8.2-cp311-cp311-win32.whl", hash = "sha256:66227a2c7b575720c1871c8800d3a0122bb8ee94edb43a5685aa9aceb2782d44", size = 203103 }, - { url = "https://files.pythonhosted.org/packages/4e/1e/7f96b798f356e531ffc0f53dd2f37185fac60fae4d6c612bbbd4639b90aa/jiter-0.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:cde031d8413842a1e7501e9129b8e676e62a657f8ec8166e18a70d94d4682855", size = 206717 }, - { url = "https://files.pythonhosted.org/packages/a1/17/c8747af8ea4e045f57d6cfd6fc180752cab9bc3de0e8a0c9ca4e8af333b1/jiter-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e6ec2be506e7d6f9527dae9ff4b7f54e68ea44a0ef6b098256ddf895218a2f8f", size = 302027 }, - { url = "https://files.pythonhosted.org/packages/3c/c1/6da849640cd35a41e91085723b76acc818d4b7d92b0b6e5111736ce1dd10/jiter-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76e324da7b5da060287c54f2fabd3db5f76468006c811831f051942bf68c9d44", size = 310326 }, - { url = "https://files.pythonhosted.org/packages/06/99/a2bf660d8ccffee9ad7ed46b4f860d2108a148d0ea36043fd16f4dc37e94/jiter-0.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:180a8aea058f7535d1c84183c0362c710f4750bef66630c05f40c93c2b152a0f", size = 334242 }, - { url = "https://files.pythonhosted.org/packages/a7/5f/cea1c17864828731f11427b9d1ab7f24764dbd9aaf4648a7f851164d2718/jiter-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025337859077b41548bdcbabe38698bcd93cfe10b06ff66617a48ff92c9aec60", size = 356654 }, - { url = "https://files.pythonhosted.org/packages/e9/13/62774b7e5e7f5d5043efe1d0f94ead66e6d0f894ae010adb56b3f788de71/jiter-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecff0dc14f409599bbcafa7e470c00b80f17abc14d1405d38ab02e4b42e55b57", size = 379967 }, - { url = "https://files.pythonhosted.org/packages/ec/fb/096b34c553bb0bd3f2289d5013dcad6074948b8d55212aa13a10d44c5326/jiter-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd9fee7d0775ebaba131f7ca2e2d83839a62ad65e8e02fe2bd8fc975cedeb9e", size = 389252 }, - { url = "https://files.pythonhosted.org/packages/17/61/beea645c0bf398ced8b199e377b61eb999d8e46e053bb285c91c3d3eaab0/jiter-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14601dcac4889e0a1c75ccf6a0e4baf70dbc75041e51bcf8d0e9274519df6887", size = 345490 }, - { url = "https://files.pythonhosted.org/packages/d5/df/834aa17ad5dcc3cf0118821da0a0cf1589ea7db9832589278553640366bc/jiter-0.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92249669925bc1c54fcd2ec73f70f2c1d6a817928480ee1c65af5f6b81cdf12d", size = 376991 }, - { url = "https://files.pythonhosted.org/packages/67/80/87d140399d382fb4ea5b3d56e7ecaa4efdca17cd7411ff904c1517855314/jiter-0.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e725edd0929fa79f8349ab4ec7f81c714df51dc4e991539a578e5018fa4a7152", size = 510822 }, - { url = "https://files.pythonhosted.org/packages/5c/37/3394bb47bac1ad2cb0465601f86828a0518d07828a650722e55268cdb7e6/jiter-0.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf55846c7b7a680eebaf9c3c48d630e1bf51bdf76c68a5f654b8524335b0ad29", size = 503730 }, - { url = "https://files.pythonhosted.org/packages/f9/e2/253fc1fa59103bb4e3aa0665d6ceb1818df1cd7bf3eb492c4dad229b1cd4/jiter-0.8.2-cp312-cp312-win32.whl", hash = "sha256:7efe4853ecd3d6110301665a5178b9856be7e2a9485f49d91aa4d737ad2ae49e", size = 203375 }, - { url = "https://files.pythonhosted.org/packages/41/69/6d4bbe66b3b3b4507e47aa1dd5d075919ad242b4b1115b3f80eecd443687/jiter-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:83c0efd80b29695058d0fd2fa8a556490dbce9804eac3e281f373bbc99045f6c", size = 204740 }, - { url = "https://files.pythonhosted.org/packages/6c/b0/bfa1f6f2c956b948802ef5a021281978bf53b7a6ca54bb126fd88a5d014e/jiter-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca1f08b8e43dc3bd0594c992fb1fd2f7ce87f7bf0d44358198d6da8034afdf84", size = 301190 }, - { url = "https://files.pythonhosted.org/packages/a4/8f/396ddb4e292b5ea57e45ade5dc48229556b9044bad29a3b4b2dddeaedd52/jiter-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5672a86d55416ccd214c778efccf3266b84f87b89063b582167d803246354be4", size = 309334 }, - { url = "https://files.pythonhosted.org/packages/7f/68/805978f2f446fa6362ba0cc2e4489b945695940656edd844e110a61c98f8/jiter-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58dc9bc9767a1101f4e5e22db1b652161a225874d66f0e5cb8e2c7d1c438b587", size = 333918 }, - { url = "https://files.pythonhosted.org/packages/b3/99/0f71f7be667c33403fa9706e5b50583ae5106d96fab997fa7e2f38ee8347/jiter-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b2998606d6dadbb5ccda959a33d6a5e853252d921fec1792fc902351bb4e2c", size = 356057 }, - { url = "https://files.pythonhosted.org/packages/8d/50/a82796e421a22b699ee4d2ce527e5bcb29471a2351cbdc931819d941a167/jiter-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ab9a87f3784eb0e098f84a32670cfe4a79cb6512fd8f42ae3d0709f06405d18", size = 379790 }, - { url = "https://files.pythonhosted.org/packages/3c/31/10fb012b00f6d83342ca9e2c9618869ab449f1aa78c8f1b2193a6b49647c/jiter-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79aec8172b9e3c6d05fd4b219d5de1ac616bd8da934107325a6c0d0e866a21b6", size = 388285 }, - { url = "https://files.pythonhosted.org/packages/c8/81/f15ebf7de57be488aa22944bf4274962aca8092e4f7817f92ffa50d3ee46/jiter-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:711e408732d4e9a0208008e5892c2966b485c783cd2d9a681f3eb147cf36c7ef", size = 344764 }, - { url = "https://files.pythonhosted.org/packages/b3/e8/0cae550d72b48829ba653eb348cdc25f3f06f8a62363723702ec18e7be9c/jiter-0.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:653cf462db4e8c41995e33d865965e79641ef45369d8a11f54cd30888b7e6ff1", size = 376620 }, - { url = "https://files.pythonhosted.org/packages/b8/50/e5478ff9d82534a944c03b63bc217c5f37019d4a34d288db0f079b13c10b/jiter-0.8.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9c63eaef32b7bebac8ebebf4dabebdbc6769a09c127294db6babee38e9f405b9", size = 510402 }, - { url = "https://files.pythonhosted.org/packages/8e/1e/3de48bbebbc8f7025bd454cedc8c62378c0e32dd483dece5f4a814a5cb55/jiter-0.8.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:eb21aaa9a200d0a80dacc7a81038d2e476ffe473ffdd9c91eb745d623561de05", size = 503018 }, - { url = "https://files.pythonhosted.org/packages/d5/cd/d5a5501d72a11fe3e5fd65c78c884e5164eefe80077680533919be22d3a3/jiter-0.8.2-cp313-cp313-win32.whl", hash = "sha256:789361ed945d8d42850f919342a8665d2dc79e7e44ca1c97cc786966a21f627a", size = 203190 }, - { url = "https://files.pythonhosted.org/packages/51/bf/e5ca301245ba951447e3ad677a02a64a8845b185de2603dabd83e1e4b9c6/jiter-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ab7f43235d71e03b941c1630f4b6e3055d46b6cb8728a17663eaac9d8e83a865", size = 203551 }, - { url = "https://files.pythonhosted.org/packages/2f/3c/71a491952c37b87d127790dd7a0b1ebea0514c6b6ad30085b16bbe00aee6/jiter-0.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b426f72cd77da3fec300ed3bc990895e2dd6b49e3bfe6c438592a3ba660e41ca", size = 308347 }, - { url = "https://files.pythonhosted.org/packages/a0/4c/c02408042e6a7605ec063daed138e07b982fdb98467deaaf1c90950cf2c6/jiter-0.8.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2dd880785088ff2ad21ffee205e58a8c1ddabc63612444ae41e5e4b321b39c0", size = 342875 }, - { url = "https://files.pythonhosted.org/packages/91/61/c80ef80ed8a0a21158e289ef70dac01e351d929a1c30cb0f49be60772547/jiter-0.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:3ac9f578c46f22405ff7f8b1f5848fb753cc4b8377fbec8470a7dc3997ca7566", size = 202374 }, -] - -[[package]] -name = "joblib" -version = "1.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 }, -] - -[[package]] -name = "jsonpath-python" -version = "1.0.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/49/e582e50b0c54c1b47e714241c4a4767bf28758bf90212248aea8e1ce8516/jsonpath-python-1.0.6.tar.gz", hash = "sha256:dd5be4a72d8a2995c3f583cf82bf3cd1a9544cfdabf2d22595b67aff07349666", size = 18121 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/8a/d63959f4eff03893a00e6e63592e3a9f15b9266ed8e0275ab77f8c7dbc94/jsonpath_python-1.0.6-py3-none-any.whl", hash = "sha256:1e3b78df579f5efc23565293612decee04214609208a2335884b3ee3f786b575", size = 7552 }, -] - -[[package]] -name = "lancedb" -version = "0.19.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "deprecation" }, - { name = "overrides" }, - { name = "packaging" }, - { name = "pydantic" }, - { name = "pylance" }, - { name = "tqdm" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/ab/b9007906e66c432af21a22bd948483d1daf16bbfd5533c8ee76eafe1ee5d/lancedb-0.19.0-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:7efd53f10b4049d1254ed29420ef15f39d3d8fa3ae763b6d94f3f494ffe4fdb5", size = 28878834 }, - { url = "https://files.pythonhosted.org/packages/5e/32/13e73ae415e85879076b9709428541321dc756cc73de6f53d2abc5f359c0/lancedb-0.19.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:be9a970eb22fe8d3c8ca0e33a90ae16bafbe06b17031e7dfa595481ecc21e905", size = 26964436 }, - { url = "https://files.pythonhosted.org/packages/f5/18/989a19ddcf0ee783796291bf6da557ac81d2c28514d59d1d41cf5ee781fb/lancedb-0.19.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e4e7b651ee9a17d12bc22d04b530edf74e6872e38c9c3469c821b1b6ae2cf83", size = 30432010 }, - { url = "https://files.pythonhosted.org/packages/2d/77/cbf6cac7248da3f1457bff0d540bc4b43885406561f9701ad341fe6ba93d/lancedb-0.19.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa780699ad3da79b9bb4191da7a6f719d012bea84a0528ca090b89625d0686b4", size = 32857633 }, - { url = "https://files.pythonhosted.org/packages/53/2a/15fd4c3600476ecd1e36bfca06c80dc9e0ec0606bbf66155e07752d13fa9/lancedb-0.19.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ecc05ac68a7abcc4299dde821035275642492cbf136b0f59fa3101e20d3686f0", size = 30136673 }, - { url = "https://files.pythonhosted.org/packages/a2/43/b30338ccc61dfb95f3ee59918b61b0e545e67456ed89dc3155ac640e0b8e/lancedb-0.19.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:83bc158e7c543409d6eb90fee068a8621ebfdcd5ef2781639ff37fd1ec18ceda", size = 32335933 }, - { url = "https://files.pythonhosted.org/packages/4e/d0/2eb0d51aedc86b9bf27d214c9a4620eb4dfb78977dc1650fba30109ffb4c/lancedb-0.19.0-cp39-abi3-win_amd64.whl", hash = "sha256:1767b47427f6b0d9a1cdfb9fcd01ea18273050454af4f48dfa39e6aa886f7e06", size = 30000319 }, -] - -[[package]] -name = "logfire-api" -version = "3.5.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/ae/44e0104022f1b52386cb5dfbcaaaa2e2e8c9a2bb335a65f609ffe34c1387/logfire_api-3.5.3.tar.gz", hash = "sha256:2c15a0ad6f12f39003f62550a35f9247c29c87b466f7839836f346867442a55c", size = 44669 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/21/7f18dc3154caa396fb2161f89c980882111537a4261dbc7a6b902cf9fc58/logfire_api-3.5.3-py3-none-any.whl", hash = "sha256:abecb0d2e43f900c58582c333f261c8f9b187e72933cf3f5aec93685666f86ae", size = 73821 }, -] - -[[package]] -name = "logging" -version = "0.4.9.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/93/4b/979db9e44be09f71e85c9c8cfc42f258adfb7d93ce01deed2788b2948919/logging-0.4.9.6.tar.gz", hash = "sha256:26f6b50773f085042d301085bd1bf5d9f3735704db9f37c1ce6d8b85c38f2417", size = 96029 } - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, -] - -[[package]] -name = "matplotlib-inline" -version = "0.1.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, -] - -[[package]] -name = "mistralai" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "eval-type-backport" }, - { name = "httpx" }, - { name = "jsonpath-python" }, - { name = "pydantic" }, - { name = "python-dateutil" }, - { name = "typing-inspect" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/16/9d/aba193fdfe0fc7403efa380189143d965becfb1bc7df3230e5c7664f8c53/mistralai-1.5.0.tar.gz", hash = "sha256:fd94bc93bc25aad9c6dd8005b1a0bc4ba1250c6b3fbf855a49936989cc6e5c0d", size = 131647 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/e7/7147c75c383a975c58c33f8e7ee7dbbb0e7390fbcb1ecd321f63e4c73efd/mistralai-1.5.0-py3-none-any.whl", hash = "sha256:9372537719f87bd6f9feef4747d0bf1f4fbe971f8c02945ca4b4bf3c94571c97", size = 271559 }, -] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, -] - -[[package]] -name = "numpy" -version = "2.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/90/8956572f5c4ae52201fdec7ba2044b2c882832dcec7d5d0922c9e9acf2de/numpy-2.2.3.tar.gz", hash = "sha256:dbdc15f0c81611925f382dfa97b3bd0bc2c1ce19d4fe50482cb0ddc12ba30020", size = 20262700 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/e1/1816d5d527fa870b260a1c2c5904d060caad7515637bd54f495a5ce13ccd/numpy-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbc6472e01952d3d1b2772b720428f8b90e2deea8344e854df22b0618e9cce71", size = 21232911 }, - { url = "https://files.pythonhosted.org/packages/29/46/9f25dc19b359f10c0e52b6bac25d3181eb1f4b4d04c9846a32cf5ea52762/numpy-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdfe0c22692a30cd830c0755746473ae66c4a8f2e7bd508b35fb3b6a0813d787", size = 14371955 }, - { url = "https://files.pythonhosted.org/packages/72/d7/de941296e6b09a5c81d3664ad912f1496a0ecdd2f403318e5e35604ff70f/numpy-2.2.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:e37242f5324ffd9f7ba5acf96d774f9276aa62a966c0bad8dae692deebec7716", size = 5410476 }, - { url = "https://files.pythonhosted.org/packages/36/ce/55f685995110f8a268fdca0f198c9a84fa87b39512830965cc1087af6391/numpy-2.2.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95172a21038c9b423e68be78fd0be6e1b97674cde269b76fe269a5dfa6fadf0b", size = 6945730 }, - { url = "https://files.pythonhosted.org/packages/4f/84/abdb9f6e22576d89c259401c3234d4755b322539491bbcffadc8bcb120d3/numpy-2.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b47c440210c5d1d67e1cf434124e0b5c395eee1f5806fdd89b553ed1acd0a3", size = 14350752 }, - { url = "https://files.pythonhosted.org/packages/e9/88/3870cfa9bef4dffb3a326507f430e6007eeac258ebeef6b76fc542aef66d/numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0391ea3622f5c51a2e29708877d56e3d276827ac5447d7f45e9bc4ade8923c52", size = 16399386 }, - { url = "https://files.pythonhosted.org/packages/02/10/3f629682dd0b457525c131945329c4e81e2dadeb11256e6ce4c9a1a6fb41/numpy-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f6b3dfc7661f8842babd8ea07e9897fe3d9b69a1d7e5fbb743e4160f9387833b", size = 15561826 }, - { url = "https://files.pythonhosted.org/packages/da/18/fd35673ba9751eba449d4ce5d24d94e3b612cdbfba79348da71488c0b7ac/numpy-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1ad78ce7f18ce4e7df1b2ea4019b5817a2f6a8a16e34ff2775f646adce0a5027", size = 18188593 }, - { url = "https://files.pythonhosted.org/packages/ce/4c/c0f897b580ea59484b4cc96a441fea50333b26675a60a1421bc912268b5f/numpy-2.2.3-cp310-cp310-win32.whl", hash = "sha256:5ebeb7ef54a7be11044c33a17b2624abe4307a75893c001a4800857956b41094", size = 6590421 }, - { url = "https://files.pythonhosted.org/packages/e5/5b/aaabbfc7060c5c8f0124c5deb5e114a3b413a548bbc64e372c5b5db36165/numpy-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:596140185c7fa113563c67c2e894eabe0daea18cf8e33851738c19f70ce86aeb", size = 12925667 }, - { url = "https://files.pythonhosted.org/packages/96/86/453aa3949eab6ff54e2405f9cb0c01f756f031c3dc2a6d60a1d40cba5488/numpy-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:16372619ee728ed67a2a606a614f56d3eabc5b86f8b615c79d01957062826ca8", size = 21237256 }, - { url = "https://files.pythonhosted.org/packages/20/c3/93ecceadf3e155d6a9e4464dd2392d8d80cf436084c714dc8535121c83e8/numpy-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5521a06a3148686d9269c53b09f7d399a5725c47bbb5b35747e1cb76326b714b", size = 14408049 }, - { url = "https://files.pythonhosted.org/packages/8d/29/076999b69bd9264b8df5e56f2be18da2de6b2a2d0e10737e5307592e01de/numpy-2.2.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:7c8dde0ca2f77828815fd1aedfdf52e59071a5bae30dac3b4da2a335c672149a", size = 5408655 }, - { url = "https://files.pythonhosted.org/packages/e2/a7/b14f0a73eb0fe77cb9bd5b44534c183b23d4229c099e339c522724b02678/numpy-2.2.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:77974aba6c1bc26e3c205c2214f0d5b4305bdc719268b93e768ddb17e3fdd636", size = 6949996 }, - { url = "https://files.pythonhosted.org/packages/72/2f/8063da0616bb0f414b66dccead503bd96e33e43685c820e78a61a214c098/numpy-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d42f9c36d06440e34226e8bd65ff065ca0963aeecada587b937011efa02cdc9d", size = 14355789 }, - { url = "https://files.pythonhosted.org/packages/e6/d7/3cd47b00b8ea95ab358c376cf5602ad21871410950bc754cf3284771f8b6/numpy-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2712c5179f40af9ddc8f6727f2bd910ea0eb50206daea75f58ddd9fa3f715bb", size = 16411356 }, - { url = "https://files.pythonhosted.org/packages/27/c0/a2379e202acbb70b85b41483a422c1e697ff7eee74db642ca478de4ba89f/numpy-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c8b0451d2ec95010d1db8ca733afc41f659f425b7f608af569711097fd6014e2", size = 15576770 }, - { url = "https://files.pythonhosted.org/packages/bc/63/a13ee650f27b7999e5b9e1964ae942af50bb25606d088df4229283eda779/numpy-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9b4a8148c57ecac25a16b0e11798cbe88edf5237b0df99973687dd866f05e1b", size = 18200483 }, - { url = "https://files.pythonhosted.org/packages/4c/87/e71f89935e09e8161ac9c590c82f66d2321eb163893a94af749dfa8a3cf8/numpy-2.2.3-cp311-cp311-win32.whl", hash = "sha256:1f45315b2dc58d8a3e7754fe4e38b6fce132dab284a92851e41b2b344f6441c5", size = 6588415 }, - { url = "https://files.pythonhosted.org/packages/b9/c6/cd4298729826af9979c5f9ab02fcaa344b82621e7c49322cd2d210483d3f/numpy-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f48ba6f6c13e5e49f3d3efb1b51c8193215c42ac82610a04624906a9270be6f", size = 12929604 }, - { url = "https://files.pythonhosted.org/packages/43/ec/43628dcf98466e087812142eec6d1c1a6c6bdfdad30a0aa07b872dc01f6f/numpy-2.2.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12c045f43b1d2915eca6b880a7f4a256f59d62df4f044788c8ba67709412128d", size = 20929458 }, - { url = "https://files.pythonhosted.org/packages/9b/c0/2f4225073e99a5c12350954949ed19b5d4a738f541d33e6f7439e33e98e4/numpy-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87eed225fd415bbae787f93a457af7f5990b92a334e346f72070bf569b9c9c95", size = 14115299 }, - { url = "https://files.pythonhosted.org/packages/ca/fa/d2c5575d9c734a7376cc1592fae50257ec95d061b27ee3dbdb0b3b551eb2/numpy-2.2.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:712a64103d97c404e87d4d7c47fb0c7ff9acccc625ca2002848e0d53288b90ea", size = 5145723 }, - { url = "https://files.pythonhosted.org/packages/eb/dc/023dad5b268a7895e58e791f28dc1c60eb7b6c06fcbc2af8538ad069d5f3/numpy-2.2.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a5ae282abe60a2db0fd407072aff4599c279bcd6e9a2475500fc35b00a57c532", size = 6678797 }, - { url = "https://files.pythonhosted.org/packages/3f/19/bcd641ccf19ac25abb6fb1dcd7744840c11f9d62519d7057b6ab2096eb60/numpy-2.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5266de33d4c3420973cf9ae3b98b54a2a6d53a559310e3236c4b2b06b9c07d4e", size = 14067362 }, - { url = "https://files.pythonhosted.org/packages/39/04/78d2e7402fb479d893953fb78fa7045f7deb635ec095b6b4f0260223091a/numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe", size = 16116679 }, - { url = "https://files.pythonhosted.org/packages/d0/a1/e90f7aa66512be3150cb9d27f3d9995db330ad1b2046474a13b7040dfd92/numpy-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:34c1b7e83f94f3b564b35f480f5652a47007dd91f7c839f404d03279cc8dd021", size = 15264272 }, - { url = "https://files.pythonhosted.org/packages/dc/b6/50bd027cca494de4fa1fc7bf1662983d0ba5f256fa0ece2c376b5eb9b3f0/numpy-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d8335b5f1b6e2bce120d55fb17064b0262ff29b459e8493d1785c18ae2553b8", size = 17880549 }, - { url = "https://files.pythonhosted.org/packages/96/30/f7bf4acb5f8db10a96f73896bdeed7a63373137b131ca18bd3dab889db3b/numpy-2.2.3-cp312-cp312-win32.whl", hash = "sha256:4d9828d25fb246bedd31e04c9e75714a4087211ac348cb39c8c5f99dbb6683fe", size = 6293394 }, - { url = "https://files.pythonhosted.org/packages/42/6e/55580a538116d16ae7c9aa17d4edd56e83f42126cb1dfe7a684da7925d2c/numpy-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d", size = 12626357 }, - { url = "https://files.pythonhosted.org/packages/0e/8b/88b98ed534d6a03ba8cddb316950fe80842885709b58501233c29dfa24a9/numpy-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bfdb06b395385ea9b91bf55c1adf1b297c9fdb531552845ff1d3ea6e40d5aba", size = 20916001 }, - { url = "https://files.pythonhosted.org/packages/d9/b4/def6ec32c725cc5fbd8bdf8af80f616acf075fe752d8a23e895da8c67b70/numpy-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23c9f4edbf4c065fddb10a4f6e8b6a244342d95966a48820c614891e5059bb50", size = 14130721 }, - { url = "https://files.pythonhosted.org/packages/20/60/70af0acc86495b25b672d403e12cb25448d79a2b9658f4fc45e845c397a8/numpy-2.2.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:a0c03b6be48aaf92525cccf393265e02773be8fd9551a2f9adbe7db1fa2b60f1", size = 5130999 }, - { url = "https://files.pythonhosted.org/packages/2e/69/d96c006fb73c9a47bcb3611417cf178049aae159afae47c48bd66df9c536/numpy-2.2.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:2376e317111daa0a6739e50f7ee2a6353f768489102308b0d98fcf4a04f7f3b5", size = 6665299 }, - { url = "https://files.pythonhosted.org/packages/5a/3f/d8a877b6e48103733ac224ffa26b30887dc9944ff95dffdfa6c4ce3d7df3/numpy-2.2.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fb62fe3d206d72fe1cfe31c4a1106ad2b136fcc1606093aeab314f02930fdf2", size = 14064096 }, - { url = "https://files.pythonhosted.org/packages/e4/43/619c2c7a0665aafc80efca465ddb1f260287266bdbdce517396f2f145d49/numpy-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52659ad2534427dffcc36aac76bebdd02b67e3b7a619ac67543bc9bfe6b7cdb1", size = 16114758 }, - { url = "https://files.pythonhosted.org/packages/d9/79/ee4fe4f60967ccd3897aa71ae14cdee9e3c097e3256975cc9575d393cb42/numpy-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b416af7d0ed3271cad0f0a0d0bee0911ed7eba23e66f8424d9f3dfcdcae1304", size = 15259880 }, - { url = "https://files.pythonhosted.org/packages/fb/c8/8b55cf05db6d85b7a7d414b3d1bd5a740706df00bfa0824a08bf041e52ee/numpy-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1402da8e0f435991983d0a9708b779f95a8c98c6b18a171b9f1be09005e64d9d", size = 17876721 }, - { url = "https://files.pythonhosted.org/packages/21/d6/b4c2f0564b7dcc413117b0ffbb818d837e4b29996b9234e38b2025ed24e7/numpy-2.2.3-cp313-cp313-win32.whl", hash = "sha256:136553f123ee2951bfcfbc264acd34a2fc2f29d7cdf610ce7daf672b6fbaa693", size = 6290195 }, - { url = "https://files.pythonhosted.org/packages/97/e7/7d55a86719d0de7a6a597949f3febefb1009435b79ba510ff32f05a8c1d7/numpy-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5b732c8beef1d7bc2d9e476dbba20aaff6167bf205ad9aa8d30913859e82884b", size = 12619013 }, - { url = "https://files.pythonhosted.org/packages/a6/1f/0b863d5528b9048fd486a56e0b97c18bf705e88736c8cea7239012119a54/numpy-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:435e7a933b9fda8126130b046975a968cc2d833b505475e588339e09f7672890", size = 20944621 }, - { url = "https://files.pythonhosted.org/packages/aa/99/b478c384f7a0a2e0736177aafc97dc9152fc036a3fdb13f5a3ab225f1494/numpy-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7678556eeb0152cbd1522b684dcd215250885993dd00adb93679ec3c0e6e091c", size = 14142502 }, - { url = "https://files.pythonhosted.org/packages/fb/61/2d9a694a0f9cd0a839501d362de2a18de75e3004576a3008e56bdd60fcdb/numpy-2.2.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2e8da03bd561504d9b20e7a12340870dfc206c64ea59b4cfee9fceb95070ee94", size = 5176293 }, - { url = "https://files.pythonhosted.org/packages/33/35/51e94011b23e753fa33f891f601e5c1c9a3d515448659b06df9d40c0aa6e/numpy-2.2.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:c9aa4496fd0e17e3843399f533d62857cef5900facf93e735ef65aa4bbc90ef0", size = 6691874 }, - { url = "https://files.pythonhosted.org/packages/ff/cf/06e37619aad98a9d03bd8d65b8e3041c3a639be0f5f6b0a0e2da544538d4/numpy-2.2.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4ca91d61a4bf61b0f2228f24bbfa6a9facd5f8af03759fe2a655c50ae2c6610", size = 14036826 }, - { url = "https://files.pythonhosted.org/packages/0c/93/5d7d19955abd4d6099ef4a8ee006f9ce258166c38af259f9e5558a172e3e/numpy-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deaa09cd492e24fd9b15296844c0ad1b3c976da7907e1c1ed3a0ad21dded6f76", size = 16096567 }, - { url = "https://files.pythonhosted.org/packages/af/53/d1c599acf7732d81f46a93621dab6aa8daad914b502a7a115b3f17288ab2/numpy-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:246535e2f7496b7ac85deffe932896a3577be7af8fb7eebe7146444680297e9a", size = 15242514 }, - { url = "https://files.pythonhosted.org/packages/53/43/c0f5411c7b3ea90adf341d05ace762dad8cb9819ef26093e27b15dd121ac/numpy-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:daf43a3d1ea699402c5a850e5313680ac355b4adc9770cd5cfc2940e7861f1bf", size = 17872920 }, - { url = "https://files.pythonhosted.org/packages/5b/57/6dbdd45ab277aff62021cafa1e15f9644a52f5b5fc840bc7591b4079fb58/numpy-2.2.3-cp313-cp313t-win32.whl", hash = "sha256:cf802eef1f0134afb81fef94020351be4fe1d6681aadf9c5e862af6602af64ef", size = 6346584 }, - { url = "https://files.pythonhosted.org/packages/97/9b/484f7d04b537d0a1202a5ba81c6f53f1846ae6c63c2127f8df869ed31342/numpy-2.2.3-cp313-cp313t-win_amd64.whl", hash = "sha256:aee2512827ceb6d7f517c8b85aa5d3923afe8fc7a57d028cffcd522f1c6fd082", size = 12706784 }, - { url = "https://files.pythonhosted.org/packages/0a/b5/a7839f5478be8f859cb880f13d90fcfe4b0ec7a9ebaff2bcc30d96760596/numpy-2.2.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3c2ec8a0f51d60f1e9c0c5ab116b7fc104b165ada3f6c58abf881cb2eb16044d", size = 21064244 }, - { url = "https://files.pythonhosted.org/packages/29/e8/5da32ffcaa7a72f7ecd82f90c062140a061eb823cb88e90279424e515cf4/numpy-2.2.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ed2cf9ed4e8ebc3b754d398cba12f24359f018b416c380f577bbae112ca52fc9", size = 6809418 }, - { url = "https://files.pythonhosted.org/packages/a8/a9/68aa7076c7656a7308a0f73d0a2ced8c03f282c9fd98fa7ce21c12634087/numpy-2.2.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39261798d208c3095ae4f7bc8eaeb3481ea8c6e03dc48028057d3cbdbdb8937e", size = 16215461 }, - { url = "https://files.pythonhosted.org/packages/17/7f/d322a4125405920401450118dbdc52e0384026bd669939484670ce8b2ab9/numpy-2.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4", size = 12839607 }, -] - -[[package]] -name = "ollama" -version = "0.4.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "httpx" }, - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b0/6d/dc77539c735bbed5d0c873fb029fb86aa9f0163df169b34152914331c369/ollama-0.4.7.tar.gz", hash = "sha256:891dcbe54f55397d82d289c459de0ea897e103b86a3f1fad0fdb1895922a75ff", size = 12843 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/83/c3ffac86906c10184c88c2e916460806b072a2cfe34cdcaf3a0c0e836d39/ollama-0.4.7-py3-none-any.whl", hash = "sha256:85505663cca67a83707be5fb3aeff0ea72e67846cea5985529d8eca4366564a1", size = 13210 }, -] - -[[package]] -name = "openai" -version = "1.63.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e6/1c/11b520deb71f9ea54ced3c52cd6a5f7131215deba63ad07f23982e328141/openai-1.63.2.tar.gz", hash = "sha256:aeabeec984a7d2957b4928ceaa339e2ead19c61cfcf35ae62b7c363368d26360", size = 356902 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/64/db3462b358072387b8e93e6e6a38d3c741a17b4a84171ef01d6c85c63f25/openai-1.63.2-py3-none-any.whl", hash = "sha256:1f38b27b5a40814c2b7d8759ec78110df58c4a614c25f182809ca52b080ff4d4", size = 472282 }, -] - -[[package]] -name = "openpyxl" -version = "3.1.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "et-xmlfile" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 }, -] - -[[package]] -name = "overrides" -version = "7.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, -] - -[[package]] -name = "packaging" -version = "24.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, -] - -[[package]] -name = "pandas" -version = "2.2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/70/c853aec59839bceed032d52010ff5f1b8d87dc3114b762e4ba2727661a3b/pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", size = 12580827 }, - { url = "https://files.pythonhosted.org/packages/99/f2/c4527768739ffa4469b2b4fff05aa3768a478aed89a2f271a79a40eee984/pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", size = 11303897 }, - { url = "https://files.pythonhosted.org/packages/ed/12/86c1747ea27989d7a4064f806ce2bae2c6d575b950be087837bdfcabacc9/pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", size = 66480908 }, - { url = "https://files.pythonhosted.org/packages/44/50/7db2cd5e6373ae796f0ddad3675268c8d59fb6076e66f0c339d61cea886b/pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", size = 13064210 }, - { url = "https://files.pythonhosted.org/packages/61/61/a89015a6d5536cb0d6c3ba02cebed51a95538cf83472975275e28ebf7d0c/pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", size = 16754292 }, - { url = "https://files.pythonhosted.org/packages/ce/0d/4cc7b69ce37fac07645a94e1d4b0880b15999494372c1523508511b09e40/pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", size = 14416379 }, - { url = "https://files.pythonhosted.org/packages/31/9e/6ebb433de864a6cd45716af52a4d7a8c3c9aaf3a98368e61db9e69e69a9c/pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", size = 11598471 }, - { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222 }, - { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274 }, - { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836 }, - { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505 }, - { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420 }, - { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457 }, - { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166 }, - { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 }, - { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 }, - { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 }, - { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 }, - { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 }, - { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 }, - { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 }, - { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 }, - { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 }, - { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 }, - { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 }, - { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 }, - { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 }, - { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 }, - { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 }, - { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 }, - { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 }, - { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 }, - { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 }, - { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, -] - -[[package]] -name = "parso" -version = "0.8.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, -] - -[[package]] -name = "pexpect" -version = "4.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ptyprocess" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, -] - -[[package]] -name = "prompt-toolkit" -version = "3.0.50" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, -] - -[[package]] -name = "psycopg2-binary" -version = "2.9.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/81/331257dbf2801cdb82105306042f7a1637cc752f65f2bb688188e0de5f0b/psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f", size = 3043397 }, - { url = "https://files.pythonhosted.org/packages/e7/9a/7f4f2f031010bbfe6a02b4a15c01e12eb6b9b7b358ab33229f28baadbfc1/psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906", size = 3274806 }, - { url = "https://files.pythonhosted.org/packages/e5/57/8ddd4b374fa811a0b0a0f49b6abad1cde9cb34df73ea3348cc283fcd70b4/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92", size = 2851361 }, - { url = "https://files.pythonhosted.org/packages/f9/66/d1e52c20d283f1f3a8e7e5c1e06851d432f123ef57b13043b4f9b21ffa1f/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007", size = 3080836 }, - { url = "https://files.pythonhosted.org/packages/a0/cb/592d44a9546aba78f8a1249021fe7c59d3afb8a0ba51434d6610cc3462b6/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0", size = 3264552 }, - { url = "https://files.pythonhosted.org/packages/64/33/c8548560b94b7617f203d7236d6cdf36fe1a5a3645600ada6efd79da946f/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4", size = 3019789 }, - { url = "https://files.pythonhosted.org/packages/b0/0e/c2da0db5bea88a3be52307f88b75eec72c4de62814cbe9ee600c29c06334/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1", size = 2871776 }, - { url = "https://files.pythonhosted.org/packages/15/d7/774afa1eadb787ddf41aab52d4c62785563e29949613c958955031408ae6/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5", size = 2820959 }, - { url = "https://files.pythonhosted.org/packages/5e/ed/440dc3f5991a8c6172a1cde44850ead0e483a375277a1aef7cfcec00af07/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5", size = 2919329 }, - { url = "https://files.pythonhosted.org/packages/03/be/2cc8f4282898306732d2ae7b7378ae14e8df3c1231b53579efa056aae887/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53", size = 2957659 }, - { url = "https://files.pythonhosted.org/packages/d0/12/fb8e4f485d98c570e00dad5800e9a2349cfe0f71a767c856857160d343a5/psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b", size = 1024605 }, - { url = "https://files.pythonhosted.org/packages/22/4f/217cd2471ecf45d82905dd09085e049af8de6cfdc008b6663c3226dc1c98/psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1", size = 1163817 }, - { url = "https://files.pythonhosted.org/packages/9c/8f/9feb01291d0d7a0a4c6a6bab24094135c2b59c6a81943752f632c75896d6/psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", size = 3043397 }, - { url = "https://files.pythonhosted.org/packages/15/30/346e4683532011561cd9c8dfeac6a8153dd96452fee0b12666058ab7893c/psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", size = 3274806 }, - { url = "https://files.pythonhosted.org/packages/66/6e/4efebe76f76aee7ec99166b6c023ff8abdc4e183f7b70913d7c047701b79/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", size = 2851370 }, - { url = "https://files.pythonhosted.org/packages/7f/fd/ff83313f86b50f7ca089b161b8e0a22bb3c319974096093cd50680433fdb/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", size = 3080780 }, - { url = "https://files.pythonhosted.org/packages/e6/c4/bfadd202dcda8333a7ccafdc51c541dbdfce7c2c7cda89fa2374455d795f/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", size = 3264583 }, - { url = "https://files.pythonhosted.org/packages/5d/f1/09f45ac25e704ac954862581f9f9ae21303cc5ded3d0b775532b407f0e90/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", size = 3019831 }, - { url = "https://files.pythonhosted.org/packages/9e/2e/9beaea078095cc558f215e38f647c7114987d9febfc25cb2beed7c3582a5/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", size = 2871822 }, - { url = "https://files.pythonhosted.org/packages/01/9e/ef93c5d93f3dc9fc92786ffab39e323b9aed066ba59fdc34cf85e2722271/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", size = 2820975 }, - { url = "https://files.pythonhosted.org/packages/a5/f0/049e9631e3268fe4c5a387f6fc27e267ebe199acf1bc1bc9cbde4bd6916c/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", size = 2919320 }, - { url = "https://files.pythonhosted.org/packages/dc/9a/bcb8773b88e45fb5a5ea8339e2104d82c863a3b8558fbb2aadfe66df86b3/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", size = 2957617 }, - { url = "https://files.pythonhosted.org/packages/e2/6b/144336a9bf08a67d217b3af3246abb1d027095dab726f0687f01f43e8c03/psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", size = 1024618 }, - { url = "https://files.pythonhosted.org/packages/61/69/3b3d7bd583c6d3cbe5100802efa5beacaacc86e37b653fc708bf3d6853b8/psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", size = 1163816 }, - { url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", size = 3044771 }, - { url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", size = 3275336 }, - { url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", size = 2851637 }, - { url = "https://files.pythonhosted.org/packages/0b/b1/cfedc0e0e6f9ad61f8657fd173b2f831ce261c02a08c0b09c652b127d813/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", size = 3082097 }, - { url = "https://files.pythonhosted.org/packages/18/ed/0a8e4153c9b769f59c02fb5e7914f20f0b2483a19dae7bf2db54b743d0d0/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", size = 3264776 }, - { url = "https://files.pythonhosted.org/packages/10/db/d09da68c6a0cdab41566b74e0a6068a425f077169bed0946559b7348ebe9/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", size = 3020968 }, - { url = "https://files.pythonhosted.org/packages/94/28/4d6f8c255f0dfffb410db2b3f9ac5218d959a66c715c34cac31081e19b95/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", size = 2872334 }, - { url = "https://files.pythonhosted.org/packages/05/f7/20d7bf796593c4fea95e12119d6cc384ff1f6141a24fbb7df5a668d29d29/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", size = 2822722 }, - { url = "https://files.pythonhosted.org/packages/4d/e4/0c407ae919ef626dbdb32835a03b6737013c3cc7240169843965cada2bdf/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", size = 2920132 }, - { url = "https://files.pythonhosted.org/packages/2d/70/aa69c9f69cf09a01da224909ff6ce8b68faeef476f00f7ec377e8f03be70/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", size = 2959312 }, - { url = "https://files.pythonhosted.org/packages/d3/bd/213e59854fafe87ba47814bf413ace0dcee33a89c8c8c814faca6bc7cf3c/psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", size = 1025191 }, - { url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031 }, - { url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", size = 3044699 }, - { url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", size = 3275245 }, - { url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", size = 2851631 }, - { url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", size = 3082140 }, - { url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", size = 3264762 }, - { url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", size = 3020967 }, - { url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", size = 2872326 }, - { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712 }, - { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155 }, - { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356 }, - { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 }, -] - -[[package]] -name = "ptyprocess" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, -] - -[[package]] -name = "pure-eval" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, -] - -[[package]] -name = "pyarrow" -version = "19.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/01/fe1fd04744c2aa038e5a11c7a4adb3d62bce09798695e54f7274b5977134/pyarrow-19.0.0.tar.gz", hash = "sha256:8d47c691765cf497aaeed4954d226568563f1b3b74ff61139f2d77876717084b", size = 1129096 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/02/1ad80ffd3c558916858a49c83b6e494a9d93009bbebc603cf0cb8263bea7/pyarrow-19.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:c318eda14f6627966997a7d8c374a87d084a94e4e38e9abbe97395c215830e0c", size = 30686262 }, - { url = "https://files.pythonhosted.org/packages/1b/f0/adab5f142eb8203db8bfbd3a816816e37a85423ae684567e7f3555658315/pyarrow-19.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:62ef8360ff256e960f57ce0299090fb86423afed5e46f18f1225f960e05aae3d", size = 32100005 }, - { url = "https://files.pythonhosted.org/packages/94/8b/e674083610e5efc48d2f205c568d842cdfdf683d12f9ff0d546e38757722/pyarrow-19.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2795064647add0f16563e57e3d294dbfc067b723f0fd82ecd80af56dad15f503", size = 41144815 }, - { url = "https://files.pythonhosted.org/packages/d5/fb/2726241a792b7f8a58789e5a63d1be9a5a4059206318fd0ff9485a578952/pyarrow-19.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a218670b26fb1bc74796458d97bcab072765f9b524f95b2fccad70158feb8b17", size = 42180380 }, - { url = "https://files.pythonhosted.org/packages/7d/09/7aef12446d8e7002dfc07bb7bc71f594c1d5844ca78b364a49f07efb65b1/pyarrow-19.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:66732e39eaa2247996a6b04c8aa33e3503d351831424cdf8d2e9a0582ac54b34", size = 40515021 }, - { url = "https://files.pythonhosted.org/packages/31/55/f05fc5608cc96060c2b24de505324d641888bd62d4eed2fa1dacd872a1e1/pyarrow-19.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:e675a3ad4732b92d72e4d24009707e923cab76b0d088e5054914f11a797ebe44", size = 42067488 }, - { url = "https://files.pythonhosted.org/packages/f0/01/097653cec7a944c16313cb748a326771133c142034b252076bd84743b98d/pyarrow-19.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:f094742275586cdd6b1a03655ccff3b24b2610c3af76f810356c4c71d24a2a6c", size = 25276726 }, - { url = "https://files.pythonhosted.org/packages/82/42/fba3a35bef5833bf88ed35e6a810dc1781236e1d4f808d2df824a7d21819/pyarrow-19.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8e3a839bf36ec03b4315dc924d36dcde5444a50066f1c10f8290293c0427b46a", size = 30711936 }, - { url = "https://files.pythonhosted.org/packages/88/7a/0da93a3eaaf251a30e32f3221e874263cdcd366c2cd6b7c05293aad91152/pyarrow-19.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ce42275097512d9e4e4a39aade58ef2b3798a93aa3026566b7892177c266f735", size = 32133182 }, - { url = "https://files.pythonhosted.org/packages/2f/df/fe43b1c50d3100d0de53f988344118bc20362d0de005f8a407454fa565f8/pyarrow-19.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9348a0137568c45601b031a8d118275069435f151cbb77e6a08a27e8125f59d4", size = 41145489 }, - { url = "https://files.pythonhosted.org/packages/45/bb/6f73b41b342a0342f2516a02db4aa97a4f9569cc35482a5c288090140cd4/pyarrow-19.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0144a712d990d60f7f42b7a31f0acaccf4c1e43e957f7b1ad58150d6f639c1", size = 42177823 }, - { url = "https://files.pythonhosted.org/packages/23/7b/f038a96f421e453a71bd7a0f78d62b1b2ae9bcac06ed51179ca532e6a0a2/pyarrow-19.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2a1a109dfda558eb011e5f6385837daffd920d54ca00669f7a11132d0b1e6042", size = 40530609 }, - { url = "https://files.pythonhosted.org/packages/b8/39/a2a6714b471c000e6dd6af4495dce00d7d1332351b8e3170dfb9f91dad1f/pyarrow-19.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:be686bf625aa7b9bada18defb3a3ea3981c1099697239788ff111d87f04cd263", size = 42081534 }, - { url = "https://files.pythonhosted.org/packages/6c/a3/8396fb06ca05d807e89980c177be26617aad15211ece3184e0caa730b8a6/pyarrow-19.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:239ca66d9a05844bdf5af128861af525e14df3c9591bcc05bac25918e650d3a2", size = 25281090 }, - { url = "https://files.pythonhosted.org/packages/bc/2e/152885f5ef421e80dae68b9c133ab261934f93a6d5e16b61d79c0ed597fb/pyarrow-19.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:a7bbe7109ab6198688b7079cbad5a8c22de4d47c4880d8e4847520a83b0d1b68", size = 30667964 }, - { url = "https://files.pythonhosted.org/packages/80/c2/08bbee9a8610a47c9a1466845f405baf53a639ddd947c5133d8ba13544b6/pyarrow-19.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:4624c89d6f777c580e8732c27bb8e77fd1433b89707f17c04af7635dd9638351", size = 32125039 }, - { url = "https://files.pythonhosted.org/packages/d2/56/06994df823212f5688d3c8bf4294928b12c9be36681872853655724d28c6/pyarrow-19.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b6d3ce4288793350dc2d08d1e184fd70631ea22a4ff9ea5c4ff182130249d9b", size = 41140729 }, - { url = "https://files.pythonhosted.org/packages/94/65/38ad577c98140a9db71e9e1e594b6adb58a7478a5afec6456a8ca2df7f70/pyarrow-19.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:450a7d27e840e4d9a384b5c77199d489b401529e75a3b7a3799d4cd7957f2f9c", size = 42202267 }, - { url = "https://files.pythonhosted.org/packages/b6/1f/966b722251a7354114ccbb71cf1a83922023e69efd8945ebf628a851ec4c/pyarrow-19.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a08e2a8a039a3f72afb67a6668180f09fddaa38fe0d21f13212b4aba4b5d2451", size = 40505858 }, - { url = "https://files.pythonhosted.org/packages/3b/5e/6bc81aa7fc9affc7d1c03b912fbcc984ca56c2a18513684da267715dab7b/pyarrow-19.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f43f5aef2a13d4d56adadae5720d1fed4c1356c993eda8b59dace4b5983843c1", size = 42084973 }, - { url = "https://files.pythonhosted.org/packages/53/c3/2f56da818b6a4758cbd514957c67bd0f078ebffa5390ee2e2bf0f9e8defc/pyarrow-19.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:2f672f5364b2d7829ef7c94be199bb88bf5661dd485e21d2d37de12ccb78a136", size = 25241976 }, - { url = "https://files.pythonhosted.org/packages/f5/b9/ba07ed3dd6b6e4f379b78e9c47c50c8886e07862ab7fa6339ac38622d755/pyarrow-19.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:cf3bf0ce511b833f7bc5f5bb3127ba731e97222023a444b7359f3a22e2a3b463", size = 30651291 }, - { url = "https://files.pythonhosted.org/packages/ad/10/0d304243c8277035298a68a70807efb76199c6c929bb3363c92ac9be6a0d/pyarrow-19.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:4d8b0c0de0a73df1f1bf439af1b60f273d719d70648e898bc077547649bb8352", size = 32100461 }, - { url = "https://files.pythonhosted.org/packages/8a/61/bcfc5182e11831bca3f849945b9b106e09fd10ded773dff466658e972a45/pyarrow-19.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92aff08e23d281c69835e4a47b80569242a504095ef6a6223c1f6bb8883431d", size = 41132491 }, - { url = "https://files.pythonhosted.org/packages/8e/87/2915a29049ec352dc69a967fbcbd76b0180319233de0daf8bd368df37099/pyarrow-19.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3b78eff5968a1889a0f3bc81ca57e1e19b75f664d9c61a42a604bf9d8402aae", size = 42192529 }, - { url = "https://files.pythonhosted.org/packages/48/18/44e5542b2707a8afaf78b5b88c608f261871ae77787eac07b7c679ca6f0f/pyarrow-19.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:b34d3bde38eba66190b215bae441646330f8e9da05c29e4b5dd3e41bde701098", size = 40495363 }, - { url = "https://files.pythonhosted.org/packages/ba/d6/5096deb7599bbd20bc2768058fe23bc725b88eb41bee58303293583a2935/pyarrow-19.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5418d4d0fab3a0ed497bad21d17a7973aad336d66ad4932a3f5f7480d4ca0c04", size = 42074075 }, - { url = "https://files.pythonhosted.org/packages/2c/df/e3c839c04c284c9ec3d62b02a8c452b795d9b07b04079ab91ce33484d4c5/pyarrow-19.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e82c3d5e44e969c217827b780ed8faf7ac4c53f934ae9238872e749fa531f7c9", size = 25239803 }, - { url = "https://files.pythonhosted.org/packages/6a/d3/a6d4088e906c7b5d47792256212606d2ae679046dc750eee0ae167338e5c/pyarrow-19.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:f208c3b58a6df3b239e0bb130e13bc7487ed14f39a9ff357b6415e3f6339b560", size = 30695401 }, - { url = "https://files.pythonhosted.org/packages/94/25/70040fd0e397dd1b937f459eaeeec942a76027357491dca0ada09d1322af/pyarrow-19.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:c751c1c93955b7a84c06794df46f1cec93e18610dcd5ab7d08e89a81df70a849", size = 32104680 }, - { url = "https://files.pythonhosted.org/packages/4e/f9/92783290cc0d80ca16d34b0c126305bfacca4b87dd889c8f16c6ef2a8fd7/pyarrow-19.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b903afaa5df66d50fc38672ad095806443b05f202c792694f3a604ead7c6ea6e", size = 41076754 }, - { url = "https://files.pythonhosted.org/packages/05/46/2c9870f50a495c72e2b8982ae29a9b1680707ea936edc0de444cec48f875/pyarrow-19.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22a4bc0937856263df8b94f2f2781b33dd7f876f787ed746608e06902d691a5", size = 42163133 }, - { url = "https://files.pythonhosted.org/packages/7b/2f/437922b902549228fb15814e8a26105bff2787ece466a8d886eb6699efad/pyarrow-19.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:5e8a28b918e2e878c918f6d89137386c06fe577cd08d73a6be8dafb317dc2d73", size = 40452210 }, - { url = "https://files.pythonhosted.org/packages/36/ef/1d7975053af9d106da973bac142d0d4da71b7550a3576cc3e0b3f444d21a/pyarrow-19.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:29cd86c8001a94f768f79440bf83fee23963af5e7bc68ce3a7e5f120e17edf89", size = 42077618 }, -] - -[[package]] -name = "pyasn1" -version = "0.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135 }, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1d/67/6afbf0d507f73c32d21084a79946bfcfca5fbc62a72057e9c23797a737c9/pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c", size = 310028 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/89/bc88a6711935ba795a679ea6ebee07e128050d6382eaa35a0a47c8032bdc/pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd", size = 181537 }, -] - -[[package]] -name = "pydantic" -version = "2.11.0a2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/71/dbe0b4f6b00e444c25c80e4fb2982be5df6552ea109ff2227c287ea9f21b/pydantic-2.11.0a2.tar.gz", hash = "sha256:5f89644a6cc2fab8ec2f7128e0b29a687e375624c3f8cb16d662d8865bab4c6c", size = 768863 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/06/127ca54e31537712c3266c72955e1cc6b7b063d0805d54e677d09b255de8/pydantic-2.11.0a2-py3-none-any.whl", hash = "sha256:3bda237b05daef87b2fa17b21cc5a77f016c54681f8549ba3ee4a90d71b197ed", size = 436784 }, -] - -[[package]] -name = "pydantic-ai" -version = "0.0.24" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic-ai-slim", extra = ["anthropic", "cohere", "groq", "mistral", "openai", "vertexai"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/10/8a/0a9ff28b5956077f162dec57e96eeb5ea322cb1bba9c85f02d008966e1b4/pydantic_ai-0.0.24.tar.gz", hash = "sha256:7af076cbdb5f4c6ac8fae5273f02b67130b69d1cdaed87a022f79f42449f83a9", size = 71314 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/94/e7dbc5acf86a24442d21df4b8453a267614c3a6d9cd52ce0c685de2d7d19/pydantic_ai-0.0.24-py3-none-any.whl", hash = "sha256:dc75f01be0218c2f46fe90a3d43ef4700d764bfbe6240fca9c18a4509f6b86cf", size = 9837 }, -] - -[[package]] -name = "pydantic-ai-slim" -version = "0.0.24" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "eval-type-backport" }, - { name = "griffe" }, - { name = "httpx" }, - { name = "logfire-api" }, - { name = "pydantic" }, - { name = "pydantic-graph" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/05/baff139032ecdb930f00760c652d782934bc63da57c2ecc1c48d4c9b8898/pydantic_ai_slim-0.0.24.tar.gz", hash = "sha256:c82d5ffed6d3a149e64701d0e4cfa09256a8ef9feb296281f908af19050d2bf1", size = 73812 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/15/715b629b0386f45345ac8a8e97f0d788ab7f728cd8bbe39f724e4c32773e/pydantic_ai_slim-0.0.24-py3-none-any.whl", hash = "sha256:7cd3aec6df572780323169a6dfa114640f76c2e2b21105efcd5cce2b459c7b17", size = 95271 }, -] - -[package.optional-dependencies] -anthropic = [ - { name = "anthropic" }, -] -cohere = [ - { name = "cohere" }, -] -groq = [ - { name = "groq" }, -] -mistral = [ - { name = "mistralai" }, -] -openai = [ - { name = "openai" }, -] -vertexai = [ - { name = "google-auth" }, - { name = "requests" }, -] - -[[package]] -name = "pydantic-core" -version = "2.29.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/79/f6/904edea98c98c09b8d618d619749af19a50ea5a71b9199ee2131a5a03dfb/pydantic_core-2.29.0.tar.gz", hash = "sha256:05cb49f30ee6fd4a554f14dc2a06538931c039f02329621d57ba00465613d28e", size = 417255 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/27/1e6beb77f3fb0c00a87a1c59a41ab1c986be582236248592c96b96c57701/pydantic_core-2.29.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:747410cab5a3a8421f45879fe3ac38f650627b874debc1789b8df609a4c99d64", size = 2038245 }, - { url = "https://files.pythonhosted.org/packages/23/82/d81ac9a227bcf5cd9a536d0613542f402d74b97014131ca50c64711e1acd/pydantic_core-2.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c06ce6eb6dfbe653425c6aee2c02d67a4eb18c31a3b1022748cfb33904bf6a1", size = 1885138 }, - { url = "https://files.pythonhosted.org/packages/87/99/c3af71a0cddd2a550d5d56c1f9df34b74d25ef62c0240f889cdc37782489/pydantic_core-2.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4ab8d727db1e346fc3ea24d25f715b36634f3f817600bd99ad740ecdea75c49", size = 1910926 }, - { url = "https://files.pythonhosted.org/packages/46/95/ac712ed8a83f25822899f0a0df6cc7622106b8d8fda3086718bf0b9ebf80/pydantic_core-2.29.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d4e5f402328c3903b1e64a7a6bb17ac211b4ef1831cb691ebc983ba71ff3c4be", size = 1997150 }, - { url = "https://files.pythonhosted.org/packages/e7/a9/82946362dc53b3c2ba2f84e92bdb79a6c44201fafccfb42d05620837d44d/pydantic_core-2.29.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f940cd3803999dc303d89f2a9502d0b3cf5322b9625e4c8ef1c221a816c885", size = 2131731 }, - { url = "https://files.pythonhosted.org/packages/86/33/44386a10c29463c96a077967efb4d94250f6e76d8cc29d5d1e69c256e705/pydantic_core-2.29.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:489efc8c83198b5dd9ede0dec585da4ec8d548a82dae451e2e6076645fb71fee", size = 2776741 }, - { url = "https://files.pythonhosted.org/packages/7d/bd/0a27e050af68b9e3673449a626bbd24989d2edbb1a1b6db4774ce9a6fa77/pydantic_core-2.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:061ee2c0c8a0a34f51fb5359dd57c27ecf9edaa8aca13a63d154d46bede4c91c", size = 2035785 }, - { url = "https://files.pythonhosted.org/packages/1e/35/bf6bde45af0bc03f1db8fe4682cfb7ea72d849c7fe8ece1439c86be71900/pydantic_core-2.29.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf95c495629f76385e5c1c5bbf75e354c015be3c80069f3f6c1a38ac3e112128", size = 2126860 }, - { url = "https://files.pythonhosted.org/packages/b6/ea/1c394c6d2732899cf7c5cc5bcbfb0f4c5641a0efbb571f99c3e16cd0ae81/pydantic_core-2.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc876229b523beb46883a7b58bdd6da19390a56417d84083254f90f56497ba74", size = 2072419 }, - { url = "https://files.pythonhosted.org/packages/2f/c8/9e06625b3ef36b148bcad3d5e7e3445efdf16285fe9d43934063e2c2dd22/pydantic_core-2.29.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d2d05f785bf0906ec135d405b8a26c96a38098c0d942ac1698613da110548f8e", size = 2251157 }, - { url = "https://files.pythonhosted.org/packages/7d/b3/e9815f22c22d190fa74167b0eeb5af5c254dfbdcd5542906c4b280c6d547/pydantic_core-2.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3e3767663d0a51507b711acc56b6a99254c48d19b889e4a12845f6df70666778", size = 2235206 }, - { url = "https://files.pythonhosted.org/packages/af/33/208ced529108cbd585dc65553cd00359a6740ff871bff9973331ae75c210/pydantic_core-2.29.0-cp310-cp310-win32.whl", hash = "sha256:07301b455b78316c85e332d1ac5219ca315239f920c261ebfe106809eba3b01b", size = 1927276 }, - { url = "https://files.pythonhosted.org/packages/8e/95/ba277da9729920d3e169966935d6ee67a276c796af96feae01c74146a10c/pydantic_core-2.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:3493dab9cf3ed2b4bf3b3664fd4f4c3099ea270adcc45a1921513207eb557976", size = 1987638 }, - { url = "https://files.pythonhosted.org/packages/25/dd/a51066d138f742e2e28a0f306463eff2e2c7258e15f8f201005d75cc61d1/pydantic_core-2.29.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:69f8936f367e59aeeb4d0ae3f1cae564a7b9be8d04c88cc22101c57c9f25ab6c", size = 2040826 }, - { url = "https://files.pythonhosted.org/packages/18/89/35543a77b34883107453657e9f43620cbe4f66a0995f723b101d05bbf1c4/pydantic_core-2.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a246d3980e6c08af0d618e19b91259e79d46a857b0cca82f10aea55f550b265", size = 1883327 }, - { url = "https://files.pythonhosted.org/packages/c2/7a/8b63417e996d38e87248b0f1180b503ac3680074df012b1e35679d9f984b/pydantic_core-2.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58c0393cf1ebdd6a300eef431bfc1192e8866b77d5f3c1b8f2979032ca2a5ad4", size = 1912271 }, - { url = "https://files.pythonhosted.org/packages/81/4e/3dc1f66d4f24b63ffff28eedb9d81ba366fc06af61e64ee784d26296004d/pydantic_core-2.29.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aa92661beb1336fe432bcbae173abe742f6d0f9c4373b5e974ef2188168707b", size = 2000479 }, - { url = "https://files.pythonhosted.org/packages/8e/90/acb9b7d59e4bf41021cf659951cef5ce8135d5517053ed2ad8ec2702f67c/pydantic_core-2.29.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3e29395683a28294df58822e118944c6a02cba26910bc933d5e86a87bc9d819", size = 2133267 }, - { url = "https://files.pythonhosted.org/packages/69/48/aca154a2098d7ca3d2918dffa14469ef1807bb83d1a239ef0cc7017680f9/pydantic_core-2.29.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:366ac244b2552f502814a16fc5d94ad2bb23eb062a3821b8b9ba5084ff2afe41", size = 2770521 }, - { url = "https://files.pythonhosted.org/packages/cd/93/01c953fe7f99236d9f8abf80503d5f0d574a9dbfa292d12a4ae66568498c/pydantic_core-2.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc3461a85b48faa8c0f3ab4d7ac5f909a80c7c9e2a3c795d42e96c3435819993", size = 2033852 }, - { url = "https://files.pythonhosted.org/packages/28/30/927fe1493bdd605d6f2fe7d3f7422be3396e3e9ec7b6bfb82a6a3381911e/pydantic_core-2.29.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e57de4d923a3568276ce7c8b6b24b1c0157d6a51ac65290203b77e2e20c0453d", size = 2128977 }, - { url = "https://files.pythonhosted.org/packages/44/6a/dcdc780fb0b97824208993a454433135d8577e97e156aeb5a1b354e0ae30/pydantic_core-2.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:05bed77fbce07eef62f9584b0552c2b35e0321f896b28067620dd18058c89e66", size = 2073638 }, - { url = "https://files.pythonhosted.org/packages/02/f6/6f37cb0f9dd621c0056ce0ff45ab1cde9d8ba0b32e15695ea5dec1807f23/pydantic_core-2.29.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:ba675f218ba689f2bfc980bf455772947cb331905800b054a94446d9e120c4f8", size = 2254289 }, - { url = "https://files.pythonhosted.org/packages/7c/46/f17a1a02bee33f4df858fe8de71b25c24f8c26f7c3410885237b93a47ced/pydantic_core-2.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6fa3dfbd761395e32ad7b2a099ba07f2caeccc26d979a67787844c437541f9c8", size = 2236760 }, - { url = "https://files.pythonhosted.org/packages/92/86/0fd8d41fe53ff1c079dc7c331829ba9b465f28d05733bee6fb32e8c6fd28/pydantic_core-2.29.0-cp311-cp311-win32.whl", hash = "sha256:04cdcafd875638aa325706e1736c7e8667d19b5de281148d7bac4de51dd7016e", size = 1928262 }, - { url = "https://files.pythonhosted.org/packages/08/68/5c68a99cab786d14bfd85aa711c8147965a109a7250e97aecc51708322cd/pydantic_core-2.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b74ef07ba7f9a0c84e594d8aeac289469d801687139a916a7b2b3ad5196ac01", size = 1985064 }, - { url = "https://files.pythonhosted.org/packages/77/a5/243de2c677958176be9890a502bd1297eb52cc9f07dd7c1fcfd0fff00380/pydantic_core-2.29.0-cp311-cp311-win_arm64.whl", hash = "sha256:dec2e35dab27a560bab25bcbff18b479de13b6f8432527449fd03f6b1b843ffd", size = 1899103 }, - { url = "https://files.pythonhosted.org/packages/77/49/2a7e4f9f770bcab564dc03d43052a43c4e52a150a9275f658a3c75a00fb2/pydantic_core-2.29.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d588c0a4225bfdc4909bbce37631e6838e210d2899978de9d95c4972bed4f8da", size = 2018130 }, - { url = "https://files.pythonhosted.org/packages/35/7d/d585f75ad9f8e7ed042dbe08c99d2ef69ee82f550fc93080a25f0892d768/pydantic_core-2.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b773a15a6992263a7e3f49c4b9784c7939e820cdde043fba8ddb4436ea96467", size = 1878298 }, - { url = "https://files.pythonhosted.org/packages/a2/9f/5ae8a350fb287364d433b727a7ff030d1f14b83f17189f367fe2dc5639af/pydantic_core-2.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:978fb22eeaf140515d34e4f34f40bf14b8e21c07788709e10f6ba25b29e537e6", size = 1897227 }, - { url = "https://files.pythonhosted.org/packages/46/37/0591eaa91f77e5497a9be19ec7ecd447de4b0f37cf0d7ad37001db26abab/pydantic_core-2.29.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a568a0e090441b2149aa5985b68a19e89d95b8adcdf963e17e9e64954149b68", size = 1980299 }, - { url = "https://files.pythonhosted.org/packages/cd/9f/541d2907af017113ecfe42f59d0335554a2ef850520f3344892c14902034/pydantic_core-2.29.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20c3fda734d977acf65a90df411dc2fa06ad31f30d7bbcd50e37858e23b7c6a5", size = 2121471 }, - { url = "https://files.pythonhosted.org/packages/f1/20/a05040154e3aa91c260c21997718693b948179c58f3ba7e951dd9fb8ffd4/pydantic_core-2.29.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55359359017d6f7eb04e054b1390b89d94e2f6d2946ed7b92ebe6dd481c582cf", size = 2700716 }, - { url = "https://files.pythonhosted.org/packages/a7/44/1b17f5dc13164ac2282ab26748ec1ec90419163d888574d0e7440a6bec5b/pydantic_core-2.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a50a8276de8d803f52cba3d61d5714b07ae1b8e95ea9b073ea3c9bed0a81e", size = 2042224 }, - { url = "https://files.pythonhosted.org/packages/4b/ac/0faee9213e5bc4c4af6a2815ae05263333760f2311385de5e0652f5a43b9/pydantic_core-2.29.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1ce0a52365771ec5337c36761a1b3ae44872d46bf67b7367acbf9120c36125f", size = 2118278 }, - { url = "https://files.pythonhosted.org/packages/1a/e3/dcce2327c34709f1c8c4e12a9b7650540fce0630a407da255b04e114a448/pydantic_core-2.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ebbdaf3d9830b2ebe7d7302cb4db37d206b0f82a9e0701da9e2375097d8708bb", size = 2058177 }, - { url = "https://files.pythonhosted.org/packages/33/61/55fb2c3c007f4a9e18197e41d6746150bb9a7ca3855a9f311ba5ad19a58b/pydantic_core-2.29.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:7411ad901151b4805abf79835b09915eb66eae6e5c8273f2cb82da2fcb9e2da5", size = 2230114 }, - { url = "https://files.pythonhosted.org/packages/c0/c1/093eb7d36e5ad4b69005ab680e18527799cb622e3469deb9a2ac4caf89bb/pydantic_core-2.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f957efe72976e2c2eacab23c81104eedd13f9c0ab2e73f3ade9c1d6fc02005c9", size = 2228186 }, - { url = "https://files.pythonhosted.org/packages/fb/f8/8d2f114b722fa18ab4e29b4cc8a24cf427a4129518dc883e537ada1dc6b5/pydantic_core-2.29.0-cp312-cp312-win32.whl", hash = "sha256:8f981b20e7d05eba911536adc43b09c2d16e599b149146fd082c692f054daeaf", size = 1909599 }, - { url = "https://files.pythonhosted.org/packages/5f/ae/998e4873bcb9f95c930349de4efc02de71e3fd7554f86dda69410365f054/pydantic_core-2.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:c64195be924b9a0658aef39e049afe10bf491c7897de84e2282152862a4afbf6", size = 1999702 }, - { url = "https://files.pythonhosted.org/packages/64/77/5441b9c31ffe49efb4a3e2458fa886befca4201cb2237d1320646cf78047/pydantic_core-2.29.0-cp312-cp312-win_arm64.whl", hash = "sha256:e37ff0c906f4931dbc6866f8b0c42613b80306772b9847f409086b7f9d762f65", size = 1894324 }, - { url = "https://files.pythonhosted.org/packages/3a/7b/9e21b3220e4ac8435d548d67ecb136f99ed3cf5dd3773f9d18994563c570/pydantic_core-2.29.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4c35461bdb979b5073e03db9e17ad552c1e79b87ffb6fc41c8826976ea7ab8a8", size = 2023244 }, - { url = "https://files.pythonhosted.org/packages/a9/80/ceae62aae8fc9cf4925eeb5e299993235986bba6a38c55af55067b6eda71/pydantic_core-2.29.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:09cbd22633b539e0dc3f5ee4f78e3d3a09b22d620e891f4d8bcbc86a61554e00", size = 1876494 }, - { url = "https://files.pythonhosted.org/packages/e1/ef/9463d0b3b549de45638fcdbad8dd43b85ef1d2001ac09525b80fdd9cc7d9/pydantic_core-2.29.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a72188f290bc97ea4c95e59194b024b58ef39e7e6ee53ee882231194ea35a02", size = 1901663 }, - { url = "https://files.pythonhosted.org/packages/8d/50/9959a025ea0a7663b7e0c9055c2add43b67de81fcaddafb62e91bcba37ff/pydantic_core-2.29.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54922d8b3b85623f2256f6a3daa3990c47c2ff3805c3660ce9fce97f455a424a", size = 1987237 }, - { url = "https://files.pythonhosted.org/packages/5c/e5/1bc652fde35f7caf1e0e8d9beb89de6e3daee45d8fbce1637a5ae5ef08ca/pydantic_core-2.29.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a58c6a92097c62bf3cad0baf44858a1a382b5ef1a2800f3d503a75f61300794", size = 2121954 }, - { url = "https://files.pythonhosted.org/packages/7f/cb/847727eb23b6f2d91f3f50e413181f1d5b3ff930bc1a90837a355b1f5e13/pydantic_core-2.29.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4643b48b19a0bedc4c0421d8488111feedf7bc7e044389988dd9bda3ef4b78a", size = 2701427 }, - { url = "https://files.pythonhosted.org/packages/db/cf/0f993819d46324134f8af0e466a932e6b3925553b101170bbe4f04ba5475/pydantic_core-2.29.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83008a96504be140e38a30a2f51584a942419b6d0a7214c84ba760685f4bf0b5", size = 2042209 }, - { url = "https://files.pythonhosted.org/packages/6b/b4/9470f68933206af897dcecec479cb6d653b2225fac576f0d2144d5b9f100/pydantic_core-2.29.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8085e8530eb6a231b2261cd03a4a67b129cb5a78ff3b16efc7b090688647014", size = 2123243 }, - { url = "https://files.pythonhosted.org/packages/dd/03/1cd776a47e2602cdaa95cf0fbe9f3fa442edabf7b8c9c14fc0e8e8e36892/pydantic_core-2.29.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:d612c46e0f1e03a3802b8cbb6da679a8ac6c97744c65df953e8ef36cc2919f5d", size = 2063509 }, - { url = "https://files.pythonhosted.org/packages/ed/f5/60b4ac2e2430cb8787e61d04c5ffbb901bc56769c33ddc71571eff2d594b/pydantic_core-2.29.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6a978c70e42dbb90c9dbea4429ff807cceeb7c4ef7e2a9e6942f22c28e1b27b1", size = 2239943 }, - { url = "https://files.pythonhosted.org/packages/b3/c5/dfb65e2f8734ce4bd36e38a95945725e897dff4c428a58a5e80390991c8c/pydantic_core-2.29.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:daa3de513ef07c89c284dfb5e18100e082629ebde7d427fa87e02ffba8f3872e", size = 2234489 }, - { url = "https://files.pythonhosted.org/packages/c5/ba/9062e319b965fcb51f1d213d94ac174ba0c4e30a5b0734c482617f1dc069/pydantic_core-2.29.0-cp313-cp313-win32.whl", hash = "sha256:d57243e26a1f995d0eca80b0f0db9e74b2f4ad65f279f26ec8df639b0e600675", size = 1916531 }, - { url = "https://files.pythonhosted.org/packages/43/78/ba1bfc367e6197e9f986479c2759ed3047367f2fbe0b09b3bd9265232be3/pydantic_core-2.29.0-cp313-cp313-win_amd64.whl", hash = "sha256:b585cc038992f6dc6431a354e0d8293548c548f6a2c2fbc223c80c32aab24aa9", size = 1998894 }, - { url = "https://files.pythonhosted.org/packages/e4/d8/6e270f4aba96895f172e79afcceff81a4bd3e9db999476141c72132a5ba6/pydantic_core-2.29.0-cp313-cp313-win_arm64.whl", hash = "sha256:d0bf68e9ab0176869ef7d62d88f408bbb1d2a110b6abdc6317af5994722f987d", size = 1900301 }, - { url = "https://files.pythonhosted.org/packages/f4/0b/6ef557823655ddfa9e86c4c75652ed47542c69327a57f5448e847addd205/pydantic_core-2.29.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a743617f622482c1ccbba82466e175687915fea994fdc83f4188233a7405207", size = 1838099 }, - { url = "https://files.pythonhosted.org/packages/2c/bb/e2e96832582cfb47fdf596377ad70d54c490eba3b656829d53688fc8d49e/pydantic_core-2.29.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c217212ea90ce79f5cccad8c953cea341d63a92bd240f0512e67a735c21a19a6", size = 2013722 }, - { url = "https://files.pythonhosted.org/packages/82/a3/aeff32afd2fcc2be65257a5b0a012658b178820466bd4874704f35892340/pydantic_core-2.29.0-cp313-cp313t-win_amd64.whl", hash = "sha256:00d6b6af6e1de398fc225b333288817555bfe9f05dd26af9ff1707543459d03c", size = 1969827 }, - { url = "https://files.pythonhosted.org/packages/9c/38/183a6b5c005c01c1d7a5b86258fbe0fb82da865ae82b34a40c7227c7886a/pydantic_core-2.29.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:09c160565ad434b785b64985bef1d4dc5776525ca57f64107e91ea9982db70b6", size = 2035190 }, - { url = "https://files.pythonhosted.org/packages/58/c7/c8e010e3df73ec9a4ebfd85b5871aac82875d3e03c5882c951efd3d53bf7/pydantic_core-2.29.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:20321fbdeec98de3dbf923e2e6dfbf2d926d072e3f1c4a24225acf87294e83db", size = 1877210 }, - { url = "https://files.pythonhosted.org/packages/de/2a/b7ba84410bf6a6aebd54be01ca230e2bb85645bfe1aa9ae2908616002e4a/pydantic_core-2.29.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d1ca8e0b3270f3e52041bea996e055dd656e8ba527ed19933d39265d5354bd7", size = 1906153 }, - { url = "https://files.pythonhosted.org/packages/c7/67/c7b1bfd11478454c5741da48c3358f0cf126abb421a77bda25c1fc1d7af5/pydantic_core-2.29.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd38661628e88ff5c7554b60575b58f6169eca736427a71271f18547104610e3", size = 2076165 }, - { url = "https://files.pythonhosted.org/packages/d9/b4/50a58e2cde99b5ca2ad56f8f4fa344cb7ac1daf8f816778b7dbf6bb14cfc/pydantic_core-2.29.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5efd58e083222c3e9404414bae9ce69fbed811a8dfdf62733e5c3106fc32501b", size = 2123305 }, - { url = "https://files.pythonhosted.org/packages/df/a4/5303294c7eabcb51995e351c0ee2bf733da733b6e6f4148fe6d234f6d6cf/pydantic_core-2.29.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:b2031357c4b661442cfbb54ad90f46af4db24c8dfa5adc43d3164616cc165137", size = 2068711 }, - { url = "https://files.pythonhosted.org/packages/d8/0b/02c69b4251c6d568bd91b3cb15167ec2f47754b3c7c91def641f7c26e20d/pydantic_core-2.29.0-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c0e2cb7d4fdb915b6c8fcedd2993e0129cd2e0d298a8df56ec6e89c67f122017", size = 2249482 }, - { url = "https://files.pythonhosted.org/packages/e2/34/ae91ff9ec4829a70bbb4b12c581fb3645e6d66913ff3b5738009d7941a81/pydantic_core-2.29.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:28717a77ed32987754da41c65005513820282fb2d94e6a0840a91ffbf7e8b50a", size = 2232551 }, - { url = "https://files.pythonhosted.org/packages/8e/ae/870d7143fa769c3046facc4a04dc9411f144b61b33016fbc669d72b9ac4c/pydantic_core-2.29.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:12f914125c9860deb7f4a47c1581d07544e4e093bf0d2ecb0dc3f69df7ff86fb", size = 2061836 }, -] - -[[package]] -name = "pydantic-graph" -version = "0.0.24" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "httpx" }, - { name = "logfire-api" }, - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/8f/ad685ecca0f610fa204e92f394d190bcab8c9392f7be8f1a83914244d36b/pydantic_graph-0.0.24.tar.gz", hash = "sha256:40550e2310002f411b1fc0636206222caa5459ede7c44da5c856b3526b3096cd", size = 13694 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/f6/2f4ef49e75fcb6070f440f05db2898612ada7f5d889209e98ede2fdb3d19/pydantic_graph-0.0.24-py3-none-any.whl", hash = "sha256:29d763cb77a2533bcbd07ff34fff36316617d1e62a926a7a15df59f6103fc74e", size = 16860 }, -] - -[[package]] -name = "pydantic-settings" -version = "2.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/73/7b/c58a586cd7d9ac66d2ee4ba60ca2d241fa837c02bca9bea80a9a8c3d22a9/pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93", size = 79920 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/46/93416fdae86d40879714f72956ac14df9c7b76f7d41a4d68aa9f71a0028b/pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd", size = 29718 }, -] - -[[package]] -name = "pygments" -version = "2.19.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, -] - -[[package]] -name = "pylance" -version = "0.23.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "pyarrow" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/44/baf24347317e4122a833db8c22e48d66259fced990acccc1857f6e603547/pylance-0.23.0-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:a77e2880cf4b9f4ea9f5bf5bba1ab9e93e28411fefd4de2127039c9a2fff2076", size = 35616653 }, - { url = "https://files.pythonhosted.org/packages/b7/fb/683256923be01b65b5c992992f5c976de5ddd9025ea20a835ba6cdc9fc88/pylance-0.23.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8736322e79e52df4a25ff87b3f93defa0e40ca70f2d335c696ba87b07cb5d1f5", size = 33035679 }, - { url = "https://files.pythonhosted.org/packages/f1/92/b07fccbf9951ce35d5a3b0989f463289fd0162a840c4f5bb8cb279a1a6d4/pylance-0.23.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e33f2601f085d03e485126fef9c67b6b6489e31c1bb5d1dcc489e1e0c508accb", size = 37052218 }, - { url = "https://files.pythonhosted.org/packages/dd/a8/348f5349f2e382b5d6147f3c4b5eac3fef17a5edcd6b234ff2a69c370e4b/pylance-0.23.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfa387fa1aa5bdb481f74d9fa3f65920944a77eb41018e38e6d25dd8ea25e79", size = 39066521 }, - { url = "https://files.pythonhosted.org/packages/19/be/71ad54973717ba24cdc6c4bf0af48a498718ae8051dab6a5a298c3adbfa0/pylance-0.23.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:95d3a66f29b0463431e85772057c56181f80f23151a33ae86722cdb1d1435be2", size = 36745305 }, - { url = "https://files.pythonhosted.org/packages/31/df/5aa7d20eebd69de7df7a64466516365ca70f3042937a5dd50738a0981a22/pylance-0.23.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1c0d648e1706caeaf55e66c26f1262e6efab63ef5ac05ce5c1e6a5a00d755c73", size = 38439865 }, - { url = "https://files.pythonhosted.org/packages/7f/9a/8bcbbaa357f23a8706b669b5880726c37ad4bb59df37359a4afd04319383/pylance-0.23.0-cp39-abi3-win_amd64.whl", hash = "sha256:e16db6e2fafdda1fa31cd8cd20f9afdaf6a6fbd4846bd90f65d6885a9e0e4917", size = 34658481 }, -] - -[[package]] -name = "python-box" -version = "7.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/f7/635eed8c500adf26208e86e985bbffb6ff039cd8950e3a4749ceca904218/python_box-7.3.2.tar.gz", hash = "sha256:028b9917129e67f311932d93347b8a4f1b500d7a5a2870ee3c035f4e7b19403b", size = 45771 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/ee/7d9a4bc3ac4e693018506fb21893f4ba253694fd31dd93f7195f57a6c8d0/python_box-7.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d136163294fd61a1554db7dd203f2e3035064798d30c17d67d948f0de5c572de", size = 1832073 }, - { url = "https://files.pythonhosted.org/packages/25/be/97c585206086f50175b72e31f092f0afb3de43013d040097c1febbc7a2ce/python_box-7.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d72e96547d8e2c2c333909826e9fae338d9a7e4cde07d5c6058cdd468432c0", size = 3954019 }, - { url = "https://files.pythonhosted.org/packages/34/6b/95d1f13ae2c2c72854f8e845385312693a67ba0b2c9499d57476d8394c61/python_box-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:3aa52e3b5cc50c80bb7ef4be3e41e81d095310f619454a7ffd61eef3209a6225", size = 1229788 }, - { url = "https://files.pythonhosted.org/packages/1e/3f/133619c00d8a9d4f86efd8626c0e4ec356c8b8dabac66da18dac5cfaf70c/python_box-7.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:32163b1cb151883de0da62b0cd3572610dc72ccf0762f2447baf1d2562e25bea", size = 1834122 }, - { url = "https://files.pythonhosted.org/packages/b7/52/51b6081562daa864847692536260200b337ccb4176d1e5f626ae48a7d493/python_box-7.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:064cb59b41e25aaf7dbd39efe53151a5f6797cc1cb3c68610f0f21a9d406d67e", size = 4305556 }, - { url = "https://files.pythonhosted.org/packages/d4/e2/6cdc8649381ae14def88c3e2e93d5b8b17a622a95896e0d1c92861270b7d/python_box-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:488f0fba9a6416c3334b602366dcd92825adb0811e07e03753dfcf0ed79cd6f7", size = 1232328 }, - { url = "https://files.pythonhosted.org/packages/45/68/0c2f289d8055d3e1b156ff258847f0e8f1010063e284cf5a612f09435575/python_box-7.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39009a2da5c20133718b24891a206592adbe09169856aedc450ad1600fc2e511", size = 1819681 }, - { url = "https://files.pythonhosted.org/packages/ce/5d/76b4d6d0e41edb676a229f032848a1ecea166890fa8d501513ea1a030f4d/python_box-7.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2a72e2f6fb97c7e472ff3272da207ecc615aa222e52e98352391428527c469", size = 4270424 }, - { url = "https://files.pythonhosted.org/packages/e4/6b/32484b2a3cd2fb5e5f56bfb53a4537d93a4d2014ccf7fc0c0017fa6f65e9/python_box-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9eead914b9fb7d98a1473f5027dcfe27d26b3a10ffa33b9ba22cf948a23cd280", size = 1211252 }, - { url = "https://files.pythonhosted.org/packages/2f/39/8bec609e93dbc5e0d3ea26cfb5af3ca78915f7a55ef5414713462fedeb59/python_box-7.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1dfc3b9b073f3d7cad1fa90de98eaaa684a494d0574bbc0666f74fa8307fd6b6", size = 1804675 }, - { url = "https://files.pythonhosted.org/packages/88/ae/baf3a8057d8129896a7e02619df43ea0d918fc5b2bb66eb6e2470595fbac/python_box-7.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca4685a7f764b5a71b6e08535ce2a96b7964bb63d8cb4df10f6bb7147b6c54b", size = 4265645 }, - { url = "https://files.pythonhosted.org/packages/43/90/72367e03033c11a5e82676ee389b572bf136647ff4e3081557392b37e1ad/python_box-7.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e143295f74d47a9ab24562ead2375c9be10629599b57f2e86717d3fff60f82a9", size = 1206740 }, - { url = "https://files.pythonhosted.org/packages/37/13/8a990c6e2b6cc12700dce16f3cb383324e6d9a30f604eca22a2fdf84c923/python_box-7.3.2-py3-none-any.whl", hash = "sha256:fd7d74d5a848623f93b5221fd9fb00b8c00ff0e130fa87f396277aa188659c92", size = 29479 }, -] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", size = 357324 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9", size = 247702 }, -] - -[[package]] -name = "python-dotenv" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, -] - -[[package]] -name = "pytz" -version = "2025.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/57/df1c9157c8d5a05117e455d66fd7cf6dbc46974f832b1058ed4856785d8a/pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e", size = 319617 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/38/ac33370d784287baa1c3d538978b5e2ea064d4c1b93ffbd12826c190dd10/pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57", size = 507930 }, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, - { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, - { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, - { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, - { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, - { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, - { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, - { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, - { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, -] - -[[package]] -name = "requests" -version = "2.32.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, -] - -[[package]] -name = "rich" -version = "13.9.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, -] - -[[package]] -name = "rsa" -version = "4.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/aa/65/7d973b89c4d2351d7fb232c2e452547ddfa243e93131e7cfa766da627b52/rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21", size = 29711 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", size = 34315 }, -] - -[[package]] -name = "scikit-learn" -version = "1.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "joblib" }, - { name = "numpy" }, - { name = "scipy" }, - { name = "threadpoolctl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9e/a5/4ae3b3a0755f7b35a280ac90b28817d1f380318973cff14075ab41ef50d9/scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e", size = 7068312 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/3a/f4597eb41049110b21ebcbb0bcb43e4035017545daa5eedcfeb45c08b9c5/scikit_learn-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d056391530ccd1e501056160e3c9673b4da4805eb67eb2bdf4e983e1f9c9204e", size = 12067702 }, - { url = "https://files.pythonhosted.org/packages/37/19/0423e5e1fd1c6ec5be2352ba05a537a473c1677f8188b9306097d684b327/scikit_learn-1.6.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0c8d036eb937dbb568c6242fa598d551d88fb4399c0344d95c001980ec1c7d36", size = 11112765 }, - { url = "https://files.pythonhosted.org/packages/70/95/d5cb2297a835b0f5fc9a77042b0a2d029866379091ab8b3f52cc62277808/scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8634c4bd21a2a813e0a7e3900464e6d593162a29dd35d25bdf0103b3fce60ed5", size = 12643991 }, - { url = "https://files.pythonhosted.org/packages/b7/91/ab3c697188f224d658969f678be86b0968ccc52774c8ab4a86a07be13c25/scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:775da975a471c4f6f467725dff0ced5c7ac7bda5e9316b260225b48475279a1b", size = 13497182 }, - { url = "https://files.pythonhosted.org/packages/17/04/d5d556b6c88886c092cc989433b2bab62488e0f0dafe616a1d5c9cb0efb1/scikit_learn-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:8a600c31592bd7dab31e1c61b9bbd6dea1b3433e67d264d17ce1017dbdce8002", size = 11125517 }, - { url = "https://files.pythonhosted.org/packages/6c/2a/e291c29670795406a824567d1dfc91db7b699799a002fdaa452bceea8f6e/scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33", size = 12102620 }, - { url = "https://files.pythonhosted.org/packages/25/92/ee1d7a00bb6b8c55755d4984fd82608603a3cc59959245068ce32e7fb808/scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d", size = 11116234 }, - { url = "https://files.pythonhosted.org/packages/30/cd/ed4399485ef364bb25f388ab438e3724e60dc218c547a407b6e90ccccaef/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2", size = 12592155 }, - { url = "https://files.pythonhosted.org/packages/a8/f3/62fc9a5a659bb58a03cdd7e258956a5824bdc9b4bb3c5d932f55880be569/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8", size = 13497069 }, - { url = "https://files.pythonhosted.org/packages/a1/a6/c5b78606743a1f28eae8f11973de6613a5ee87366796583fb74c67d54939/scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415", size = 11139809 }, - { url = "https://files.pythonhosted.org/packages/0a/18/c797c9b8c10380d05616db3bfb48e2a3358c767affd0857d56c2eb501caa/scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b", size = 12104516 }, - { url = "https://files.pythonhosted.org/packages/c4/b7/2e35f8e289ab70108f8cbb2e7a2208f0575dc704749721286519dcf35f6f/scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2", size = 11167837 }, - { url = "https://files.pythonhosted.org/packages/a4/f6/ff7beaeb644bcad72bcfd5a03ff36d32ee4e53a8b29a639f11bcb65d06cd/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f", size = 12253728 }, - { url = "https://files.pythonhosted.org/packages/29/7a/8bce8968883e9465de20be15542f4c7e221952441727c4dad24d534c6d99/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86", size = 13147700 }, - { url = "https://files.pythonhosted.org/packages/62/27/585859e72e117fe861c2079bcba35591a84f801e21bc1ab85bce6ce60305/scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52", size = 11110613 }, - { url = "https://files.pythonhosted.org/packages/2e/59/8eb1872ca87009bdcdb7f3cdc679ad557b992c12f4b61f9250659e592c63/scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322", size = 12010001 }, - { url = "https://files.pythonhosted.org/packages/9d/05/f2fc4effc5b32e525408524c982c468c29d22f828834f0625c5ef3d601be/scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1", size = 11096360 }, - { url = "https://files.pythonhosted.org/packages/c8/e4/4195d52cf4f113573fb8ebc44ed5a81bd511a92c0228889125fac2f4c3d1/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348", size = 12209004 }, - { url = "https://files.pythonhosted.org/packages/94/be/47e16cdd1e7fcf97d95b3cb08bde1abb13e627861af427a3651fcb80b517/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97", size = 13171776 }, - { url = "https://files.pythonhosted.org/packages/34/b0/ca92b90859070a1487827dbc672f998da95ce83edce1270fc23f96f1f61a/scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb", size = 11071865 }, - { url = "https://files.pythonhosted.org/packages/12/ae/993b0fb24a356e71e9a894e42b8a9eec528d4c70217353a1cd7a48bc25d4/scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236", size = 11955804 }, - { url = "https://files.pythonhosted.org/packages/d6/54/32fa2ee591af44507eac86406fa6bba968d1eb22831494470d0a2e4a1eb1/scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35", size = 11100530 }, - { url = "https://files.pythonhosted.org/packages/3f/58/55856da1adec655bdce77b502e94a267bf40a8c0b89f8622837f89503b5a/scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691", size = 12433852 }, - { url = "https://files.pythonhosted.org/packages/ff/4f/c83853af13901a574f8f13b645467285a48940f185b690936bb700a50863/scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f", size = 11337256 }, -] - -[[package]] -name = "scipy" -version = "1.15.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/df/ef233fff6838fe6f7840d69b5ef9f20d2b5c912a8727b21ebf876cb15d54/scipy-1.15.2-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a2ec871edaa863e8213ea5df811cd600734f6400b4af272e1c011e69401218e9", size = 38692502 }, - { url = "https://files.pythonhosted.org/packages/5c/20/acdd4efb8a68b842968f7bc5611b1aeb819794508771ad104de418701422/scipy-1.15.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6f223753c6ea76983af380787611ae1291e3ceb23917393079dcc746ba60cfb5", size = 30085508 }, - { url = "https://files.pythonhosted.org/packages/42/55/39cf96ca7126f1e78ee72a6344ebdc6702fc47d037319ad93221063e6cf4/scipy-1.15.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ecf797d2d798cf7c838c6d98321061eb3e72a74710e6c40540f0e8087e3b499e", size = 22359166 }, - { url = "https://files.pythonhosted.org/packages/51/48/708d26a4ab8a1441536bf2dfcad1df0ca14a69f010fba3ccbdfc02df7185/scipy-1.15.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:9b18aa747da280664642997e65aab1dd19d0c3d17068a04b3fe34e2559196cb9", size = 25112047 }, - { url = "https://files.pythonhosted.org/packages/dd/65/f9c5755b995ad892020381b8ae11f16d18616208e388621dfacc11df6de6/scipy-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87994da02e73549dfecaed9e09a4f9d58a045a053865679aeb8d6d43747d4df3", size = 35536214 }, - { url = "https://files.pythonhosted.org/packages/de/3c/c96d904b9892beec978562f64d8cc43f9cca0842e65bd3cd1b7f7389b0ba/scipy-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69ea6e56d00977f355c0f84eba69877b6df084516c602d93a33812aa04d90a3d", size = 37646981 }, - { url = "https://files.pythonhosted.org/packages/3d/74/c2d8a24d18acdeae69ed02e132b9bc1bb67b7bee90feee1afe05a68f9d67/scipy-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:888307125ea0c4466287191e5606a2c910963405ce9671448ff9c81c53f85f58", size = 37230048 }, - { url = "https://files.pythonhosted.org/packages/42/19/0aa4ce80eca82d487987eff0bc754f014dec10d20de2f66754fa4ea70204/scipy-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9412f5e408b397ff5641080ed1e798623dbe1ec0d78e72c9eca8992976fa65aa", size = 40010322 }, - { url = "https://files.pythonhosted.org/packages/d0/d2/f0683b7e992be44d1475cc144d1f1eeae63c73a14f862974b4db64af635e/scipy-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:b5e025e903b4f166ea03b109bb241355b9c42c279ea694d8864d033727205e65", size = 41233385 }, - { url = "https://files.pythonhosted.org/packages/40/1f/bf0a5f338bda7c35c08b4ed0df797e7bafe8a78a97275e9f439aceb46193/scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4", size = 38703651 }, - { url = "https://files.pythonhosted.org/packages/de/54/db126aad3874601048c2c20ae3d8a433dbfd7ba8381551e6f62606d9bd8e/scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1", size = 30102038 }, - { url = "https://files.pythonhosted.org/packages/61/d8/84da3fffefb6c7d5a16968fe5b9f24c98606b165bb801bb0b8bc3985200f/scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971", size = 22375518 }, - { url = "https://files.pythonhosted.org/packages/44/78/25535a6e63d3b9c4c90147371aedb5d04c72f3aee3a34451f2dc27c0c07f/scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655", size = 25142523 }, - { url = "https://files.pythonhosted.org/packages/e0/22/4b4a26fe1cd9ed0bc2b2cb87b17d57e32ab72c346949eaf9288001f8aa8e/scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e", size = 35491547 }, - { url = "https://files.pythonhosted.org/packages/32/ea/564bacc26b676c06a00266a3f25fdfe91a9d9a2532ccea7ce6dd394541bc/scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0", size = 37634077 }, - { url = "https://files.pythonhosted.org/packages/43/c2/bfd4e60668897a303b0ffb7191e965a5da4056f0d98acfb6ba529678f0fb/scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40", size = 37231657 }, - { url = "https://files.pythonhosted.org/packages/4a/75/5f13050bf4f84c931bcab4f4e83c212a36876c3c2244475db34e4b5fe1a6/scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462", size = 40035857 }, - { url = "https://files.pythonhosted.org/packages/b9/8b/7ec1832b09dbc88f3db411f8cdd47db04505c4b72c99b11c920a8f0479c3/scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737", size = 41217654 }, - { url = "https://files.pythonhosted.org/packages/4b/5d/3c78815cbab499610f26b5bae6aed33e227225a9fa5290008a733a64f6fc/scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd", size = 38756184 }, - { url = "https://files.pythonhosted.org/packages/37/20/3d04eb066b471b6e171827548b9ddb3c21c6bbea72a4d84fc5989933910b/scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301", size = 30163558 }, - { url = "https://files.pythonhosted.org/packages/a4/98/e5c964526c929ef1f795d4c343b2ff98634ad2051bd2bbadfef9e772e413/scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93", size = 22437211 }, - { url = "https://files.pythonhosted.org/packages/1d/cd/1dc7371e29195ecbf5222f9afeedb210e0a75057d8afbd942aa6cf8c8eca/scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20", size = 25232260 }, - { url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095 }, - { url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371 }, - { url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390 }, - { url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276 }, - { url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317 }, - { url = "https://files.pythonhosted.org/packages/53/40/09319f6e0f276ea2754196185f95cd191cb852288440ce035d5c3a931ea2/scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf", size = 38717587 }, - { url = "https://files.pythonhosted.org/packages/fe/c3/2854f40ecd19585d65afaef601e5e1f8dbf6758b2f95b5ea93d38655a2c6/scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37", size = 30100266 }, - { url = "https://files.pythonhosted.org/packages/dd/b1/f9fe6e3c828cb5930b5fe74cb479de5f3d66d682fa8adb77249acaf545b8/scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d", size = 22373768 }, - { url = "https://files.pythonhosted.org/packages/15/9d/a60db8c795700414c3f681908a2b911e031e024d93214f2d23c6dae174ab/scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb", size = 25154719 }, - { url = "https://files.pythonhosted.org/packages/37/3b/9bda92a85cd93f19f9ed90ade84aa1e51657e29988317fabdd44544f1dd4/scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27", size = 35163195 }, - { url = "https://files.pythonhosted.org/packages/03/5a/fc34bf1aa14dc7c0e701691fa8685f3faec80e57d816615e3625f28feb43/scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0", size = 37255404 }, - { url = "https://files.pythonhosted.org/packages/4a/71/472eac45440cee134c8a180dbe4c01b3ec247e0338b7c759e6cd71f199a7/scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32", size = 36860011 }, - { url = "https://files.pythonhosted.org/packages/01/b3/21f890f4f42daf20e4d3aaa18182dddb9192771cd47445aaae2e318f6738/scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d", size = 39657406 }, - { url = "https://files.pythonhosted.org/packages/0d/76/77cf2ac1f2a9cc00c073d49e1e16244e389dd88e2490c91d84e1e3e4d126/scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f", size = 40961243 }, - { url = "https://files.pythonhosted.org/packages/4c/4b/a57f8ddcf48e129e6054fa9899a2a86d1fc6b07a0e15c7eebff7ca94533f/scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9", size = 38870286 }, - { url = "https://files.pythonhosted.org/packages/0c/43/c304d69a56c91ad5f188c0714f6a97b9c1fed93128c691148621274a3a68/scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f", size = 30141634 }, - { url = "https://files.pythonhosted.org/packages/44/1a/6c21b45d2548eb73be9b9bff421aaaa7e85e22c1f9b3bc44b23485dfce0a/scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6", size = 22415179 }, - { url = "https://files.pythonhosted.org/packages/74/4b/aefac4bba80ef815b64f55da06f62f92be5d03b467f2ce3668071799429a/scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af", size = 25126412 }, - { url = "https://files.pythonhosted.org/packages/b1/53/1cbb148e6e8f1660aacd9f0a9dfa2b05e9ff1cb54b4386fe868477972ac2/scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274", size = 34952867 }, - { url = "https://files.pythonhosted.org/packages/2c/23/e0eb7f31a9c13cf2dca083828b97992dd22f8184c6ce4fec5deec0c81fcf/scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776", size = 36890009 }, - { url = "https://files.pythonhosted.org/packages/03/f3/e699e19cabe96bbac5189c04aaa970718f0105cff03d458dc5e2b6bd1e8c/scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828", size = 36545159 }, - { url = "https://files.pythonhosted.org/packages/af/f5/ab3838e56fe5cc22383d6fcf2336e48c8fe33e944b9037fbf6cbdf5a11f8/scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28", size = 39136566 }, - { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705 }, -] - -[[package]] -name = "simplejson" -version = "3.20.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/92/51b417685abd96b31308b61b9acce7ec50d8e1de8fbc39a7fd4962c60689/simplejson-3.20.1.tar.gz", hash = "sha256:e64139b4ec4f1f24c142ff7dcafe55a22b811a74d86d66560c8815687143037d", size = 85591 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/c4/627214fb418cd4a17fb0230ff0b6c3bb4a85cbb48dd69c85dcc3b85df828/simplejson-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e580aa65d5f6c3bf41b9b4afe74be5d5ddba9576701c107c772d936ea2b5043a", size = 93790 }, - { url = "https://files.pythonhosted.org/packages/15/ca/56a6a2a33cbcf330c4d71af3f827c47e4e0ba791e78f2642f3d1ab02ff31/simplejson-3.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a586ce4f78cec11f22fe55c5bee0f067e803aab9bad3441afe2181693b5ebb5", size = 75707 }, - { url = "https://files.pythonhosted.org/packages/a9/c8/3d92b67e03a3b6207d97202669f9454ed700b35ade9bd4428265a078fb6c/simplejson-3.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74a1608f9e6e8c27a4008d70a54270868306d80ed48c9df7872f9f4b8ac87808", size = 75700 }, - { url = "https://files.pythonhosted.org/packages/74/30/20001219d6fdca4aaa3974c96dfb6955a766b4e2cc950505a5b51fd050b0/simplejson-3.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03db8cb64154189a92a7786209f24e391644f3a3fa335658be2df2af1960b8d8", size = 138672 }, - { url = "https://files.pythonhosted.org/packages/21/47/50157810876c2a7ebbd6e6346ec25eda841fe061fecaa02538a7742a3d2a/simplejson-3.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eea7e2b7d858f6fdfbf0fe3cb846d6bd8a45446865bc09960e51f3d473c2271b", size = 146616 }, - { url = "https://files.pythonhosted.org/packages/95/60/8c97cdc93096437b0aca2745aca63c880fe2315fd7f6a6ce6edbb344a2ae/simplejson-3.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e66712b17d8425bb7ff8968d4c7c7fd5a2dd7bd63728b28356223c000dd2f91f", size = 134344 }, - { url = "https://files.pythonhosted.org/packages/bb/9e/da184f0e9bb3a5d7ffcde713bd41b4fe46cca56b6f24d9bd155fac56805a/simplejson-3.20.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2cc4f6486f9f515b62f5831ff1888886619b84fc837de68f26d919ba7bbdcbc", size = 138017 }, - { url = "https://files.pythonhosted.org/packages/31/db/00d1a8d9b036db98f678c8a3c69ed17d2894d1768d7a00576e787ad3e546/simplejson-3.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a3c2df555ee4016148fa192e2b9cd9e60bc1d40769366134882685e90aee2a1e", size = 140118 }, - { url = "https://files.pythonhosted.org/packages/52/21/57fc47eab8c1c73390b933a5ba9271f08e3e1ec83162c580357f28f5b97c/simplejson-3.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:78520f04b7548a5e476b5396c0847e066f1e0a4c0c5e920da1ad65e95f410b11", size = 140314 }, - { url = "https://files.pythonhosted.org/packages/ad/cc/7cfd78d1e0fa5e57350b98cfe77353b6dfa13dce21afa4060e1019223852/simplejson-3.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f4bd49ecde87b0fe9f55cc971449a32832bca9910821f7072bbfae1155eaa007", size = 148544 }, - { url = "https://files.pythonhosted.org/packages/63/26/1c894a1c2bd95dc8be0cf5a2fa73b0d173105b6ca18c90cb981ff10443d0/simplejson-3.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7eaae2b88eb5da53caaffdfa50e2e12022553949b88c0df4f9a9663609373f72", size = 141172 }, - { url = "https://files.pythonhosted.org/packages/93/27/0717dccc10cd9988dbf1314def52ab32678a95a95328bb37cafacf499400/simplejson-3.20.1-cp310-cp310-win32.whl", hash = "sha256:e836fb88902799eac8debc2b642300748f4860a197fa3d9ea502112b6bb8e142", size = 74181 }, - { url = "https://files.pythonhosted.org/packages/5f/af/593f896573f306519332d4287b1ab8b7b888c239bbd5159f7054d7055c2d/simplejson-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a19b552b212fc3b5b96fc5ce92333d4a9ac0a800803e1f17ebb16dac4be5", size = 75738 }, - { url = "https://files.pythonhosted.org/packages/76/59/74bc90d1c051bc2432c96b34bd4e8036875ab58b4fcbe4d6a5a76985f853/simplejson-3.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:325b8c107253d3217e89d7b50c71015b5b31e2433e6c5bf38967b2f80630a8ca", size = 92132 }, - { url = "https://files.pythonhosted.org/packages/71/c7/1970916e0c51794fff89f76da2f632aaf0b259b87753c88a8c409623d3e1/simplejson-3.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88a7baa8211089b9e58d78fbc1b0b322103f3f3d459ff16f03a36cece0d0fcf0", size = 74956 }, - { url = "https://files.pythonhosted.org/packages/c8/0d/98cc5909180463f1d75fac7180de62d4cdb4e82c4fef276b9e591979372c/simplejson-3.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:299b1007b8101d50d95bc0db1bf5c38dc372e85b504cf77f596462083ee77e3f", size = 74772 }, - { url = "https://files.pythonhosted.org/packages/e1/94/a30a5211a90d67725a3e8fcc1c788189f2ae2ed2b96b63ed15d0b7f5d6bb/simplejson-3.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ec618ed65caab48e81e3ed29586236a8e57daef792f1f3bb59504a7e98cd10", size = 143575 }, - { url = "https://files.pythonhosted.org/packages/ee/08/cdb6821f1058eb5db46d252de69ff7e6c53f05f1bae6368fe20d5b51d37e/simplejson-3.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2cdead1d3197f0ff43373cf4730213420523ba48697743e135e26f3d179f38", size = 153241 }, - { url = "https://files.pythonhosted.org/packages/4c/2d/ca3caeea0bdc5efc5503d5f57a2dfb56804898fb196dfada121323ee0ccb/simplejson-3.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3466d2839fdc83e1af42e07b90bc8ff361c4e8796cd66722a40ba14e458faddd", size = 141500 }, - { url = "https://files.pythonhosted.org/packages/e1/33/d3e0779d5c58245e7370c98eb969275af6b7a4a5aec3b97cbf85f09ad328/simplejson-3.20.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d492ed8e92f3a9f9be829205f44b1d0a89af6582f0cf43e0d129fa477b93fe0c", size = 144757 }, - { url = "https://files.pythonhosted.org/packages/54/53/2d93128bb55861b2fa36c5944f38da51a0bc6d83e513afc6f7838440dd15/simplejson-3.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f924b485537b640dc69434565463fd6fc0c68c65a8c6e01a823dd26c9983cf79", size = 144409 }, - { url = "https://files.pythonhosted.org/packages/99/4c/dac310a98f897ad3435b4bdc836d92e78f09e38c5dbf28211ed21dc59fa2/simplejson-3.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e8eacf6a3491bf76ea91a8d46726368a6be0eb94993f60b8583550baae9439e", size = 146082 }, - { url = "https://files.pythonhosted.org/packages/ee/22/d7ba958cfed39827335b82656b1c46f89678faecda9a7677b47e87b48ee6/simplejson-3.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d04bf90b4cea7c22d8b19091633908f14a096caa301b24c2f3d85b5068fb8", size = 154339 }, - { url = "https://files.pythonhosted.org/packages/b8/c8/b072b741129406a7086a0799c6f5d13096231bf35fdd87a0cffa789687fc/simplejson-3.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:69dd28d4ce38390ea4aaf212902712c0fd1093dc4c1ff67e09687c3c3e15a749", size = 147915 }, - { url = "https://files.pythonhosted.org/packages/6c/46/8347e61e9cf3db5342a42f7fd30a81b4f5cf85977f916852d7674a540907/simplejson-3.20.1-cp311-cp311-win32.whl", hash = "sha256:dfe7a9da5fd2a3499436cd350f31539e0a6ded5da6b5b3d422df016444d65e43", size = 73972 }, - { url = "https://files.pythonhosted.org/packages/01/85/b52f24859237b4e9d523d5655796d911ba3d46e242eb1959c45b6af5aedd/simplejson-3.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:896a6c04d7861d507d800da7642479c3547060bf97419d9ef73d98ced8258766", size = 75595 }, - { url = "https://files.pythonhosted.org/packages/8d/eb/34c16a1ac9ba265d024dc977ad84e1659d931c0a700967c3e59a98ed7514/simplejson-3.20.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f31c4a3a7ab18467ee73a27f3e59158255d1520f3aad74315edde7a940f1be23", size = 93100 }, - { url = "https://files.pythonhosted.org/packages/41/fc/2c2c007d135894971e6814e7c0806936e5bade28f8db4dd7e2a58b50debd/simplejson-3.20.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:884e6183d16b725e113b83a6fc0230152ab6627d4d36cb05c89c2c5bccfa7bc6", size = 75464 }, - { url = "https://files.pythonhosted.org/packages/0f/05/2b5ecb33b776c34bb5cace5de5d7669f9b60e3ca13c113037b2ca86edfbd/simplejson-3.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03d7a426e416fe0d3337115f04164cd9427eb4256e843a6b8751cacf70abc832", size = 75112 }, - { url = "https://files.pythonhosted.org/packages/fe/36/1f3609a2792f06cd4b71030485f78e91eb09cfd57bebf3116bf2980a8bac/simplejson-3.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:000602141d0bddfcff60ea6a6e97d5e10c9db6b17fd2d6c66199fa481b6214bb", size = 150182 }, - { url = "https://files.pythonhosted.org/packages/2f/b0/053fbda38b8b602a77a4f7829def1b4f316cd8deb5440a6d3ee90790d2a4/simplejson-3.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af8377a8af78226e82e3a4349efdde59ffa421ae88be67e18cef915e4023a595", size = 158363 }, - { url = "https://files.pythonhosted.org/packages/d1/4b/2eb84ae867539a80822e92f9be4a7200dffba609275faf99b24141839110/simplejson-3.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15c7de4c88ab2fbcb8781a3b982ef883696736134e20b1210bca43fb42ff1acf", size = 148415 }, - { url = "https://files.pythonhosted.org/packages/e0/bd/400b0bd372a5666addf2540c7358bfc3841b9ce5cdbc5cc4ad2f61627ad8/simplejson-3.20.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:455a882ff3f97d810709f7b620007d4e0aca8da71d06fc5c18ba11daf1c4df49", size = 152213 }, - { url = "https://files.pythonhosted.org/packages/50/12/143f447bf6a827ee9472693768dc1a5eb96154f8feb140a88ce6973a3cfa/simplejson-3.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fc0f523ce923e7f38eb67804bc80e0a028c76d7868500aa3f59225574b5d0453", size = 150048 }, - { url = "https://files.pythonhosted.org/packages/5e/ea/dd9b3e8e8ed710a66f24a22c16a907c9b539b6f5f45fd8586bd5c231444e/simplejson-3.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76461ec929282dde4a08061071a47281ad939d0202dc4e63cdd135844e162fbc", size = 151668 }, - { url = "https://files.pythonhosted.org/packages/99/af/ee52a8045426a0c5b89d755a5a70cc821815ef3c333b56fbcad33c4435c0/simplejson-3.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19c2da8c043607bde4d4ef3a6b633e668a7d2e3d56f40a476a74c5ea71949f", size = 158840 }, - { url = "https://files.pythonhosted.org/packages/68/db/ab32869acea6b5de7d75fa0dac07a112ded795d41eaa7e66c7813b17be95/simplejson-3.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2578bedaedf6294415197b267d4ef678fea336dd78ee2a6d2f4b028e9d07be3", size = 154212 }, - { url = "https://files.pythonhosted.org/packages/fa/7a/e3132d454977d75a3bf9a6d541d730f76462ebf42a96fea2621498166f41/simplejson-3.20.1-cp312-cp312-win32.whl", hash = "sha256:339f407373325a36b7fd744b688ba5bae0666b5d340ec6d98aebc3014bf3d8ea", size = 74101 }, - { url = "https://files.pythonhosted.org/packages/bc/5d/4e243e937fa3560107c69f6f7c2eed8589163f5ed14324e864871daa2dd9/simplejson-3.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:627d4486a1ea7edf1f66bb044ace1ce6b4c1698acd1b05353c97ba4864ea2e17", size = 75736 }, - { url = "https://files.pythonhosted.org/packages/c4/03/0f453a27877cb5a5fff16a975925f4119102cc8552f52536b9a98ef0431e/simplejson-3.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:71e849e7ceb2178344998cbe5ade101f1b329460243c79c27fbfc51c0447a7c3", size = 93109 }, - { url = "https://files.pythonhosted.org/packages/74/1f/a729f4026850cabeaff23e134646c3f455e86925d2533463420635ae54de/simplejson-3.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b63fdbab29dc3868d6f009a59797cefaba315fd43cd32ddd998ee1da28e50e29", size = 75475 }, - { url = "https://files.pythonhosted.org/packages/e2/14/50a2713fee8ff1f8d655b1a14f4a0f1c0c7246768a1b3b3d12964a4ed5aa/simplejson-3.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1190f9a3ce644fd50ec277ac4a98c0517f532cfebdcc4bd975c0979a9f05e1fb", size = 75112 }, - { url = "https://files.pythonhosted.org/packages/45/86/ea9835abb646755140e2d482edc9bc1e91997ed19a59fd77ae4c6a0facea/simplejson-3.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1336ba7bcb722ad487cd265701ff0583c0bb6de638364ca947bb84ecc0015d1", size = 150245 }, - { url = "https://files.pythonhosted.org/packages/12/b4/53084809faede45da829fe571c65fbda8479d2a5b9c633f46b74124d56f5/simplejson-3.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e975aac6a5acd8b510eba58d5591e10a03e3d16c1cf8a8624ca177491f7230f0", size = 158465 }, - { url = "https://files.pythonhosted.org/packages/a9/7d/d56579468d1660b3841e1f21c14490d103e33cf911886b22652d6e9683ec/simplejson-3.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a6dd11ee282937ad749da6f3b8d87952ad585b26e5edfa10da3ae2536c73078", size = 148514 }, - { url = "https://files.pythonhosted.org/packages/19/e3/874b1cca3d3897b486d3afdccc475eb3a09815bf1015b01cf7fcb52a55f0/simplejson-3.20.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab980fcc446ab87ea0879edad41a5c28f2d86020014eb035cf5161e8de4474c6", size = 152262 }, - { url = "https://files.pythonhosted.org/packages/32/84/f0fdb3625292d945c2bd13a814584603aebdb38cfbe5fe9be6b46fe598c4/simplejson-3.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f5aee2a4cb6b146bd17333ac623610f069f34e8f31d2f4f0c1a2186e50c594f0", size = 150164 }, - { url = "https://files.pythonhosted.org/packages/95/51/6d625247224f01eaaeabace9aec75ac5603a42f8ebcce02c486fbda8b428/simplejson-3.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:652d8eecbb9a3b6461b21ec7cf11fd0acbab144e45e600c817ecf18e4580b99e", size = 151795 }, - { url = "https://files.pythonhosted.org/packages/7f/d9/bb921df6b35be8412f519e58e86d1060fddf3ad401b783e4862e0a74c4c1/simplejson-3.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8c09948f1a486a89251ee3a67c9f8c969b379f6ffff1a6064b41fea3bce0a112", size = 159027 }, - { url = "https://files.pythonhosted.org/packages/03/c5/5950605e4ad023a6621cf4c931b29fd3d2a9c1f36be937230bfc83d7271d/simplejson-3.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cbbd7b215ad4fc6f058b5dd4c26ee5c59f72e031dfda3ac183d7968a99e4ca3a", size = 154380 }, - { url = "https://files.pythonhosted.org/packages/66/ad/b74149557c5ec1e4e4d55758bda426f5d2ec0123cd01a53ae63b8de51fa3/simplejson-3.20.1-cp313-cp313-win32.whl", hash = "sha256:ae81e482476eaa088ef9d0120ae5345de924f23962c0c1e20abbdff597631f87", size = 74102 }, - { url = "https://files.pythonhosted.org/packages/db/a9/25282fdd24493e1022f30b7f5cdf804255c007218b2bfaa655bd7ad34b2d/simplejson-3.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:1b9fd15853b90aec3b1739f4471efbf1ac05066a2c7041bf8db821bb73cd2ddc", size = 75736 }, - { url = "https://files.pythonhosted.org/packages/4b/30/00f02a0a921556dd5a6db1ef2926a1bc7a8bbbfb1c49cfed68a275b8ab2b/simplejson-3.20.1-py3-none-any.whl", hash = "sha256:8a6c1bbac39fa4a79f83cbf1df6ccd8ff7069582a9fd8db1e52cea073bc2c697", size = 57121 }, -] - -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.38" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/08/9a90962ea72acd532bda71249a626344d855c4032603924b1b547694b837/sqlalchemy-2.0.38.tar.gz", hash = "sha256:e5a4d82bdb4bf1ac1285a68eab02d253ab73355d9f0fe725a97e1e0fa689decb", size = 9634782 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/10/16ed1503e18c0ec4e17a1819ff44604368607eed3db1e1d89d33269fe5b9/SQLAlchemy-2.0.38-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5e1d9e429028ce04f187a9f522818386c8b076723cdbe9345708384f49ebcec6", size = 2105151 }, - { url = "https://files.pythonhosted.org/packages/79/e5/2e9a0807cba2e625204d04bc39a18a47478e4bacae353ae8a7f2e784c341/SQLAlchemy-2.0.38-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b87a90f14c68c925817423b0424381f0e16d80fc9a1a1046ef202ab25b19a444", size = 2096335 }, - { url = "https://files.pythonhosted.org/packages/c1/97/8fa5cc6ed994eab611dcf0bc431161308f297c6f896f02a3ebb8d8aa06ea/SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:402c2316d95ed90d3d3c25ad0390afa52f4d2c56b348f212aa9c8d072a40eee5", size = 3078705 }, - { url = "https://files.pythonhosted.org/packages/a9/99/505feb8a9bc7027addaa2b312b8b306319cacbbd8a5231c4123ca1fa082a/SQLAlchemy-2.0.38-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6493bc0eacdbb2c0f0d260d8988e943fee06089cd239bd7f3d0c45d1657a70e2", size = 3086958 }, - { url = "https://files.pythonhosted.org/packages/39/26/fb7cef8198bb2627ac527b2cf6c576588db09856d634d4f1017280f8ab64/SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0561832b04c6071bac3aad45b0d3bb6d2c4f46a8409f0a7a9c9fa6673b41bc03", size = 3042798 }, - { url = "https://files.pythonhosted.org/packages/cc/7c/b6f9e0ee4e8e993fdce42477f9290b2b8373e672fb1dc0272179f0aeafb4/SQLAlchemy-2.0.38-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:49aa2cdd1e88adb1617c672a09bf4ebf2f05c9448c6dbeba096a3aeeb9d4d443", size = 3068318 }, - { url = "https://files.pythonhosted.org/packages/e6/22/903497e8202960c4249ffc340ec8de63f7fbdd4856bdfe854f617e124e90/SQLAlchemy-2.0.38-cp310-cp310-win32.whl", hash = "sha256:64aa8934200e222f72fcfd82ee71c0130a9c07d5725af6fe6e919017d095b297", size = 2077434 }, - { url = "https://files.pythonhosted.org/packages/20/a8/08f6ceccff5e0abb4a22e2e91c44b0e39911fda06b5d0c905dfc642de57a/SQLAlchemy-2.0.38-cp310-cp310-win_amd64.whl", hash = "sha256:c57b8e0841f3fce7b703530ed70c7c36269c6d180ea2e02e36b34cb7288c50c7", size = 2101608 }, - { url = "https://files.pythonhosted.org/packages/00/6c/9d3a638f297fce288ba12a4e5dbd08ef1841d119abee9300c100eba00217/SQLAlchemy-2.0.38-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf89e0e4a30714b357f5d46b6f20e0099d38b30d45fa68ea48589faf5f12f62d", size = 2106330 }, - { url = "https://files.pythonhosted.org/packages/0e/57/d5fdee56f418491267701965795805662b1744de40915d4764451390536d/SQLAlchemy-2.0.38-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8455aa60da49cb112df62b4721bd8ad3654a3a02b9452c783e651637a1f21fa2", size = 2096730 }, - { url = "https://files.pythonhosted.org/packages/42/84/205f423f8b28329c47237b7e130a7f93c234a49fab20b4534bd1ff26a06a/SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53c0d6a859b2db58332e0e6a921582a02c1677cc93d4cbb36fdf49709b327b2", size = 3215023 }, - { url = "https://files.pythonhosted.org/packages/77/41/94a558d47bffae5a361b0cfb3721324ea4154829dd5432f80bd4cfeecbc9/SQLAlchemy-2.0.38-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c4817dff8cef5697f5afe5fec6bc1783994d55a68391be24cb7d80d2dbc3a6", size = 3214991 }, - { url = "https://files.pythonhosted.org/packages/74/a0/cc3c030e7440bd17ce67c1875f50edb41d0ef17b9c76fbc290ef27bbe37f/SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9cea5b756173bb86e2235f2f871b406a9b9d722417ae31e5391ccaef5348f2c", size = 3151854 }, - { url = "https://files.pythonhosted.org/packages/24/ab/8ba2588c2eb1d092944551354d775ef4fc0250badede324d786a4395d10e/SQLAlchemy-2.0.38-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40e9cdbd18c1f84631312b64993f7d755d85a3930252f6276a77432a2b25a2f3", size = 3172158 }, - { url = "https://files.pythonhosted.org/packages/e0/73/2a3d6217e8e6abb553ed410ce5adc0bdec7effd684716f0fbaee5831d677/SQLAlchemy-2.0.38-cp311-cp311-win32.whl", hash = "sha256:cb39ed598aaf102251483f3e4675c5dd6b289c8142210ef76ba24aae0a8f8aba", size = 2076965 }, - { url = "https://files.pythonhosted.org/packages/a4/17/364a99c8c5698492c7fa40fc463bf388f05b0b03b74028828b71a79dc89d/SQLAlchemy-2.0.38-cp311-cp311-win_amd64.whl", hash = "sha256:f9d57f1b3061b3e21476b0ad5f0397b112b94ace21d1f439f2db472e568178ae", size = 2102169 }, - { url = "https://files.pythonhosted.org/packages/5a/f8/6d0424af1442c989b655a7b5f608bc2ae5e4f94cdf6df9f6054f629dc587/SQLAlchemy-2.0.38-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12d5b06a1f3aeccf295a5843c86835033797fea292c60e72b07bcb5d820e6dd3", size = 2104927 }, - { url = "https://files.pythonhosted.org/packages/25/80/fc06e65fca0a19533e2bfab633a5633ed8b6ee0b9c8d580acf84609ce4da/SQLAlchemy-2.0.38-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e036549ad14f2b414c725349cce0772ea34a7ab008e9cd67f9084e4f371d1f32", size = 2095317 }, - { url = "https://files.pythonhosted.org/packages/98/2d/5d66605f76b8e344813237dc160a01f03b987201e974b46056a7fb94a874/SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee3bee874cb1fadee2ff2b79fc9fc808aa638670f28b2145074538d4a6a5028e", size = 3244735 }, - { url = "https://files.pythonhosted.org/packages/73/8d/b0539e8dce90861efc38fea3eefb15a5d0cfeacf818614762e77a9f192f9/SQLAlchemy-2.0.38-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e185ea07a99ce8b8edfc788c586c538c4b1351007e614ceb708fd01b095ef33e", size = 3255581 }, - { url = "https://files.pythonhosted.org/packages/ac/a5/94e1e44bf5bdffd1782807fcc072542b110b950f0be53f49e68b5f5eca1b/SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b79ee64d01d05a5476d5cceb3c27b5535e6bb84ee0f872ba60d9a8cd4d0e6579", size = 3190877 }, - { url = "https://files.pythonhosted.org/packages/91/13/f08b09996dce945aec029c64f61c13b4788541ac588d9288e31e0d3d8850/SQLAlchemy-2.0.38-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afd776cf1ebfc7f9aa42a09cf19feadb40a26366802d86c1fba080d8e5e74bdd", size = 3217485 }, - { url = "https://files.pythonhosted.org/packages/13/8f/8cfe2ba5ba6d8090f4de0e658330c53be6b7bf430a8df1b141c2b180dcdf/SQLAlchemy-2.0.38-cp312-cp312-win32.whl", hash = "sha256:a5645cd45f56895cfe3ca3459aed9ff2d3f9aaa29ff7edf557fa7a23515a3725", size = 2075254 }, - { url = "https://files.pythonhosted.org/packages/c2/5c/e3c77fae41862be1da966ca98eec7fbc07cdd0b00f8b3e1ef2a13eaa6cca/SQLAlchemy-2.0.38-cp312-cp312-win_amd64.whl", hash = "sha256:1052723e6cd95312f6a6eff9a279fd41bbae67633415373fdac3c430eca3425d", size = 2100865 }, - { url = "https://files.pythonhosted.org/packages/21/77/caa875a1f5a8a8980b564cc0e6fee1bc992d62d29101252561d0a5e9719c/SQLAlchemy-2.0.38-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ecef029b69843b82048c5b347d8e6049356aa24ed644006c9a9d7098c3bd3bfd", size = 2100201 }, - { url = "https://files.pythonhosted.org/packages/f4/ec/94bb036ec78bf9a20f8010c807105da9152dd84f72e8c51681ad2f30b3fd/SQLAlchemy-2.0.38-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c8bcad7fc12f0cc5896d8e10fdf703c45bd487294a986903fe032c72201596b", size = 2090678 }, - { url = "https://files.pythonhosted.org/packages/7b/61/63ff1893f146e34d3934c0860209fdd3925c25ee064330e6c2152bacc335/SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0ef3f98175d77180ffdc623d38e9f1736e8d86b6ba70bff182a7e68bed7727", size = 3177107 }, - { url = "https://files.pythonhosted.org/packages/a9/4f/b933bea41a602b5f274065cc824fae25780ed38664d735575192490a021b/SQLAlchemy-2.0.38-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ac78898c50e2574e9f938d2e5caa8fe187d7a5b69b65faa1ea4648925b096", size = 3190435 }, - { url = "https://files.pythonhosted.org/packages/f5/23/9e654b4059e385988de08c5d3b38a369ea042f4c4d7c8902376fd737096a/SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9eb4fa13c8c7a2404b6a8e3772c17a55b1ba18bc711e25e4d6c0c9f5f541b02a", size = 3123648 }, - { url = "https://files.pythonhosted.org/packages/83/59/94c6d804e76ebc6412a08d2b086a8cb3e5a056cd61508e18ddaf3ec70100/SQLAlchemy-2.0.38-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5dba1cdb8f319084f5b00d41207b2079822aa8d6a4667c0f369fce85e34b0c86", size = 3151789 }, - { url = "https://files.pythonhosted.org/packages/b2/27/17f143013aabbe1256dce19061eafdce0b0142465ce32168cdb9a18c04b1/SQLAlchemy-2.0.38-cp313-cp313-win32.whl", hash = "sha256:eae27ad7580529a427cfdd52c87abb2dfb15ce2b7a3e0fc29fbb63e2ed6f8120", size = 2073023 }, - { url = "https://files.pythonhosted.org/packages/e2/3e/259404b03c3ed2e7eee4c179e001a07d9b61070334be91124cf4ad32eec7/SQLAlchemy-2.0.38-cp313-cp313-win_amd64.whl", hash = "sha256:b335a7c958bc945e10c522c069cd6e5804f4ff20f9a744dd38e748eb602cbbda", size = 2096908 }, - { url = "https://files.pythonhosted.org/packages/aa/e4/592120713a314621c692211eba034d09becaf6bc8848fabc1dc2a54d8c16/SQLAlchemy-2.0.38-py3-none-any.whl", hash = "sha256:63178c675d4c80def39f1febd625a6333f44c0ba269edd8a468b156394b27753", size = 1896347 }, -] - -[[package]] -name = "sqlalchemy-orm" -version = "1.2.10" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cached-property" }, - { name = "inflection" }, - { name = "sqlalchemy" }, - { name = "typing-inspect" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c8/98/b0451ae949f8b16287965d4fc4a180fae50a78d9b186300e135ec22e1883/sqlalchemy-orm-1.2.10.tar.gz", hash = "sha256:7ab46d2a54a429d4fd384df9a37ad639dc87ff93be5205ed649c5ca4dad164bb", size = 21846 } - -[[package]] -name = "stack-data" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asttokens" }, - { name = "executing" }, - { name = "pure-eval" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6b/be/af287da44f310088ea7c6b40f02d4f626a8903afbb678098ede01995b662/stack_data-0.5.1.tar.gz", hash = "sha256:95eb784942e861a3d80efd549ff9af6cf847d88343a12eb681d7157cfcb6e32b", size = 43435 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/57/dc/9367ef8074e2331706fbad14d749157341fbffd21339c43820e07664ec94/stack_data-0.5.1-py3-none-any.whl", hash = "sha256:5120731a18ba4c82cefcf84a945f6f3e62319ef413bfc210e32aca3a69310ba2", size = 24247 }, -] - -[[package]] -name = "starlette" -version = "0.45.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ff/fb/2984a686808b89a6781526129a4b51266f678b2d2b97ab2d325e56116df8/starlette-0.45.3.tar.gz", hash = "sha256:2cbcba2a75806f8a41c722141486f37c28e30a0921c5f6fe4346cb0dcee1302f", size = 2574076 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/61/f2b52e107b1fc8944b33ef56bf6ac4ebbe16d91b94d2b87ce013bf63fb84/starlette-0.45.3-py3-none-any.whl", hash = "sha256:dfb6d332576f136ec740296c7e8bb8c8a7125044e7c6da30744718880cdd059d", size = 71507 }, -] - -[[package]] -name = "threadpoolctl" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/55/b5148dcbf72f5cde221f8bfe3b6a540da7aa1842f6b491ad979a6c8b84af/threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107", size = 41936 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/2c/ffbf7a134b9ab11a67b0cf0726453cedd9c5043a4fe7a35d1cefa9a1bcfb/threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467", size = 18414 }, -] - -[[package]] -name = "tokenizers" -version = "0.21.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/20/41/c2be10975ca37f6ec40d7abd7e98a5213bb04f284b869c1a24e6504fd94d/tokenizers-0.21.0.tar.gz", hash = "sha256:ee0894bf311b75b0c03079f33859ae4b2334d675d4e93f5a4132e1eae2834fe4", size = 343021 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/5c/8b09607b37e996dc47e70d6a7b6f4bdd4e4d5ab22fe49d7374565c7fefaf/tokenizers-0.21.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3c4c93eae637e7d2aaae3d376f06085164e1660f89304c0ab2b1d08a406636b2", size = 2647461 }, - { url = "https://files.pythonhosted.org/packages/22/7a/88e58bb297c22633ed1c9d16029316e5b5ac5ee44012164c2edede599a5e/tokenizers-0.21.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:f53ea537c925422a2e0e92a24cce96f6bc5046bbef24a1652a5edc8ba975f62e", size = 2563639 }, - { url = "https://files.pythonhosted.org/packages/f7/14/83429177c19364df27d22bc096d4c2e431e0ba43e56c525434f1f9b0fd00/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b177fb54c4702ef611de0c069d9169f0004233890e0c4c5bd5508ae05abf193", size = 2903304 }, - { url = "https://files.pythonhosted.org/packages/7e/db/3433eab42347e0dc5452d8fcc8da03f638c9accffefe5a7c78146666964a/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b43779a269f4629bebb114e19c3fca0223296ae9fea8bb9a7a6c6fb0657ff8e", size = 2804378 }, - { url = "https://files.pythonhosted.org/packages/57/8b/7da5e6f89736c2ade02816b4733983fca1c226b0c42980b1ae9dc8fcf5cc/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aeb255802be90acfd363626753fda0064a8df06031012fe7d52fd9a905eb00e", size = 3095488 }, - { url = "https://files.pythonhosted.org/packages/4d/f6/5ed6711093dc2c04a4e03f6461798b12669bc5a17c8be7cce1240e0b5ce8/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b09dbeb7a8d73ee204a70f94fc06ea0f17dcf0844f16102b9f414f0b7463ba", size = 3121410 }, - { url = "https://files.pythonhosted.org/packages/81/42/07600892d48950c5e80505b81411044a2d969368cdc0d929b1c847bf6697/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:400832c0904f77ce87c40f1a8a27493071282f785724ae62144324f171377273", size = 3388821 }, - { url = "https://files.pythonhosted.org/packages/22/06/69d7ce374747edaf1695a4f61b83570d91cc8bbfc51ccfecf76f56ab4aac/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84ca973b3a96894d1707e189c14a774b701596d579ffc7e69debfc036a61a04", size = 3008868 }, - { url = "https://files.pythonhosted.org/packages/c8/69/54a0aee4d576045b49a0eb8bffdc495634309c823bf886042e6f46b80058/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eb7202d231b273c34ec67767378cd04c767e967fda12d4a9e36208a34e2f137e", size = 8975831 }, - { url = "https://files.pythonhosted.org/packages/f7/f3/b776061e4f3ebf2905ba1a25d90380aafd10c02d406437a8ba22d1724d76/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:089d56db6782a73a27fd8abf3ba21779f5b85d4a9f35e3b493c7bbcbbf0d539b", size = 8920746 }, - { url = "https://files.pythonhosted.org/packages/d8/ee/ce83d5ec8b6844ad4c3ecfe3333d58ecc1adc61f0878b323a15355bcab24/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:c87ca3dc48b9b1222d984b6b7490355a6fdb411a2d810f6f05977258400ddb74", size = 9161814 }, - { url = "https://files.pythonhosted.org/packages/18/07/3e88e65c0ed28fa93aa0c4d264988428eef3df2764c3126dc83e243cb36f/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4145505a973116f91bc3ac45988a92e618a6f83eb458f49ea0790df94ee243ff", size = 9357138 }, - { url = "https://files.pythonhosted.org/packages/15/b0/dc4572ca61555fc482ebc933f26cb407c6aceb3dc19c301c68184f8cad03/tokenizers-0.21.0-cp39-abi3-win32.whl", hash = "sha256:eb1702c2f27d25d9dd5b389cc1f2f51813e99f8ca30d9e25348db6585a97e24a", size = 2202266 }, - { url = "https://files.pythonhosted.org/packages/44/69/d21eb253fa91622da25585d362a874fa4710be600f0ea9446d8d0217cec1/tokenizers-0.21.0-cp39-abi3-win_amd64.whl", hash = "sha256:87841da5a25a3a5f70c102de371db120f41873b854ba65e52bccd57df5a3780c", size = 2389192 }, -] - -[[package]] -name = "tqdm" -version = "4.67.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, -] - -[[package]] -name = "traitlets" -version = "5.14.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, -] - -[[package]] -name = "tree-sitter" -version = "0.24.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/a2/698b9d31d08ad5558f8bfbfe3a0781bd4b1f284e89bde3ad18e05101a892/tree-sitter-0.24.0.tar.gz", hash = "sha256:abd95af65ca2f4f7eca356343391ed669e764f37748b5352946f00f7fc78e734", size = 168304 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/9a/bd627a02e41671af73222316e1fcf87772c7804dc2fba99405275eb1f3eb/tree_sitter-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f3f00feff1fc47a8e4863561b8da8f5e023d382dd31ed3e43cd11d4cae445445", size = 140890 }, - { url = "https://files.pythonhosted.org/packages/5b/9b/b1ccfb187f8be78e2116176a091a2f2abfd043a06d78f80c97c97f315b37/tree_sitter-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f9691be48d98c49ef8f498460278884c666b44129222ed6217477dffad5d4831", size = 134413 }, - { url = "https://files.pythonhosted.org/packages/01/39/e25b0042a049eb27e991133a7aa7c49bb8e49a8a7b44ca34e7e6353ba7ac/tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:098a81df9f89cf254d92c1cd0660a838593f85d7505b28249216661d87adde4a", size = 560427 }, - { url = "https://files.pythonhosted.org/packages/1c/59/4d132f1388da5242151b90acf32cc56af779bfba063923699ab28b276b62/tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b26bf9e958da6eb7e74a081aab9d9c7d05f9baeaa830dbb67481898fd16f1f5", size = 574327 }, - { url = "https://files.pythonhosted.org/packages/ec/97/3914e45ab9e0ff0f157e493caa91791372508488b97ff0961a0640a37d25/tree_sitter-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2a84ff87a2f2a008867a1064aba510ab3bd608e3e0cd6e8fef0379efee266c73", size = 577171 }, - { url = "https://files.pythonhosted.org/packages/c5/b0/266a529c3eef171137b73cde8ad7aa282734354609a8b2f5564428e8f12d/tree_sitter-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c012e4c345c57a95d92ab5a890c637aaa51ab3b7ff25ed7069834b1087361c95", size = 120260 }, - { url = "https://files.pythonhosted.org/packages/c1/c3/07bfaa345e0037ff75d98b7a643cf940146e4092a1fd54eed0359836be03/tree_sitter-0.24.0-cp310-cp310-win_arm64.whl", hash = "sha256:033506c1bc2ba7bd559b23a6bdbeaf1127cee3c68a094b82396718596dfe98bc", size = 108416 }, - { url = "https://files.pythonhosted.org/packages/66/08/82aaf7cbea7286ee2a0b43e9b75cb93ac6ac132991b7d3c26ebe5e5235a3/tree_sitter-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de0fb7c18c6068cacff46250c0a0473e8fc74d673e3e86555f131c2c1346fb13", size = 140733 }, - { url = "https://files.pythonhosted.org/packages/8c/bd/1a84574911c40734d80327495e6e218e8f17ef318dd62bb66b55c1e969f5/tree_sitter-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7c9c89666dea2ce2b2bf98e75f429d2876c569fab966afefdcd71974c6d8538", size = 134243 }, - { url = "https://files.pythonhosted.org/packages/46/c1/c2037af2c44996d7bde84eb1c9e42308cc84b547dd6da7f8a8bea33007e1/tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddb113e6b8b3e3b199695b1492a47d87d06c538e63050823d90ef13cac585fd", size = 562030 }, - { url = "https://files.pythonhosted.org/packages/4c/aa/2fb4d81886df958e6ec7e370895f7106d46d0bbdcc531768326124dc8972/tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ea01a7003b88b92f7f875da6ba9d5d741e0c84bb1bd92c503c0eecd0ee6409", size = 575585 }, - { url = "https://files.pythonhosted.org/packages/e3/3c/5f997ce34c0d1b744e0f0c0757113bdfc173a2e3dadda92c751685cfcbd1/tree_sitter-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:464fa5b2cac63608915a9de8a6efd67a4da1929e603ea86abaeae2cb1fe89921", size = 578203 }, - { url = "https://files.pythonhosted.org/packages/d5/1f/f2bc7fa7c3081653ea4f2639e06ff0af4616c47105dbcc0746137da7620d/tree_sitter-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b1f3cbd9700e1fba0be2e7d801527e37c49fc02dc140714669144ef6ab58dce", size = 120147 }, - { url = "https://files.pythonhosted.org/packages/c0/4c/9add771772c4d72a328e656367ca948e389432548696a3819b69cdd6f41e/tree_sitter-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:f3f08a2ca9f600b3758792ba2406971665ffbad810847398d180c48cee174ee2", size = 108302 }, - { url = "https://files.pythonhosted.org/packages/e9/57/3a590f287b5aa60c07d5545953912be3d252481bf5e178f750db75572bff/tree_sitter-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14beeff5f11e223c37be7d5d119819880601a80d0399abe8c738ae2288804afc", size = 140788 }, - { url = "https://files.pythonhosted.org/packages/61/0b/fc289e0cba7dbe77c6655a4dd949cd23c663fd62a8b4d8f02f97e28d7fe5/tree_sitter-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26a5b130f70d5925d67b47db314da209063664585a2fd36fa69e0717738efaf4", size = 133945 }, - { url = "https://files.pythonhosted.org/packages/86/d7/80767238308a137e0b5b5c947aa243e3c1e3e430e6d0d5ae94b9a9ffd1a2/tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fc5c3c26d83c9d0ecb4fc4304fba35f034b7761d35286b936c1db1217558b4e", size = 564819 }, - { url = "https://files.pythonhosted.org/packages/bf/b3/6c5574f4b937b836601f5fb556b24804b0a6341f2eb42f40c0e6464339f4/tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:772e1bd8c0931c866b848d0369b32218ac97c24b04790ec4b0e409901945dd8e", size = 579303 }, - { url = "https://files.pythonhosted.org/packages/0a/f4/bd0ddf9abe242ea67cca18a64810f8af230fc1ea74b28bb702e838ccd874/tree_sitter-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:24a8dd03b0d6b8812425f3b84d2f4763322684e38baf74e5bb766128b5633dc7", size = 581054 }, - { url = "https://files.pythonhosted.org/packages/8c/1c/ff23fa4931b6ef1bbeac461b904ca7e49eaec7e7e5398584e3eef836ec96/tree_sitter-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9e8b1605ab60ed43803100f067eed71b0b0e6c1fb9860a262727dbfbbb74751", size = 120221 }, - { url = "https://files.pythonhosted.org/packages/b2/2a/9979c626f303177b7612a802237d0533155bf1e425ff6f73cc40f25453e2/tree_sitter-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:f733a83d8355fc95561582b66bbea92ffd365c5d7a665bc9ebd25e049c2b2abb", size = 108234 }, - { url = "https://files.pythonhosted.org/packages/61/cd/2348339c85803330ce38cee1c6cbbfa78a656b34ff58606ebaf5c9e83bd0/tree_sitter-0.24.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d4a6416ed421c4210f0ca405a4834d5ccfbb8ad6692d4d74f7773ef68f92071", size = 140781 }, - { url = "https://files.pythonhosted.org/packages/8b/a3/1ea9d8b64e8dcfcc0051028a9c84a630301290995cd6e947bf88267ef7b1/tree_sitter-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0992d483677e71d5c5d37f30dfb2e3afec2f932a9c53eec4fca13869b788c6c", size = 133928 }, - { url = "https://files.pythonhosted.org/packages/fe/ae/55c1055609c9428a4aedf4b164400ab9adb0b1bf1538b51f4b3748a6c983/tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57277a12fbcefb1c8b206186068d456c600dbfbc3fd6c76968ee22614c5cd5ad", size = 564497 }, - { url = "https://files.pythonhosted.org/packages/ce/d0/f2ffcd04882c5aa28d205a787353130cbf84b2b8a977fd211bdc3b399ae3/tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25fa22766d63f73716c6fec1a31ee5cf904aa429484256bd5fdf5259051ed74", size = 578917 }, - { url = "https://files.pythonhosted.org/packages/af/82/aebe78ea23a2b3a79324993d4915f3093ad1af43d7c2208ee90be9273273/tree_sitter-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d5d9537507e1c8c5fa9935b34f320bfec4114d675e028f3ad94f11cf9db37b9", size = 581148 }, - { url = "https://files.pythonhosted.org/packages/a1/b4/6b0291a590c2b0417cfdb64ccb8ea242f270a46ed429c641fbc2bfab77e0/tree_sitter-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:f58bb4956917715ec4d5a28681829a8dad5c342cafd4aea269f9132a83ca9b34", size = 120207 }, - { url = "https://files.pythonhosted.org/packages/a8/18/542fd844b75272630229c9939b03f7db232c71a9d82aadc59c596319ea6a/tree_sitter-0.24.0-cp313-cp313-win_arm64.whl", hash = "sha256:23641bd25dcd4bb0b6fa91b8fb3f46cc9f1c9f475efe4d536d3f1f688d1b84c8", size = 108232 }, -] - -[[package]] -name = "tree-sitter-solidity" -version = "1.2.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/a7/79cac34bc3edaf21e6eac066a7f46e39db51bca57890c9b1fbac5b9c70e5/tree_sitter_solidity-1.2.11.tar.gz", hash = "sha256:1dbc0ec400c23a56af4d8441a6f0da0593433e875e963319cb0cbd19aec01b98", size = 144334 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/0e/c819d542d58a02e7b8030d016db0f8b26be21c78e9626aeab4472b290c0a/tree_sitter_solidity-1.2.11-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0f8fa2a304299620b35df00ed56fcdcb25211c87b6e3cf35a8e7a80102729ee3", size = 55584 }, - { url = "https://files.pythonhosted.org/packages/e3/89/28a819ee9b8a8596b09701a8f07819508419994b88ef9cbb75bd24c97b24/tree_sitter_solidity-1.2.11-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:46dfc6a989d3b42efde1f5cd141e41495e9f89b480dcad01b7719f875dd31e7e", size = 57902 }, - { url = "https://files.pythonhosted.org/packages/28/5b/e5d1cad769756b9daeabd4f05ff30a1d18b6679ae4403f85a0e79a35b327/tree_sitter_solidity-1.2.11-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0478d2311fe792cb5a926859209f2042b6e108778505692c2654aaf35c61ff59", size = 90232 }, - { url = "https://files.pythonhosted.org/packages/80/cf/94ba0f07b260da6b4616b558d4f0a2351d08b1d11023707711b14c8d1188/tree_sitter_solidity-1.2.11-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ce8e726808dfef3aba452a6abd9f476a252555296149bd6b6bc79b9e3f271a0", size = 88092 }, - { url = "https://files.pythonhosted.org/packages/e9/1c/52f07e9561ad09afd29efa05a5b1ac4bb86142b28093da62a8ed84c94661/tree_sitter_solidity-1.2.11-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:83f763768dce265a83e9ac068fa09d0e9b0ab705b8b6e321560ef9f7b174c393", size = 86820 }, - { url = "https://files.pythonhosted.org/packages/c6/4a/49999d394ed2216c753c5c617098c79fb059caac1f9eef1c08c708945fc8/tree_sitter_solidity-1.2.11-cp38-abi3-win_amd64.whl", hash = "sha256:b3c831b1e425bcd3131310ac307dd2cdc4f71b806a4e39eaae5f29c24b0ef02d", size = 64010 }, - { url = "https://files.pythonhosted.org/packages/14/53/58658502ab5bcc14830d65b01501628dfa6d33fdffbbc492e39fd720c0f2/tree_sitter_solidity-1.2.11-cp38-abi3-win_arm64.whl", hash = "sha256:20b0714fba7b09f882b9a5512b6fb6845ae2406ed53c173e1459a1f802cd7bb1", size = 57726 }, -] - -[[package]] -name = "types-requests" -version = "2.32.0.20241016" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fa/3c/4f2a430c01a22abd49a583b6b944173e39e7d01b688190a5618bd59a2e22/types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95", size = 18065 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/01/485b3026ff90e5190b5e24f1711522e06c79f4a56c8f4b95848ac072e20f/types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747", size = 15836 }, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, -] - -[[package]] -name = "typing-inspect" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 }, -] - -[[package]] -name = "tzdata" -version = "2025.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/0f/fa4723f22942480be4ca9527bbde8d43f6c3f2fe8412f00e7f5f6746bc8b/tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694", size = 194950 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762 }, -] - -[[package]] -name = "urllib3" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, -] - -[[package]] -name = "wcwidth" -version = "0.2.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, -] From 3adbe13463cc3b8eaa9228e77b522d8cc538f1cb Mon Sep 17 00:00:00 2001 From: K2 Date: Tue, 18 Feb 2025 03:10:45 -0500 Subject: [PATCH 09/10] didn't see us using scikit-learn (which had some bugs) --- .gitignore | 2 +- =1.5.0 | 0 pyproject.toml | 3 +-- 3 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 =1.5.0 diff --git a/.gitignore b/.gitignore index 79be880d..f4c452e9 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ tests/* uv.lock callgraph.json lancedbsrc1/lancedb_src* -*/FiniteMonkey.egg-info/* \ No newline at end of file +FiniteMonkey*/* diff --git a/=1.5.0 b/=1.5.0 new file mode 100644 index 00000000..e69de29b diff --git a/pyproject.toml b/pyproject.toml index c8b48a5f..6bbe8fcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,11 +44,10 @@ dependencies = [ "pytz>=2024.2", "requests>=2.32.3", "rich>=13.9.4", - "scikit-learn>=1.3.2", "scipy>=1.10.1", "simplejson>=3.19.3", "six>=1.16.0", - "sqlalchemy[asyncpg]>=2.0.35", + "sqlalchemy>=2.0.38", "sqlalchemy-orm>=1.2.10", "threadpoolctl>=3.5.0", "tqdm>=4.66.5", From 1899496eda75a0a00035e8e805b87c43d3d23c3e Mon Sep 17 00:00:00 2001 From: K2 Date: Sun, 23 Feb 2025 01:01:00 -0500 Subject: [PATCH 10/10] This next verison is getting really big so I'm pushing a commit to my dev branch to keep my sanity. ** ASYNC/SYNC ** Auto/AI Translation's for everything ** New pipeline execution flow, very simple to modify context/execution ** pydantic integration, prettymuch any AI service + local/ollama/llma.cpp/vllm.../OpenAI *** Lots of small bugfixes for type hinting and overall cleanup to reduce any bugs *** IPython shell embedded *** Easy path to web interfaces *** Lots of new dependencies but no docker and nothing heavy, just python. Also did not want to require deeply compliling, uv build system is super easy/fast *** Now that we have this phase nearly done, maybe 1 more commit, next version will be a cleanup again and make sure everyhthing we had last version is working for this one. Will add the data-flow/cfg/ddg/business-context graphing from a few hundred lines of tree-sitter, will include arbitrary queries for users, scope-refinement engine updates and ability to tune in real time... possible integration now with some tools like debuggers, blockchain context that would sort of give you an instant set of flow's througha an app... min/max outliears etc... --- .python-version | 1 + =1.5.0 | 0 app/core.py | 174 ++++ app/ipython.py | 200 +++++ app/translation.py | 43 + docs/flows.md | 32 + models/schemas.py | 15 + path/to/app/core.py | 21 + path/to/src/library/parsing/SolidityParser.py | 23 + path/to/src/runui.py | 51 ++ path/to/ui/widgets.py | 13 + pyproject.toml | 31 +- requirements-from-uv.txt | 119 +++ rx.py | 4 + services/cache.py | 21 + services/llm.py | 54 ++ services/translation.py | 23 + src/agents/chat_app.html | 81 -- src/agents/chat_app.py | 219 ----- src/agents/chat_app.ts | 0 src/agents/md_output.py | 206 ++++- src/agents/sql_generate.py | 182 ---- src/ai_async_eng.py | 497 +++++++++++ src/ai_engine.py | 2 +- src/codebaseQA/arag_processor.py | 26 +- src/dao/aentity.py | 12 +- src/dao/atask_mgr.py | 23 +- src/dynamic_governor.py | 48 ++ src/library/app.css | 45 + src/library/parsing/SolidityLexer.py | 8 +- src/library/parsing/callgraph.py | 1 + src/library/sgp/parser/SolidityLexer.py | 8 +- src/library/sgp/parser/SolidityParser.py | 13 +- src/library/sgp/sgp_visitor.py | 144 +++- src/main.py | 4 +- src/main_pipeline.py | 84 ++ src/nodes_config.py | 44 +- src/planning/aplanning_v2.py | 799 +++++++----------- src/project/aproject_audit.py | 4 +- src/res_processor/ares_processor.py | 3 +- src/run.py | 245 +++--- src/run_context.py | 14 + src/runui.py | 513 +++++++++++ src/tracing.py | 27 + ui/widgets.py | 79 ++ 45 files changed, 2928 insertions(+), 1228 deletions(-) create mode 100644 .python-version delete mode 100644 =1.5.0 create mode 100644 app/core.py create mode 100644 app/ipython.py create mode 100644 app/translation.py create mode 100644 docs/flows.md create mode 100644 models/schemas.py create mode 100644 path/to/app/core.py create mode 100644 path/to/src/library/parsing/SolidityParser.py create mode 100644 path/to/src/runui.py create mode 100644 path/to/ui/widgets.py create mode 100644 requirements-from-uv.txt create mode 100644 rx.py create mode 100644 services/cache.py create mode 100644 services/llm.py create mode 100644 services/translation.py delete mode 100644 src/agents/chat_app.html delete mode 100644 src/agents/chat_app.py delete mode 100644 src/agents/chat_app.ts delete mode 100644 src/agents/sql_generate.py create mode 100644 src/ai_async_eng.py create mode 100644 src/dynamic_governor.py create mode 100644 src/library/app.css create mode 100644 src/main_pipeline.py create mode 100644 src/run_context.py create mode 100644 src/runui.py create mode 100644 src/tracing.py create mode 100644 ui/widgets.py diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..f3fe474a --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12.9 diff --git a/=1.5.0 b/=1.5.0 deleted file mode 100644 index e69de29b..00000000 diff --git a/app/core.py b/app/core.py new file mode 100644 index 00000000..52e5de64 --- /dev/null +++ b/app/core.py @@ -0,0 +1,174 @@ +from textual import work +from textual.app import App +from textual.widgets import Footer, Header +from models.schemas import LogCtxData +from ui.widgets import ThinSpinner, BilingualTable, TranslationToggle +import asyncio +from textual.containers import VerticalScroll, Horizontal +from textual.widgets import Input +import asyncio +import sys +import io +from textual import app, work +from textual.widgets import Input, RichLog +from textual.widgets import ProgressBar +from textual.reactive import reactive +#from rich.layout import VerticalScroll +from textual.app import ComposeResult +from textual.containers import Vertical, VerticalScroll, Horizontal + + +from textual.containers import Container, Horizontal, Vertical +from textual.widgets import DataTable, Switch, Static +from textual import work, on +from textual.reactive import var +from app.translation import TranslationService + + +class CoreApp(App): + CSS = """ + #main-content { + layout: grid; + grid-size: 2; + grid-columns: 1fr 1fr; + height: 1fr; + } + """ + translation_enabled = var(True) # Enabled by default + + async def on_mount(self): + self.main_loop = asyncio.get_running_loop() + self.start_ipython() + + @work(thread=True, exclusive=True) + def start_ipython(self): + """Start IPython in background thread""" + import sys + from IPython import start_ipython + + class IPythonWrapper: + def __init__(self, app): + self.app = app + + def write(self, data): + asyncio.run_coroutine_threadsafe( + self.app.post_output(data), + loop=self.app.main_loop + ) + def flush(self): + pass + + def isatty(self): + return False # Crucial fix for prompt_toolkit integration + + def fileno(self): + return -1 # Indicate no real file descriptor + + + sys.stdout = IPythonWrapper(self) + start_ipython(argv=[], user_ns={ + "app": self, + "toggle_translation": self.toggle_translation + }) + + async def post_output(self, data: str): + """Handle output from IPython thread""" + if self.translation_enabled: + # Add translation logic here + self.query_one(BilingualTable).add_row(data) + else: + self.query_one(BilingualTable).add_row(data) + self.refresh() + + def action_quit(self): + self.exit() + + def toggle_translation(self, enabled: bool): + """Toggle translation mode from IPython""" + self.translation_enabled = enabled + self.notify(f"Translation {'enabled' if enabled else 'disabled'}") + + CSS = """ + #main-content { + layout: grid; + grid-size: 2; + grid-columns: 1fr 1fr; + height: 1fr; + } + """ + translation_enabled = var(True) # Enabled by default + + def compose(self): + # Top controls + yield Horizontal( + ThinSpinner(), + TranslationToggle(value=True), # Toggle enabled by default + id="spinner-container" + ) + + # Main content - side by side tables + yield Horizontal( + BilingualTable("English", lang="EN"), + BilingualTable("中文", lang="CN"), + id="main-content" + ) + + # Input at bottom + yield Horizontal( + Input(placeholder="Input text/commands >>> "), + id="input-container" + ) + + async def process_output(self, data: str): + """Process all output with bidirectional translation""" + try: + # Detect input language + is_english = self._is_english(data) + source_lang = "EN" if is_english else "CN" + target_lang = "CN" if is_english else "EN" + + # Create translation context + ctx = LogCtxData( + txtENG=data if is_english else "", + txtCN=data if not is_english else "", + detail="Auto-translation" + ) + + # Always show original + translation when enabled + if self.translation_enabled: + translated = await self.translation.translate(ctx, target_lang) + self._update_tables( + source_text=data, + translated_text=translated, + source_lang=source_lang + ) + else: + self._update_single_table(data, source_lang) + + except Exception as e: + self.log_error(f"Translation error: {str(e)}") + + def _update_tables(self, source_text: str, translated_text: str, source_lang: str): + """Update both tables with original and translation""" + en_table = self.query_one("#english-table") + zh_table = self.query_one("#chinese-table") + + if source_lang == "EN": + en_table.add_row(source_text) + zh_table.add_row(translated_text) + else: + zh_table.add_row(source_text) + en_table.add_row(translated_text) + + def _update_single_table(self, text: str, source_lang: str): + """Update only the relevant table when translation off""" + table = self.query_one("#english-table" if source_lang == "EN" else "#chinese-table") + table.add_row(text) + + @on(Input.Submitted) + async def handle_input(self, event: Input.Submitted): + """Handle user input with immediate translation""" + input_text = event.value + if input_text: + await self.process_output(input_text) + event.input.clear() \ No newline at end of file diff --git a/app/ipython.py b/app/ipython.py new file mode 100644 index 00000000..7140e25c --- /dev/null +++ b/app/ipython.py @@ -0,0 +1,200 @@ +# app/ipython.py +import asyncio +import sys +from typing import Callable, Optional +from textual.widgets import Input +from textual.app import App + # Rest of your mount logic +class IPythonIO(io.TextIOBase): + """Thread-safe I/O redirection for IPython""" + def __init__(self, queue: asyncio.Queue, main_loop: asyncio.AbstractEventLoop): + self.queue = queue + self.main_loop = main_loop + + def write(self, data: str) -> int: + asyncio.run_coroutine_threadsafe( + self.queue.put(data), + loop=self.main_loop + ) + return len(data) + + def flush(self) -> None: + pass +class IPythonConsole(App): +# class IPythonConsole(BaseWindow): + """IPython-integrated console inheriting from base window""" + def on_mount(self) -> None: + # Initialize IPython components + self.main_loop = asyncio.get_running_loop() + self.start_ipython() + self.set_interval(0.05, self.process_output) + + # Keep previous IPython integration methods + @work(thread=True) + def start_ipython(self) -> None: + # Same thread setup as before + ... + + def process_output(self) -> None: + # Same output handling + ... + + # Rest of your IPython methods +# class IPythonConsole(app.App): + CSS = """ + Screen { + layout: vertical; + } + + Vertical { + height: auto; + } + + ThinSpinner { + height: 1; + margin: 0; + } + + /* Rest of your CSS */ + """ + + async def on_mount(self) -> None: + # Mount spinner first in the layout + await self.mount( + ThinSpinner(show_eta=False, total=100, show_bar=True ), # Explicit init + CustomRichUI(), + VerticalScroll( + RichLog(id="output-view"), + Input(placeholder=">>> ") + ) + ) + +# class IPythonConsole(app.App): + """Textual app hosting an IPython REPL with async integration""" + CSS = """ + RichLog { + height: 1fr; + overflow-y: auto; + } + Input { + dock: bottom; + } + """ + + def __init__(self): + super().__init__() + self.input_queue = asyncio.Queue() + self.output_queue = asyncio.Queue() + self.main_loop = None + + async def on_mount(self) -> None: + """Initialize application components""" + self.main_loop = asyncio.get_running_loop() + self.rich_log = RichLog() + self.input_widget = Input(placeholder=">>> ") + await self.mount(self.rich_log, self.input_widget) + self.start_ipython() + self.set_interval(0.05, self.process_output) + + @work(thread=True) + def start_ipython(self) -> None: + """Launch IPython in a background thread""" + ipy_loop = asyncio.new_event_loop() + asyncio.set_event_loop(ipy_loop) + + sys.stdin = self + sys.stdout = IPythonIO(self.output_queue, self.main_loop) + sys.stderr = IPythonIO(self.output_queue, self.main_loop) + + from IPython import start_ipython + try: + start_ipython( + argv=[], + user_ns=self.get_ipython_namespace(), + display_banner=False + ) + finally: + sys.stdin = sys.__stdin__ + sys.stdout = sys.__stdout__ + + def get_ipython_namespace(self) -> dict: + """Provide objects accessible in IPython REPL""" + return { + "app": self, + "run_async": self.run_in_main_loop, + "fetch_data": self.sample_async_method + } + + def run_in_main_loop(self, coro) -> any: + """Execute async code in main thread's event loop""" + return asyncio.run_coroutine_threadsafe( + coro, + loop=self.main_loop + ).result() + + async def sample_async_method(self) -> str: + """Example async method callable from IPython""" + await asyncio.sleep(1) + return "Data fetched successfully!" + + async def process_output(self) -> None: + """Update UI with output from IPython""" + while not self.output_queue.empty(): + data = await self.output_queue.get() + self.rich_log.write(data) + self.rich_log.scroll_end(animate=False) + + def readline(self, size: int = -1) -> str: + """Get input from queue (blocking in IPython thread)""" + return asyncio.run_coroutine_threadsafe( + self.input_queue.get(), + loop=self.main_loop + ).result() + + async def on_input_submitted(self, event: Input.Submitted) -> None: + """Handle user input submissions""" + await self.input_queue.put(event.value + "\n") + self.input_widget.clear() + + +# class IPythonIO: +# """Thread-safe I/O redirection with proper event loop handling""" +# def __init__(self, callback: Callable, main_loop: asyncio.AbstractEventLoop): +# self.callback = callback +# self.main_loop = main_loop + +# def write(self, data: str): +# if data.strip(): +# # Use the main thread's event loop +# asyncio.run_coroutine_threadsafe( +# self.callback(data), +# loop=self.main_loop +# ) + +# def flush(self): +# pass + +# class IPythonHost: +# """Managed IPython integration with lifecycle control""" +# def __init__(self, translation_callback: Callable): +# self.translation_callback = translation_callback +# self.main_loop: Optional[asyncio.AbstractEventLoop] = None + +# def start(self, main_loop: asyncio.AbstractEventLoop): +# """Start IPython with proper loop reference""" +# self.main_loop = main_loop +# sys.stdout = IPythonIO(self.handle_output, main_loop) + +# from IPython import start_ipython +# start_ipython(argv=[], user_ns={ +# "app": self, +# "translate": self.translation_callback +# }) + +# async def handle_output(self, data: str): +# """Process output through translation pipeline""" +# try: +# if self.main_loop and self.main_loop.is_running(): +# await self.translation_callback(data) +# except Exception as e: +# print(f"Output handling error: {e}") diff --git a/app/translation.py b/app/translation.py new file mode 100644 index 00000000..2364ce64 --- /dev/null +++ b/app/translation.py @@ -0,0 +1,43 @@ +from textwrap import dedent + + +from models.schemas import LogCtxData + +class TranslationService: + async def translate(self, ctx: LogCtxData, target_lang: str) -> str: + """Bidirectional translation with quality checks""" + if target_lang == "CN": + return await self._translate_english_to_chinese(ctx) + return await self._translate_chinese_to_english(ctx) + + async def _translate_english_to_chinese(self, ctx: LogCtxData) -> str: + prompt = dedent(f""" + Translate this technical text to colloquial Chinese: + {ctx.txtENG} + + Requirements: + - Maintain technical accuracy + - Use natural spoken Chinese + - Preserve numbers and proper nouns + """) + response = await self.ingress.chat.completions.create( + model="towerinstruct", + messages=[{"role": "user", "content": prompt}] + ) + return response.choices[0].message.content + + async def _translate_chinese_to_english(self, ctx: LogCtxData) -> str: + prompt = dedent(f""" + Translate this Chinese text to clear English: + {ctx.txtCN} + + Requirements: + - Keep technical terms in English + - Use simple, direct phrasing + - Maintain original formatting + """) + response = await self.egress.chat.completions.create( + model="towerinstruct", + messages=[{"role": "user", "content": prompt}] + ) + return response.choices[0].message.content \ No newline at end of file diff --git a/docs/flows.md b/docs/flows.md new file mode 100644 index 00000000..895b47fd --- /dev/null +++ b/docs/flows.md @@ -0,0 +1,32 @@ + +#internals +```mermaid +sequenceDiagram + participant M as Main __main__ + participant AM as async_main + participant TT as Thread Tasks (to_thread) + participant TG as TaskGroup + participant MP as message_pump + participant AG as Agent Stream + participant LS as Live Display + + M->>AM: asyncio.run async_main + AM->>AM: Load config, create engine, init Context + AM->>TT: await to_threadproject_audit.parse... + TT-->>AM: Parsing complete + AM->>TT: await to_threadplanning.do_planning + TT-->>AM: Planning complete + AM->>TT: await to_thread ai_engine.do_scan + TT-->>AM: Scan complete + AM->>TT: await to_thread ai_engine.check_function_vul + TT-->>AM: Vulnerability check complete + AM->>TG: Enter TaskGroup + TG->>MP: Create task: message_pump + MP->>AG: agent.run_streamprompt + loop For each message + AG-->>MP: yield message + MP->>LS: live.updateMarkdownmessage + end + AG-->>MP: Stream complete + TG-->>AM: TaskGroup exits all tasks done +``` \ No newline at end of file diff --git a/models/schemas.py b/models/schemas.py new file mode 100644 index 00000000..f9d38e2e --- /dev/null +++ b/models/schemas.py @@ -0,0 +1,15 @@ +from dataclasses import dataclass + +@dataclass +class LogCtxData: + txtENG: str = "" + txtCN: str = "" + detail: str = "" + + @property + def source(self) -> str: + return self.txtENG or self.txtCN + + @property + def source_lang(self) -> str: + return "EN" if self.txtENG else "CN" \ No newline at end of file diff --git a/path/to/app/core.py b/path/to/app/core.py new file mode 100644 index 00000000..69b04db1 --- /dev/null +++ b/path/to/app/core.py @@ -0,0 +1,21 @@ +# app/core.py +from textual import work + +from services.translation import TranslationProcessor + + +class CoreApp: + def __init__(self): + self.translation_enabled = False + self.main_loop = None + self.translation = TranslationProcessor() # Ensure this attribute exists + + @work(thread=True, exclusive=True) + async def start_ipython(self): + # Existing code remains the same + pass + + async def _update_tables(self, ctx, target_lang): + if self.translation_enabled: + translated = await self.translation.translate(ctx, target_lang) + self._update_tables() # Ensure this method exists and is correctly named \ No newline at end of file diff --git a/path/to/src/library/parsing/SolidityParser.py b/path/to/src/library/parsing/SolidityParser.py new file mode 100644 index 00000000..1805e17d --- /dev/null +++ b/path/to/src/library/parsing/SolidityParser.py @@ -0,0 +1,23 @@ +from typing import Any +from antlr4 import CommonTokenStream +from src.library.parsing.SolidityParser import getSubcontract, parseToken + +SUBCONTRACT_ID = "subcontract" + +class SolidityParser: + def __init__(self): + pass + + def parse_stream(self, stream: CommonTokenStream) -> list: + result = [] + max_length = len(stream.tokens) + i = 0 + + while i < max_length: + id, content, loc = parseToken(str(stream.tokens[i])) + if id is not None: + if id == SUBCONTRACT_ID: + i, subcontract, subcontract_entry = getSubcontract(i+1, stream.tokens, max_length, id, loc) + result.append(subcontract_entry) + + return result \ No newline at end of file diff --git a/path/to/src/runui.py b/path/to/src/runui.py new file mode 100644 index 00000000..659c98fe --- /dev/null +++ b/path/to/src/runui.py @@ -0,0 +1,51 @@ +from textual import work +from app import translation +from app.core import App +import asyncio +from rich.table import Table +zh_table = Table("Translation") +en_table = Table("Source") + +class IPythonConsole(App): + CSS = """ + Screen { + layout: vertical; + } + """ + + @work(thread=True, exclusive=True) + def start_ipython(self): + """Start IPython in background thread""" + import sys + from IPython import start_ipython + + class IPythonWrapper: + def __init__(self, app): + self.app = app + + def write(self, data): + asyncio.run_coroutine_threadsafe( + self.app.post_output(data), + loop=self.app.main_loop + ) + + def flush(self): + pass + + def isatty(self): + return False # Crucial fix for prompt_toolkit integration + + def fileno(self): + return -1 # Indicate no real file descriptor + + sys.stdout = IPythonWrapper(self) + start_ipython(argv=[], user_ns={ + "app": self, + "toggle_translation": self.toggle_translation + }) + +def some_function(): + source = "Default Source" + style_class = "default" # Define a default style class + en_table.add_row(f"[{style_class}]{source}") + zh_table.add_row(f"[{style_class}]{translation}") \ No newline at end of file diff --git a/path/to/ui/widgets.py b/path/to/ui/widgets.py new file mode 100644 index 00000000..ba0d6cbe --- /dev/null +++ b/path/to/ui/widgets.py @@ -0,0 +1,13 @@ +# ui/widgets.py +from textual.widgets import Switch +class TranslationToggle(Switch): + def __init__(self, title: str = "🌐 TRANSLATE OFF"): + super().__init__(title) + self.border_title = title + self.add_column("Content") # Fixed method name + + def add_row(self, text: str): + super().addRow(text) # Ensure the method name matches if necessary + + def on_change(self, value: bool): + self.label = "🌐 TRANSLATE ON" if value else "🌐 TRANSLATE OFF" \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 6bbe8fcd..e7d3905c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,23 +12,18 @@ dependencies = [ "asyncpg>=0.30.0", "certifi>=2024.8.30", "charset-normalizer>=3.4.0", - "colorama>=0.4.6", - "dependency-injector>=4.45.0", "devtools>=0.9.0", - "et-xmlfile>=1.1.0", "fastapi>=0.115.8", "future>=1.0.0", - "idna>=3.10", - "ipython>=8.32.0", "jiter>=0.8.2", "joblib>=1.4.2", "lancedb>=0.19.0", "logging>=0.4.9.6", + "loguru>=0.7.3", "networkx>=3.4.2", "numpy>=1.24.4", "ollama>=0.4.7", "openai>=1.63.0", - "openpyxl>=3.1.5", "pandas>=1.24.4", "psycopg2-binary>=2.9.9", "pydantic>=2.11.0a2", @@ -37,25 +32,30 @@ dependencies = [ "pydantic-settings>=2.7.1", "pydot>=3.0.4", "pylance>=0.23.0", - "pyperclip>=1.9.0", "python-box>=7.3.2", "python-dateutil==2.8.2", - "python-dotenv>=1.0.1", "pytz>=2024.2", "requests>=2.32.3", "rich>=13.9.4", "scipy>=1.10.1", - "simplejson>=3.19.3", "six>=1.16.0", - "sqlalchemy>=2.0.38", - "sqlalchemy-orm>=1.2.10", + "sqlalchemy[async,asyncio,asyncnpg,asyncpg,asynnpg,asynpg]>=2.0.38", "threadpoolctl>=3.5.0", - "tqdm>=4.66.5", "tree-sitter>=0.24.0", "tree-sitter-solidity>=1.2.11", "typing_extensions>=4.12.2", - "tzdata>=2024.2", "urllib3>=2.2.3", + "httpx[http2]>=0.28.1", + "nest-asyncio>=1.6.0", + "prompt-toolkit>=3.0.50", + "pygments>=2.19.1", + "asyncio[subprocess]>=3.4.3", + "textual-dev>=1.7.0", + "textual[demo,dev]>=2.1.0", + "textual-web>=0.4.2", + "ipython>=8.32.0", + "openpyxl>=3.1.5", + "typing[io]>=3.10.0.0", ] [project.urls] @@ -78,6 +78,11 @@ build-backend = "setuptools.build_meta" [tool.finite-monkey-engine] AZURE_API_VERSION = "test" COMMON_PROJECT = "pyproject" +GEMINI_API_KEY = "gemini_key" +AZURE_OR_OPENAI = "OPENAI" +id="12123" +output="/tmp/121" +base_dir="/export/md0/contracts-eb12d93c17cf93b27cba7b3a49ebdc9536d7d894/" [tool.hatch.build.targets.wheel] packages = ["finite_monkey_engine"] diff --git a/requirements-from-uv.txt b/requirements-from-uv.txt new file mode 100644 index 00000000..063b77ff --- /dev/null +++ b/requirements-from-uv.txt @@ -0,0 +1,119 @@ +annotated-types==0.7.0 +anthropic==0.45.2 +antlr4-python3-runtime==4.13.2 +anyio==4.8.0 +asttokens==2.4.1 +asyncio==3.4.3 +asyncpg==0.30.0 +cachetools==5.5.1 +certifi==2025.1.31 +charset-normalizer==3.4.1 +cohere==5.13.12 +colorama==0.4.6 +decorator==5.1.1 +dependency-injector==4.45.0 +deprecation==2.1.0 +devtools==0.12.2 +distro==1.9.0 +eval-type-backport==0.2.2 +executing==2.2.0 +fastapi==0.115.8 +fastavro==1.10.0 +filelock==3.17.0 +-e file:///home/files/git/finite-monkey-engine +fsspec==2025.2.0 +future==1.0.0 +google-api-core==2.24.1 +google-auth==2.38.0 +google-cloud-core==2.4.1 +google-cloud-translate==3.20.0 +googleapis-common-protos==1.67.0 +greenlet==3.1.1 +griffe==1.5.7 +groq==0.18.0 +grpc-google-iam-v1==0.14.0 +grpcio==1.70.0 +grpcio-status==1.70.0 +h11==0.14.0 +h2==4.2.0 +hpack==4.1.0 +httpcore==1.0.7 +httpx==0.28.1 +httpx-sse==0.4.0 +huggingface-hub==0.28.1 +hyperframe==6.1.0 +idna==3.10 +ipython==8.32.0 +jedi==0.19.2 +jiter==0.8.2 +joblib==1.4.2 +jsonpath-python==1.0.6 +lancedb==0.19.0 +linkify-it-py==2.0.3 +logfire-api==3.5.3 +logging==0.4.9.6 +loguru==0.7.3 +markdown-it-py==3.0.0 +matplotlib-inline==0.1.7 +mdit-py-plugins==0.4.2 +mdurl==0.1.2 +mistralai==1.5.0 +mypy-extensions==1.0.0 +nest-asyncio==1.6.0 +networkx==3.4.2 +numpy==2.2.3 +ollama==0.4.7 +openai==1.63.2 +overrides==7.7.0 +packaging==24.2 +pandas==2.2.3 +parso==0.8.4 +pexpect==4.9.0 +platformdirs==4.3.6 +prompt-toolkit==3.0.50 +proto-plus==1.26.0 +protobuf==5.29.3 +psycopg2-binary==2.9.10 +ptyprocess==0.7.0 +pure-eval==0.2.3 +pyarrow==19.0.0 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 +pydantic==2.11.0a2 +pydantic-ai==0.0.24 +pydantic-ai-slim==0.0.24 +pydantic-core==2.29.0 +pydantic-graph==0.0.24 +pydantic-settings==2.7.1 +pydot==3.0.4 +pygments==2.19.1 +pylance==0.23.0 +pyparsing==3.2.1 +python-box==7.3.2 +python-dateutil==2.8.2 +python-dotenv==1.0.1 +pytz==2025.1 +pyyaml==6.0.2 +requests==2.32.3 +rich==13.9.4 +rsa==4.9 +scipy==1.15.2 +six==1.17.0 +sniffio==1.3.1 +sqlalchemy==2.0.38 +stack-data==0.6.3 +starlette==0.45.3 +textual==2.0.4 +threadpoolctl==3.5.0 +tokenizers==0.21.0 +tqdm==4.67.1 +traitlets==5.14.3 +tree-sitter==0.24.0 +tree-sitter-solidity==1.2.11 +types-requests==2.32.0.20241016 +typing-extensions==4.12.2 +typing-inspect==0.9.0 +tzdata==2025.1 +uc-micro-py==1.0.3 +urllib3==2.3.0 +wcwidth==0.2.13 diff --git a/rx.py b/rx.py new file mode 100644 index 00000000..e2367e22 --- /dev/null +++ b/rx.py @@ -0,0 +1,4 @@ +from app.core import CoreApp + +app = CoreApp() +app.run() diff --git a/services/cache.py b/services/cache.py new file mode 100644 index 00000000..0fc0f87a --- /dev/null +++ b/services/cache.py @@ -0,0 +1,21 @@ +import shelve +from datetime import datetime + +class TranslationCache: + def __init__(self): + self.store = shelve.open("translations.db") + + def get(self, key: str): + return self.store.get(key) + + def set(self, key: str, value: str): + self.store[key] = { + 'value': value, + 'timestamp': datetime.now().isoformat() + } + + def clear(self): + self.store.clear() + + def __del__(self): + self.store.close() diff --git a/services/llm.py b/services/llm.py new file mode 100644 index 00000000..2d9e3b8b --- /dev/null +++ b/services/llm.py @@ -0,0 +1,54 @@ +from httpx import AsyncClient +from openai import AsyncOpenAI +from models.schemas import LogCtxData + +from openai import AsyncOpenAI +from httpx import AsyncClient +from pydantic_ai.models.openai import OpenAIModel +from pydantic_ai.agent import Agent +from pydantic_ai.result import RunResult +from typing import Optional +from rich.console import Console +class LLMClientv1: + def __init__(self): + self.http_client = AsyncClient() + self.client = AsyncOpenAI( + base_url="http://127.0.0.1:11434/v1", + api_key="k", + http_client=self.http_client + ) + + async def translate(self, text: str, direction: str) -> str: + prompt = f"Translate this text to {direction}: {text}" + response = await self.client.chat.completions.create( + model="towerinstruct", + messages=[{"role": "user", "content": prompt}] + ) + return response.choices[0].message.content + + +class OpenAIModel: + """Wrapper for OpenAI client with custom config""" + def __init__(self, model: str, api_key: str, base_url: str, http_client: AsyncClient): + self.client = AsyncOpenAI( + base_url=base_url, + api_key=api_key, + http_client=http_client + ) + self.model = model + +class LLMClient: + def __init__(self): + self.http = AsyncClient() + self.ingress = OpenAIModel( + "hf.co/mradermacher/TowerInstruct-WMT24-Chat-7B-i1-GGUF:Q4_K_M", + api_key="k", + base_url="http://127.0.0.1:11434/v1", + http_client=self.http + ) + self.egress = OpenAIModel( + "hf.co/mradermacher/TowerInstruct-WMT24-Chat-7B-i1-GGUF:Q4_K_M", + api_key="k", + base_url="http://127.0.0.1:11434/v1", + http_client=self.http + ) \ No newline at end of file diff --git a/services/translation.py b/services/translation.py new file mode 100644 index 00000000..094a483a --- /dev/null +++ b/services/translation.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass +from typing import Protocol, Any + +class TranslationClient(Protocol): + async def translate(self, text: str, direction: str) -> str: ... + +@dataclass +class TranslationProcessor: + client: TranslationClient + cache: Any + + async def process(self, text: str) -> tuple[str, str]: + # Implementation using injected dependencies + if cached := self.cache.get(text): + return cached + + direction = await self.detect_language(text) + result = await self.client.translate(text, direction) + self.cache.set(text, result) + return (text, result) if direction == "EN->CN" else (result, text) + + async def detect_language(self, text: str) -> str: + return "EN->CN" if any(ord(c) > 127 for c in text) else "CN->EN" diff --git a/src/agents/chat_app.html b/src/agents/chat_app.html deleted file mode 100644 index 76b44c7b..00000000 --- a/src/agents/chat_app.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - Chat App - - - - -
-

Chat App

-

Ask me anything...

-
-
-
-
-
- -
- -
-
-
- Error occurred, check the browser developer console for more information. -
-
- - - - diff --git a/src/agents/chat_app.py b/src/agents/chat_app.py deleted file mode 100644 index 8e5043cb..00000000 --- a/src/agents/chat_app.py +++ /dev/null @@ -1,219 +0,0 @@ -"""Simple chat app example build with FastAPI. - -Run with: - - uv run -m pydantic_ai_examples.chat_app -""" - -from __future__ import annotations as _annotations - -import asyncio -import json -import sqlite3 -from collections.abc import AsyncIterator -from concurrent.futures.thread import ThreadPoolExecutor -from contextlib import asynccontextmanager -from dataclasses import dataclass -from datetime import datetime, timezone -from functools import partial -from pathlib import Path -from typing import Annotated, Any, Callable, Literal, TypeVar - -import fastapi -from fastapi import Depends, Request -from fastapi.responses import FileResponse, Response, StreamingResponse -from typing_extensions import LiteralString, ParamSpec, TypedDict - -from pydantic_ai import Agent -from pydantic_ai.exceptions import UnexpectedModelBehavior -from pydantic_ai.messages import ( - ModelMessage, - ModelMessagesTypeAdapter, - ModelRequest, - ModelResponse, - TextPart, - UserPromptPart, -) - -agent = Agent('openai:gpt-4o') -THIS_DIR = Path(__file__).parent - - -@asynccontextmanager -async def lifespan(_app: fastapi.FastAPI): - async with Database.connect() as db: - yield {'db': db} - - -app = fastapi.FastAPI(lifespan=lifespan) - -@app.get('/') -async def index() -> FileResponse: - return FileResponse((THIS_DIR / 'chat_app.html'), media_type='text/html') - - -@app.get('/chat_app.ts') -async def main_ts() -> FileResponse: - """Get the raw typescript code, it's compiled in the browser, forgive me.""" - return FileResponse((THIS_DIR / 'chat_app.ts'), media_type='text/plain') - - -async def get_db(request: Request) -> Database: - return request.state.db - - -@app.get('/chat/') -async def get_chat(database: Database = Depends(get_db)) -> Response: - msgs = await database.get_messages() - return Response( - b'\n'.join(json.dumps(to_chat_message(m)).encode('utf-8') for m in msgs), - media_type='text/plain', - ) - - -class ChatMessage(TypedDict): - """Format of messages sent to the browser.""" - - role: Literal['user', 'model'] - timestamp: str - content: str - - -def to_chat_message(m: ModelMessage) -> ChatMessage: - first_part = m.parts[0] - if isinstance(m, ModelRequest): - if isinstance(first_part, UserPromptPart): - return { - 'role': 'user', - 'timestamp': first_part.timestamp.isoformat(), - 'content': first_part.content, - } - elif isinstance(m, ModelResponse): - if isinstance(first_part, TextPart): - return { - 'role': 'model', - 'timestamp': m.timestamp.isoformat(), - 'content': first_part.content, - } - raise UnexpectedModelBehavior(f'Unexpected message type for chat app: {m}') - - -@app.post('/chat/') -async def post_chat( - prompt: Annotated[str, fastapi.Form()], database: Database = Depends(get_db) -) -> StreamingResponse: - async def stream_messages(): - """Streams new line delimited JSON `Message`s to the client.""" - # stream the user prompt so that can be displayed straight away - yield ( - json.dumps( - { - 'role': 'user', - 'timestamp': datetime.now(tz=timezone.utc).isoformat(), - 'content': prompt, - } - ).encode('utf-8') - + b'\n' - ) - # get the chat history so far to pass as context to the agent - messages = await database.get_messages() - # run the agent with the user prompt and the chat history - async with agent.run_stream(prompt, message_history=messages) as result: - async for text in result.stream(debounce_by=0.01): - # text here is a `str` and the frontend wants - # JSON encoded ModelResponse, so we create one - m = ModelResponse(parts=[TextPart(text)], timestamp=result.timestamp()) - yield json.dumps(to_chat_message(m)).encode('utf-8') + b'\n' - - # add new messages (e.g. the user prompt and the agent response in this case) to the database - await database.add_messages(result.new_messages_json()) - - return StreamingResponse(stream_messages(), media_type='text/plain') - - -P = ParamSpec('P') -R = TypeVar('R') - - -@dataclass -class Database: - """Rudimentary database to store chat messages in SQLite. - - The SQLite standard library package is synchronous, so we - use a thread pool executor to run queries asynchronously. - """ - - con: sqlite3.Connection - _loop: asyncio.AbstractEventLoop - _executor: ThreadPoolExecutor - - @classmethod - @asynccontextmanager - async def connect( - cls, file: Path = THIS_DIR / '.chat_app_messages.sqlite' - ) -> AsyncIterator[Database]: - with logfire.span('connect to DB'): - loop = asyncio.get_event_loop() - executor = ThreadPoolExecutor(max_workers=1) - con = await loop.run_in_executor(executor, cls._connect, file) - slf = cls(con, loop, executor) - try: - yield slf - finally: - await slf._asyncify(con.close) - - @staticmethod - def _connect(file: Path) -> sqlite3.Connection: - con = sqlite3.connect(str(file)) - con = logfire.instrument_sqlite3(con) - cur = con.cursor() - cur.execute( - 'CREATE TABLE IF NOT EXISTS messages (id INT PRIMARY KEY, message_list TEXT);' - ) - con.commit() - return con - - async def add_messages(self, messages: bytes): - await self._asyncify( - self._execute, - 'INSERT INTO messages (message_list) VALUES (?);', - messages, - commit=True, - ) - await self._asyncify(self.con.commit) - - async def get_messages(self) -> list[ModelMessage]: - c = await self._asyncify( - self._execute, 'SELECT message_list FROM messages order by id' - ) - rows = await self._asyncify(c.fetchall) - messages: list[ModelMessage] = [] - for row in rows: - messages.extend(ModelMessagesTypeAdapter.validate_json(row[0])) - return messages - - def _execute( - self, sql: LiteralString, *args: Any, commit: bool = False - ) -> sqlite3.Cursor: - cur = self.con.cursor() - cur.execute(sql, args) - if commit: - self.con.commit() - return cur - - async def _asyncify( - self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs - ) -> R: - return await self._loop.run_in_executor( # type: ignore - self._executor, - partial(func, **kwargs), - *args, # type: ignore - ) - - -if __name__ == '__main__': - import uvicorn - - uvicorn.run( - 'pydantic_ai_examples.chat_app:app', reload=True, reload_dirs=[str(THIS_DIR)] - ) diff --git a/src/agents/chat_app.ts b/src/agents/chat_app.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/agents/md_output.py b/src/agents/md_output.py index a4c7c71d..80336764 100644 --- a/src/agents/md_output.py +++ b/src/agents/md_output.py @@ -1,49 +1,164 @@ - import asyncio -import os +import threading +from typing import Any, TextIO -from rich.console import Console, ConsoleOptions, RenderResult +from httpx import AsyncClient +from rich.console import Console, ConsoleOptions, RenderResult,Group from rich.live import Live from rich.markdown import CodeBlock, Markdown from rich.syntax import Syntax from rich.text import Text +from rich.columns import Columns -from pydantic_ai import Agent from pydantic_ai.models import KnownModelName +from rich.panel import Panel +from pydantic_ai.agent import Agent, RunContext +from pydantic.dataclasses import dataclass +from pydantic_ai.models.openai import OpenAIModel +from rich.logging import RichHandler -agent = Agent() -# models to try, and the appropriate env var -models: list[tuple[KnownModelName, str]] = [ - ('google-gla:gemini-1.5-flash', 'GEMINI_API_KEY'), - ('openai:gpt-4o-mini', 'OPENAI_API_KEY'), - ('groq:llama-3.3-70b-versatile', 'GROQ_API_KEY'), -] +console = Console() + +from loguru import logger as logging +def logdata(message): + logging.info(message) + +class LogToMarkdown: + def __init__(self, filename): + self.filename = filename + self.file = open(filename, 'a+') -async def main(): - prettier_code_blocks() - console = Console() - prompt = 'Show me a short example of using Pydantic.' - console.log(f'Asking: {prompt}...', style='cyan') - for model, env_var in models: - if env_var in os.environ: - console.log(f'Using model: {model}') - with Live('', console=console, vertical_overflow='visible') as live: - async with agent.run_stream(prompt, model=model) as result: - async for message in result.stream(): - live.update(Markdown(message)) - console.log(result.usage()) - else: - console.log(f'{model} requires {env_var} to be set.') + def write(self, message): + # Translate and log the message to Markdown + translated_message = self.translate_to_markdown(message) + self.file.write(translated_message) + def flush(self): + self.file.flush() -def prettier_code_blocks(): - """Make rich code blocks prettier and easier to copy. + @staticmethod + def translate_to_markdown(text): + # Simple translation logic (you can customize this part as needed) + return text.replace('\n', ' \n') + '\n' + + def close(self): + self.file.close() + + +@dataclass +class LogCtxData: + txtENG: str = "" + txtCN: str = "" + detail: str = "" + + +h = AsyncClient() +oai = OpenAIModel( + "hf.co/mradermacher/TowerInstruct-WMT24-Chat-7B-i1-GGUF:Q4_K_M", + api_key="k", + base_url="http://127.0.0.1:11434/v1", + http_client=h, +) +sys_prompt = """You are a very concise and capable translation helper. +You are very good at making clear and accurate translations for technical phrases and slang into a familure and understandable phrase of the language indicated by the user. +You adapt technical jargon as best as possible and maintain clear translations. +After commencing through a translation, you do not add any exantemperanious information, only the text being evaluated is to be reflected in you're output as that is needed.""" +egress_txt = Agent( + oai, + deps_type=LogCtxData, + system_prompt=sys_prompt +) + +ingress_txt = Agent( + oai, + deps_type=LogCtxData, + system_prompt=sys_prompt +) + + +class LogView: + def __init__(self): + self.scroll_offset = 0 + self.content1 = "" + self.content2 = "" + self.minimized_column = None + + def setup_logging(self): + ... + # logging.basicConfig( + # level=logging.DEBUG, + # format="%(message)s", + # datefmt="[%X]", + # handlers=[RichHandler(rich_tracebacks=True)]) + + async def logdata(self, info: str, details: str = ""): + lang = await self.detect_language(info) + eng = zh = "" + + ctx = LogCtxData(txtENG=info, txtCN=info, detail=details) + try: + trans = await ingress_txt.run( + deps=ctx, + user_prompt=f"{lang}" + ) + if lang.startswith("CN->EN"): + eng = info + zh = trans.data + else: + eng = trans.data + zh = info + except Exception as e: + console.print(f"Error during translation: {e}") + return - From https://github.com/samuelcolvin/aicli/blob/v0.8.0/samuelcolvin_aicli.py#L22 - """ + self.content1 += f"{eng}\n" + self.content2 += f"{zh}\n" + async def detect_language(self, text: str) -> str: + # Return EN for chinese and ZH for english as we want to know what language to translate into + return "CN->EN: Translate the text following this sentence from Chinese (zh) to English (en) ### " if any("\u4e00" <= ch <= "\u9fff" for ch in text) else "EN->CN: Translate the text following this sentence from English (en) to Chinese (zh) ### " + + + async def render(self): + if self.minimized_column == 1: + left_panel = Panel(Markdown(self.content1[self.scroll_offset:]), title="Chinese") + right_panel = "" + elif self.minimized_column == 2: + left_panel = "" + right_panel = Panel(Markdown(self.content2[self.scroll_offset:]), title="English") + else: + left_panel = Panel(Markdown(self.content1[self.scroll_offset:]), title="Chinese") + right_panel = Panel(Markdown(self.content2[self.scroll_offset:]), title="English") + spinner_frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] + # Reconfigure logging to write debug logs to "debug.log" instead of the UI console. + for handler in logging.root.handlers[:]: + logging.root.removeHandler(handler) + file_handler = logging.FileHandler("debug.log") + file_handler.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") + file_handler.setFormatter(formatter) + logging.getLogger().addHandler(file_handler) + spinner = spinner_frames[int(asyncio.get_event_loop().time() * 10) % len(spinner_frames)] + status_bar = Panel(Markdown(f"{spinner} UI Active"), style="bold green", height=1) + renderable = Group(Columns([left_panel, right_panel]), status_bar) + console.clear() # Clear previous output + console.print(renderable) + # return + + # columns_txt = Columns([left_panel, right_panel]) + + +logView = LogView() + +async def setup_live_console(): + prettier_code_blocks() + console = Console() + with Live('', console=console, vertical_overflow='visible') as live: + async for message in egress_txt.stream(): + live.update(Markdown(message)) +def prettier_code_blocks(): class SimpleCodeBlock(CodeBlock): def __rich_console__( self, console: Console, options: ConsoleOptions @@ -60,7 +175,36 @@ def __rich_console__( yield Text(f'/{self.lexer_name}', style='dim') Markdown.elements['fence'] = SimpleCodeBlock + +def start_event_loop(loop: asyncio.AbstractEventLoop): + asyncio.set_event_loop(loop) + loop.run_forever() + +# Create a new event loop for the group of coroutines. +new_loop = asyncio.new_event_loop() + +# Start the new event loop in a dedicated thread. +t = threading.Thread(target=start_event_loop, args=(new_loop,)) +t.start() + +# Now, schedule your coroutines on the new loop: +async def run_console_output(name): + await setup_live_console() + print(f"Task {name} done in isolated loop") if __name__ == '__main__': - asyncio.run(main()) + asyncio.run(run_console_output("logging")) + + + +# # Create a new event loop for the group of coroutines. +# new_loop: asyncio.AbstractEventLoop = asyncio.new_event_loop() +# # Start the new event loop in a dedicated thread. +# t = threading.Thread(target=start_event_loop, args=(new_loop,)) +# t.start() + +# # Now, schedule your coroutines on the new loop: +# async def run_console_output(name): +# await setup_live_console(logView) +# print(f"Task {name} done in isolated loop") diff --git a/src/agents/sql_generate.py b/src/agents/sql_generate.py deleted file mode 100644 index 13f0d502..00000000 --- a/src/agents/sql_generate.py +++ /dev/null @@ -1,182 +0,0 @@ - -"""PydanticAI to generate SQL queries based on user input. - -Run postgres with: - - mkdir postgres-data - docker run --rm -e POSTGRES_PASSWORD=postgres -p 54320:5432 postgres - -Run with: - - uv run -m pydantic_ai_examples.sql_gen "show me logs from yesterday, with level 'error'" -""" - -import asyncio -import sys -from collections.abc import AsyncGenerator -from contextlib import asynccontextmanager -from dataclasses import dataclass -from datetime import date -from typing import Annotated, Any, Union - -import asyncpg -import logfire -from annotated_types import MinLen -from devtools import debug -from pydantic import BaseModel, Field -from typing_extensions import TypeAlias - -from pydantic_ai import Agent, ModelRetry, RunContext -from pydantic_ai.format_as_xml import format_as_xml - -# 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured -logfire.configure(send_to_logfire='if-token-present') -logfire.instrument_asyncpg() - -DB_SCHEMA = """ -CREATE TABLE records ( - created_at timestamptz, - start_timestamp timestamptz, - end_timestamp timestamptz, - trace_id text, - span_id text, - parent_span_id text, - level log_level, - span_name text, - message text, - attributes_json_schema text, - attributes jsonb, - tags text[], - is_exception boolean, - otel_status_message text, - service_name text -); -""" -SQL_EXAMPLES = [ - { - 'request': 'show me records where foobar is false', - 'response': "SELECT * FROM records WHERE attributes->>'foobar' = false", - }, - { - 'request': 'show me records where attributes include the key "foobar"', - 'response': "SELECT * FROM records WHERE attributes ? 'foobar'", - }, - { - 'request': 'show me records from yesterday', - 'response': "SELECT * FROM records WHERE start_timestamp::date > CURRENT_TIMESTAMP - INTERVAL '1 day'", - }, - { - 'request': 'show me error records with the tag "foobar"', - 'response': "SELECT * FROM records WHERE level = 'error' and 'foobar' = ANY(tags)", - }, -] - - -@dataclass -class Deps: - conn: asyncpg.Connection - - -class Success(BaseModel): - """Response when SQL could be successfully generated.""" - - sql_query: Annotated[str, MinLen(1)] - explanation: str = Field( - '', description='Explanation of the SQL query, as markdown' - ) - - -class InvalidRequest(BaseModel): - """Response the user input didn't include enough information to generate SQL.""" - - error_message: str - - -Response: TypeAlias = Union[Success, InvalidRequest] -agent: Agent[Deps, Response] = Agent( - 'google-gla:gemini-1.5-flash', - # Type ignore while we wait for PEP-0747, nonetheless unions will work fine everywhere else - result_type=Response, # type: ignore - deps_type=Deps, -) - - -@agent.system_prompt -async def system_prompt() -> str: - return f"""\ -Given the following PostgreSQL table of records, your job is to -write a SQL query that suits the user's request. - -Database schema: - -{DB_SCHEMA} - -today's date = {date.today()} - -{format_as_xml(SQL_EXAMPLES)} -""" - - -@agent.result_validator -async def validate_result(ctx: RunContext[Deps], result: Response) -> Response: - if isinstance(result, InvalidRequest): - return result - - # gemini often adds extraneous backslashes to SQL - result.sql_query = result.sql_query.replace('\\', '') - if not result.sql_query.upper().startswith('SELECT'): - raise ModelRetry('Please create a SELECT query') - - try: - await ctx.deps.conn.execute(f'EXPLAIN {result.sql_query}') - except asyncpg.exceptions.PostgresError as e: - raise ModelRetry(f'Invalid query: {e}') from e - else: - return result - - -async def main(): - if len(sys.argv) == 1: - prompt = 'show me logs from yesterday, with level "error"' - else: - prompt = sys.argv[1] - - async with database_connect( - 'postgresql://postgres:postgres@localhost:54320', 'pydantic_ai_sql_gen' - ) as conn: - deps = Deps(conn) - result = await agent.run(prompt, deps=deps) - debug(result.data) - - -# pyright: reportUnknownMemberType=false -# pyright: reportUnknownVariableType=false -@asynccontextmanager -async def database_connect(server_dsn: str, database: str) -> AsyncGenerator[Any, None]: - with logfire.span('check and create DB'): - conn = await asyncpg.connect(server_dsn) - try: - db_exists = await conn.fetchval( - 'SELECT 1 FROM pg_database WHERE datname = $1', database - ) - if not db_exists: - await conn.execute(f'CREATE DATABASE {database}') - finally: - await conn.close() - - conn = await asyncpg.connect(f'{server_dsn}/{database}') - try: - with logfire.span('create schema'): - async with conn.transaction(): - if not db_exists: - await conn.execute( - "CREATE TYPE log_level AS ENUM ('debug', 'info', 'warning', 'error', 'critical')" - ) - await conn.execute(DB_SCHEMA) - yield conn - finally: - await conn.close() - - -if __name__ == '__main__': - asyncio.run(main()) diff --git a/src/ai_async_eng.py b/src/ai_async_eng.py new file mode 100644 index 00000000..7c0646fa --- /dev/null +++ b/src/ai_async_eng.py @@ -0,0 +1,497 @@ +import asyncio +from concurrent.futures import ThreadPoolExecutor +import os +from typing import Any, Coroutine, List +from venv import logger +from tqdm import tqdm +# import lancedb +from dao.atask_mgr import AProject_Task, AProjectTaskMgr +from planning.aplanning_v2 import APlanningV2 +from ai_engine import PromptAssembler, as_completed +import re +from json import loads,JSONDecodeError +import time +# ... other imports ... + +class AiEngine: + def __init__(self, planning, taskmgr, lance, lance_table_name, project_audit): + # Step 1: 获取results + self.planning:APlanningV2 = planning + self.project_taskmgr:AProjectTaskMgr = taskmgr + self.lancedb:Coroutine[Any, Any, Any] = lance + self.lance_table_name = lance_table_name + self.project_audit = project_audit + self.executor = ThreadPoolExecutor(max_workers=int(os.getenv("MAX_THREADS_OF_SCAN", 5))) + + async def do_scan(self, is_gpt4=False, filter_func=None): + tasks: Coroutine[Any, Any, Any] = self.project_taskmgr.get_task_list() + if not tasks: + logger.warn("AI ASYNC ENG no Tasks in the project, is the DB ok?") + return + + async def process_task(task): + # Assemble prompt based on environment; assume PromptAssembler methods are synchronous. + scan_mode = os.getenv("SCAN_MODE", "COMMON_VUL") + if scan_mode == "OPTIMIZE": + prompt = PromptAssembler.assemble_optimize_prompt(task.content) + elif scan_mode == "COMMON_PROJECT": + prompt = PromptAssembler.assemble_prompt_common(task.content) + elif scan_mode == "PURE_SCAN": + prompt = PromptAssembler.assemble_prompt_pure(task.content) + elif scan_mode == "SPECIFIC_PROJECT": + business_types = task.recommendation.split(',') + prompt = PromptAssembler.assemble_prompt_for_specific_project(task.content, business_types) + else: + prompt = PromptAssembler.assemble_prompt_common(task.content) + + # Call async LLM API via our async wrapper + response_vul = await async_ask_claude(prompt) + response_vul = response_vul or "no" + # Update task result; if update_result is blocking, offload it: + await asyncio.to_thread(self.project_taskmgr.update_result, task.id, response_vul, "", "") + + await asyncio.gather(*(process_task(task) for task in tasks)) + return tasks + + async def check_function_vul(self): + tasks = self.project_taskmgr.get_task_list() + if not tasks: + return + + async def process_check(task): + prompt = PromptAssembler.assemble_vul_check_prompt(task.content, task.get_result(False)) + # Assume we create an async wrapper for confirmation API calls; + # if not, wrap the blocking call via asyncio.to_thread: + initial_response = await asyncio.to_thread(common_ask_confirmation, prompt) + if not initial_response: + print(f"Empty response for task {task.id}") + return + # Additional processing, JSON parsing, voting logic etc. + # Finally, update results (offload DB call): + await asyncio.to_thread(self.project_taskmgr.update_result, task.id, task.get_result(False), "final_status", "final_response") + + await asyncio.gather(*(process_check(task) for task in tasks)) + return tasks + def do_planning(self): + self.planning.do_planning() + def extract_title_from_text(self,input_text): + try: + # Regular expression pattern to capture the value of the title field + pattern = r'"title"\s*:\s*"([^"]+)"' + + # Searching for the pattern in the input text + match = re.search(pattern, input_text) + + # Extracting the value if the pattern is found + if match: + return match.group(1) + else: + return "Logic Error" + except Exception as e: + # Handling any exception that occurs and returning a message + return f"Logic Error {str(e)}" + + def process_task_do_scan(self,task, filter_func = None, is_gpt4 = False): + + response_final = "" + response_vul = "" + + # print("query vul %s - %s" % (task.name, task.rule)) + + result = task.get_result(is_gpt4) + business_flow_code = task.business_flow_code + if_business_flow_scan = task.if_business_flow_scan + function_code=task.content + + # 要进行检测的代码粒度 + code_to_be_tested=business_flow_code if if_business_flow_scan=="1" else function_code + if result is not None and len(result) > 0 and str(result).strip() != "NOT A VUL IN RES no": + print("\t skipped (scanned)") + else: + to_scan = filter_func is None or filter_func(task) + if not to_scan: + print("\t skipped (filtered)") + else: + print("\t to scan") + if os.getenv("SCAN_MODE","COMMON_VUL")=="OPTIMIZE": + prompt=PromptAssembler.assemble_optimize_prompt(code_to_be_tested) + elif os.getenv("SCAN_MODE","COMMON_VUL")=="COMMON_PROJECT": + prompt=PromptAssembler.assemble_prompt_common(code_to_be_tested) + elif os.getenv("SCAN_MODE","COMMON_VUL")=="PURE_SCAN": + prompt=PromptAssembler.assemble_prompt_pure(code_to_be_tested) + elif os.getenv("SCAN_MODE","COMMON_VUL")=="SPECIFIC_PROJECT": + # 构建提示来判断业务类型 + business_type=task.recommendation + print(f"[DEBUG] business_type: {business_type}") + # 数据库中保存的形式是xxxx,xxxxx,xxxx... 转成assemble_prompt_for_specific_project可以接收的数组形式 + business_type_list=business_type.split(',') + print(f"[DEBUG] business_type_list: {business_type_list}") + prompt = PromptAssembler.assemble_prompt_for_specific_project(code_to_be_tested, business_type_list) + print(f"[DEBUG] Generated prompt: {prompt}") + response_vul=ask_claude(prompt) + print(f"[DEBUG] Claude response: {response_vul}") + response_vul = response_vul if response_vul is not None else "no" + self.project_taskmgr.update_result(task.id, response_vul, "","") + def do_scan(self, is_gpt4=False, filter_func=None): + # self.llm.init_conversation() + + tasks = self.project_taskmgr.get_task_list() + if len(tasks) == 0: + return + + # 定义线程池中的线程数量 + max_threads = int(os.getenv("MAX_THREADS_OF_SCAN", 5)) + + with ThreadPoolExecutor(max_workers=max_threads) as executor: + futures = [executor.submit(self.process_task_do_scan, task, filter_func, is_gpt4) for task in tasks] + + with tqdm(total=len(tasks), desc="Processing tasks") as pbar: + for future in as_completed(futures): + future.result() # 等待每个任务完成 + pbar.update(1) # 更新进度条 + + return tasks + def process_task_check_vul(self, task:AProject_Task): + print("\n" + "="*80) + print(f"Processing Task ID: {task.id}") + print("="*80) + starttime = time.time() + result = task.get_result(False) + result_CN = task.get_result_CN() + category_mark = task.get_category() + + if result_CN is not None and len(result_CN) > 0 and result_CN != "None" and category_mark is not None and len(category_mark)>0: + print("\n🔄 Task already processed, skipping...") + return + + print("\n🔍 Starting vulnerability confirmation process...") + function_code = task.content + if_business_flow_scan = task.if_business_flow_scan + business_flow_code = task.business_flow_code + business_flow_context = task.business_flow_context + + code_to_be_tested = business_flow_code+"\n"+business_flow_context if if_business_flow_scan=="1" else function_code + + # 第一轮分析 + print("\n=== First Round Analysis ===") + print("📝 Analyzing potential vulnerability...") + prompt = PromptAssembler.assemble_vul_check_prompt(code_to_be_tested, result) + # 把prompot保存到临时文件 + with open("prompt.txt", "w") as file: + file.write(prompt) + + initial_response = common_ask_confirmation(prompt) + if not initial_response or initial_response == "": + print(f"❌ Error: Empty response received for task {task.id}") + return + + print("\n📊 Initial Analysis Result:") + print("-" * 80) + print(initial_response) + print("-" * 80) + + # 提取需要的额外信息 + required_info = self.extract_required_info(initial_response) + + combined_code = code_to_be_tested + if required_info: + print("\n=== Additional Information Required ===") + print("🔎 Required context/information:") + for i, info in enumerate(required_info, 1): + print(f"{i}. {info}") + + print("\n📥 Retrieving additional context...") + additional_context = self.get_additional_context(required_info) + + if additional_context: + print("\n📦 Retrieved additional context (length: {len(additional_context)} chars)") + if len(additional_context) < 500: + print("\nAdditional context details:") + print("-" * 80) + print(additional_context) + print("-" * 80) + + combined_code = f"""Original Code: + {code_to_be_tested} + + First Round Analysis: + {initial_response} + + Additional Context: + {additional_context}""" + + # 进行三轮确认 + confirmation_results = [] + response_final = None # 初始化 response_final + final_response = None # 初始化 final_response + + for i in range(3): + if response_final == "no": # 如果已经确认为 no,直接跳过后续循环 + break + + print(f"\n📊 Round {i+1}/3 Analysis:") + prompt = PromptAssembler.assemble_vul_check_prompt_final(combined_code, result) + round_response = common_ask_confirmation(prompt) + + print("-" * 80) + print(round_response) + print("-" * 80) + + prompt_translate_to_json = PromptAssembler.brief_of_response() + print("\n🔍 Brief Response Prompt:") + print(prompt_translate_to_json) + + round_json_response = str(common_ask_for_json(round_response+"\n"+prompt_translate_to_json)) + print("\n📋 JSON Response:") + print(round_json_response) + + try: + response_data = loads(round_json_response) + result_status = response_data.get("result", "").lower() + print("\n🎯 Extracted Result Status:") + print(result_status) + + confirmation_results.append(result_status) + + # 如果发现一个明确的 "no",立即确认为不存在漏洞 + if "no" in result_status: + print("\n🛑 Clear 'no vulnerability' detected - stopping further analysis") + response_final = "no" + final_response = f"Analysis stopped after round {i+1} due to clear 'no vulnerability' result" + continue # 使用 continue 让循环能够在下一轮开始时通过上面的 break 检查退出 + + except JSONDecodeError: + print("\n⚠️ JSON Decode Error - marking as 'not sure'") + confirmation_results.append("not sure") + + # 只有在没有提前退出(找到明确的 no)的情况下才进行多数投票 + if response_final != "no": # 修改判断条件 + # 统计结果 + yes_count = sum(1 for r in confirmation_results if "yes" in r or "confirmed" in r) + no_count = sum(1 for r in confirmation_results if "no" in r and "vulnerability" in r) + + if yes_count >= 2: + response_final = "yes" + print("\n⚠️ Final Result: Vulnerability Confirmed (2+ positive confirmations)") + elif no_count >= 2: + response_final = "no" + print("\n✅ Final Result: No Vulnerability (2+ negative confirmations)") + else: + response_final = "not sure" + print("\n❓ Final Result: Not Sure (inconclusive results)") + + final_response = "\n".join([f"Round {i+1} Analysis:\n{resp}" for i, resp in enumerate(confirmation_results)]) + + self.project_taskmgr.update_result(task.id, result, response_final, final_response) + + endtime = time.time() + time_cost = endtime - starttime + + print("\n=== Task Summary ===") + print(f"⏱️ Time cost: {time_cost:.2f} seconds") + print(f"📝 Analyses performed: {len(confirmation_results)}") + print(f"🏁 Final status: {response_final}") + print("=" * 80 + "\n") + def get_related_functions(self,query,k=3): + query_embedding = common_get_embedding(query) + table = self.lancedb.open_table(self.lance_table_name) + return table.search(query_embedding).limit(k).to_list() + + def extract_related_functions_by_level(self, function_names: List[str], level: int) -> str: + """ + 从call_trees中提取指定函数相关的上下游函数信息并扁平化处理 + + Args: + function_names: 要分析的函数名列表 + level: 要分析的层级深度 + + Returns: + str: 所有相关函数内容的拼接文本 + """ + def get_functions_from_tree(tree, current_level=0, max_level=level, collected_funcs=None, level_stats=None): + """递归获取树中指定层级内的所有函数信息""" + if collected_funcs is None: + collected_funcs = [] + if level_stats is None: + level_stats = {} + + if not tree or current_level > max_level: + return collected_funcs, level_stats + + # 添加当前节点的函数信息 + if tree['function_data']: + collected_funcs.append(tree['function_data']) + # 更新层级统计 + level_stats[current_level] = level_stats.get(current_level, 0) + 1 + + # 递归处理子节点 + if current_level < max_level: + for child in tree['children']: + get_functions_from_tree(child, current_level + 1, max_level, collected_funcs, level_stats) + + return collected_funcs, level_stats + + all_related_functions = [] + statistics: dict[str, dict[int, int]] = { + 'upstream_stats': {}, + 'downstream_stats': {} + } + + # 使用集合进行更严格的去重 + seen_functions = set() # 存储函数的唯一标识符 + unique_functions = [] # 存储去重后的函数 + + # 遍历每个指定的函数名 + for func_name in function_names: + # 在call_trees中查找对应的树 + for tree_data in self.project_audit.call_trees: + if tree_data['function'] == func_name: + # 处理上游调用树 + if tree_data['upstream_tree']: + upstream_funcs, upstream_stats = get_functions_from_tree(tree_data['upstream_tree']) + all_related_functions.extend(upstream_funcs) + # 合并上游统计信息 + for level, count in upstream_stats.items(): + if isinstance(statistics['upstream_stats'], dict): + statistics['upstream_stats'][level] = statistics['upstream_stats'].get(level, 0) + count + else: # the following line is unreachable + statistics['upstream_stats'] = {level: count} + + # 处理下游调用树 + if tree_data['downstream_tree']: + downstream_funcs, downstream_stats = get_functions_from_tree(tree_data['downstream_tree']) + all_related_functions.extend(downstream_funcs) + # 合并下游统计信息 + for level, count in downstream_stats.items(): + # TODO #?? Double check this + statistics['downstream_stats'][level] = statistics['downstream_stats'].get(level, 0) + count + + # 添加原始函数本身 + for func in self.project_audit.functions_to_check: + if func['name'].split('.')[-1] == func_name: + all_related_functions.append(func) + break + + break + + # 增强的去重处理 + for func in all_related_functions: + # 创建一个更精确的唯一标识符,包含函数名和内容的hash + func_identifier = f"{func['name']}_{hash(func['content'])}" + if func_identifier not in seen_functions: + seen_functions.add(func_identifier) + unique_functions.append(func) + + # 拼接所有函数内容,包括状态变量 + combined_text_parts = [] + for func in unique_functions: + # 查找对应的状态变量 + state_vars = None + for tree_data in self.project_audit.call_trees: + if tree_data['function'] == func['name'].split('.')[-1]: + state_vars = tree_data.get('state_variables', '') + break + + # 构建函数文本,包含状态变量 + function_text = [] + if state_vars: + function_text.append("// Contract State Variables:") + function_text.append(state_vars) + function_text.append("\n// Function Implementation:") + function_text.append(func['content']) + + combined_text_parts.append('\n'.join(function_text)) + + combined_text = '\n\n'.join(combined_text_parts) + + # 打印统计信息 + print("\nFunction Call Tree Statistics:") + print(f"Total Layers Analyzed: {level}") + print("\nUpstream Statistics:") + for layer, count in statistics['upstream_stats'].items(): + print(f"Layer {layer}: {count} functions") + print("\nDownstream Statistics:") + for layer, count in statistics['downstream_stats'].items(): + print(f"Layer {layer}: {count} functions") + print(f"\nTotal Unique Functions: {len(unique_functions)}") + + return combined_text + + + def check_function_vul(self): + # self.llm.init_conversation() + tasks = self.project_taskmgr.get_task_list() + # 用codebaseQA的形式进行,首先通过rag和task中的vul获取相应的核心三个最相关的函数 + for task in tqdm(tasks,desc="Processing tasks for update business_flow_context"): + if task.score=="1": + continue + if task.if_business_flow_scan=="1": + # 获取business_flow_context + code_to_be_tested=task.business_flow_code + else: + code_to_be_tested=task.content + related_functions=self.get_related_functions(code_to_be_tested,5) + related_functions_names=[func['name'].split('.')[-1] for func in related_functions] + combined_text=self.extract_related_functions_by_level(related_functions_names,6) + # 更新task对应的business_flow_context + self.project_taskmgr.update_business_flow_context(task.id,combined_text) + self.project_taskmgr.update_score(task.id,"1") + + + if len(tasks) == 0: + return + + # 定义线程池中的线程数量, 从env获取 + max_threads = int(os.getenv("MAX_THREADS_OF_CONFIRMATION", 5)) + + with ThreadPoolExecutor(max_workers=max_threads) as executor: + futures = [executor.submit(self.process_task_check_vul, task) for task in tasks] + + with tqdm(total=len(tasks), desc="Checking vulnerabilities") as pbar: + for future in as_completed(futures): + future.result() # 等待每个任务完成 + pbar.update(1) # 更新进度条 + + return tasks + + def extract_required_info(self, claude_response): + """Extract information that needs further investigation from Claude's response""" + prompt = """ + Please extract all information points that need further understanding or confirmation from the following analysis response. + If the analysis explicitly states "no additional information needed" or similar, return empty. + If the analysis mentions needing more information, extract these information points. + + Analysis response: + {response} + """ + + extraction_result = ask_claude(prompt.format(response=claude_response)) + if not extraction_result or extraction_result.isspace(): + return [] + + # If response contains negative phrases, return empty list + if any(phrase in extraction_result.lower() for phrase in ["no need", "not needed", "no additional", "no more"]): + return [] + + return [extraction_result] + + def get_additional_context(self, query_contents): + """获取额外的上下文信息""" + if not query_contents: + return "" + + # 使用所有查询内容获取相关信息 + related_functions = [] + for query in query_contents: + results = self.get_related_functions(query, k=10) # 获取最相关的3个匹配 + if results: + related_functions.extend(results) + + # 提取这些函数的上下文 + if related_functions: + function_names = [func['name'].split('.')[-1] for func in related_functions] + return self.extract_related_functions_by_level(function_names, 2) + return "" + +if __name__ == "__main__": + pass \ No newline at end of file diff --git a/src/ai_engine.py b/src/ai_engine.py index 850f01d3..89438ca8 100644 --- a/src/ai_engine.py +++ b/src/ai_engine.py @@ -6,7 +6,7 @@ from typing import List import requests import tqdm -from sklearn.metrics.pairwise import cosine_similarity +#from sklearn.metrics.pairwise import cosine_similarity from concurrent.futures import ThreadPoolExecutor, as_completed from tqdm import tqdm import warnings diff --git a/src/codebaseQA/arag_processor.py b/src/codebaseQA/arag_processor.py index a0012ec7..a8706d03 100644 --- a/src/codebaseQA/arag_processor.py +++ b/src/codebaseQA/arag_processor.py @@ -11,6 +11,16 @@ from project.aproject_audit import AProjectAudit class ARAGProcessor: + + async def acheck_data_count(self, expected_count: int) -> bool: + """检查表中的数据数量是否匹配""" + try: + table = await self.db.open_table(self.table_name) + actual_count = len(await table.to_lance()) + return actual_count == expected_count + except Exception: + return False + def __init__(self, id: str = None, audit: AProjectAudit = None): self.db_path: str = os.path.join(os.getcwd(), f"Alancedb{id}") self.audit:AProjectAudit = audit @@ -33,13 +43,6 @@ def __init__(self, id: str = None, audit: AProjectAudit = None): pa.field("state_mutability", pa.string()) ]) - # 检查表是否存在且数据量匹配 - if self.table_exists() and self.check_data_count(len(functions_to_check)): - print(f"Table {self.table_name} already exists with correct data count. Skipping processing.") - return - - asyncio.run(self._create_database(functions_to_check)) - async def table_exists(self) -> bool: """检查表是否存在""" try: @@ -48,15 +51,6 @@ async def table_exists(self) -> bool: except Exception: return False - async def check_data_count(self, expected_count: int) -> bool: - """检查表中的数据数量是否匹配""" - try: - table = await self.db.open_table(self.table_name) - actual_count = len(await table.to_lance()) - return actual_count == expected_count - except Exception: - return False - def process_function(self, func: Dict[str, Any]) -> Dict[str, Any]: return { "id": f"{func['name']}_{func['start_line']}", diff --git a/src/dao/aentity.py b/src/dao/aentity.py index c6701747..b578ccb2 100644 --- a/src/dao/aentity.py +++ b/src/dao/aentity.py @@ -1,9 +1,15 @@ import random +from sqlalchemy import Column, Integer, String, select from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.ext.asyncio import AsyncAttrs from sqlalchemy.orm import sessionmaker, declarative_base from library.utils import str_hash +from sqlalchemy.ext.asyncio import AsyncAttrs +from sqlalchemy.orm import DeclarativeBase +from nodes_config import Settings -Base = declarative_base() +class Base (AsyncAttrs,DeclarativeBase): + pass class ACacheEntry(Base): __tablename__ = 'prompt_cache2' @@ -130,7 +136,7 @@ def get_similarity_with_rule(self): return None if result == '' else result # Create an async engine and sessionmaker -DATABASE_URL = "postgresql+asyncpg://user:password@localhost/dbname" +DATABASE_URL = "postgresql+asyncpg://postgres:1234@127.0.0.1:5432/postgres" engine = create_async_engine(DATABASE_URL, echo=True) AsyncSessionLocal = sessionmaker( bind=engine, @@ -149,7 +155,7 @@ async def get_aproject_task_by_id(task_id: int): return result.scalars().first() # Example of inserting a new project task -async def create_aproject_task(project_id, name, content, keyword, business_type, sub_business_type, function_type, rule): +async def create_aproject_task(project_id, name, content, keyword, business_type, sub_business_type, function_type, rule) -> AProject_Task: async with AsyncSessionLocal() as session: new_task = AProject_Task( project_id=project_id, diff --git a/src/dao/atask_mgr.py b/src/dao/atask_mgr.py index d21cba09..656b5a4f 100644 --- a/src/dao/atask_mgr.py +++ b/src/dao/atask_mgr.py @@ -1,22 +1,33 @@ +import asyncio import csv from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.ext.asyncio.engine import AsyncEngine -from sqlalchemy.orm import sessionmaker, declarative_base +from sqlalchemy.orm import declarative_base from sqlalchemy.future import select from sqlalchemy.exc import IntegrityError from tqdm.asyncio import tqdm as tqdm_asyncio +from sqlalchemy.ext.asyncio import AsyncAttrs +from sqlalchemy.orm import DeclarativeBase +from sqlalchemy.ext.asyncio import async_sessionmaker from dao.aentity import AProject_Task -Base = declarative_base() - +# Base = declarative_base() +class Base(AsyncAttrs, DeclarativeBase): + pass class AProjectTaskMgr: def __init__(self, project_id, engine_url): self.project_id = project_id # Create async engine - self.engine: AsyncEngine = create_async_engine(engine_url) + self.engine: AsyncEngine = create_async_engine("postgresql+asyncpg://postgres:1234@127.0.0.1:5432/postgres") # Ensure table is created - Base.metadata.create_all(self.engine) + #Base.metadata.create_all(self.engine) + async def init_models(): + async with self.engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + await conn.run_sync(Base.metadata.create_all) + + init_models() # Configure sessionmaker to use AsyncSession - self.Session = sessionmaker(bind=self.engine, class_=AsyncSession) + self.Session = async_sessionmaker(bind=self.engine, class_=AsyncSession) async def _operate_in_session(self, func, *args, **kwargs): """Generic function to handle operations within an async session.""" diff --git a/src/dynamic_governor.py b/src/dynamic_governor.py new file mode 100644 index 00000000..8ff3885f --- /dev/null +++ b/src/dynamic_governor.py @@ -0,0 +1,48 @@ +import asyncio +import time +from collections import deque + +class DynamicGovernor: + def __init__(self, initial_limit=5, window_size=10, fast_threshold=1.0, slow_threshold=3.0): + self.limit = initial_limit + self.semaphore = asyncio.Semaphore(self.limit) + self.window_size = window_size + # Store the last window_size durations + self.task_durations = deque(maxlen=window_size) + self.fast_threshold = fast_threshold + self.slow_threshold = slow_threshold + + async def acquire(self): + await self.semaphore.acquire() + + def release(self, duration: float): + self.task_durations.append(duration) + self.semaphore.release() + self.adjust_limit() + + def adjust_limit(self): + if not self.task_durations: + return + # Compute the moving average of task durations + avg_duration = sum(self.task_durations) / len(self.task_durations) + new_limit = self.limit + if avg_duration < self.fast_threshold: + new_limit = self.limit * 2 + elif avg_duration > self.slow_threshold: + new_limit = max(1, int(self.limit * 2 / 3)) + # Only adjust if there is a change + if new_limit != self.limit: + print(f"Adjusting concurrency limit from {self.limit} to {new_limit} (avg_duration={avg_duration:.2f}s)") + self.limit = new_limit + # Reinitialize the semaphore to the new limit. + self.semaphore = asyncio.Semaphore(self.limit) + +async def governed_task(task_fn, governor: DynamicGovernor, *args, **kwargs): + await governor.acquire() + start = time.monotonic() + try: + result = await task_fn(*args, **kwargs) + return result + finally: + duration = time.monotonic() - start + governor.release(duration) diff --git a/src/library/app.css b/src/library/app.css new file mode 100644 index 00000000..9e5c0188 --- /dev/null +++ b/src/library/app.css @@ -0,0 +1,45 @@ +/* app.css */ + +/* Style for the input widget */ +#text_input { + height: 3; + padding: 1; + border: tall #00FF00; + background: #1E1E1E; + color: white; +} + +/* Style for the dual language view container */ +#dual_view { + margin-top: 1; + margin-bottom: 1; +} + +/* Style for the detail view widget */ +#detail_view { + margin-top: 1; + margin-bottom: 1; + background: #222222; + border: round #888888; + padding: 1; + color: #CCCCCC; +} + +/* Header and Footer styling */ +Header { + background: #333333; + color: #FFFFFF; + padding: 1; +} + +Footer { + background: #333333; + color: #FFFFFF; + padding: 1; +} + +/* Optional: Style for panels within the dual view */ +Panel { + border: round #555555; + padding: 1; +} diff --git a/src/library/parsing/SolidityLexer.py b/src/library/parsing/SolidityLexer.py index cfba6b5b..ce7116db 100644 --- a/src/library/parsing/SolidityLexer.py +++ b/src/library/parsing/SolidityLexer.py @@ -1,11 +1,9 @@ # Generated from .\SolidityLexer.g4 by ANTLR 4.12.0 -from antlr4 import * +# Removed wildcard import; explicit imports follow from io import StringIO import sys -if sys.version_info[1] > 5: - from typing import TextIO -else: - from typing.io import TextIO +from antlr4 import Lexer, LexerATNSimulator, ATNDeserializer, DFA, PredictionContextCache +from typing import TextIO def serializedATN(): diff --git a/src/library/parsing/callgraph.py b/src/library/parsing/callgraph.py index e8175465..12fbcc14 100644 --- a/src/library/parsing/callgraph.py +++ b/src/library/parsing/callgraph.py @@ -135,6 +135,7 @@ def __parse_all_files(self): continue self.files[os.path.abspath(os.path.join(root, file))] = parseString(open(os.path.join(root, file), "r", encoding="utf-8", errors="ignore").read()) + ### TODO!! asyncio.create_subprocess_exec def __run_jar(self): dir_name = os.path.abspath(os.path.dirname(__file__)) jar_file = os.path.join(dir_name, "jars/SolidityCallgraph-1.0-SNAPSHOT-standalone.jar") diff --git a/src/library/sgp/parser/SolidityLexer.py b/src/library/sgp/parser/SolidityLexer.py index f7dfdf27..e8bd5833 100644 --- a/src/library/sgp/parser/SolidityLexer.py +++ b/src/library/sgp/parser/SolidityLexer.py @@ -1,11 +1,9 @@ # Generated from Solidity.g4 by ANTLR 4.13.1 -from antlr4 import * +from antlr4 import DFA, ATNDeserializer, LexerATNSimulator, PredictionContextCache +from antlr4.Lexer import Lexer from io import StringIO import sys -if sys.version_info[1] > 5: - from typing import TextIO -else: - from typing.io import TextIO +from typing import TextIO def serializedATN(): diff --git a/src/library/sgp/parser/SolidityParser.py b/src/library/sgp/parser/SolidityParser.py index 8db8235d..a11a3e0f 100644 --- a/src/library/sgp/parser/SolidityParser.py +++ b/src/library/sgp/parser/SolidityParser.py @@ -1,12 +1,13 @@ # Generated from Solidity.g4 by ANTLR 4.13.1 # encoding: utf-8 -from antlr4 import * +from antlr4 import DFA, Parser, RuleContext, Token, TokenStream, ParserRuleContext, PredictionContextCache, ParseTreeVisitor, ParseTreeListener, ParserATNSimulator from io import StringIO import sys -if sys.version_info[1] > 5: - from typing import TextIO -else: - from typing.io import TextIO +from antlr4.atn.ATNDeserializer import ATNDeserializer +from antlr4 import RecognitionException +from antlr4.atn.ATN import ATN +from antlr4.error.Errors import NoViableAltException +from typing import TextIO def serializedATN(): return [ @@ -9011,7 +9012,7 @@ def stringLiteral(self): def sempred(self, localctx:RuleContext, ruleIndex:int, predIndex:int): - if self._predicates == None: + if not hasattr(self, '_predicates') or self._predicates is None: self._predicates = dict() self._predicates[38] = self.typeName_sempred self._predicates[70] = self.expression_sempred diff --git a/src/library/sgp/sgp_visitor.py b/src/library/sgp/sgp_visitor.py index c3538b21..bce441ea 100644 --- a/src/library/sgp/sgp_visitor.py +++ b/src/library/sgp/sgp_visitor.py @@ -1,5 +1,92 @@ -from typing import Any, List, Optional, Tuple, Union +from typing import Any, List, Optional, Union, cast from typing_extensions import override +from library.sgp.ast_node_types import ( + UNARY_OP_VALUES, + InheritanceSpecifier, + NameValueExpression, + BINARY_OP_VALUES, + Conditional, + NameValueList, + ArrayTypeName, + AssemblyBlock, + AssemblyCase, + AssemblyCall, + AssemblyFunctionDefinition, + AssemblyIf, + AssemblyFor, + AssemblyAssignment, + AssemblyItem, + AssemblyLocalDefinition, + AssemblyMemberAccess, + AssemblyStackAssignment, + AssemblySwitch, + BaseASTNode, + Block, + BinaryOperation, + BooleanLiteral, + Break, + BreakStatement, + CatchClause, + Continue, + ContinueStatement, + ContractDefinition, + CustomErrorDefinition, + DecimalNumber, + DoWhileStatement, + ElementaryTypeName, + EmitStatement, + EnumDefinition, + EnumValue, + EventDefinition, + Expression, + ExpressionStatement, + FileLevelConstant, + ForStatement, + FunctionCall, + FunctionDefinition, + FunctionTypeName, + HexLiteral, + HexNumber, + Identifier, + IfStatement, + ImportDirective, + IndexAccess, + IndexRangeAccess, + LabelDefinition, + Location, + Mapping, + MemberAccess, + ModifierDefinition, + ModifierInvocation, + NewExpression, + NumberLiteral, + PrimaryExpression, + PragmaDirective, + Range, + ReturnStatement, + RevertStatement, + SimpleStatement, + SourceUnit, + StateVariableDeclaration, + StateVariableDeclarationVariable, + Statement, + StringLiteral, + StructDefinition, + ThrowStatement, + TryStatement, + TupleExpression, + TypeDefinition, + TypeName, + UnaryOperation, + UncheckedStatement, + UserDefinedTypeName, + UsingForDeclaration, + VariableDeclaration, + VariableDeclarationStatement, + WhileStatement, + InlineAssemblyStatement, # added inline assembly type + AssemblyExpression, # added assembly expression type +) from antlr4.tree.Tree import ErrorNode from antlr4 import ParserRuleContext @@ -12,10 +99,6 @@ from library.sgp.parser.SolidityParser import SolidityParser as SP from library.sgp.parser.SolidityVisitor import SolidityVisitor - -from .ast_node_types import * - - class SGPVisitorOptions: def __init__( self, @@ -43,7 +126,7 @@ def __init__( class SGPVisitor(SolidityVisitor): def __init__(self, options: SGPVisitorOptions): super().__init__() - self._current_contract = None + self._current_contract: Optional[str] = None self._options = options @override @@ -169,7 +252,7 @@ def visitVariableDeclaration( identifierCtx = ctx.identifier() node = VariableDeclaration( - type_name=self.visitTypeName(ctx.typeName()), + type_name=cast(TypeName, self.visitTypeName(ctx.typeName())), name=self._to_text(identifierCtx), identifier=self.visitIdentifier(identifierCtx), storage_location=storageLocation, @@ -199,11 +282,11 @@ def visitVariableDeclarationStatement( ctxExpression = ctx.expression() if ctxExpression: initialValue = self.visitExpression(ctxExpression) - - node = VariableDeclarationStatement( - variables=variables, + node = VariableDeclarationStatement( + variables=[cast(Optional[BaseASTNode], v) for v in variables], initial_value=initialValue, - ) + ) + return self._add_meta(node, ctx) @@ -213,7 +296,7 @@ def visitStatement(self, ctx: SP.StatementContext) -> Statement: def visitSimpleStatement( self, ctx: SP.SimpleStatementContext ) -> SimpleStatement: - if ctx.children == None: + if ctx.children is None: return self.visitErrorNode(ctx.start) return self.visit(ctx.getChild(0)) # Assuming the child type is SimpleStatement @@ -223,7 +306,7 @@ def visitEventDefinition( parameters = [ self._add_meta( VariableDeclaration( - type_name=self.visitTypeName(paramCtx.typeName()), + type_name=cast(TypeName, self.visitTypeName(paramCtx.typeName())), name=self._to_text(paramCtx.identifier()) if paramCtx.identifier() else None, @@ -472,7 +555,7 @@ def visitUsingForDeclaration( # using Lib for ... node = UsingForDeclaration( is_global=isGlobal, - type_name=typeName, + type_name=cast(TypeName, typeName), library_name=self._to_text(userDefinedTypeNameCtx), functions=[], operators=[], @@ -492,7 +575,7 @@ def visitUsingForDeclaration( node = UsingForDeclaration( is_global=isGlobal, - type_name=typeName, + type_name=cast(TypeName, typeName), library_name=None, functions=functions, operators=operators, @@ -608,7 +691,7 @@ def visitFunctionTypeParameter( return self._add_meta(node, ctx) def visitThrowStatement(self, ctx: SP.ThrowStatementContext) -> ThrowStatement: - node = ThrowStatement(type="ThrowStatement") + node = ThrowStatement() return self._add_meta(node, ctx) @@ -916,9 +999,9 @@ def visitExpression(self, ctx: SP.ExpressionContext) -> Expression: ): node = TupleExpression( components=[ - self.visitExpression( + cast(BaseASTNode, self.visitExpression( ctx.getTypedRuleContext(SP.ExpressionContext, 0) - ) + )) ], isArray=False, ) @@ -1144,7 +1227,7 @@ def visitPrimaryExpression(self, ctx: SP.PrimaryExpressionContext) -> Union[Prim fragments_info.append({"value": value, "is_unicode": is_unicode}) parts = [x["value"] for x in fragments_info] - node = StringLiteral(value="".join(parts), parts=parts, is_unicode=[x["is_unicode"] for x in fragments_info]) + node = StringLiteral(value="".join(str(p) for p in parts), parts=parts, is_unicode=[x["is_unicode"] for x in fragments_info]) return self._add_meta(node, ctx) if ctx.numberLiteral(): @@ -1157,7 +1240,7 @@ def visitPrimaryExpression(self, ctx: SP.PrimaryExpressionContext) -> Union[Prim if ctx.typeName(): return self.visitTypeName(ctx.typeName()) - if ctx.children == None: + if ctx.children is None: return self.visitErrorNode(ctx.start) return self.visit(ctx.getChild(0)) @@ -1594,7 +1677,7 @@ def visitBreakStatement(self, ctx: SP.BreakStatementContext) -> BreakStatement: return self._add_meta(node, ctx) - def _to_text(self, ctx: ParserRuleContext or ParseTree) -> str: + def _to_text(self, ctx: Union[ParserRuleContext | ParseTree]) -> str: text = ctx.getText() if text is None: raise ValueError("Assertion error: text should never be undefined") @@ -1626,21 +1709,20 @@ def _loc(self, ctx) -> Location: ) return source_location + def _range(self, ctx) -> Range: + start_offset = ctx.start.start + end_offset = ctx.stop.stop if ctx.stop else ctx.start.stop + return Range(start=start_offset, end=end_offset) - def _range(self, ctx) -> Tuple[int, int]: - return Range(ctx.start.start, ctx.stop.stop if ctx.stop else ctx.start.stop) - - def _add_meta( - self, node: Union[BaseASTNode, NameValueList], ctx - ) -> Union[BaseASTNode, NameValueList]: + from typing import TypeVar + T = TypeVar("T", bound=BaseASTNode) + + def _add_meta(self, node: T, ctx) -> T: # node_with_meta = {"type": node.type} - if self._options.loc: node.add_loc(self._loc(ctx)) - if self._options.range: node.add_range(self._range(ctx)) - return node def _map_commas_to_nulls( @@ -1649,7 +1731,7 @@ def _map_commas_to_nulls( if len(children) == 0: return [] - values = [] + values: list[Optional[ParseTree]] = [] comma = True for el in children: diff --git a/src/main.py b/src/main.py index d29475b9..624824a4 100644 --- a/src/main.py +++ b/src/main.py @@ -1,5 +1,4 @@ import argparse -import ast import os import time import audit_config @@ -7,7 +6,6 @@ from project import ProjectAudit from library.dataset_utils import load_dataset, Project from planning import PlanningV2 -from prompts import prompts from sqlalchemy import create_engine from dao import CacheManager, ProjectTaskMgr import pandas as pd @@ -196,7 +194,7 @@ def generate_excel(output_path, project_id): # check_function_vul(engine) # confirm # elif args.cmd == 'all': lancedb=scan_project(project, engine) # scan - check_function_vul(engine,lancedb) # confirm + check_function_vul(engine,lancedb, lance_table_name, project_audit) # confirm end_time = time.time() print("Total time:", end_time -start_time) diff --git a/src/main_pipeline.py b/src/main_pipeline.py new file mode 100644 index 00000000..1ad6a42f --- /dev/null +++ b/src/main_pipeline.py @@ -0,0 +1,84 @@ +import asyncio +from pydantic_ai import Agent +from pydantic_ai.models.openai import OpenAIModel + +# --- Phase 1: Planning Phase --- +async def planning_phase(code_snippet: str) -> dict: + planning_model = OpenAIModel( + model_name="openai-planning-model", + base_url="http://127.0.0.1:11434/v1" + ) + planning_agent = Agent(planning_model, retries=3, result_type=dict) + prompt = ( + f"Analyze the following code and extract its business flow as JSON:\n\n{code_snippet}" + ) + planning_result = await planning_agent.run(prompt) + print("[Planning] Result:", planning_result) + return planning_result + +# --- Phase 2: Scanning Phase with Dynamic Governor --- +# Import the dynamic governor from above +from dynamic_governor import DynamicGovernor, governed_task + +async def scanning_phase(business_flow: dict) -> dict: + scanning_model = OpenAIModel( + model_name="openai-scanning-model", + base_url="http://127.0.0.1:11434/v1" + ) + scanning_agent = Agent(scanning_model, retries=3, result_type=dict) + prompt = ( + f"Given the business flow JSON:\n{business_flow}\n" + f"Identify potential vulnerabilities and output a JSON summary." + ) + # Initialize dynamic governor for this phase + governor = DynamicGovernor(initial_limit=5, window_size=10) + # Wrap the scanning API call in a governed task: + async def scan_call(p): + return await scanning_agent.run(p) + scanning_result = await governed_task(scan_call, governor, prompt) + print("[Scanning] Result:", scanning_result) + return scanning_result + +# --- Phase 3: Confirmation Phase --- +async def confirmation_phase(scan_result: dict) -> dict: + confirmation_model = OpenAIModel( + model_name="openai-confirmation-model", + base_url="http://127.0.0.1:11434/v1" + ) + confirmation_agent = Agent(confirmation_model, retries=3, result_type=dict) + prompt = ( + f"Review the following scan result:\n{scan_result}\n" + f"Confirm or refute the findings and output your conclusion in JSON." + ) + confirmation_result = await confirmation_agent.run(prompt) + print("[Confirmation] Result:", confirmation_result) + return confirmation_result + +# Phase 4: Final Aggregation / Message Pump +async def final_phase(confirmation_data: dict, run_context): + final_model = OpenAIModel( + model_name="openai-final-model", + base_url="http://127.0.0.1:11434/v1" + ) + final_agent = Agent(final_model, retries=3, result_type=str) + prompt = ( + f"Aggregate the following confirmation data into a final report in Markdown:\n{confirmation_data}\n " + f"Output the final report." + ) + async with final_agent.run_stream(prompt) as stream: + async for message in stream.stream(): + # Inject output into the local RunContext for rendering. + run_context.update_markdown(message) + +# Main Pipeline: Chaining all phases together. +async def main_pipeline(run_context): + # In practice, the code snippet might come from your project audit/planning module. + code_snippet = """ + function transfer(address recipient, uint256 amount) public returns (bool) { + // business logic here... + } + """ + plan_result = await planning_phase(code_snippet) + scan_result = await scanning_phase(plan_result) + confirmation_result = await confirmation_phase(scan_result) + await final_phase(confirmation_result, run_context) \ No newline at end of file diff --git a/src/nodes_config.py b/src/nodes_config.py index e7c0f9ac..1355eba9 100644 --- a/src/nodes_config.py +++ b/src/nodes_config.py @@ -2,7 +2,7 @@ from griffe import DocstringStyle from pydantic_settings import BaseSettings, CliPositionalArg, PydanticBaseSettingsSource, PyprojectTomlConfigSettingsSource, SettingsConfigDict from typing import Tuple, Type - +from os import environ from pydantic_settings import ( BaseSettings, PydanticBaseSettingsSource, @@ -21,11 +21,11 @@ class Settings(BaseSettings): strict=False ) #database_dsn:PostgresDsn = None - id:str - base_dir:str - src_dir:str + id:str="" + base_dir:str="" + src_dir:str="" output:str="." - AZURE_OR_OPENAI:str="OPENAI" + AZURE_OR_OPENAI:str="" AZURE_API_BASE:str="" AZURE_API_KEY:str="" AZURE_API_VERSION:str="" @@ -37,7 +37,8 @@ class Settings(BaseSettings): CONFIRMATION_MODEL:str="" DATABASE_SQLITE:str="" DATABASE_SETTINGS_URL:str="" - DATABASE_URL:str='postgresql://postgres:1234@127.0.0.1:5432/postgres' + DATABASE_URL:str="postgresql://postgres:1234@127.0.0.1:5432/postgres" + ASYNC_DB_URL:str="postgresql+asyncpg://postgres:1234@127.0.0.1:5432/postgres" IGNORE_FOLDERS:str="test" MAX_THREADS_OF_CONFIRMATION:str="8" MAX_THREADS_OF_SCAN:str="8" @@ -50,10 +51,29 @@ class Settings(BaseSettings): SPECIFIC_PROJECT:str="" SWITCH_BUSINESS_CODE:str="True" SWITCH_FUNCTION_CODE:str="False" - + GEMINI_API_KEY:str="k" + PYTHONASYNCIODEBUG:str="1" + FORCE_COLOR:str="1" + @classmethod + def settings_customise_sources( + cls, + settings_cls: Type[BaseSettings], + env_settings: PydanticBaseSettingsSource, + init_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + + ) -> Tuple[PydanticBaseSettingsSource, ...]: + return ( + init_settings, + env_settings, + dotenv_settings, + PyprojectTomlConfigSettingsSource(settings_cls), + ) class nodes_config(Settings): - model_config:SettingsConfigDict = SettingsConfigDict( + def nodes_config(self): + model_config:SettingsConfigDict = SettingsConfigDict( cli_parse_args=True, cli_prog_name='finite-monkey-engine', pyproject_toml_depth=0, @@ -63,7 +83,8 @@ class nodes_config(Settings): env_file ='.env', env_ignore_empty=True, strict=False, - ) + ) + self.settings = Settings() @classmethod def settings_customise_sources( cls, @@ -76,7 +97,8 @@ def settings_customise_sources( ) -> Tuple[PydanticBaseSettingsSource, ...]: return ( init_settings, + PyprojectTomlConfigSettingsSource(settings_cls), env_settings, dotenv_settings, - PyprojectTomlConfigSettingsSource(settings_cls), - ) \ No newline at end of file + ) + diff --git a/src/planning/aplanning_v2.py b/src/planning/aplanning_v2.py index f601972d..45a65efb 100644 --- a/src/planning/aplanning_v2.py +++ b/src/planning/aplanning_v2.py @@ -1,556 +1,329 @@ import asyncio +import json import os +from typing import AsyncGenerator, Callable, Any + +from dao.atask_mgr import AProjectTaskMgr + +# Assume AProjectTaskMgr and other dependencies are imported + +# class APlanningV2: +# def __init__(self, project, mgr:AProjectTaskMgr ): +# self.project = project +# self.taskmgr = AProjectTaskMgr +# self.scan_list_for_larget_context: list[Any] = [] + +# async def do_planning(self) -> AsyncGenerator[Callable[[], Any], None]: +# """ +# Asynchronously plan the business flows for each function. +# Instead of returning a final result, yield a lambda that, when invoked, +# returns a future (a coroutine) for each iteration of planning. +# This allows an external agent to evaluate each stage as a future. +# """ +# # For example, fetch tasks from the DB. +# tasks = await self.taskmgr.get_task_list_by_id(self.project.project_id) +# if tasks: +# # If tasks already exist, we consider planning already done. +# return + +# # Filter functions (example logic). +# functions_to_check = [f for f in self.project.functions_to_check if "test" not in f['name']] +# self.project.functions_to_check = functions_to_check + +# # If business code switching is enabled, get all business flows. +# switch_business_code = eval(os.environ.get('SWITCH_BUSINESS_CODE', 'True')) +# if switch_business_code: +# all_business_flow, all_business_flow_line, all_business_flow_context = await self.get_all_business_flow(self.project.functions_to_check) +# else: +# all_business_flow = all_business_flow_line = all_business_flow_context = {} + +# # Iterate over each function to plan its business flow. +# for function in self.project.functions_to_check: +# name = function['name'] +# contract_name = function['contract_name'] +# # Log processing +# print(f"Processing function: {name}") + +# # Use your existing search_business_flow to get the flow code. +# business_flow_code, line_info_list, other_contract_context = await self.search_business_flow( +# all_business_flow, all_business_flow_line, all_business_flow_context, +# name.split(".")[1], contract_name +# ) +# # Build a prompt for type checking. +# type_check_prompt = ( +# "分析以下智能合约代码,判断它属于哪些业务类型。可能的类型包括:\n" +# "chainlink, dao, inline assembly, lending, liquidation, liquidity manager, signature, " +# "slippage, univ3, other\n" +# "请以JSON格式返回结果,格式为: {\"business_types\": [\"type1\", \"type2\"]}\n\n" +# "代码:\n{0}" +# ) +# formatted_prompt = type_check_prompt.format(business_flow_code + "\n" + other_contract_context + "\n" + function['content']) + +# # Instead of directly calling a REST method to get the answer, yield a lambda +# # that returns a coroutine to be evaluated later by the caller. +# yield lambda: self.common_ask_for_json(formatted_prompt) + +# # Optionally, you could also yield the current intermediate planning context. +# # For example: +# # yield lambda: asyncio.sleep(0, result={"function": name, "prompt": formatted_prompt}) + +# async def common_ask_for_json(self, prompt: str) -> dict: +# """ +# Simulate an asynchronous call to an LLM API that returns a JSON result. +# In your production code, this would be replaced with a pydantic-ai Agent call. +# """ +# # Simulated delay +# await asyncio.sleep(0.5) +# # For demonstration, return a dummy JSON response. +# # In reality, your agent would process the prompt. +# dummy_response = {"business_types": ["example_type1", "example_type2"]} +# print(f"Processed prompt: {prompt[:60]}... -> {dummy_response}") +# return dummy_response + +# # Placeholder methods for get_all_business_flow and search_business_flow +# async def get_all_business_flow(self, functions_to_check): +# # Simulated async processing. +# await asyncio.sleep(0.5) +# # Return dummy dictionaries. +# return {}, {}, {} + +# async def search_business_flow(self, all_flow, all_flow_line, all_flow_context, function_name, contract_name): +# # Simulated async search. +# await asyncio.sleep(0.5) +# # Return dummy business flow code and context. +# return f"business flow code for {function_name}", [], f"context for {contract_name}" +import asyncio import json +import os import re -from pydantic_ai import Agent -from tqdm import tqdm -import random -from dao.aentity import AProject_Task -from dao.atask_mgr import AProjectTaskMgr -from planning.aplanning_v2 import ATaskManager - -# from pydantic_ai.agent import Agent -# from pydantic_ai.models.gemini import GeminiModel -from pydantic_ai.models.openai import OpenAIModel - -from src.ai_engine import ModelSettings -# from pydantic_ai.models.anthropic import AnthropicModel -# #from pydantic_ai.models..cohere import Model -# from pydantic_ai.models.function import FunctionModel -# from pydantic_ai.models.groq import GroqModel -# from pydantic_ai.models.mistral import MistralModel -# from pydantic_ai.models.vertexai import VertexAIModel - -# class Project_Task: -# def __init__(self, **kwargs): -# for key, value in kwargs.items(): -# setattr(self, key, value) - -# class TaskManager: -# async def get_task_list_by_id(self, project_id): -# # Simulate fetching tasks from a database asynchronously -# await asyncio.sleep(0.1) -# return [] - -# async def add_task_in_one(self, task): -# # Simulate adding a task to the database asynchronously -# await asyncio.sleep(0.1) - -# class Project: -# def __init__(self, project_id, functions_to_check): -# self.project_id = project_id -# self.functions_to_check = functions_to_check +from typing import AsyncGenerator, Callable, Any, List, Tuple + +# Simulated pydantic model for LLM responses (for illustration) +from pydantic import BaseModel + +class TranslationResult(BaseModel): + english: str + chinese: str + +# Dummy implementations for demonstration. +# In production, replace these with real pydantic‑ai Agent calls. class APlanningV2: - def __init__(self, project): + def __init__(self, project, tm): self.project = project - self.taskmgr = AProjectTaskMgr() + self.taskmgr = tm # Assume this is defined elsewhere self.scan_list_for_larget_context = [] - async def get_all_business_flow(self, functions_to_check): - from library.sgp.utilities.contract_extractor import group_functions_by_contract - from library.sgp.utilities.contract_extractor import check_function_if_public_or_external - from library.sgp.utilities.contract_extractor import check_function_if_view_or_pure - - grouped_functions = group_functions_by_contract(functions_to_check) - contexts = self.identify_contexts(functions_to_check) - # 遍历grouped_functions,按每个合约代码进行业务流抽取 - all_business_flow = {} - all_business_flow_line={} - all_business_flow_context = {} - print("grouped contract count:",len(grouped_functions)) - - for contract_info in grouped_functions: - print("———————————————————————processing contract_info:",contract_info['contract_name'],"—————————————————————————") - contract_name = contract_info['contract_name'] - functions = contract_info['functions'] - contract_code_without_comments = contract_info['contract_code_without_comment'] # Assuming this is the correct key - - # 初始化合约名字典 - all_business_flow[contract_name] = {} - all_business_flow_line[contract_name]={} - all_business_flow_context[contract_name] = {} - - # New logic for determining function visibility - language_patterns = { - '.rust': lambda f: True, # No visibility filter for Rust - '.python': lambda f: True, # No visibility filter for Python - '.move': lambda f: f['visibility'] == 'public', - '.fr': lambda f: f['visibility'] == 'public', - '.java': lambda f: f['visibility'] in ['public', 'protected'], - '.cairo': lambda f: f['visibility'] == 'public', - '.tact': lambda f: f['visibility'] == 'public', - '.func': lambda f: f['visibility'] == 'public' - } - - def get_file_extension(funcs): - for func in funcs: - file_path = func['relative_file_path'] - for ext in language_patterns: - if file_path.endswith(ext): - return ext - return None - - file_ext = get_file_extension(functions) - visibility_filter = language_patterns.get(file_ext, lambda f: True) - - all_public_external_function_names = [ - function['name'].split(".")[1] for function in functions - if visibility_filter(function) - ] - - print("all_public_external_function_names count:",len(all_public_external_function_names)) - # if len(self.scan_list_for_larget_context)>0 and contract_name not in self.scan_list_for_larget_context: - # continue - # 有了函数名列表,有了contract_code_without_comments,可以进行业务流的GPT提问了 - print("-----------------asking openai for business flow-----------------") - for public_external_function_name in all_public_external_function_names: - # time.sleep(10) - print("***public_external_function_name***:",public_external_function_name) - if "_python" in str(contract_name) and len(all_public_external_function_names)==1: - key = all_public_external_function_names[0] - data = {key: all_public_external_function_names} - business_flow_list = json.dumps(data) - else: - try: - business_flow_list = self.ask_openai_for_business_flow(public_external_function_name, contract_code_without_comments) - except Exception as e: - business_flow_list=[] - if (not business_flow_list) or (len(business_flow_list)==0): - continue - # 返回一个list,这个list中包含着多条从public_external_function_name开始的业务流函数名 - try: - function_lists = self.extract_filtered_functions(business_flow_list) - # 判断function_lists中是否包含public_external_function_name,如果包含,则去掉 - if public_external_function_name in function_lists: - function_lists.remove(public_external_function_name) - except Exception as e: - print(e) - print("business_flow_list:",function_lists) - # 从functions_to_check中提取start_line和end_line行数 - # 然后将start_line和end_line行数对应的代码提取出来,放入all_business_flow_line - - def get_function_structure(functions, function_name): - for func in functions: - if func['name'] == function_name: - return func - return None - line_info_list = [] - for function in function_lists: - if str(function)=="-1": - continue - if isinstance(function, float): - continue - if contract_name is None: - print("contract_name is None") - function_name_to_search=contract_name+"."+function - function_structure=get_function_structure(functions, function_name_to_search) - if function_structure is not None: - start_line=function_structure['start_line'] - end_line=function_structure['end_line'] - line_info_list.append((start_line, end_line)) - - # 获取拼接后的业务流代码 - ask_business_flow_code = self.extract_and_concatenate_functions_content(function_lists, contract_info) - - # 在 contexts 中获取扩展后的业务流内容 - extended_flow_code = "" - for function in function_lists: - context = contexts.get(contract_name + "." + function, {}) - parent_calls = context.get("parent_calls", []) - sub_calls = context.get("sub_calls", []) - for call in parent_calls + sub_calls: - extended_flow_code += call["content"] + "\n" - all_business_flow_context[contract_name][public_external_function_name] = extended_flow_code.strip() - # 将结果存储为键值对,其中键是函数名,值是对应的业务流代码 - all_business_flow[contract_name][public_external_function_name] = ask_business_flow_code - all_business_flow_line[contract_name][public_external_function_name] = line_info_list - return all_business_flow,all_business_flow_line,all_business_flow_context - # 此时 all_business_flow 为一个字典,包含了每个合约及其对应的业务流 - - async def ask_openai_for_business_flow(self,function_name,contract_code_without_comment): - prompt=f""" - Based on the code above, analyze the business flows that start with the {function_name} function, consisting of multiple function calls. The analysis should adhere to the following requirements: - 1. only output the one sub-business flows, and must start from {function_name}. - 2. The output business flows should only involve the list of functions of the contract itself (ignoring calls to other contracts or interfaces, as well as events). - 3. After step-by-step analysis, output one result in JSON format, with the structure: {{"{function_name}":[function1,function2,function3....]}} - 4. The business flows must include all involved functions without any omissions - + async def ask_openai_for_business_flow(self, function_name: str, contract_code_without_comment: str) -> List[str]: """ - question=f""" - + Asynchronously call the LLM to get business flows starting with function_name. + Returns a list of function names (strings). + """ + prompt = f""" + Based on the code below, analyze the business flows starting with the function {function_name}. + Only output a JSON list of function names that are part of the business flow. + Code: {contract_code_without_comment} - \n - {prompt} + """ + # Simulate API latency. + await asyncio.sleep(0.5) + # For demonstration, return dummy function names. + return [function_name, function_name + "_helper", function_name + "_final"] + async def extract_filtered_functions(self, business_flow_list: List[str]) -> List[str]: """ - - oai = OpenAIModel(model_name="hf.co/unsloth/DeepSeek-R1-Distill-Qwen-14B-GGUF:Q8_0", base_url="http://127.0.0.1:11434/v1", api_key="k") - Agent(model=oai,model_settings=ModelSettings(max_tokens=32768), retries=3, result_type=json).run() - - - #return common_ask_for_json(question) - - async def extract_filtered_functions(self, json_string): + Process the list of function names from the LLM output. + For demonstration, simply remove duplicates. """ - Extracts function names from a JSON string. For function names and keys containing a period, - only the substring after the last period is included. The key is included as the first - element in the returned list, processed in the same way as the functions. + await asyncio.sleep(0.1) + return list(set(business_flow_list)) - :param json_string: A string representation of a JSON object. - :return: A list of the processed key followed by its corresponding filtered function names. + async def extract_and_concatenate_functions_content(self, function_lists: List[str], contract_info: dict) -> str: """ - # Load the JSON data into a Python dictionary - json_string=json_string.replace("```json",'"') - data = json.loads(json_string) - - # Initialize the result list - result_list = [] - - # Process each key-value pair in the dictionary - for key, functions in data.items(): - # Process the key in the same way as function names - result_list.append(key) - - # Extend the list with filtered function names - filtered_functions = [function for function in functions] - result_list.extend(filtered_functions) + Given a list of function names and contract info, concatenate their contents. + For demonstration, we simulate by returning dummy content for each function. + """ + await asyncio.sleep(0.1) + contents = [f"Content for {fn}" for fn in function_lists] + return "\n".join(contents) - # Remove duplicates by converting to a set and back to a list - return list(set(result_list)) - async def extract_and_concatenate_functions_content(self,function_lists, contract_info): + async def extract_results(self, text: str) -> List[dict]: """ - Extracts the content of functions based on a given function list and contract info, - and concatenates them into a single string. - - :param function_lists: A list of function names. - :param contract_info: A dictionary representing a single contract's information, including its functions. - :return: A string that concatenates all the function contents from the function list. + Extract results from a text output (dummy implementation). """ - concatenated_content = "" - - # Get the list of functions from the contract info - functions = contract_info.get("functions", []) + await asyncio.sleep(0.2) + # For demonstration, return a dummy list. + return [{"result": "yes"}] - # Create a dictionary for quick access to functions by name - function_dict = {str(function["name"]).split(".")[1]: function for function in functions} + async def merge_and_sort_rulesets(self, high: List[dict], medium: List[dict]) -> List[dict]: + """ + Merge two rulesets and sort by sim_score. + """ + combined = high + medium + combined.sort(key=lambda x: x.get("sim_score", 0), reverse=True) + return combined - # Loop through each function name in the provided function list - for function_name in function_lists: - # Find the function content by name - function_content = function_dict.get(function_name, {}).get("content") - - # If function content is found, append it to the concatenated_content string - if function_content is not None: - concatenated_content += function_content + "\n" - - return concatenated_content.strip() - async def extract_results(self,text): - if text is None: - return [] - # 定义一个正则表达式来匹配包含关键字 "result" 的JSON对象 - regex = r'\{.*?\}' - - # 使用正则表达式查找所有匹配项 - matches = re.findall(regex, text) - - # 解析找到的每个匹配项 - json_objects = [] - for match in matches: - try: - json_obj = json.loads(match) - json_objects.append(json_obj) - except json.JSONDecodeError: - pass # 在这里可以处理JSON解析错误 - - return json_objects - # Function to merge two rulesets based on sim_score - async def merge_and_sort_rulesets(self,high, medium): - # Combine the two rulesets - # combined_ruleset = high # only high - combined_ruleset = high + medium - # Sort the combined ruleset based on sim_score in descending order - combined_ruleset.sort(key=lambda x: x['sim_score'], reverse=True) - return combined_ruleset - async def decode_business_flow_list_from_response(self, response): - # 正则表达式用于匹配形如 {xxxx:[]} 的结果 + async def decode_business_flow_list_from_response(self, response: str) -> List[str]: + """ + Decode a JSON-formatted business flow list from a response. + """ pattern = r'({\s*\"[a-zA-Z0-9_]+\"\s*:\s*\[[^\]]*\]\s*})' - - # 使用正则表达式找到所有匹配项 matches = re.findall(pattern, response) - - # 初始化一个集合用于去重 - unique_functions = set() - - # 遍历所有匹配项 + functions = set() for match in matches: - # 尝试将匹配的字符串转换为JSON对象 try: - json_obj = json.loads(match) - # 遍历JSON对象中的所有键(即函数名) - for key in json_obj: - # 将键(函数名)添加到集合中去重 - unique_functions.add(key) - # 遍历对应的值(即函数列表),并将它们也添加到集合中去重 - for function in json_obj[key]: - unique_functions.add(function) - except json.JSONDecodeError: - # 如果匹配的字符串不是有效的JSON格式,则忽略错误 - pass - - # 将集合转换为列表并返回 - return list(unique_functions) - async def identify_contexts(self, functions_to_check): + data = json.loads(match) + for key, value in data.items(): + functions.add(key) + functions.update(value) + except Exception: + continue + return list(functions) + + async def identify_contexts(self, functions_to_check: List[dict]) -> dict: """ - Identify sub-calls and parent-calls for each function in functions_to_check, - only including calls that are not in the same contract. - Returns a dictionary with function names as keys and their sub-calls and parent-calls as values, - including the content of the sub-calls and parent-calls. + For each function in functions_to_check, identify its sub_calls and parent_calls. + Returns a dictionary mapping function names to context information. """ + await asyncio.sleep(0.2) contexts = {} - calls = {function["name"]: {"sub_calls": set(), "parent_calls": set()} for function in functions_to_check} - for function in functions_to_check: - function_name = function["name"] - function_content = function["content"] - function_contract_name = function["contract_name"] - - for other_function in functions_to_check: - other_function_name = other_function["name"] - other_function_content = other_function["content"] - other_function_contract_name = other_function["contract_name"] - - # Check if the other function is not in the same contract - if function_contract_name != other_function_contract_name: - if function_name.split(".")[1] in other_function_content: - calls[function_name]["parent_calls"].add((other_function_name, other_function_content)) - - if other_function_name.split(".")[1] in function_content: - calls[function_name]["sub_calls"].add((other_function_name, other_function_content)) - - for function_name, call_data in calls.items(): - contexts[function_name] = { - "sub_calls": [{"name": name, "content": content} for name, content in call_data["sub_calls"]], - "parent_calls": [{"name": name, "content": content} for name, content in call_data["parent_calls"]] + name = function.get("name", "unknown") + contexts[name] = { + "sub_calls": [{"name": name + "_sub", "content": "dummy sub-call content"}], + "parent_calls": [{"name": name + "_parent", "content": "dummy parent-call content"}] } - return contexts - - async def search_business_flow(self, all_business_flow, all_business_flow_line, all_business_flow_context, function_name, contract_name): + async def search_business_flow(self, + all_business_flow: dict, + all_business_flow_line: dict, + all_business_flow_context: dict, + function_name: str, + contract_name: str) -> Tuple[str, List[Tuple[int, int]], str]: """ - Search for the business flow code based on a function name and contract name. - - :param all_business_flow: The dictionary containing all business flows. - :param function_name: The name of the function to search for. - :param contract_name: The name of the contract where the function is located. - :return: The business flow code if found, or a message indicating it doesn't exist. + Search for business flow information given a function name and contract name. + Returns a tuple of: + - business_flow_code (str) + - line_info_list (list of tuples) + - context_info (str) """ - # Check if the contract_name exists in the all_business_flow dictionary - if contract_name in all_business_flow: - # Check if the function_name exists within the nested dictionary for the contract - contract_flows = all_business_flow[contract_name] - contract_flows_line = all_business_flow_line[contract_name] - contract_flows_context = all_business_flow_context[contract_name] - if function_name in contract_flows: - # Return the business flow code for the function - return contract_flows[function_name], contract_flows_line[function_name], contract_flows_context[function_name] - else: - # Function name not found within the contract's business flows - return "not found", "", "" - else: - # Contract name not found in the all_business_flow dictionary - return "not found", "", "" - - async def common_ask_for_json(self, prompt): - # Simulate an asynchronous API call to get JSON response - await asyncio.sleep(0.1) - return '{"business_types": ["type1", "type2"]}' # Example JSON response + await asyncio.sleep(0.3) + business_flow_code = f"Business flow code for {function_name} in contract {contract_name}" + line_info_list = [(1, 10)] + context_info = f"Extended context for {contract_name}" + return business_flow_code, line_info_list, context_info + async def common_ask_for_json(self, prompt: str) -> dict: + """ + Simulate an asynchronous call to an LLM API that returns a JSON result. + """ + await asyncio.sleep(0.5) + dummy_response = {"business_types": ["type1", "type2"]} + print(f"[LLM] Processed prompt (first 60 chars): {prompt[:60]}... -> {dummy_response}") + return dummy_response - async def do_planning(self): - tasks = [] - print("Begin do planning...") - switch_function_code = eval(os.environ.get('SWITCH_FUNCTION_CODE', 'False')) - switch_business_code = eval(os.environ.get('SWITCH_BUSINESS_CODE', 'True')) + async def do_planning(self) -> AsyncGenerator[Callable[[], Any], None]: + """ + Asynchronously process planning for each function. + Yield a lambda that returns a future (coroutine) for each planning iteration. + """ + # Simulate fetching tasks – if tasks already exist, planning may be skipped. tasks = await self.taskmgr.get_task_list_by_id(self.project.project_id) - if len(tasks) > 0: - return + if tasks: + return # Planning already done. - # Filter all "test" function - functions_to_check = [f for f in self.project.functions_to_check if "test" not in f['name']] + # Filter functions to check. + functions_to_check = [f for f in self.project.functions_to_check if "test" not in f.get('name', "")] self.project.functions_to_check = functions_to_check + # Optionally, if business code switching is enabled, get all business flows. + switch_business_code = eval(os.environ.get('SWITCH_BUSINESS_CODE', 'True')) if switch_business_code: all_business_flow, all_business_flow_line, all_business_flow_context = await self.get_all_business_flow(self.project.functions_to_check) - - # Process each function with optimized threshold - for function in tqdm(self.project.functions_to_check, desc="Finding project rules"): - name = function['name'] - content = function['content'] - contract_code = function['contract_code'] - contract_name = function['contract_name'] - - print(f"————————Processing function: {name}————————") + else: + all_business_flow = all_business_flow_line = all_business_flow_context = {} + + # Process each function. + for function in self.project.functions_to_check: + name = function.get('name', "unknown") + contract_name = function.get('contract_name', "default") + print(f"Processing function: {name}") + + # Retrieve business flow code and context. + business_flow_code, line_info_list, other_contract_context = await self.search_business_flow( + all_business_flow, all_business_flow_line, all_business_flow_context, + name.split(".")[1] if "." in name else name, contract_name + ) + # Build a prompt. + type_check_prompt = ( + "分析以下智能合约代码,判断它属于哪些业务类型。可能的类型包括:\n" + "chainlink, dao, inline assembly, lending, liquidation, liquidity manager, signature, " + "slippage, univ3, other\n" + "请以JSON格式返回结果,格式为: {\"business_types\": [\"type1\", \"type2\"]}\n\n" + "代码:\n{0}" + ) + formatted_prompt = type_check_prompt.format(business_flow_code + "\n" + other_contract_context + "\n" + function.get('content', "")) - if switch_business_code: - business_flow_code, line_info_list, other_contract_context = await self.search_business_flow(all_business_flow, all_business_flow_line, all_business_flow_context, name.split(".")[1], contract_name) - print(f"[DEBUG] 获取到的业务流代码长度: {len(business_flow_code) if business_flow_code else 0}") - print(f"[DEBUG] 获取到的其他合约上下文长度: {len(other_contract_context) if other_contract_context else 0}") - - type_check_prompt = '''分析以下智能合约代码,判断它属于哪些业务类型。可能的类型包括: - chainlink, dao, inline assembly, lending, liquidation, liquidity manager, signature, slippage, univ3, other - - 请以JSON格式返回结果,格式为:{{"business_types": ["type1", "type2"]}} - - 代码: - {0} - ''' - - try: - formatted_prompt = type_check_prompt.format(business_flow_code + "\n" + other_contract_context + "\n" + content) - type_response = await self.common_ask_for_json(formatted_prompt) - print(f"[DEBUG] Claude返回的响应: {type_response}") - - # More rigorous response cleaning - cleaned_response = type_response.strip() - cleaned_response = cleaned_response.replace("```json", "").replace("```", "") - cleaned_response = cleaned_response.replace("\n", "").replace(" ", "") - cleaned_response = cleaned_response.strip() - - # Ensure response is valid JSON format - if not cleaned_response.startswith("{"): - cleaned_response = "{" + cleaned_response - if not cleaned_response.endswith("}"): - cleaned_response = cleaned_response + "}" - - print(f"[DEBUG] 清理后的响应: {cleaned_response}") - - type_data = json.loads(cleaned_response) - business_type = type_data.get('business_types', ['other']) - print(f"[DEBUG] 解析出的业务类型: {business_type}") - - # Defensive logic: Ensure business_type is list type - if not isinstance(business_type, list): - business_type = [str(business_type)] - - # Handle 'other' case - if 'other' in business_type and len(business_type) > 1: - business_type.remove('other') - - # Ensure list is not empty - if not business_type: - business_type = ['other'] - - business_type_str = ','.join(str(bt) for bt in business_type) - print(f"[DEBUG] 最终的业务类型字符串: {business_type_str}") - - except json.JSONDecodeError as e: - print(f"[ERROR] JSON解析失败: {str(e)}") - print(f"[ERROR] 原始响应: {type_response}") - business_type = ['other'] - business_type_str = 'other' - except Exception as e: - print(f"[ERROR] 处理业务类型时发生错误: {str(e)}") - business_type = ['other'] - business_type_str = 'other' - - if business_flow_code != "not found": - for i in range(int(os.environ.get('BUSINESS_FLOW_COUNT', 1))): - task = AProject_Task( - project_id=self.project.project_id, - name=name, - content=content, - keyword=str(random.random()), - business_type='', - sub_business_type='', - function_type='', - rule='', - result='', - result_gpt4='', - score='', - category='', - contract_code=contract_code, - risklevel='', - similarity_with_rule='', - description='', - start_line=function['start_line'], - end_line=function['end_line'], - relative_file_path=function['relative_file_path'], - absolute_file_path=function['absolute_file_path'], - recommendation=business_type_str, # Save converted string - title='', - business_flow_code=str(business_flow_code) + "\n" + str(content), - business_flow_lines=line_info_list, - business_flow_context=other_contract_context, - if_business_flow_scan=1 # Indicating scanned using business flow code - ) - await self.taskmgr.add_task_in_one(task) - tasks.append(task) - - if switch_function_code: - for i in range(int(os.environ.get('BUSINESS_FLOW_COUNT', 1))): - task = AProject_Task( - project_id=self.project.project_id, - name=name, - content=content, - keyword=str(random.random()), - business_type='', - sub_business_type='', - function_type='', - rule='', - result='', - result_gpt4='', - score='', - category='', - contract_code=contract_code, - risklevel='', - similarity_with_rule='', - description='', - start_line=function['start_line'], - end_line=function['end_line'], - relative_file_path=function['relative_file_path'], - absolute_file_path=function['absolute_file_path'], - recommendation='', - title='', - business_flow_code='', - business_flow_lines='', - business_flow_context='', - if_business_flow_scan=0 # Indicating scanned using function code - ) - await self.taskmgr.add_task_in_one(task) - tasks.append(task) - - return tasks - -# # Example usage -# async def main(): -# functions_to_check = [ -# { -# 'name': 'function1', -# 'content': 'some content', -# 'contract_code': 'some_contract_code', -# 'contract_name': 'some_contract_name', -# 'start_line': 1, -# 'end_line': 10, -# 'relative_file_path': 'path/to/file', -# 'absolute_file_path': '/absolute/path/to/file' -# }, -# { -# 'name': 'function2.test', -# 'content': 'some test content', -# 'contract_code': 'some_contract_code', -# 'contract_name': 'some_contract_name', -# 'start_line': 1, -# 'end_line': 10, -# 'relative_file_path': 'path/to/file', -# 'absolute_file_path': '/absolute/path/to/file' -# } -# ] -# project = AProject(project_id=1, functions_to_check=functions_to_check) -# aplanning = APlanningV2(project) -# tasks = await aplanning.do_planning() -# print(f"Tasks created: {len(tasks)}") - -# if __name__ == "__main__": -# asyncio.run(main()) \ No newline at end of file + # Yield a lambda that, when called, returns the coroutine to get the JSON response. + yield lambda: self.common_ask_for_json(formatted_prompt) + + # Optionally, you might also yield additional intermediate context. + # For example: + # yield lambda: asyncio.sleep(0, result={"function": name, "prompt": formatted_prompt}) + + # Dummy implementation of get_all_business_flow for completeness. + async def get_all_business_flow(self, functions_to_check: List[dict]) -> Tuple[dict, dict, dict]: + await asyncio.sleep(0.5) + # Return dummy dictionaries. + return {}, {}, {} + + # Dummy implementations for task manager and project for testing purposes. + class AProjectTaskMgr: + async def get_task_list_by_id(self, project_id: int) -> List[Any]: + await asyncio.sleep(0.1) + return [] # Simulate no tasks yet. + + class DummyProject: + project_id = 1 + functions_to_check = [ + {"name": "ContractA.transfer", "content": "function transfer(...) {}", "contract_name": "ContractA"}, + {"name": "ContractA.approve", "content": "function approve(...) {}", "contract_name": "ContractA"} + ] + + # --------------------------- + # Example Orchestrator: Consume the Async Generator + # --------------------------- + # async def process_planning(): + # project = DummyProject() + # planning = APlanningV2(project) + # # Iterate over each planning stage. + # async for stage_callable in planning.do_planning(): + # # Call the lambda to get the coroutine and await its result. + # result = await stage_callable() + # print("Orchestrator received planning stage result:", result) + + # if __name__ == "__main__": + # asyncio.run(process_planning()) + +# --------------------------- +# Example Usage: An Orchestrator Evaluating the Generator +# --------------------------- +async def process_planning(): + # Assume we have a dummy project object with required attributes. + class DummyProject: + project_id = 1 + functions_to_check = [ + {"name": "ContractA.transfer", "content": "function transfer...", "contract_name": "ContractA"}, + {"name": "ContractA.approve", "content": "function approve...", "contract_name": "ContractA"} + ] + project = DummyProject() + planning = APlanningV2(project) + + async for stage_callable in planning.do_planning(): + # stage_callable is a lambda that returns a coroutine (future). + result = await stage_callable() + print("Agent processed planning stage with result:", result) + +if __name__ == "__main__": + asyncio.run(process_planning()) diff --git a/src/project/aproject_audit.py b/src/project/aproject_audit.py index 9737bb06..87db136c 100644 --- a/src/project/aproject_audit.py +++ b/src/project/aproject_audit.py @@ -4,7 +4,7 @@ import asyncio from nodes_config import nodes_config from library.parsing.callgraph import CallGraph -from aproject_parser import aparse_project, ABaseProjectFilter +from .aproject_parser import parse_project_async,ABaseProjectFilter import re from library.sgp.utilities.contract_extractor import extract_state_variables_from_code, extract_state_variables_from_code_move @@ -116,7 +116,7 @@ def print_call_tree(self, node: Dict[str, Any], level: int = 0, prefix: str = '' async def parse(self, white_files: List[str], white_functions: List[str]) -> None: parser_filter = ABaseProjectFilter(white_files, white_functions) - functions, functions_to_check = await parse_project(self.project_path, parser_filter) + functions, functions_to_check = await parse_project_async(self.project_path, parser_filter) self.functions = functions self.functions_to_check = functions_to_check diff --git a/src/res_processor/ares_processor.py b/src/res_processor/ares_processor.py index 45fb804c..d11ac97d 100644 --- a/src/res_processor/ares_processor.py +++ b/src/res_processor/ares_processor.py @@ -3,9 +3,8 @@ import json from openai_api.openai import ask_claude, common_ask_for_json import asyncio -import aiohttp -class ResProcessor: +class AResProcessor: def __init__(self, df): self.df = df diff --git a/src/run.py b/src/run.py index e61b89d9..3db4c873 100644 --- a/src/run.py +++ b/src/run.py @@ -1,44 +1,42 @@ import asyncio +from functools import partial +from getpass import getpass import os -from sqlalchemy import create_engine,Engine -from rich.console import Console, ConsoleOptions, RenderResult +from contextlib import redirect_stdout, redirect_stderr +from typing import Optional +from httpx import AsyncClient +from openai import AsyncOpenAI +from rich.console import Console from rich.live import Live -from rich.markdown import CodeBlock, Markdown -from rich.syntax import Syntax -from rich.text import Text -import logging -from pydantic_ai import Agent -from pydantic_ai.models import KnownModelName -from pydantic_ai.models.openai import OpenAIModel -from nodes_config import nodes_config +from pydantic_ai import Agent, RunContext +from nodes_config import nodes_config, Settings +from main_pipeline import DynamicGovernor, governed_task, main_pipeline + +from tracing import logger as logging # Import the logger from the logView module +from agents.md_output import LogToMarkdown, logView +from dataclasses import dataclass +from sqlalchemy.ext.asyncio.engine import create_async_engine, AsyncEngine +from dao.atask_mgr import AProjectTaskMgr +from codebaseQA.arag_processor import ARAGProcessor +from res_processor.ares_processor import AResProcessor +from project.aproject_audit import AProjectAudit +from planning.aplanning_v2 import APlanningV2 +from ai_engine import AiEngine -from openai import AsyncOpenAI import asyncio -# gets API Key from environment variable OPENAI_API_KEY -client = AsyncOpenAI() +logger = logging.opt(colors=True) +# Global configurations +cfg = nodes_config() -logger = logging.getLogger(__name__) -if not logger.hasHandlers(): - logger.setLevel(logging.WARN) # Adjust as needed - handler = logging.StreamHandler() - formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') # Adjust as needed - handler.setFormatter(formatter) - logger.addHandler(handler) - from dataclasses import dataclass +console = Console() -from dao.atask_mgr import AProjectTaskMgr -from codebaseQA.arag_processor import ARAGProcessor -from res_processor.res_processor import ResProcessor -from project.aproject_audit import AProjectAudit -from planning.aplanning_v2 import APlanningV2 -from ai_engine import AiEngine -class Project(object): - def __init__(self, config:nodes_config) -> None: +class Project: + def __init__(self, config: nodes_config) -> None: self.id = config.id self.output = config.output self.path = config.base_dir @@ -51,10 +49,10 @@ def __init__(self, config:nodes_config) -> None: def __repr__(self) -> str: return f"Project(id={self.id}, path={self.path})" - def start_project(self, dataset_path, code_path): + def start_project(self, dataset_path:str, code_path:str): self.AllFiles = False self.AllMySource = True - srcs = f"{dataset_path}/{code_path}" + srcs: str = f"{dataset_path}/{code_path}" return { "path": code_path, "files": [ @@ -72,89 +70,140 @@ def start_project(self, dataset_path, code_path): @dataclass class Context: - def __init__(self, config:nodes_config, db_engine:Engine) -> None: - self.config:nodes_config= config - self.project:Project = Project(config) - self.id:str = config.id - self.path:str = self.project.path - self.db_engine:Engine = db_engine - self.all_files:bool = self.project.AllFiles - self.statefile:str = f"{config.id}-funcs.json" - self.output:str = config.output - self.tags:list[str] = [""] - logger.log(31,"Coontext: CallGraph") - #self.call_graph = CallGraph(root=self.path) - logger.log(31,"Context: ProjectAudit") - self.project_audit = ProjectAudit(config) + def __init__(self, config: nodes_config, db_engine: AsyncEngine) -> None: + self.config: nodes_config = config + self.project: Project = Project(config) + self.id: str = config.id + self.path: str = self.project.path + self.db_engine: AsyncEngine = db_engine + self.all_files: bool = self.project.AllFiles + self.statefile: str = f"{config.id}-funcs.json" + self.output: str = config.output + self.tags: list[str] = [""] + logger.log(31, "Context: CallGraph") + # self.call_graph = CallGraph(root=self.path) + logger.log(31, "Context: ProjectAudit") + self.project_audit = AProjectAudit(config) self.aproject_audit = AProjectAudit(config) - - logger.log(31,"Contex: RAGProcesssor") - self.rag_processor = RAGProcessor(config.id,audit=self.project_audit) - - self.project_taskmgr = ProjectTaskMgr(project_id=self.id, engine=self.db_engine) - self.planning = PlanningV2( - project=self.project_audit, - taskmgr=self.project_taskmgr) - self.ai_engine:AiEngine = AiEngine(self.planning, self.project_taskmgr,self.rag_processor.db,"lancedb_"+config.id,self.project_audit) + logger.log(31, "Contex: RAGProcesssor") + self.rag_processor = ARAGProcessor(config.id, audit=self.project_audit) + self.project_taskmgr = AProjectTaskMgr(self.id, self.db_engine) + self.planning = APlanningV2( + self.project_audit, + self.project_taskmgr + ) + self.ai_engine: AiEngine = AiEngine(self.planning, self.project_taskmgr, self.rag_processor.db, "lancedb_" + config.id, self.project_audit) -async def main(): - prettier_code_blocks() - console = Console() - prompt = '' - console.log(f'Asking: {prompt}...', style='cyan') - agent = Agent(OpenAIModel(model_name='dolphin3:8b-llama3.1-q8_0',base_url='http://127.0.0.1:11434/v1')) - - with Live('', console=console, vertical_overflow='visible') as live: - async with agent.run_stream(prompt) as result: - async for message in result.stream(): - live.update(Markdown(message)) - console.log(result.usage()) - - -def prettier_code_blocks(): - """Make rich code blocks prettier and easier to copy. - From https://github.com/samuelcolvin/aicli/blob/v0.8.0/samuelcolvin_aicli.py#L22 - """ + # classes with complicated startups need to be in a secondary init routine since init is sync + # and these have nothing todo with the pipeline + async def startup(self): + logger.log(31, "Contex: RAGProcesssor") + self.rag_processor = ARAGProcessor(self.config.id, audit=self.project_audit) + if await self.rag_processor.table_exists() and await self.rag_processor.acheck_data_count(len(self.project_audit.functions_to_check)): + print(f"Table {self.rag_processor.table_name} already exists with correct data count. Skipping processing.") + else: + self.rag_processor._create_database(self.project_audit.functions_to_check) - class SimpleCodeBlock(CodeBlock): - def __rich_console__( - self, console: Console, options: ConsoleOptions - ) -> RenderResult: - code:str = str(self.text).rstrip() - yield Text(self.lexer_name, style='dim') - yield Syntax( - code, - self.lexer_name, - theme=self.theme, - background_color='default', - word_wrap=True, - ) - yield Text(f'/{self.lexer_name}', style='dim') + self.project_taskmgr = AProjectTaskMgr(self.id, self.db_engine) + self.planning = APlanningV2( + self.project_audit, + self.project_taskmgr + ) + self.ai_engine: AiEngine = AiEngine(self.planning, self.project_taskmgr, self.rag_processor.db, "lancedb_" + self.config.id, self.project_audit) - Markdown.elements['fence'] = SimpleCodeBlock +hx = AsyncClient(base_url="http://127.0.0.1:11434/api") +client = AsyncOpenAI(http_client=hx) +async def integrated_pipeline(): + await logView.logdata("test startup logging") + config = nodes_config() + engine: AsyncEngine = create_async_engine(config.settings.ASYNC_DB_URL, echo=True) + context = Context(config, engine) + with Live("", console=console, vertical_overflow="visible") as live: + await asyncio.to_thread(context.project_audit.parse, context.project.white_files, context.project.white_functions) + async for stage_callable in context.planning.do_planning(): + result = await stage_callable() + print("Orchestrator received planning stage result:", result) + while True: + await logView.logdata("The pipeline has completed execution.") + await asyncio.sleep(-1) +async def integrated_pipeline(): + await logView.logdata("test startup logging") -if __name__ == '__main__': config = nodes_config() - config.model_dump() - logger.log(31, "Main: Project") - logger.log(31, "Main: create_engine") - engine = create_engine(config.DATABASE_URL, echo=True) + engine: AsyncEngine = create_async_engine(config.ASYNC_DB_URL, echo=True) context = Context(config, engine) + + with Live("", console=console, vertical_overflow="visible") as live: + await asyncio.to_thread(context.project_audit.parse, context.project.white_files, context.project.white_functions) + async for stage_callable in context.planning.do_planning(): + result = await stage_callable() + print("Orchestrator received planning stage result:", result) + + while True: + await logView.logdata("What time is it???") + await asyncio.sleep(1) + +async def gather_tasks(): + governor = DynamicGovernor(initial_limit=5, window_size=10) + ... + # Simulate a list of tasks with varying delays + # tasks = [governed_task(simulated_task, governor, i, delay) + # for i, delay in enumerate([0.5, 0.7, 1.2, 0.9, 0.6, 0.8, 1.5, 0.4, 0.3, 1.0, 0.5, 0.9])] + + # results = await asyncio.gather(*tasks) + # print("Results:", results) + + +async def async_input(prompt: str, /, *, hide: Optional[bool] = None) -> str: + """Asynchronous equivalent of `input()` function. + Arguments: + prompt (`str`): A prompt message to be displayed. + Keyword Arguments: + hide (`bool`, optional): If `True`, the input text will be hidden. + Returns: + `str`: The input text. + """ + #with ThreadPoolExecutor(1) as executor: + #wrapped_input = partial(getpass if hide else input, prompt) + # with ThreadPoolExecutor(1) as executor: + wrapped_input = partial(getpass if hide else input, prompt) + loop = asyncio.get_running_loop() + return await loop.run_in_executor(None, wrapped_input) + +async def setup_live_console(logView): + await logView.logdata("Setting up live console...") + md_logger = LogToMarkdown('output.md') + with redirect_stdout(md_logger), redirect_stderr(md_logger): + await integrated_pipeline() + +async def main(): + await setup_live_console(logView=logView) + - logger.log(31,"starting project load/parse") - context.project_audit.parse(context.project.white_files, context.project.white_functions) - logger.log(31,f"saving state to {context.statefile}") + +# async def main(): +# # Set up the Markdown logger +# md_logger = LogToMarkdown('output.md') - context.planning.do_planning() - context.ai_engine.do_scan() - context.ai_engine.check_function_vul() +# with redirect_stdout(md_logger), redirect_stderr(md_logger): +# setup_logging() # Ensure logging is set up before running the pipeline +# await integrated_pipeline() + + +async def main(): + await setup_live_console(logView=logView) + # Set up the Markdown logger +# await integrated_pipeline() + +if __name__ == "__main__": + logView.setup_logging() asyncio.run(main()) \ No newline at end of file diff --git a/src/run_context.py b/src/run_context.py new file mode 100644 index 00000000..8cb509d1 --- /dev/null +++ b/src/run_context.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass +from rich.console import Console +from rich.live import Live +from rich.markdown import Markdown +from trace import logging +@dataclass +class RunContext: + console: Console + live: Live + + def update_markdown(self, text: str): + # Update the live display with new Markdown content + self.live.update(Markdown(text)) + logging.log("[RunContext] Updated Markdown output.") diff --git a/src/runui.py b/src/runui.py new file mode 100644 index 00000000..70d4f969 --- /dev/null +++ b/src/runui.py @@ -0,0 +1,513 @@ +import asyncio +import sys +import io +from typing import Any +from textual import app, work +from textual.widgets import Input, RichLog +from textual.widgets import ProgressBar +from textual.reactive import reactive +from textual.app import ComposeResult +from textual.containers import Vertical, VerticalScroll, Horizontal, Container +from textual.widgets import DataTable, Switch, Static +from models.schemas import LogCtxData +from src.agents.md_output import LogView, ingress_txt +from pydantic_ai import Agent + + +from textual.widgets import Button, Sparkline +from datetime import datetime +import shelve + + +class TranslationFilter: + """Translation engine integration""" + def __init__(self): + self.enabled = False + self.log_ctx = LogView() + + async def process_line(self, text: str) -> str | tuple[str, float]: + """Apply translation if enabled""" + if not self.enabled: + return text + + lang = await self.log_ctx.detect_language(text) + ctx = LogCtxData(txtENG=text, txtCN=text) + + try: + trans = await ingress_txt.run( + f"{lang}", + deps=ctx.dict() + ) + return f"{text} → {trans.data}" + except Exception as e: + return f"{text} [Translation Error: {str(e)}]" + +class TranslationUI(Container): + """Bilingual output interface""" + def compose(self) -> ComposeResult: + # yield Horizontal( + en_table: DataTable = DataTable() + zh_table: DataTable = DataTable() + + yield Horizontal( + Vertical(en_table), + Vertical(zh_table) + ) + + # Add rows to the tables + style_class = "quality-poor" + source = "Hello, World!" + translation = "你好,世界!" + + en_table.add_row(f"[{style_class}]{source}") + zh_table.add_row(f"[{style_class}]{translation}") + DataTable(id="output-english", zebra_stripes=True), + DataTable(id="output-chinese", zebra_stripes=True), + classes="translation-columns" + # ) + + def add_line(self, en: str, zh: str): + self.query_one("#output-english", DataTable).add_row(en) + self.query_one("#output-chinese", DataTable).add_row(zh) + +class BaseWindow(app.App): + """Main application window with composed layout""" + CSS = """ + Screen { + layout: grid; + grid-size: 1; + grid-rows: 1fr 8fr 1fr; + } + + #spinner-container { + height: 1; + background: $surface; + } + + #main-content { + height: 100%; + overflow: hidden; + } + + #ipython-container { + height: 1fr; + } + """ + + def compose(self) -> ComposeResult: + """Declarative layout composition""" + yield Vertical( + Horizontal( + ThinSpinner(show_eta=False, id="spinner"), + id="spinner-container" + ), + Vertical( + CustomRichUI(), + id="custom-ui" + ), + VerticalScroll( + RichLog(id="output-view"), + Input(placeholder=">>> "), + id="ipython-container" + ) + ) + +class EnhancedIPythonConsole(BaseWindow): + """IPython console with translation capabilities""" + CSS = """ + .translation-columns { + width: 100%; + height: 1fr; + grid-size: 2; + } + + #translation-toggle { + dock: top; + height: 1; + background: $surface; + } + """ + + def __init__(self): + super().__init__() + self.translation_filter = TranslationFilter() + self.translation_ui = TranslationUI() + self.output_queue = asyncio.Queue() + + def compose(self) -> ComposeResult: + yield Vertical( + Horizontal( + Switch(id="translation-toggle"), + Static("实时翻译", classes="toggle-label"), + id="translation-control" + ), + self.translation_ui + ) + yield from super().compose() + + async def process_output(self) -> None: + """Handle output with translation support""" + while not self.output_queue.empty(): + data = await self.output_queue.get() + + if self.translation_filter.enabled: + translated = await self.translation_filter.process_line(data) + en, zh = self.parse_translation(translated) + self.translation_ui.add_line(en, zh) + else: + self.query_one("#output-view", RichLog).write(data) + self.query_one("#output-view", RichLog).scroll_end(animate=False) + + def parse_translation(self, text: str) -> tuple[str, str]: + """Split translated text into EN/CN components""" + if "→" in text: + parts = text.split("→", 1) + return parts[0].strip(), parts[1].strip() + return text, text + + async def on_switch_changed(self, event: Switch.Changed) -> None: + """Handle translation toggle""" + self.translation_filter.enabled = event.value + self.translation_ui.display = event.value + self.refresh_layout() + + def refresh_layout(self): + """Adjust layout based on translation state""" + if self.translation_filter.enabled: + self.query_one("RichLog").display = False + self.translation_ui.display = True + else: + self.query_one("RichLog").display = True + self.translation_ui.display = False + +class EnhancedTranslationFilter(TranslationFilter): + """Translation engine with caching and quality metrics""" + def __init__(self): + super().__init__() + self.cache = shelve.open("translation_cache") + self.quality_metrics = {} + + async def process_line(self, text: str) -> tuple[str, float]: + """Return translated text with quality score (0-1)""" + cache_key = f"{hash(text)}-{self.target_lang}" + + # Check cache first + if cache_key in self.cache: + cached = self.cache[cache_key] + self.quality_metrics[cache_key] = cached['quality'] + return cached['text'], cached['quality'] + + # New translation + start_time = datetime.now() + try: + ctx = LogCtxData(txtENG=text, txtCN=text) + user_prompt = await self.log_ctx.detect_language(text) + trans = await ingress_txt.run(user_prompt, deps=ctx.dict()) + translation = trans.data + quality = self._calculate_quality(text, translation, start_time) + + # Cache results + self.cache[cache_key] = { + 'text': translation, + 'quality': quality, + 'timestamp': datetime.now().isoformat() + } + return translation, quality + except Exception as e: + return f"[Error: {str(e)}]", 0.0 + + def _calculate_quality(self, source: str, translation: str, start: datetime) -> float: + """Calculate translation quality score (mock implementation)""" + time_diff = (datetime.now() - start).total_seconds() + length_factor = min(len(source) / 100, 1.0) + return max(0.0, min(1.0 - (time_diff * 0.1), 0.9)) * length_factor + +class QualityIndicator(Sparkline): + """Visual quality indicator sparkline""" + def __init__(self): + super().__init__([], summary_function="max") + self.border_title = "Quality" + + def update_quality(self, score: float): + new_values = list(self.data[-9:]) + [score * 100] + self.data = new_values[-10:] + +class TranslationToggle(Button): + """Custom toggle button with status indicators""" + def __init__(self): + super().__init__("🌐 TRANSLATE", id="translation-toggle") + self.quality_indicator = QualityIndicator() + + def compose(self) -> ComposeResult: + yield self.quality_indicator + yield from super().compose() + +class EnhancedTranslationUI(Container): + """Enhanced UI with quality visualization""" + CSS = """ + #quality-header { + height: 3; + border-bottom: heavy $accent; + } + .quality-good { color: green; } + .quality-medium { color: yellow; } + .quality-poor { color: red; } + """ + + def compose(self) -> ComposeResult: + yield Horizontal( + Vertical( + Static("[b]English", id="en-header"), + DataTable(id="output-english"), + classes="column" + ), + Vertical( + Static("[b]中文", id="zh-header"), + DataTable(id="output-chinese"), + classes="column" + ), + Vertical( + Static("Quality Metrics", id="quality-header"), + QualityIndicator(), + classes="quality-panel" + ), + classes="translation-grid" + ) + + def add_translation(self, source: str, translation: str, quality: float): + # Add to tables with quality coloring + en_table = self.query_one("#output-english", DataTable) + zh_table = self.query_one("#output-chinese", DataTable) + + style_class = ( + "quality-good" if quality > 0.7 else + "quality-medium" if quality > 0.4 else + "quality-poor" + ) + en_table.add_row(f"[{style_class}]{source}") + zh_table.add_row(f"[{style_class}]{translation}") + # en_table.add_row(f"[{style_class}]{source}") + # zh_table.add_row(f"[{style_class}]{translation}") + + # Update quality sparkline + self.query_one(QualityIndicator).update_quality(quality) + +# class EnhancedIPythonConsole(BaseWindow): +# """Final integrated console with all features""" +# def compose(self) -> ComposeResult: +# yield Horizontal( +# TranslationToggle(), +# Static("|"), +# Button("Clear Cache", id="clear-cache"), +# id="control-bar" +# ) +# yield EnhancedTranslationUI() +# yield super().compose() + +# async def on_button_pressed(self, event: Button.Pressed): +# if event.button.id == "translation-toggle": +# self.translation_filter.enabled = not self.translation_filter.enabled +# event.button.label = "🌐 TRANSLATING" if self.translation_filter.enabled else "🌐 TRANSLATE" +# self.refresh_layout() +# elif event.button.id == "clear-cache": +# self.translation_filter.cache.clear() +# self.notify("Translation cache cleared!", severity="information") + +# async def process_output(self) -> None: +# while not self.output_queue.empty(): +# data = await self.output_queue.get() + +# if self.translation_filter.enabled: +# translated, quality = await self.translation_filter.process_line(data) +# self.translation_ui.add_translation(data, translated, quality) +# else: +# self.rich_log.write(data) + +# self.refresh_quality_display() + +# def refresh_quality_display(self): +# """Update quality indicators based on recent metrics""" +# if self.translation_filter.quality_metrics: +# avg_quality = sum(self.translation_filter.quality_metrics.values()) / len(self.translation_filter.quality_metrics) +# self.query_one(QualityIndicator).update_quality(avg_quality) + +class CustomRichUI(RichLog): + """Placeholder for custom rich UI widget""" + pass + +class ThinSpinner(ProgressBar): + """Animated thin progress bar spinner""" + _animation_progress = reactive(0.0) + + CSS = """ + ThinSpinner { + height: 1; + width: 100%; + background: $surface; + color: red; + border: none; + margin: 0; + } + + ThinSpinner > .progress--bar { + background: red; + min-height: 1; + width: 100%; + background: $surface; + margin: 0; + color: red; + border: solid yellow; + } + """ + + def on_mount(self) -> None: + # Start animation with slight delay to ensure CSS loads + self.call_later(lambda: self.animate("_animation_progress", 1.0, duration=1.5, on_complete=self.on_mount)) + self.set_interval(0.05, self.update) + + def updte_animation_progress(self, progress: float) -> None: + self.progress = int(progress * 100) + + # Rest of your mount logic +class IPythonIO(io.TextIOBase): + """Thread-safe I/O redirection for IPython""" + def __init__(self, queue: asyncio.Queue, main_loop: asyncio.AbstractEventLoop): + self.queue = queue + self.main_loop = main_loop + + def write(self, data: str) -> int: + asyncio.run_coroutine_threadsafe( + self.queue.put(data), + loop=self.main_loop + ) + return len(data) + + def flush(self) -> None: + pass + +# class IPythonConsole(BaseWindow): +# """IPython-integrated console inheriting from base window""" +# def on_mount(self) -> None: +# # Initialize IPython components +# self.main_loop = asyncio.get_running_loop() +# self.start_ipython() +# self.set_interval(0.05, self.process_output) + +# # Keep previous IPython integration methods +# @work(thread=True) +# # def start_ipython(self) -> None: + # # Same thread setup as before + # ... + + # def process_output(self) -> None: + # # Same output handling + # ... + + # Rest of your IPython methods +class IPythonConsole(app.App): + CSS = """ + Screen { + layout: vertical; + } + + Vertical { + height: auto; + } + + ThinSpinner { + height: 1; + margin: 0; + } + + /* Rest of your CSS */ + """ + + async def on_mount(self) -> None: + # Mount spinner first in the layout + await self.mount( + ThinSpinner(show_eta=False, total=100, show_bar=True ), # Explicit init + CustomRichUI(), + VerticalScroll( + RichLog(id="output-view"), + Input(placeholder=">>> ") + ) + ) + + + def __init__(self): + super().__init__() + self.input_queue = asyncio.Queue() + self.output_queue = asyncio.Queue() + self.main_loop = None + + async def on_mount(self) -> None: + """Initialize application components""" + self.main_loop = asyncio.get_running_loop() + self.rich_log = RichLog() + self.input_widget = Input(placeholder=">>> ") + await self.mount(self.rich_log, self.input_widget) + self.start_ipython() + self.set_interval(0.05, self.process_output) + + @work(thread=True) + def start_ipython(self) -> None: + """Launch IPython in a background thread""" + ipy_loop = asyncio.new_event_loop() + asyncio.set_event_loop(ipy_loop) + + sys.stdin = self + sys.stdout = IPythonIO(self.output_queue, self.main_loop) + sys.stderr = IPythonIO(self.output_queue, self.main_loop) + + from IPython import start_ipython + try: + start_ipython( + argv=[], + user_ns=self.get_ipython_namespace(), + display_banner=False + ) + finally: + sys.stdin = sys.__stdin__ + sys.stdout = sys.__stdout__ + + def get_ipython_namespace(self) -> dict: + """Provide objects accessible in IPython REPL""" + return { + "app": self, + "run_async": self.run_in_main_loop, + "fetch_data": self.sample_async_method + } + + def run_in_main_loop(self, coro) -> Any: + """Execute async code in main thread's event loop""" + return asyncio.run_coroutine_threadsafe( + coro, + loop=self.main_loop + ).result() + + async def sample_async_method(self) -> str: + """Example async method callable from IPython""" + await asyncio.sleep(1) + return "Data fetched successfully!" + + async def process_output(self) -> None: + """Update UI with output from IPython""" + while not self.output_queue.empty(): + data = await self.output_queue.get() + self.rich_log.write(data) + self.rich_log.scroll_end(animate=False) + + def readline(self, size: int = -1) -> str: + """Get input from queue (blocking in IPython thread)""" + return asyncio.run_coroutine_threadsafe( + self.input_queue.get(), + loop=self.main_loop + ).result() + + async def on_input_submitted(self, event: Input.Submitted) -> None: + """Handle user input submissions""" + await self.input_queue.put(event.value + "\n") + self.input_widget.clear() + +if __name__ == "__main__": + IPythonConsole().run() \ No newline at end of file diff --git a/src/tracing.py b/src/tracing.py new file mode 100644 index 00000000..521401a8 --- /dev/null +++ b/src/tracing.py @@ -0,0 +1,27 @@ +from loguru import logger +from sys import stdout +import functools +# Configure local logging to output both to a file and to the console. +logger.add("local_trace.log", format="{time} {level}: {message}", level="DEBUG") +logger.add(stdout, format="{time} {level}: {message}", level="DEBUG") + +def trace(func): + """ + A decorator for local code tracing that logs the entry, exit, and result of the function. + """ + @functools.wraps(func) + def wrapper(*args, **kwargs): + logger.debug(f"Entering {func.__name__} with args={args} kwargs={kwargs}") + logger = func(*args, **kwargs) + logger.debug(f"Exiting {func.__name__} with result={result}") + return result + return wrapper + +# Example usage: +@trace +def example_function(x, y): + return x + y + +if __name__ == "__main__": + result = example_function(2, 3) + print("Result:", result) diff --git a/ui/widgets.py b/ui/widgets.py new file mode 100644 index 00000000..d3b92fba --- /dev/null +++ b/ui/widgets.py @@ -0,0 +1,79 @@ +from typing import Any, Union +from rich.text import Text +from textual.widgets import ProgressBar, DataTable, Switch +from textual.reactive import reactive + +# class BilingualTable(DataTable): +# def __init__(self, title: str): +# super().__init__(zebra_stripes=True) +# self.border_title = title +# self.add_column("Content") +class ThinSpinner(ProgressBar): + """Animated thin progress bar spinner""" + _animation_progress = reactive(0.0) + + CSS = """ + ThinSpinner { + height: 1; + width: 100%; + background: $surface; + color: red; + border: none; + margin: 0; + } + + ThinSpinner > .progress--bar { + background: red; + min-height: 1; + width: 100%; + background: $surface; + margin: 0; + color: red; + # border: solid yellow; + } + """ + + def on_mount(self) -> None: + # Start animation with slight delay to ensure CSS loads + self.call_later(lambda: self.animate("_animation_progress", 1.0, duration=1.5, on_complete=self.on_mount)) + self.set_interval(0.05, self.update) + + def updte_animation_progress(self, progress: float) -> None: + self.progress = int(progress * 100) +# python +# python +class TranslationToggle(Switch): + def __init__(self, value: bool = False): + super().__init__(value) + self.label = "🌐 TRANSLATE ON" if value else "🌐 TRANSLATE OFF" + + def on_change(self, event: Switch.Changed): + self.label = "🌐 TRANSLATE ON" if event.value else "🌐 TRANSLATE OFF" + + + + +# class TranslationToggle(Switch): +# def __init__(self): +# super().__init__("🌐 TRANSLATE OFF") + +# def on_change(self, event: Switch.Changed): +# self.label = "🌐 TRANSLATE ON" if event.value else "🌐 TRANSLATE OFF" + +class BilingualTable(DataTable): + STYLES = { + "EN": ("bold #1F618D", "▷"), + "CN": ("bold #C0392B", "◁") + } + + def __init__(self, title: str, lang: str): + super().__init__(zebra_stripes=True, cursor_type="row") + self.border_title = f"{self.STYLES[lang][1]} {title}" + self.lang = lang + self.add_columns("Content") + def add_row(self, *cells: Any, height: int = 1, key: str = "", label: Union[str, Text] = "") -> Any: + #def add_row(self, *cells: Any, height: int = 1, key: str = "", label: str = "") -> Any: + return super().add_row(*cells, height=height, key=key, label=label) + #color, _ = self.STYLES[self.lang] + #styled = f"[{color}]{text}[/]" + #super().add_row(styled) \ No newline at end of file