From 77e4d073d71d41caa58738a649f0b784e80b4674 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Wed, 18 Sep 2024 05:55:16 -0700 Subject: [PATCH 001/554] feat: update README.md file --- README.md | 255 +++++++++++++++--------------------------------------- 1 file changed, 71 insertions(+), 184 deletions(-) diff --git a/README.md b/README.md index ba69bdae..395cf4ba 100644 --- a/README.md +++ b/README.md @@ -1,213 +1,100 @@ -
+# BTCopilot Subnet -# **Bittensor Subnet Template** -[![Discord Chat](https://img.shields.io/discord/308323056592486420.svg)](https://discord.gg/bittensor) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +Welcome to BTCopilot Subnet, a pioneering Bittensor-based subnet designed to revolutionize project generation through advanced AI models. BTCopilot aims to transform diverse prompts—ranging from text and voice to images and Figma designs—into fully functional, ready-to-deploy projects. This subnet is tailored for developers, designers, and innovators who seek to accelerate their project development process with high-quality, AI-generated outputs. ---- +## Table of Contents -## The Incentivized Internet +- [Overview](#overview) +- [Features](#features) +- [Incentive Mechanism](#incentive-mechanism) +- [Roadmap](#roadmap) -[Discord](https://discord.gg/bittensor) • [Network](https://taostats.io/) • [Research](https://bittensor.com/whitepaper) -
+## Overview ---- -- [Quickstarter template](#quickstarter-template) -- [Introduction](#introduction) - - [Example](#example) -- [Installation](#installation) - - [Before you proceed](#before-you-proceed) - - [Install](#install) -- [Writing your own incentive mechanism](#writing-your-own-incentive-mechanism) -- [Writing your own subnet API](#writing-your-own-subnet-api) -- [Subnet Links](#subnet-links) -- [License](#license) +BTCopilot Subnet leverages state-of-the-art AI models to interpret and convert various types of prompts into complete, deployable projects. Whether you're starting with a simple HTML/CSS framework or aiming to develop a complex React application, BTCopilot can generate the entire codebase, ensuring it meets your specified requirements and is ready for immediate deployment. ---- -## Quickstarter template +### Vision -This template contains all the required installation instructions, scripts, and files and functions for: -- Building Bittensor subnets. -- Creating custom incentive mechanisms and running these mechanisms on the subnets. +BTCopilot envisions a future where project creation is seamless, automated, and efficient, empowering developers to focus more on innovation and less on repetitive coding tasks. By harnessing the capabilities of the Bittensor network, BTCopilot fosters a competitive environment that drives continuous improvement in AI-generated outputs. -In order to simplify the building of subnets, this template abstracts away the complexity of the underlying blockchain and other boilerplate code. While the default behavior of the template is sufficient for a simple subnet, you should customize the template in order to meet your specific requirements. ---- +### Purpose -## Introduction +The primary purpose of BTCopilot is to: -**IMPORTANT**: If you are new to Bittensor subnets, read this section before proceeding to [Installation](#installation) section. +- Automate Project Generation: Provide a platform that can autonomously generate high-quality projects from diverse input prompts. +- Enhance Productivity: Reduce the time and effort required for project development, enabling developers to quickly bring their ideas to life. +- Promote Innovation: Encourage innovative solutions and optimizations in project generation through competitive incentivization. -The Bittensor blockchain hosts multiple self-contained incentive mechanisms called **subnets**. Subnets are playing fields in which: -- Subnet miners who produce value, and -- Subnet validators who produce consensus +## Features -determine together the proper distribution of TAO for the purpose of incentivizing the creation of value, i.e., generating digital commodities, such as intelligence or data. +- **Text Prompt**: Generate projects by describing them in text. +- **Voice Prompt**: Create projects by giving voice commands. +- **Image Prompt**: Upload an image of a website or app, and BTCopilot will generate a pixel-perfect project. +- **Figma Prompt**: Convert Figma designs into functional projects. +- **Automated Downloads**: Directly download the generated projects as complete folders. -Each subnet consists of: -- Subnet miners and subnet validators. -- A protocol using which the subnet miners and subnet validators interact with one another. This protocol is part of the incentive mechanism. -- The Bittensor API using which the subnet miners and subnet validators interact with Bittensor's onchain consensus engine [Yuma Consensus](https://bittensor.com/documentation/validating/yuma-consensus). The Yuma Consensus is designed to drive these actors: subnet validators and subnet miners, into agreement on who is creating value and what that value is worth. +## Incentive Mechanism -This starter template is split into three primary files. To write your own incentive mechanism, you should edit these files. These files are: -1. `template/protocol.py`: Contains the definition of the protocol used by subnet miners and subnet validators. -2. `neurons/miner.py`: Script that defines the subnet miner's behavior, i.e., how the subnet miner responds to requests from subnet validators. -3. `neurons/validator.py`: This script defines the subnet validator's behavior, i.e., how the subnet validator requests information from the subnet miners and determines the scores. +The BTCopilot subnet incentivizes miners and validators to ensure high-quality outputs. Here’s how it works specifically for this subnet: -### Example +- Task Assignment: Subnet miners are assigned tasks related to generating and improving machine learning models based on various prompts (text, voice, image, Figma). +- Performance Evaluation: Validators evaluate the outputs produced by miners. The evaluation criteria include accuracy, efficiency, and innovation. +- Ranking and Rewarding: Validators rank the miners according to their performance. The Bittensor blockchain’s Yuma Consensus mechanism determines the TAO rewards distribution based on these rankings. -The Bittensor Subnet 1 for Text Prompting is built using this template. See [prompting](https://github.com/macrocosm-os/prompting) for how to configure the files and how to add monitoring and telemetry and support multiple miner types. Also see this Subnet 1 in action on [Taostats](https://taostats.io/subnets/netuid-1/) explorer. +## Evaluation Process ---- +- For Miners: -## Installation + Miners in the BTCopilot subnet are tasked with generating project outputs based on various types of prompts. Their outputs are evaluated based on the following criteria: -### Before you proceed -Before you proceed with the installation of the subnet, note the following: + 1. Accuracy: -- Use these instructions to run your subnet locally for your development and testing, or on Bittensor testnet or on Bittensor mainnet. -- **IMPORTANT**: We **strongly recommend** that you first run your subnet locally and complete your development and testing before running the subnet on Bittensor testnet. Furthermore, make sure that you next run your subnet on Bittensor testnet before running it on the Bittensor mainnet. -- You can run your subnet either as a subnet owner, or as a subnet validator or as a subnet miner. -- **IMPORTANT:** Make sure you are aware of the minimum compute requirements for your subnet. See the [Minimum compute YAML configuration](./min_compute.yml). -- Note that installation instructions differ based on your situation: For example, installing for local development and testing will require a few additional steps compared to installing for testnet. Similarly, installation instructions differ for a subnet owner vs a validator or a miner. + Correctness: The generated code must accurately reflect the requirements stated in the prompt. -### Install + Functionality: The output should be fully functional with minimal to no errors. -- **Running locally**: Follow the step-by-step instructions described in this section: [Running Subnet Locally](./docs/running_on_staging.md). -- **Running on Bittensor testnet**: Follow the step-by-step instructions described in this section: [Running on the Test Network](./docs/running_on_testnet.md). -- **Running on Bittensor mainnet**: Follow the step-by-step instructions described in this section: [Running on the Main Network](./docs/running_on_mainnet.md). + 2. Efficiency: ---- + Resource Utilization: The output should be produced using the least amount of computational resources without compromising on quality. -## Writing your own incentive mechanism + Speed: Faster generation times are favored, provided the output meets all other criteria. -As described in [Quickstarter template](#quickstarter-template) section above, when you are ready to write your own incentive mechanism, update this template repository by editing the following files. The code in these files contains detailed documentation on how to update the template. Read the documentation in each of the files to understand how to update the template. There are multiple **TODO**s in each of the files identifying sections you should update. These files are: -- `template/protocol.py`: Contains the definition of the wire-protocol used by miners and validators. -- `neurons/miner.py`: Script that defines the miner's behavior, i.e., how the miner responds to requests from validators. -- `neurons/validator.py`: This script defines the validator's behavior, i.e., how the validator requests information from the miners and determines the scores. -- `template/forward.py`: Contains the definition of the validator's forward pass. -- `template/reward.py`: Contains the definition of how validators reward miner responses. + 3. Innovation: -In addition to the above files, you should also update the following files: -- `README.md`: This file contains the documentation for your project. Update this file to reflect your project's documentation. -- `CONTRIBUTING.md`: This file contains the instructions for contributing to your project. Update this file to reflect your project's contribution guidelines. -- `template/__init__.py`: This file contains the version of your project. -- `setup.py`: This file contains the metadata about your project. Update this file to reflect your project's metadata. -- `docs/`: This directory contains the documentation for your project. Update this directory to reflect your project's documentation. + Novelty: Unique and creative approaches to solving the prompt are rewarded. -__Note__ -The `template` directory should also be renamed to your project name. ---- + Optimization: Innovative optimizations that improve the performance or usability of the generated project are highly valued. -# Writing your own subnet API -To leverage the abstract `SubnetsAPI` in Bittensor, you can implement a standardized interface. This interface is used to interact with the Bittensor network and can be used by a client to interact with the subnet through its exposed axons. - -What does Bittensor communication entail? Typically two processes, (1) preparing data for transit (creating and filling `synapse`s) and (2), processing the responses received from the `axon`(s). +- For Validators: -This protocol uses a handler registry system to associate bespoke interfaces for subnets by implementing two simple abstract functions: -- `prepare_synapse` -- `process_responses` - -These can be implemented as extensions of the generic `SubnetsAPI` interface. E.g.: - - -This is abstract, generic, and takes(`*args`, `**kwargs`) for flexibility. See the extremely simple base class: -```python -class SubnetsAPI(ABC): - def __init__(self, wallet: "bt.wallet"): - self.wallet = wallet - self.dendrite = bt.dendrite(wallet=wallet) - - async def __call__(self, *args, **kwargs): - return await self.query_api(*args, **kwargs) - - @abstractmethod - def prepare_synapse(self, *args, **kwargs) -> Any: - """ - Prepare the synapse-specific payload. - """ - ... - - @abstractmethod - def process_responses(self, responses: List[Union["bt.Synapse", Any]]) -> Any: - """ - Process the responses from the network. - """ - ... - -``` - - -Here is a toy example: - -```python -from bittensor.subnets import SubnetsAPI -from MySubnet import MySynapse - -class MySynapseAPI(SubnetsAPI): - def __init__(self, wallet: "bt.wallet"): - super().__init__(wallet) - self.netuid = 99 - - def prepare_synapse(self, prompt: str) -> MySynapse: - # Do any preparatory work to fill the synapse - data = do_prompt_injection(prompt) - - # Fill the synapse for transit - synapse = StoreUser( - messages=[data], - ) - # Send it along - return synapse - - def process_responses(self, responses: List[Union["bt.Synapse", Any]]) -> str: - # Look through the responses for information required by your application - for response in responses: - if response.dendrite.status_code != 200: - continue - # potentially apply post processing - result_data = postprocess_data_from_response(response) - # return data to the client - return result_data -``` - -You can use a subnet API to the registry by doing the following: -1. Download and install the specific repo you want -1. Import the appropriate API handler from bespoke subnets -1. Make the query given the subnet specific API - - - -# Subnet Links -In order to see real-world examples of subnets in-action, see the `subnet_links.py` document or access them from inside the `template` package by: -```python -import template -template.SUBNET_LINKS -[{'name': 'sn0', 'url': ''}, - {'name': 'sn1', 'url': 'https://github.com/opentensor/prompting/'}, - {'name': 'sn2', 'url': 'https://github.com/bittranslateio/bittranslate/'}, - {'name': 'sn3', 'url': 'https://github.com/gitphantomman/scraping_subnet/'}, - {'name': 'sn4', 'url': 'https://github.com/manifold-inc/targon/'}, -... -] -``` - -## License -This repository is licensed under the MIT License. -```text -# The MIT License (MIT) -# Copyright © 2024 Opentensor Foundation - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. -``` + Validators play a crucial role in ensuring the quality of outputs. They are responsible for evaluating and ranking the miners’ contributions. The evaluation process involves: + + 1. Initial Review: Validators perform an initial check to ensure that the submitted outputs meet basic functional requirements. + 2. Detailed Assessment: Each output is thoroughly reviewed against the criteria of accuracy, efficiency, and innovation. + 3. Feedback Provision: Validators provide detailed feedback to miners, highlighting strengths and areas for improvement. + 4. Ranking Submission: Validators rank the outputs from different miners. These rankings are submitted to the Bittensor blockchain. + +### Example Scenario + +- Prompt: A miner receives a text prompt to create a React-based TodoList application. +- Generation: The miner generates the code for the application and submits it. +- Evaluation: Validators review the submission: + - Accuracy: Does the application have all the features mentioned in the prompt? + - Efficiency: Is the code optimized for performance? + - Innovation: Does the application include any additional features or optimizations not explicitly requested but beneficial? +- Ranking: Validators rank this submission against others. +- Rewarding: Based on the ranking, the miner receives TAO rewards. + +## Roadmap + +Phase 1: Generate HTML/CSS projects from text prompts. + +Phase 2: Enable voice prompts for project generation. + +Phase 3: Support image prompts to generate pixel-perfect projects. + +Phase 4: Integrate Figma designs as input for project generation. + +Phase 5: Automate the downloading of fully functional project folders. + +Phase 6: Expand to generate full framework-based projects like React, Angular, etc. From 1dae750ee7b4c1b822ccfcb9cb9230ce44a337ce Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Wed, 18 Sep 2024 06:02:28 -0700 Subject: [PATCH 002/554] feat: update setup.py appropriate --- setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index f76ec9b2..7c560404 100644 --- a/setup.py +++ b/setup.py @@ -63,16 +63,16 @@ def read_requirements(path): version_string = version_match.group(1) setup( - name="bittensor_subnet_template", # TODO(developer): Change this value to your module subnet name. + name="btcopilot", version=version_string, - description="bittensor_subnet_template", # TODO(developer): Change this value to your module subnet description. + description="BTCopilot aims to transform diverse prompts—ranging from text and voice to images and Figma designs—into fully functional, ready-to-deploy projects.", long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/opentensor/bittensor-subnet-template", # TODO(developer): Change this url to your module subnet github url. - author="bittensor.com", # TODO(developer): Change this value to your module subnet author name. + url="https://github.com/BTCopilot/btcopilot", + author="Sangar", packages=find_packages(), include_package_data=True, - author_email="", # TODO(developer): Change this value to your module subnet author email. + author_email="sangar.work1028@gmail.com", license="MIT", python_requires=">=3.8", install_requires=requirements, From 9b8837ad0e54d36b4fc0e8cb32531127a1079b02 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Wed, 18 Sep 2024 06:09:24 -0700 Subject: [PATCH 003/554] feat: update CONTRIBUTING.md file --- contrib/CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/CONTRIBUTING.md b/contrib/CONTRIBUTING.md index ba33ce3c..2393c621 100644 --- a/contrib/CONTRIBUTING.md +++ b/contrib/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to Bittensor Subnet Development +# Contributing to BTCopilot -The following is a set of guidelines for contributing to the Bittensor ecosystem. These are **HIGHLY RECOMMENDED** guidelines, but not hard-and-fast rules. Use your best judgment, and feel free to propose changes to this document in a pull request. +The following is a set of guidelines for contributing to the BTCopilot Subnet. These are **HIGHLY RECOMMENDED** guidelines, but not hard-and-fast rules. Use your best judgment, and feel free to propose changes to this document in a pull request. ## Table Of Contents 1. [How Can I Contribute?](#how-can-i-contribute) @@ -16,10 +16,10 @@ The following is a set of guidelines for contributing to the Bittensor ecosystem ## How Can I Contribute? -TODO(developer): Define your desired contribution procedure. +You can fork this repository and create a PR with your codes and request review from our development team. You can reference [Pull Request Philosophy](#pull-request-philosophy). ## Communication Channels -TODO(developer): Place your communication channels here +Contact us on [Our discord server](https://discord.gg/P2XRwVEJ) > Please follow the Bittensor Subnet [style guide](./STYLE.md) regardless of your contribution type. @@ -99,7 +99,7 @@ After you submit a pull request, it will be reviewed by the maintainers. They ma > Note: Be sure to merge the latest from "upstream" before making a pull request: ```bash -git remote add upstream https://github.com/opentensor/bittensor.git # TODO(developer): replace with your repo URL +git remote add upstream https://github.com/BTCopilot/btcopilot.git git fetch upstream git merge upstream/ git push origin From 3a34d7b21ccaf2bc9379cc9642f92c39aa9fe1ff Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Wed, 18 Sep 2024 06:11:35 -0700 Subject: [PATCH 004/554] feat: replaced template with btcopilot --- {template => btcopilot}/__init__.py | 0 {template => btcopilot}/api/__init__.py | 0 {template => btcopilot}/api/dummy.py | 2 +- {template => btcopilot}/api/get_query_axons.py | 0 {template => btcopilot}/base/__init__.py | 0 {template => btcopilot}/base/miner.py | 4 ++-- {template => btcopilot}/base/neuron.py | 8 ++++---- {template => btcopilot}/base/utils/__init__.py | 0 {template => btcopilot}/base/utils/weight_utils.py | 0 {template => btcopilot}/base/validator.py | 8 ++++---- {template => btcopilot}/mock.py | 0 {template => btcopilot}/protocol.py | 0 {template => btcopilot}/subnet_links.py | 0 {template => btcopilot}/utils/__init__.py | 0 {template => btcopilot}/utils/config.py | 0 {template => btcopilot}/utils/logging.py | 0 {template => btcopilot}/utils/misc.py | 0 {template => btcopilot}/utils/uids.py | 0 {template => btcopilot}/validator/__init__.py | 0 {template => btcopilot}/validator/forward.py | 6 +++--- {template => btcopilot}/validator/reward.py | 0 neurons/miner.py | 12 ++++++------ neurons/validator.py | 4 ++-- tests/test_template_validator.py | 8 ++++---- 24 files changed, 26 insertions(+), 26 deletions(-) rename {template => btcopilot}/__init__.py (100%) rename {template => btcopilot}/api/__init__.py (100%) rename {template => btcopilot}/api/dummy.py (98%) rename {template => btcopilot}/api/get_query_axons.py (100%) rename {template => btcopilot}/base/__init__.py (100%) rename {template => btcopilot}/base/miner.py (98%) rename {template => btcopilot}/base/neuron.py (96%) rename {template => btcopilot}/base/utils/__init__.py (100%) rename {template => btcopilot}/base/utils/weight_utils.py (100%) rename {template => btcopilot}/base/validator.py (98%) rename {template => btcopilot}/mock.py (100%) rename {template => btcopilot}/protocol.py (100%) rename {template => btcopilot}/subnet_links.py (100%) rename {template => btcopilot}/utils/__init__.py (100%) rename {template => btcopilot}/utils/config.py (100%) rename {template => btcopilot}/utils/logging.py (100%) rename {template => btcopilot}/utils/misc.py (100%) rename {template => btcopilot}/utils/uids.py (100%) rename {template => btcopilot}/validator/__init__.py (100%) rename {template => btcopilot}/validator/forward.py (95%) rename {template => btcopilot}/validator/reward.py (100%) diff --git a/template/__init__.py b/btcopilot/__init__.py similarity index 100% rename from template/__init__.py rename to btcopilot/__init__.py diff --git a/template/api/__init__.py b/btcopilot/api/__init__.py similarity index 100% rename from template/api/__init__.py rename to btcopilot/api/__init__.py diff --git a/template/api/dummy.py b/btcopilot/api/dummy.py similarity index 98% rename from template/api/dummy.py rename to btcopilot/api/dummy.py index f6a433f1..b1d0e13b 100644 --- a/template/api/dummy.py +++ b/btcopilot/api/dummy.py @@ -19,7 +19,7 @@ import bittensor as bt from typing import List, Optional, Union, Any, Dict -from template.protocol import Dummy +from btcopilot.protocol import Dummy from bittensor.subnets import SubnetsAPI diff --git a/template/api/get_query_axons.py b/btcopilot/api/get_query_axons.py similarity index 100% rename from template/api/get_query_axons.py rename to btcopilot/api/get_query_axons.py diff --git a/template/base/__init__.py b/btcopilot/base/__init__.py similarity index 100% rename from template/base/__init__.py rename to btcopilot/base/__init__.py diff --git a/template/base/miner.py b/btcopilot/base/miner.py similarity index 98% rename from template/base/miner.py rename to btcopilot/base/miner.py index 1788e24b..d81c9632 100644 --- a/template/base/miner.py +++ b/btcopilot/base/miner.py @@ -23,8 +23,8 @@ import bittensor as bt -from template.base.neuron import BaseNeuron -from template.utils.config import add_miner_args +from btcopilot.base.neuron import BaseNeuron +from btcopilot.utils.config import add_miner_args from typing import Union diff --git a/template/base/neuron.py b/btcopilot/base/neuron.py similarity index 96% rename from template/base/neuron.py rename to btcopilot/base/neuron.py index 9b2ce7b2..269cf0d4 100644 --- a/template/base/neuron.py +++ b/btcopilot/base/neuron.py @@ -23,10 +23,10 @@ from abc import ABC, abstractmethod # Sync calls set weights and also resyncs the metagraph. -from template.utils.config import check_config, add_args, config -from template.utils.misc import ttl_get_block -from template import __spec_version__ as spec_version -from template.mock import MockSubtensor, MockMetagraph +from btcopilot.utils.config import check_config, add_args, config +from btcopilot.utils.misc import ttl_get_block +from btcopilot import __spec_version__ as spec_version +from btcopilot.mock import MockSubtensor, MockMetagraph class BaseNeuron(ABC): diff --git a/template/base/utils/__init__.py b/btcopilot/base/utils/__init__.py similarity index 100% rename from template/base/utils/__init__.py rename to btcopilot/base/utils/__init__.py diff --git a/template/base/utils/weight_utils.py b/btcopilot/base/utils/weight_utils.py similarity index 100% rename from template/base/utils/weight_utils.py rename to btcopilot/base/utils/weight_utils.py diff --git a/template/base/validator.py b/btcopilot/base/validator.py similarity index 98% rename from template/base/validator.py rename to btcopilot/base/validator.py index c1ca07ed..cdc9afa3 100644 --- a/template/base/validator.py +++ b/btcopilot/base/validator.py @@ -28,13 +28,13 @@ from typing import List, Union from traceback import print_exception -from template.base.neuron import BaseNeuron -from template.base.utils.weight_utils import ( +from btcopilot.base.neuron import BaseNeuron +from btcopilot.base.utils.weight_utils import ( process_weights_for_netuid, convert_weights_and_uids_for_emit, ) # TODO: Replace when bittensor switches to numpy -from template.mock import MockDendrite -from template.utils.config import add_validator_args +from btcopilot.mock import MockDendrite +from btcopilot.utils.config import add_validator_args class BaseValidatorNeuron(BaseNeuron): diff --git a/template/mock.py b/btcopilot/mock.py similarity index 100% rename from template/mock.py rename to btcopilot/mock.py diff --git a/template/protocol.py b/btcopilot/protocol.py similarity index 100% rename from template/protocol.py rename to btcopilot/protocol.py diff --git a/template/subnet_links.py b/btcopilot/subnet_links.py similarity index 100% rename from template/subnet_links.py rename to btcopilot/subnet_links.py diff --git a/template/utils/__init__.py b/btcopilot/utils/__init__.py similarity index 100% rename from template/utils/__init__.py rename to btcopilot/utils/__init__.py diff --git a/template/utils/config.py b/btcopilot/utils/config.py similarity index 100% rename from template/utils/config.py rename to btcopilot/utils/config.py diff --git a/template/utils/logging.py b/btcopilot/utils/logging.py similarity index 100% rename from template/utils/logging.py rename to btcopilot/utils/logging.py diff --git a/template/utils/misc.py b/btcopilot/utils/misc.py similarity index 100% rename from template/utils/misc.py rename to btcopilot/utils/misc.py diff --git a/template/utils/uids.py b/btcopilot/utils/uids.py similarity index 100% rename from template/utils/uids.py rename to btcopilot/utils/uids.py diff --git a/template/validator/__init__.py b/btcopilot/validator/__init__.py similarity index 100% rename from template/validator/__init__.py rename to btcopilot/validator/__init__.py diff --git a/template/validator/forward.py b/btcopilot/validator/forward.py similarity index 95% rename from template/validator/forward.py rename to btcopilot/validator/forward.py index af5e7ee0..9ccf1bc1 100644 --- a/template/validator/forward.py +++ b/btcopilot/validator/forward.py @@ -20,9 +20,9 @@ import time import bittensor as bt -from template.protocol import Dummy -from template.validator.reward import get_rewards -from template.utils.uids import get_random_uids +from btcopilot.protocol import Dummy +from btcopilot.validator.reward import get_rewards +from btcopilot.utils.uids import get_random_uids async def forward(self): diff --git a/template/validator/reward.py b/btcopilot/validator/reward.py similarity index 100% rename from template/validator/reward.py rename to btcopilot/validator/reward.py diff --git a/neurons/miner.py b/neurons/miner.py index 5f7b9500..cd00004e 100644 --- a/neurons/miner.py +++ b/neurons/miner.py @@ -22,10 +22,10 @@ import bittensor as bt # Bittensor Miner Template: -import template +import btcopilot # import base miner class which takes care of most of the boilerplate -from template.base.miner import BaseMinerNeuron +from btcopilot.base.miner import BaseMinerNeuron class Miner(BaseMinerNeuron): @@ -43,8 +43,8 @@ def __init__(self, config=None): # TODO(developer): Anything specific to your use case you can do here async def forward( - self, synapse: template.protocol.Dummy - ) -> template.protocol.Dummy: + self, synapse: btcopilot.protocol.Dummy + ) -> btcopilot.protocol.Dummy: """ Processes the incoming 'Dummy' synapse by performing a predefined operation on the input data. This method should be replaced with actual logic relevant to the miner's purpose. @@ -63,7 +63,7 @@ async def forward( return synapse async def blacklist( - self, synapse: template.protocol.Dummy + self, synapse: btcopilot.protocol.Dummy ) -> typing.Tuple[bool, str]: """ Determines whether an incoming request should be blacklisted and thus ignored. Your implementation should @@ -124,7 +124,7 @@ async def blacklist( ) return False, "Hotkey recognized!" - async def priority(self, synapse: template.protocol.Dummy) -> float: + async def priority(self, synapse: btcopilot.protocol.Dummy) -> float: """ The priority function determines the order in which requests are handled. More valuable or higher-priority requests are processed before others. You should design your own priority mechanism with care. diff --git a/neurons/validator.py b/neurons/validator.py index e28b972c..290e2a2c 100644 --- a/neurons/validator.py +++ b/neurons/validator.py @@ -24,9 +24,9 @@ import bittensor as bt # import base validator class which takes care of most of the boilerplate -from template.base.validator import BaseValidatorNeuron +from btcopilot.base.validator import BaseValidatorNeuron # Bittensor Validator Template: -from template.validator import forward +from btcopilot.validator import forward class Validator(BaseValidatorNeuron): diff --git a/tests/test_template_validator.py b/tests/test_template_validator.py index 48e015a9..92f82d36 100644 --- a/tests/test_template_validator.py +++ b/tests/test_template_validator.py @@ -23,10 +23,10 @@ import torch from neurons.validator import Validator -from template.base.validator import BaseValidatorNeuron -from template.protocol import Dummy -from template.utils.uids import get_random_uids -from template.validator.reward import get_rewards +from btcopilot.base.validator import BaseValidatorNeuron +from btcopilot.protocol import Dummy +from btcopilot.utils.uids import get_random_uids +from btcopilot.validator.reward import get_rewards class TemplateValidatorNeuronTestCase(unittest.TestCase): From 38ff64209d3a7a40b87b0a4a057b8d392519b788 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Wed, 18 Sep 2024 06:14:50 -0700 Subject: [PATCH 005/554] feat: update btcopilot/__init__.py file --- btcopilot/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/btcopilot/__init__.py b/btcopilot/__init__.py index cb07b8c0..4115dac0 100644 --- a/btcopilot/__init__.py +++ b/btcopilot/__init__.py @@ -1,6 +1,6 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# TODO(developer): Set your name +# Sangar # Copyright © 2023 # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated @@ -17,9 +17,9 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -# TODO(developer): Change this value when updating your code base. -# Define the version of the template module. -__version__ = "0.0.0" +# Change this value when updating your code base. +# Define the version of the btcopilot. +__version__ = "0.0.1" version_split = __version__.split(".") __spec_version__ = ( (1000 * int(version_split[0])) From b63bf4d49ae904289475145b0050697827086ddb Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Wed, 18 Sep 2024 06:16:45 -0700 Subject: [PATCH 006/554] feat: update setup.py --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 7c560404..18ca652c 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ # The MIT License (MIT) -# Copyright © 2023 Yuma Rao -# TODO(developer): Set your name -# Copyright © 2023 +# Copyright © 2023 BTCopilot +# Sangar +# Copyright © 2023 # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation From c97712ea0e2af0b45f87869c70a32734ab9f5bad Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Wed, 18 Sep 2024 06:22:26 -0700 Subject: [PATCH 007/554] feat: update gitignore file --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 72a70c3a..6b79b60a 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,6 @@ testing/ # Editors .vscode/settings.json + +.DS_Store +temp.txt \ No newline at end of file From 89a13c32688550cef4c6bde60c023a2d113f3901 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Fri, 27 Sep 2024 15:22:32 -0700 Subject: [PATCH 008/554] feat: specified author's name --- btcopilot/base/validator.py | 3 +-- btcopilot/protocol.py | 3 +-- btcopilot/validator/forward.py | 3 +-- btcopilot/validator/reward.py | 3 +-- neurons/miner.py | 3 +-- neurons/validator.py | 3 +-- 6 files changed, 6 insertions(+), 12 deletions(-) diff --git a/btcopilot/base/validator.py b/btcopilot/base/validator.py index cdc9afa3..dc464428 100644 --- a/btcopilot/base/validator.py +++ b/btcopilot/base/validator.py @@ -1,7 +1,6 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# TODO(developer): Set your name -# Copyright © 2023 +# Copyright © 2024 Sangar # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation diff --git a/btcopilot/protocol.py b/btcopilot/protocol.py index c601e58a..b7105bc4 100644 --- a/btcopilot/protocol.py +++ b/btcopilot/protocol.py @@ -1,7 +1,6 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# TODO(developer): Set your name -# Copyright © 2023 +# Copyright © 2024 Sangar # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation diff --git a/btcopilot/validator/forward.py b/btcopilot/validator/forward.py index 9ccf1bc1..f679e907 100644 --- a/btcopilot/validator/forward.py +++ b/btcopilot/validator/forward.py @@ -1,7 +1,6 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# TODO(developer): Set your name -# Copyright © 2023 +# Copyright © 2024 Sangar # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation diff --git a/btcopilot/validator/reward.py b/btcopilot/validator/reward.py index 58492183..58f920a4 100644 --- a/btcopilot/validator/reward.py +++ b/btcopilot/validator/reward.py @@ -1,7 +1,6 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# TODO(developer): Set your name -# Copyright © 2023 +# Copyright © 2024 Sangar # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation diff --git a/neurons/miner.py b/neurons/miner.py index cd00004e..8e350ec9 100644 --- a/neurons/miner.py +++ b/neurons/miner.py @@ -1,7 +1,6 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# TODO(developer): Set your name -# Copyright © 2023 +# Copyright © 2023 Sangar # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation diff --git a/neurons/validator.py b/neurons/validator.py index 290e2a2c..ca71c02b 100644 --- a/neurons/validator.py +++ b/neurons/validator.py @@ -1,7 +1,6 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# TODO(developer): Set your name -# Copyright © 2023 +# Copyright © 2024 Sangar # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation From dd6a1256e05ad56e677d2e1e3f83e33d0ea78958 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Thu, 31 Oct 2024 10:04:04 -0500 Subject: [PATCH 009/554] feat: complete miner and validator part for initial workflow --- btcopilot/base/miner.py | 3 +- btcopilot/base/neuron.py | 5 +- btcopilot/base/validator.py | 5 +- btcopilot/miners/dummy_miner.py | 116 +++++++++++++++++ btcopilot/protocol.py | 135 +++++++++++-------- btcopilot/rewards/__init__.py | 5 + btcopilot/rewards/gpt.py | 10 ++ btcopilot/rewards/penalty.py | 11 ++ btcopilot/rewards/reward.py | 2 + btcopilot/rewards/reward_manager.py | 67 ++++++++++ btcopilot/rewards/speed.py | 15 +++ btcopilot/solution/__init__.py | 1 + btcopilot/solution/solution.py | 7 + btcopilot/task_generator/__init__.py | 1 + btcopilot/task_generator/task_generator.py | 12 ++ btcopilot/tasks/__init__.py | 1 + btcopilot/tasks/task.py | 22 ++++ btcopilot/utils/config.py | 7 + btcopilot/validator/forward.py | 75 ++++++++--- docs/stream_tutorial/client.py | 2 +- lang_chain_test.py | 144 +++++++++++++++++++++ neurons/miner.py | 39 +++--- neurons/validator.py | 8 +- requirements.txt | 5 +- run_miner.sh | 3 + run_validator.sh | 2 + 26 files changed, 603 insertions(+), 100 deletions(-) create mode 100644 btcopilot/miners/dummy_miner.py create mode 100644 btcopilot/rewards/__init__.py create mode 100644 btcopilot/rewards/gpt.py create mode 100644 btcopilot/rewards/penalty.py create mode 100644 btcopilot/rewards/reward.py create mode 100644 btcopilot/rewards/reward_manager.py create mode 100644 btcopilot/rewards/speed.py create mode 100644 btcopilot/solution/__init__.py create mode 100644 btcopilot/solution/solution.py create mode 100644 btcopilot/task_generator/__init__.py create mode 100644 btcopilot/task_generator/task_generator.py create mode 100644 btcopilot/tasks/__init__.py create mode 100644 btcopilot/tasks/task.py create mode 100644 lang_chain_test.py create mode 100644 run_miner.sh create mode 100644 run_validator.sh diff --git a/btcopilot/base/miner.py b/btcopilot/base/miner.py index d81c9632..5f3e3402 100644 --- a/btcopilot/base/miner.py +++ b/btcopilot/base/miner.py @@ -186,7 +186,8 @@ def __exit__(self, exc_type, exc_value, traceback): def resync_metagraph(self): """Resyncs the metagraph and updates the hotkeys and moving averages based on the new metagraph.""" - bt.logging.info("resync_metagraph()") + #TODO: Implement this + #bt.logging.info("resync_metagraph()") # Sync the metagraph. self.metagraph.sync(subtensor=self.subtensor) diff --git a/btcopilot/base/neuron.py b/btcopilot/base/neuron.py index 269cf0d4..8c41320a 100644 --- a/btcopilot/base/neuron.py +++ b/btcopilot/base/neuron.py @@ -169,9 +169,8 @@ def should_set_weights(self) -> bool: ) # don't set weights if you're a miner def save_state(self): - bt.logging.warning( - "save_state() not implemented for this neuron. You can implement this function to save model checkpoints or other useful data." - ) + #TODO: Implement this + pass def load_state(self): bt.logging.warning( diff --git a/btcopilot/base/validator.py b/btcopilot/base/validator.py index cdc9afa3..5e2ea255 100644 --- a/btcopilot/base/validator.py +++ b/btcopilot/base/validator.py @@ -54,6 +54,7 @@ def __init__(self, config=None): # Save a copy of the hotkeys to local memory. self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) + print("=== self.hotkeys ===>", self.hotkeys) # Dendrite lets us send messages to other nodes (axons) in the network. if self.config.mock: @@ -280,7 +281,9 @@ def set_weights(self): def resync_metagraph(self): """Resyncs the metagraph and updates the hotkeys and moving averages based on the new metagraph.""" - bt.logging.info("resync_metagraph()") + #TODO: Implement this + + #bt.logging.info("resync_metagraph()") # Copies state of metagraph before syncing. previous_metagraph = copy.deepcopy(self.metagraph) diff --git a/btcopilot/miners/dummy_miner.py b/btcopilot/miners/dummy_miner.py new file mode 100644 index 00000000..13336d6c --- /dev/null +++ b/btcopilot/miners/dummy_miner.py @@ -0,0 +1,116 @@ +import json +from functools import partial +from starlette.types import Send +import time +from typing import Dict + +from typing import Dict, Awaitable +from langchain_openai import ChatOpenAI +from dotenv import load_dotenv, find_dotenv +from langchain.prompts import ChatPromptTemplate +from langchain_core.output_parsers import StrOutputParser +from langchain_core.runnables.base import RunnableSequence + +import bittensor as bt +import btcopilot +import os + +def miner_init(self): + bt.logging.debug(f"Dummy Miner initialized") + _ = load_dotenv(find_dotenv()) + api_key = os.getenv("OPENAI_API_KEY") + # Set openai key and other args + self.model = ChatOpenAI( + api_key=api_key, + model_name="gpt-4", + ) + +def miner_forward(self, synapse: btcopilot.protocol.BtCopilotSynapse)->Awaitable: + + async def _forward(self, chain: RunnableSequence, chain_formatter: Dict[str, str], timeout_threshold: float, init_time: float, send: Send): + try: + json_response = { + "css": "body { background-color: red; }", + "html": "

Hello World

", + } + await send( + { + "type": "http.response.body", + "body": json.dumps(json_response).encode("utf-8"), + "more_body": False, + } + ) + # buffer = [] + + # timeout_reached = False + # is_in_code_block = True + # is_first_line = True + + # for token in chain.stream(chain_formatter): + # if is_first_line and token.startswith("```"): + # is_in_code_block = False + + # if is_in_code_block and not token.startswith("```"): + # buffer.append(token) + + # if time.time() - init_time > timeout_threshold: + # bt.logging.debug(f"⏰ Timeout reached, stopping streaming") + # timeout_reached = True + # break + + # if len(buffer) == self.config.neuron.streaming_batch_size: + # joined_buffer = "".join(buffer) + # bt.logging.debug(f"Streamed tokens: {joined_buffer}") + + # await send( + # { + # "type": "http.response.body", + # "body": joined_buffer.encode("utf-8"), + # "more_body": True, + # } + # ) + # buffer = [] + + # if is_first_line and token.endswith("\n"): + # is_first_line = False + # is_in_code_block = True + + # if ( + # buffer and not timeout_reached + # ): # Don't send the last buffer of data if timeout. + # joined_buffer = "".join(buffer) + # await send( + # { + # "type": "http.response.body", + # "body": joined_buffer.encode("utf-8"), + # "more_body": False, + # } + # ) + except Exception as e: + bt.logging.error(f"Dummy Miner Error: {e}") + + bt.logging.debug(f"Dummy Miner Query received, forwarding synapse: {synapse}") + + prompt = ChatPromptTemplate.from_messages([ + ("system", "You are an expert programmer. Generate code based on the following request without explanations:\n\n{query}\n\nProvide only the code."), + ("user", "{query}"), + ]) + chain = prompt | self.model | StrOutputParser() + + query = synapse.task.query + bt.logging.debug(f"Dummy Miner Query received: {synapse}") + time.sleep(2) + chain_formatter = {"query": query} + + init_time = time.time() + timeout_threshold = float(synapse.timeout) + + token_streamer = partial( + _forward, + self, + chain, + chain_formatter, + timeout_threshold, + init_time, + ) + return synapse.create_streaming_response(token_streamer) \ No newline at end of file diff --git a/btcopilot/protocol.py b/btcopilot/protocol.py index c601e58a..db4e6ffd 100644 --- a/btcopilot/protocol.py +++ b/btcopilot/protocol.py @@ -1,7 +1,6 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# TODO(developer): Set your name -# Copyright © 2023 +# Copyright © 2023 Dominique Hayes # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation @@ -17,60 +16,94 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -import typing -import bittensor as bt - -# TODO(developer): Rewrite with your protocol definition. - -# This is the protocol for the dummy miner and validator. -# It is a simple request-response protocol where the validator sends a request -# to the miner, and the miner responds with a dummy response. - -# ---- miner ---- -# Example usage: -# def dummy( synapse: Dummy ) -> Dummy: -# synapse.dummy_output = synapse.dummy_input + 1 -# return synapse -# axon = bt.axon().attach( dummy ).serve(netuid=...).start() +import pydantic +import json +from typing import AsyncIterator, Union, Any +from starlette.responses import StreamingResponse -# ---- validator --- -# Example usage: -# dendrite = bt.dendrite() -# dummy_output = dendrite.query( Dummy( dummy_input = 1 ) ) -# assert dummy_output == 2 +import bittensor as bt +from btcopilot.tasks import Task +from btcopilot.solution import Solution -class Dummy(bt.Synapse): +class BtCopilotSynapse(bt.StreamingSynapse): """ - A simple dummy protocol representation which uses bt.Synapse as its base. - This protocol helps in handling dummy request and response communication between - the miner and the validator. - - Attributes: - - dummy_input: An integer value representing the input request sent by the validator. - - dummy_output: An optional integer value which, when filled, represents the response from the miner. + A protocol for the BtCopilot. """ - # Required request input, filled by sending dendrite caller. - dummy_input: int - - # Optional request output, filled by receiving axon. - dummy_output: typing.Optional[int] = None - - def deserialize(self) -> int: + task: Union[Task, None] = pydantic.Field( + None, + title="Task", + description="A task to be sent to miners." + ) + + solution: Union[Solution, None] = pydantic.Field( + None, + title="Solution", + description="A solution received from miners." + ) + + completion: str = pydantic.Field( + "", + title="Completion", + description="The completion response from miners." + ) + + async def process_streaming_response(self, response: StreamingResponse) -> AsyncIterator[str]: """ - Deserialize the dummy output. This method retrieves the response from - the miner in the form of dummy_output, deserializes it and returns it - as the output of the dendrite.query() call. - - Returns: - - int: The deserialized response, which in this case is the value of dummy_output. - - Example: - Assuming a Dummy instance has a dummy_output value of 5: - >>> dummy_instance = Dummy(dummy_input=4) - >>> dummy_instance.dummy_output = 5 - >>> dummy_instance.deserialize() - 5 + Processes a streaming response from a miner. + """ + if self.completion is None: + self.completion = "" + async for chunk in response.content.iter_any(): + tokens = chunk.decode("utf-8") + + for token in tokens: + if token: + self.completion += token + yield tokens + + def deserialize(self) -> Union[Any, None]: + """ + Deserializes the response. + """ + try: + bt.logging.debug(f"completion: {self.completion}") + json_response = json.loads(self.completion) + css = json_response.get("css", None) + html = json_response.get("html", None) + if css is None and html is None: + bt.logging.error(f"Invalid response: {json_response}") + return None + + css = str(css) + html = str(html) + + process_time = self.dendrite.process_time + bt.logging.debug(f"css: {css}, html: {html}, process_time: {process_time}") + self.solution = Solution(css=css, html=html, process_time=process_time, miner_uid=0) + return self + except Exception as e: + bt.logging.error(f"Failed to parse completion: {e}") + return None + + + def extract_response_json(self, response: StreamingResponse) -> dict: + """ + Extracts the response JSON. """ - return self.dummy_output + headers = { + k.decode("utf-8"): v.decode("utf-8") + for k, v in response.__dict__["_raw_headers"] + } + + def extract_info(prefix:str) -> dict: + return { + key.split("_")[-1]: value + for key, value in headers.items() + if key.startswith(prefix) + } + return { + "completion": self.completion + } + \ No newline at end of file diff --git a/btcopilot/rewards/__init__.py b/btcopilot/rewards/__init__.py new file mode 100644 index 00000000..9ff3b958 --- /dev/null +++ b/btcopilot/rewards/__init__.py @@ -0,0 +1,5 @@ +from .reward import Reward +from .gpt import GPTReward +from .speed import SpeedReward +from .penalty import PenaltyReward +from .reward_manager import RewardManager \ No newline at end of file diff --git a/btcopilot/rewards/gpt.py b/btcopilot/rewards/gpt.py new file mode 100644 index 00000000..2fe0e99b --- /dev/null +++ b/btcopilot/rewards/gpt.py @@ -0,0 +1,10 @@ +from btcopilot.rewards import Reward +from btcopilot.tasks import Task +from btcopilot.solution import Solution + +class GPTReward(Reward): + def __init__(self): + pass + + def reward(self, task: Task, solution: Solution) -> float: + return 0.0 \ No newline at end of file diff --git a/btcopilot/rewards/penalty.py b/btcopilot/rewards/penalty.py new file mode 100644 index 00000000..3dd02638 --- /dev/null +++ b/btcopilot/rewards/penalty.py @@ -0,0 +1,11 @@ +from btcopilot.rewards import Reward +from btcopilot.tasks import Task +from btcopilot.solution import Solution + +class PenaltyReward(Reward): + + def __init__(self): + pass + + def reward(self, task: Task, solution: Solution) -> float: + return 0.0 \ No newline at end of file diff --git a/btcopilot/rewards/reward.py b/btcopilot/rewards/reward.py new file mode 100644 index 00000000..39f95bd1 --- /dev/null +++ b/btcopilot/rewards/reward.py @@ -0,0 +1,2 @@ +class Reward: + pass \ No newline at end of file diff --git a/btcopilot/rewards/reward_manager.py b/btcopilot/rewards/reward_manager.py new file mode 100644 index 00000000..60f7d191 --- /dev/null +++ b/btcopilot/rewards/reward_manager.py @@ -0,0 +1,67 @@ +from typing import Dict, List, Tuple + +from btcopilot.rewards import Reward +from btcopilot.rewards import GPTReward +from btcopilot.rewards import SpeedReward +from btcopilot.rewards import PenaltyReward +from btcopilot.tasks import Task +from btcopilot.solution import Solution + +class RewardManager: + """ + A singleton manager for the reward models. + """ + reward_models: Dict[str, Reward] = {} + + def __init__(self): + self.reward_models = { + "gpt": GPTReward(), + "speed": SpeedReward(), + "penalty": PenaltyReward(), + } + + def _penalty(self, task: Task, solution: Solution) -> float: + """ + Penalize the solution based on the penalty models. + """ + penalty = 0 + for penalty_model in task.penalty_models: + model = self.reward_models[penalty_model[0]] + weight = penalty_model[1] + + penalty += weight * model.reward(task, solution) + return penalty + + def _reward(self, task: Task, solution: Solution) -> float: + """ + Reward the solution based on the reward models. + """ + reward = 0 + for reward_model in task.reward_models: + model = self.reward_models[reward_model[0]] + weight = reward_model[1] + reward += weight * model.reward(task, solution) + + return reward + + def _score(self, task: Task, solution: Solution) -> float: + """ + Score the solution based on the reward and penalty models. + """ + score = task.reward_weight * self._reward(task, solution) - task.penalty_weight * self._penalty(task, solution) + if score < 0: + score = 0 + return score + + def score(self, task: Task, results: List[Solution]) -> Tuple[List[float], List[int]]: + """ + Score the solutions based on the reward and penalty models. + """ + scores = [] + miner_uids = [] + for solution in results: + score = self._score(task, solution) + scores.append(score) + miner_uids.append(solution.miner_uid) + + return scores, miner_uids \ No newline at end of file diff --git a/btcopilot/rewards/speed.py b/btcopilot/rewards/speed.py new file mode 100644 index 00000000..0fd47655 --- /dev/null +++ b/btcopilot/rewards/speed.py @@ -0,0 +1,15 @@ + +from btcopilot.rewards import Reward +from btcopilot.tasks import Task +from btcopilot.solution import Solution + +class SpeedReward(Reward): + + def __init__(self): + pass + + def reward(self, task: Task, solution: Solution) -> float: + if (task.timeout == 0): + return 1.0 + return 1.0 - (solution.process_time / task.timeout) + \ No newline at end of file diff --git a/btcopilot/solution/__init__.py b/btcopilot/solution/__init__.py new file mode 100644 index 00000000..fb1bbb15 --- /dev/null +++ b/btcopilot/solution/__init__.py @@ -0,0 +1 @@ +from .solution import Solution \ No newline at end of file diff --git a/btcopilot/solution/solution.py b/btcopilot/solution/solution.py new file mode 100644 index 00000000..1a5ddec5 --- /dev/null +++ b/btcopilot/solution/solution.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel, Field + +class Solution(BaseModel): + css: str = Field("", description="The css solution") + html: str = Field("", description="The html solution") + process_time: float = Field(0, description="The time it took to process the solution") + miner_uid: int = Field(0, description="The uid of the miner that processed the solution") \ No newline at end of file diff --git a/btcopilot/task_generator/__init__.py b/btcopilot/task_generator/__init__.py new file mode 100644 index 00000000..e4507b44 --- /dev/null +++ b/btcopilot/task_generator/__init__.py @@ -0,0 +1 @@ +from .task_generator import TaskGenerator \ No newline at end of file diff --git a/btcopilot/task_generator/task_generator.py b/btcopilot/task_generator/task_generator.py new file mode 100644 index 00000000..cee9aa7a --- /dev/null +++ b/btcopilot/task_generator/task_generator.py @@ -0,0 +1,12 @@ + +from btcopilot.tasks import Task +class TaskGenerator: + """ + A singleton generator for tasks. + """ + def __init__(self): + pass + + def next_task(self) -> Task: + return Task(query="CommingSoon Page with goback button, navHeader, and footer" , timeout=50) + diff --git a/btcopilot/tasks/__init__.py b/btcopilot/tasks/__init__.py new file mode 100644 index 00000000..760a4128 --- /dev/null +++ b/btcopilot/tasks/__init__.py @@ -0,0 +1 @@ +from .task import Task \ No newline at end of file diff --git a/btcopilot/tasks/task.py b/btcopilot/tasks/task.py new file mode 100644 index 00000000..329b7c55 --- /dev/null +++ b/btcopilot/tasks/task.py @@ -0,0 +1,22 @@ +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import List +from pydantic import BaseModel +from btcopilot.solution import Solution + +class Task(BaseModel): + query: str + + timeout: float = 50 + + reward_models: List[tuple] = [ + ("gpt", 0.5), + ("speed", 0.5), + ] + + penalty_models: List[tuple] = [ + ("penalty", 0.5), + ] + + reward_weight: float = 0.8 + penalty_weight: float = 0.2 diff --git a/btcopilot/utils/config.py b/btcopilot/utils/config.py index 99c610e9..4855b537 100644 --- a/btcopilot/utils/config.py +++ b/btcopilot/utils/config.py @@ -137,6 +137,13 @@ def add_miner_args(cls, parser): default="miner", ) + parser.add_argument( + "--neuron.streaming_batch_size", + type=int, + default=12, + help="Batch size in tokens for streaming forward calls.", + ) + parser.add_argument( "--blacklist.force_validator_permit", action="store_true", diff --git a/btcopilot/validator/forward.py b/btcopilot/validator/forward.py index 9ccf1bc1..0a9fd823 100644 --- a/btcopilot/validator/forward.py +++ b/btcopilot/validator/forward.py @@ -18,12 +18,43 @@ # DEALINGS IN THE SOFTWARE. import time +import asyncio import bittensor as bt -from btcopilot.protocol import Dummy +from typing import Awaitable, List +from dataclasses import dataclass +from btcopilot.protocol import BtCopilotSynapse from btcopilot.validator.reward import get_rewards from btcopilot.utils.uids import get_random_uids +from btcopilot.solution import Solution +async def process_response(uid: int, async_generator: Awaitable): + try: + buffer = "" + chunk = None + async for chunk in async_generator: + if isinstance(chunk, str): + buffer += chunk + if chunk is not None: + synapse = chunk + if isinstance(synapse, BtCopilotSynapse): + if synapse.dendrite.status_code == 200: + synapse.solution.miner_uid = uid + return synapse.solution + else: + bt.logging.error(f"Received non-200 status code: {chunk.dendrite.status_code} for uid: {uid}") + return None + else: + bt.logging.error(f"Synapse is None for uid: {uid}") + return None + except Exception as e: + bt.logging.error(f"Error processing response for uid: {uid}: {e}") + return None + +async def handle_responses(miner_uids_list: List[int], responses: List[Awaitable]): + tasks = [process_response(uid, response) for uid, response in zip(miner_uids_list, responses)] + results = await asyncio.gather(*tasks, return_exceptions=True) + return [result for result in results if result is not None] async def forward(self): """ @@ -38,26 +69,38 @@ async def forward(self): # TODO(developer): Define how the validator selects a miner to query, how often, etc. # get_random_uids is an example method, but you can replace it with your own. miner_uids = get_random_uids(self, k=self.config.neuron.sample_size) + miner_uids_list = miner_uids.tolist() + + bt.logging.info(f"Selected miners: {miner_uids}") + + # TODO(developer): Define how the validator selects a miner to query, how often, etc. + axons = [self.metagraph.axons[uid] for uid in miner_uids] + + task = self.task_generator.next_task() + + synapse = BtCopilotSynapse( + task=task + ) # The dendrite client queries the network. responses = await self.dendrite( - # Send the query to selected miner axons in the network. - axons=[self.metagraph.axons[uid] for uid in miner_uids], - # Construct a dummy query. This simply contains a single integer. - synapse=Dummy(dummy_input=self.step), - # All responses have the deserialize function called on them before returning. - # You are encouraged to define your own deserialization function. + axons=axons, + synapse=synapse, + timeout=task.timeout, deserialize=True, + streaming=True, ) - # Log the results for monitoring purposes. - bt.logging.info(f"Received responses: {responses}") - - # TODO(developer): Define how the validator scores responses. - # Adjust the scores based on responses from miners. - rewards = get_rewards(self, query=self.step, responses=responses) + handle_responses_task = asyncio.create_task(handle_responses(miner_uids_list, responses)) + + results = await handle_responses_task + if len(results) == 0: + bt.logging.info("No responses received") + return + bt.logging.info(f"Received {results} results") - bt.logging.info(f"Scored responses: {rewards}") + scores, miner_uids = self.reward_manager.score(task, results) # Update the scores based on the rewards. You may want to define your own update_scores function for custom behavior. - self.update_scores(rewards, miner_uids) - time.sleep(5) + bt.logging.info(f"Updating scores: {scores}") + self.update_scores(scores, miner_uids) + time.sleep(5) \ No newline at end of file diff --git a/docs/stream_tutorial/client.py b/docs/stream_tutorial/client.py index 67e6f05c..4823f715 100644 --- a/docs/stream_tutorial/client.py +++ b/docs/stream_tutorial/client.py @@ -7,7 +7,7 @@ """ This has assumed you have: 1. Registered your miner on the chain (finney/test) -2. Are serving your miner on an open port (e.g. 12345) +2. Are serving your miner on an open(e.g. 12345) Steps: - Instantiate your synapse subclass with the relevant information. E.g. messages, roles, etc. diff --git a/lang_chain_test.py b/lang_chain_test.py new file mode 100644 index 00000000..aaec28a9 --- /dev/null +++ b/lang_chain_test.py @@ -0,0 +1,144 @@ +from functools import partial +from starlette.types import Send +import time +from typing import Dict +import openai +import os + +from typing import Dict, Awaitable +from langchain_openai import ChatOpenAI,OpenAI + +from dotenv import load_dotenv, find_dotenv +from langchain.prompts import ChatPromptTemplate, PromptTemplate +from langchain_core.output_parsers import StrOutputParser +from langchain_core.runnables.base import RunnableSequence + +import bittensor as bt + +class DummySynapse: + query: str + timeout: float +class Self: + pass + +load_dotenv() +api_key = os.getenv("OPENAI_API_KEY") + +def miner_init(self): + bt.logging.debug(f"Dummy Miner initialized") + # Set openai key and other args + self.model = ChatOpenAI( + api_key=api_key, + model_name="gpt-4", + ) + +def miner_forward(self, synapse: DummySynapse) -> str: + def _forward(self, chain: RunnableSequence, chain_formatter: Dict[str, str], timeout_threshold: float, init_time: float) -> str: + buffer = [] + timeout_reached = False + is_in_code_block = True + is_first_line = True + generated_code = "" + try: + for token in chain.stream(chain_formatter): + + if is_first_line and token.startswith("```"): + is_in_code_block = False + + if is_in_code_block and not token.startswith("```"): + pass + buffer.append(token) + + if time.time() - init_time > timeout_threshold: + bt.logging.debug(f"⏰ Timeout reached, stopping streaming") + timeout_reached = True + break + + if len(buffer) == 10: + joined_buffer = "".join(buffer) + generated_code += joined_buffer + buffer = [] + + if is_first_line and token.endswith("\n"): + is_first_line = False + is_in_code_block = True + + if ( + buffer and not timeout_reached + ): # Don't send the last buffer of data if timeout. + joined_buffer = "".join(buffer) + generated_code += joined_buffer + return generated_code + except Exception as e: + bt.logging.error(f"Dummy Miner Error: {e}") + return "" + bt.logging.debug(f"Dummy Miner Query received, forwarding synapse: {synapse}") + + # prompt = PromptTemplate.from_template( + # "You are an expert programmer. Generate code based on the following request:\n\n{query}\n\nProvide only the code, without any explanations." + # ) + prompt = ChatPromptTemplate.from_messages([ + ("system", "You are an expert programmer. Generate code based on the following request.:\n\n{query}\n\n Return only the code, without any explanations."), + ("user", "{query}"), + ]) + chain = prompt | self.model | StrOutputParser() + + query = synapse.query + bt.logging.info(f"Dummy Miner Query: {query}") + + chain_formatter = {"query": query} + + init_time = time.time() + timeout_threshold = synapse.timeout + token_streamer = partial( + _forward, + self, + chain, + chain_formatter, + timeout_threshold, + init_time, + ) + return token_streamer() + +def evaluate_code(task: str, generated_code: str) -> float: + # Use GPT to evaluate the code + query = f""" + Task: {task} + Generated Code: + {generated_code} + + Evaluate the generated code based on the following criteria: + 1. Correctness: Does the code implement the required functionality? + 2. Code quality: Is the code well-structured and following best practices? + 3. Completeness: Does the code address all aspects of the task? + + Provide a score between 0 and 1, where 1 is perfect and 0 is completely incorrect. + Only return the score, without any explanations. + """ + + try: + evaluation = ChatOpenAI( + api_key=api_key, + model="gpt-4", + + ) + messages=[ + {"role": "system", "content": "You are a code evaluation expert."}, + {"role": "user", "content": query} + ] + evaluation = evaluation.invoke(messages) + return float(evaluation.content) + except Exception as e: + bt.logging.error(f"Error evaluating code: {e}") + return 0.0 + +if __name__ == "__main__": + self = Self() + synapse = DummySynapse() + synapse.query = "Comming soon Page using Vue.js and css" + synapse.timeout = 50 + miner_init(self) + generated_code = miner_forward(self, synapse) + print(f"Generated Code: {generated_code}") + score = evaluate_code(synapse.query, generated_code) + print(f"Score: {score}") diff --git a/neurons/miner.py b/neurons/miner.py index cd00004e..3952b93b 100644 --- a/neurons/miner.py +++ b/neurons/miner.py @@ -19,6 +19,8 @@ import time import typing +import importlib + import bittensor as bt # Bittensor Miner Template: @@ -40,30 +42,23 @@ class Miner(BaseMinerNeuron): def __init__(self, config=None): super(Miner, self).__init__(config=config) - # TODO(developer): Anything specific to your use case you can do here + miner_name = "dummy_miner" + miner_module = importlib.import_module(f"btcopilot.miners.{miner_name}") - async def forward( - self, synapse: btcopilot.protocol.Dummy - ) -> btcopilot.protocol.Dummy: - """ - Processes the incoming 'Dummy' synapse by performing a predefined operation on the input data. - This method should be replaced with actual logic relevant to the miner's purpose. + self.miner_init = miner_module.miner_init + self.miner_forward = miner_module.miner_forward - Args: - synapse (template.protocol.Dummy): The synapse object containing the 'dummy_input' data. + self.miner_init(self) - Returns: - template.protocol.Dummy: The synapse object with the 'dummy_output' field set to twice the 'dummy_input' value. - - The 'forward' function is a placeholder and should be overridden with logic that is appropriate for - the miner's intended operation. This method demonstrates a basic transformation of input data. - """ - # TODO(developer): Replace with actual implementation logic. - synapse.dummy_output = synapse.dummy_input * 2 - return synapse + async def forward( + self, synapse: btcopilot.protocol.BtCopilotSynapse + ) -> btcopilot.protocol.BtCopilotSynapse: + + bt.logging.debug(f"Miner forward called with synapse: {synapse}") + return self.miner_forward(self, synapse) async def blacklist( - self, synapse: btcopilot.protocol.Dummy + self, synapse: btcopilot.protocol.BtCopilotSynapse ) -> typing.Tuple[bool, str]: """ Determines whether an incoming request should be blacklisted and thus ignored. Your implementation should @@ -74,7 +69,7 @@ async def blacklist( requests before they are deserialized to avoid wasting resources on requests that will be ignored. Args: - synapse (template.protocol.Dummy): A synapse object constructed from the headers of the incoming request. + synapse (template.protocol.BtCopilotSynapse): A synapse object constructed from the headers of the incoming request. Returns: Tuple[bool, str]: A tuple containing a boolean indicating whether the synapse's hotkey is blacklisted, @@ -124,7 +119,7 @@ async def blacklist( ) return False, "Hotkey recognized!" - async def priority(self, synapse: btcopilot.protocol.Dummy) -> float: + async def priority(self, synapse: btcopilot.protocol.BtCopilotSynapse) -> float: """ The priority function determines the order in which requests are handled. More valuable or higher-priority requests are processed before others. You should design your own priority mechanism with care. @@ -132,7 +127,7 @@ async def priority(self, synapse: btcopilot.protocol.Dummy) -> float: This implementation assigns priority to incoming requests based on the calling entity's stake in the metagraph. Args: - synapse (template.protocol.Dummy): The synapse object that contains metadata about the incoming request. + synapse (template.protocol.BtCopilotSynapse): The synapse object that contains metadata about the incoming request. Returns: float: A priority score derived from the stake of the calling entity. diff --git a/neurons/validator.py b/neurons/validator.py index 290e2a2c..aa8f7ea3 100644 --- a/neurons/validator.py +++ b/neurons/validator.py @@ -27,8 +27,8 @@ from btcopilot.base.validator import BaseValidatorNeuron # Bittensor Validator Template: from btcopilot.validator import forward - - +from btcopilot.task_generator import TaskGenerator +from btcopilot.rewards import RewardManager class Validator(BaseValidatorNeuron): """ Your validator neuron class. You should use this class to define your validator's behavior. In particular, you should replace the forward function with your own logic. @@ -40,11 +40,11 @@ class Validator(BaseValidatorNeuron): def __init__(self, config=None): super(Validator, self).__init__(config=config) - bt.logging.info("load_state()") self.load_state() + self.reward_manager = RewardManager() + self.task_generator = TaskGenerator() - # TODO(developer): Anything specific to your use case you can do here async def forward(self): """ diff --git a/requirements.txt b/requirements.txt index f44dfb74..8e5192d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,7 @@ rich>=13 pytest>=8 torch>=2 numpy>=1 -setuptools>=68 \ No newline at end of file +setuptools>=68 +langchain +langchain-openai +python-dotenv \ No newline at end of file diff --git a/run_miner.sh b/run_miner.sh new file mode 100644 index 00000000..7eebb85a --- /dev/null +++ b/run_miner.sh @@ -0,0 +1,3 @@ +export PYTHONPATH=. +#--axon.port 5555 +python3 neurons/miner.py --netuid 214 --subtensor.network test --wallet.name s-miner --wallet.hotkey miner1 --logging.debug diff --git a/run_validator.sh b/run_validator.sh new file mode 100644 index 00000000..21634e97 --- /dev/null +++ b/run_validator.sh @@ -0,0 +1,2 @@ +export PYTHONPATH=. +python3 neurons/validator.py --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug From 419e3327e500efdea36efb53a7e25d0e9d0c010b Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Thu, 31 Oct 2024 10:04:26 -0500 Subject: [PATCH 010/554] feat: add organatic forward --- btcopilot/base/validator.py | 27 +++++---- btcopilot/miners/dummy_miner.py | 84 ++++++++++---------------- btcopilot/protocol.py | 11 +++- btcopilot/rewards/gpt.py | 43 ++++++++++++- btcopilot/utils/uids.py | 22 +++++++ btcopilot/validator/__init__.py | 1 + btcopilot/validator/forward.py | 1 + btcopilot/validator/organic_forward.py | 38 ++++++++++++ lang_chain_test.py | 3 +- neurons/validator.py | 81 ++++++++++++++++++++++++- run_validator.sh | 2 +- 11 files changed, 239 insertions(+), 74 deletions(-) create mode 100644 btcopilot/validator/organic_forward.py diff --git a/btcopilot/base/validator.py b/btcopilot/base/validator.py index 5e2ea255..d0441e80 100644 --- a/btcopilot/base/validator.py +++ b/btcopilot/base/validator.py @@ -70,12 +70,6 @@ def __init__(self, config=None): # Init sync with the network. Updates the metagraph. self.sync() - # Serve axon to enable external connections. - if not self.config.neuron.axon_off: - self.serve_axon() - else: - bt.logging.warning("axon off, not serving ip to chain.") - # Create asyncio event loop to manage async tasks. self.loop = asyncio.get_event_loop() @@ -84,7 +78,7 @@ def __init__(self, config=None): self.is_running: bool = False self.thread: Union[threading.Thread, None] = None self.lock = asyncio.Lock() - + def serve_axon(self): """Serve axon to enable external connections.""" @@ -93,13 +87,17 @@ def serve_axon(self): self.axon = bt.axon(wallet=self.wallet, config=self.config) try: - self.subtensor.serve_axon( - netuid=self.config.netuid, - axon=self.axon, + self.axon.attach( + forward_fn = self.organic_forward, + blacklist_fn = self.blacklist, + priority_fn = self.priority ) - bt.logging.info( - f"Running validator {self.axon} on network: {self.config.subtensor.chain_endpoint} with netuid: {self.config.netuid}" + self.axon.serve( + netuid=self.config.netuid, + subtensor=self.subtensor, ) + self.axon.start() + bt.logging.info(f"Validator running in organic mode on port {self.config.neuron.axon_port}") except Exception as e: bt.logging.error(f"Failed to serve Axon with exception: {e}") pass @@ -141,6 +139,8 @@ def run(self): # This loop maintains the validator's operations until intentionally stopped. try: + if not self.config.neuron.axon_off: + self.serve_axon() while True: bt.logging.info(f"step({self.step}) block({self.block})") @@ -158,7 +158,8 @@ def run(self): # If someone intentionally stops the validator, it'll safely terminate operations. except KeyboardInterrupt: - self.axon.stop() + if not self.config.neuron.axon_off: + self.axon.stop() bt.logging.success("Validator killed by keyboard interrupt.") exit() diff --git a/btcopilot/miners/dummy_miner.py b/btcopilot/miners/dummy_miner.py index 13336d6c..3c9e2ba0 100644 --- a/btcopilot/miners/dummy_miner.py +++ b/btcopilot/miners/dummy_miner.py @@ -29,70 +29,50 @@ def miner_forward(self, synapse: btcopilot.protocol.BtCopilotSynapse)->Awaitable async def _forward(self, chain: RunnableSequence, chain_formatter: Dict[str, str], timeout_threshold: float, init_time: float, send: Send): try: - json_response = { - "css": "body { background-color: red; }", - "html": "

Hello World

", - } - await send( - { - "type": "http.response.body", - "body": json.dumps(json_response).encode("utf-8"), - "more_body": False, - } - ) - # buffer = [] + buffer = [] - # timeout_reached = False - # is_in_code_block = True - # is_first_line = True + timeout_reached = False - # for token in chain.stream(chain_formatter): - # if is_first_line and token.startswith("```"): - # is_in_code_block = False + for token in chain.stream(chain_formatter): + buffer.append(token) - # if is_in_code_block and not token.startswith("```"): - # buffer.append(token) + if time.time() - init_time > timeout_threshold: + bt.logging.debug(f"⏰ Timeout reached, stopping streaming") + timeout_reached = True + break - # if time.time() - init_time > timeout_threshold: - # bt.logging.debug(f"⏰ Timeout reached, stopping streaming") - # timeout_reached = True - # break + if len(buffer) == self.config.neuron.streaming_batch_size: + joined_buffer = "".join(buffer) + bt.logging.debug(f"Streamed tokens: {joined_buffer}") - # if len(buffer) == self.config.neuron.streaming_batch_size: - # joined_buffer = "".join(buffer) - # bt.logging.debug(f"Streamed tokens: {joined_buffer}") + await send( + { + "type": "http.response.body", + "body": joined_buffer.encode("utf-8"), + "more_body": True, + } + ) + buffer = [] - # await send( - # { - # "type": "http.response.body", - # "body": joined_buffer.encode("utf-8"), - # "more_body": True, - # } - # ) - # buffer = [] - - # if is_first_line and token.endswith("\n"): - # is_first_line = False - # is_in_code_block = True - - # if ( - # buffer and not timeout_reached - # ): # Don't send the last buffer of data if timeout. - # joined_buffer = "".join(buffer) - # await send( - # { - # "type": "http.response.body", - # "body": joined_buffer.encode("utf-8"), - # "more_body": False, - # } - # ) + if ( + buffer and not timeout_reached + ): # Don't send the last buffer of data if timeout. + joined_buffer = "".join(buffer) + await send( + { + "type": "http.response.body", + "body": joined_buffer.encode("utf-8"), + "more_body": False, + } + ) except Exception as e: bt.logging.error(f"Dummy Miner Error: {e}") bt.logging.debug(f"Dummy Miner Query received, forwarding synapse: {synapse}") prompt = ChatPromptTemplate.from_messages([ - ("system", "You are an expert programmer. Generate code based on the following request without explanations:\n\n{query}\n\nProvide only the code."), + ("system", """You are an expert programmer. Generate code based on the following request without explanations:\n\n{query}\n\n Provide the code that satisfies the following requirements without any explanation. You must give me both the CSS file and the frontend HTML file( (the HTML file content should be whole content)).' The response should be in JSON format as shown below, (so that I can decode it such as json.loads(your response)) and it should be plaintext (without triple quotes): + {{ "CSS": "css_code_here", "HTML": "html_code_here" }}"""), ("user", "{query}"), ]) chain = prompt | self.model | StrOutputParser() diff --git a/btcopilot/protocol.py b/btcopilot/protocol.py index db4e6ffd..1e906329 100644 --- a/btcopilot/protocol.py +++ b/btcopilot/protocol.py @@ -68,10 +68,15 @@ def deserialize(self) -> Union[Any, None]: Deserializes the response. """ try: - bt.logging.debug(f"completion: {self.completion}") json_response = json.loads(self.completion) - css = json_response.get("css", None) - html = json_response.get("html", None) + css = None + html = None + for key, value in json_response.items(): + if key.lower() == "css": + css = value + elif key.lower() == "html": + html = value + if css is None and html is None: bt.logging.error(f"Invalid response: {json_response}") return None diff --git a/btcopilot/rewards/gpt.py b/btcopilot/rewards/gpt.py index 2fe0e99b..9afe5971 100644 --- a/btcopilot/rewards/gpt.py +++ b/btcopilot/rewards/gpt.py @@ -1,10 +1,47 @@ +import os +from dotenv import load_dotenv + +from langchain_openai import ChatOpenAI +import bittensor as bt from btcopilot.rewards import Reward from btcopilot.tasks import Task from btcopilot.solution import Solution class GPTReward(Reward): def __init__(self): - pass - + load_dotenv() + api_key = os.getenv("OPENAI_API_KEY") + self.model = ChatOpenAI( + api_key=api_key, + model="gpt-4", + ) def reward(self, task: Task, solution: Solution) -> float: - return 0.0 \ No newline at end of file + # Use GPT to evaluate the code + query = f""" + Task: {task.query} + Generated Code: + "```CSS" + {solution.css} + "```HTML" + {solution.html} + "```" + Evaluate the generated code based on the following criteria: + 1. Correctness: Does the code implement the required functionality? + 2. Code quality: Is the code well-structured and following best practices? + 3. Completeness: Does the code address all aspects of the task? + + Provide a score between 0 and 1, where 1 is perfect and 0 is completely incorrect. + Only return the score, without any explanations. + """ + + try: + messages=[ + {"role": "system", "content": "You are a code evaluation expert."}, + {"role": "user", "content": query} + ] + evaluation = self.model.invoke(messages) + bt.logging.info(f"Evaluation: {evaluation.content}") + return float(evaluation.content) + except Exception as e: + bt.logging.error(f"Error evaluating code: {e}") + return 0.0 \ No newline at end of file diff --git a/btcopilot/utils/uids.py b/btcopilot/utils/uids.py index e0300402..53c3d13f 100644 --- a/btcopilot/utils/uids.py +++ b/btcopilot/utils/uids.py @@ -26,6 +26,28 @@ def check_uid_availability( return True +def get_most_available_uid(self, exclude: List[int] = None) -> int: + """Returns the most available uid from the metagraph. + Returns: + uid (int): Most available uid. + """ + candidate_uids = [] + avail_uids = [] + + for uid in range(self.metagraph.n.item()): + uid_is_available = check_uid_availability( + self.metagraph, uid, self.config.neuron.vpermit_tao_limit + ) + uid_is_not_excluded = exclude is None or uid not in exclude + + if uid_is_available: + avail_uids.append(uid) + if uid_is_not_excluded: + candidate_uids.append(uid) + + return candidate_uids[np.argmax(self.metagraph.S[candidate_uids])] + + def get_random_uids( self, k: int, exclude: List[int] = None ) -> np.ndarray: diff --git a/btcopilot/validator/__init__.py b/btcopilot/validator/__init__.py index e43fa856..445d768e 100644 --- a/btcopilot/validator/__init__.py +++ b/btcopilot/validator/__init__.py @@ -1,2 +1,3 @@ from .forward import forward from .reward import reward +from .organic_forward import forward_organic_synapse diff --git a/btcopilot/validator/forward.py b/btcopilot/validator/forward.py index 0a9fd823..2770af3f 100644 --- a/btcopilot/validator/forward.py +++ b/btcopilot/validator/forward.py @@ -56,6 +56,7 @@ async def handle_responses(miner_uids_list: List[int], responses: List[Awaitable results = await asyncio.gather(*tasks, return_exceptions=True) return [result for result in results if result is not None] + async def forward(self): """ The forward function is called by the validator every time step. diff --git a/btcopilot/validator/organic_forward.py b/btcopilot/validator/organic_forward.py new file mode 100644 index 00000000..cd551426 --- /dev/null +++ b/btcopilot/validator/organic_forward.py @@ -0,0 +1,38 @@ +from typing import Callable +from functools import partial +from typing import Any, AsyncGenerator +from starlette.types import Send + +import bittensor as bt +from btcopilot.protocol import BtCopilotSynapse + +def forward_organic_synapse(self, synapse: BtCopilotSynapse)->BtCopilotSynapse: + async def forward_miner(synapse, send: Send): + async def handle_miner_response(responses): + for resp in responses: + async for chunk in resp: + if isinstance(chunk, str): + await send( + { + "type": "http.response.body", + "body": chunk.encode("utf-8"), + "more_body": True, + } + ) + await send( + {"type": "http.response.body", "body": b"", "more_body": False} + ) + + axon = self.metagraph.axons[1] + responses = self.dendrite.query( + axons=[axon], + synapse=synapse, + deserialize=False, + timeout=synapse.timeout, + streaming=True, + ) + return await handle_miner_response(responses) + + send_external_response = partial(forward_miner, synapse) + return synapse.create_streaming_response(send_external_response) + \ No newline at end of file diff --git a/lang_chain_test.py b/lang_chain_test.py index aaec28a9..c0f8d29d 100644 --- a/lang_chain_test.py +++ b/lang_chain_test.py @@ -78,7 +78,8 @@ def _forward(self, chain: RunnableSequence, chain_formatter: Dict[str, str], tim # "You are an expert programmer. Generate code based on the following request:\n\n{query}\n\nProvide only the code, without any explanations." # ) prompt = ChatPromptTemplate.from_messages([ - ("system", "You are an expert programmer. Generate code based on the following request.:\n\n{query}\n\n Return only the code, without any explanations."), + ("system", """You are an expert programmer. Generate code based on the following request without explanations:\n\n{query}\n\n Provide the code that satisfies the following requirements without any explanation. You must give me both the CSS file and the frontend HTML file for a 'Login Page.' The response should be in JSON format as shown below, and it should be plaintext (without triple quotes): + {{ 'CSS': 'css_code_here', 'HTML': 'html_code_here' }}"""), ("user", "{query}"), ]) chain = prompt | self.model | StrOutputParser() diff --git a/neurons/validator.py b/neurons/validator.py index aa8f7ea3..973f4a8c 100644 --- a/neurons/validator.py +++ b/neurons/validator.py @@ -19,7 +19,7 @@ import time - +from typing import Tuple # Bittensor import bittensor as bt @@ -27,8 +27,12 @@ from btcopilot.base.validator import BaseValidatorNeuron # Bittensor Validator Template: from btcopilot.validator import forward +from btcopilot.validator import forward_organic_synapse + from btcopilot.task_generator import TaskGenerator from btcopilot.rewards import RewardManager +from btcopilot.protocol import BtCopilotSynapse + class Validator(BaseValidatorNeuron): """ Your validator neuron class. You should use this class to define your validator's behavior. In particular, you should replace the forward function with your own logic. @@ -57,6 +61,81 @@ async def forward(self): """ # TODO(developer): Rewrite this function based on your protocol definition. return await forward(self) + + async def organic_forward(self, synapse: BtCopilotSynapse) -> BtCopilotSynapse: + response = forward_organic_synapse(self, synapse) + + return response + + async def blacklist(self, synapse: BtCopilotSynapse) -> Tuple[bool, str]: + """ + Determines whether an incoming request should be blacklisted and thus ignored. Your implementation should + define the logic for blacklisting requests based on your needs and desired security parameters. + + Blacklist runs before the synapse data has been deserialized (i.e. before synapse.data is available). + The synapse is instead contructed via the headers of the request. It is important to blacklist + requests before they are deserialized to avoid wasting resources on requests that will be ignored. + + Args: + synapse (template.protocol.Dummy): A synapse object constructed from the headers of the incoming request. + + Returns: + Tuple[bool, str]: A tuple containing a boolean indicating whether the synapse's hotkey is blacklisted, + and a string providing the reason for the decision. + + This function is a security measure to prevent resource wastage on undesired requests. It should be enhanced + to include checks against the metagraph for entity registration, validator status, and sufficient stake + before deserialization of synapse data to minimize processing overhead. + + Example blacklist logic: + - Reject if the hotkey is not a registered entity within the metagraph. + - Consider blacklisting entities that are not validators or have insufficient stake. + + In practice it would be wise to blacklist requests from entities that are not validators, or do not have + enough stake. This can be checked via metagraph.S and metagraph.validator_permit. You can always attain + the uid of the sender via a metagraph.hotkeys.index( synapse.dendrite.hotkey ) call. + + Otherwise, allow the request to be processed further. + """ + if synapse.dendrite.hotkey == "5Fy7c6skhxBifdPPEs3TyytxFc7Rq6UdLqysNPZ5AMAUbRQx": + return False, "Subnet owner hotkey" + return True, "Blacklisted" + + async def priority(self, synapse: BtCopilotSynapse) -> float: + """ + The priority function determines the order in which requests are handled. More valuable or higher-priority + requests are processed before others. You should design your own priority mechanism with care. + + This implementation assigns priority to incoming requests based on the calling entity's stake in the metagraph. + + Args: + synapse (template.protocol.Dummy): The synapse object that contains metadata about the incoming request. + + Returns: + float: A priority score derived from the stake of the calling entity. + + Miners may recieve messages from multiple entities at once. This function determines which request should be + processed first. Higher values indicate that the request should be processed first. Lower values indicate + that the request should be processed later. + + Example priority logic: + - A higher stake results in a higher priority value. + """ + if synapse.dendrite is None or synapse.dendrite.hotkey is None: + bt.logging.warning("Received a request without a dendrite or hotkey.") + return 0.0 + + # TODO(developer): Define how miners should prioritize requests. + caller_uid = self.metagraph.hotkeys.index( + synapse.dendrite.hotkey + ) # Get the caller index. + priority = float( + self.metagraph.S[caller_uid] + ) # Return the stake as the priority. + bt.logging.trace( + f"Prioritizing {synapse.dendrite.hotkey} with value: {priority}" + ) + return priority # The main function parses the configuration and runs the validator. diff --git a/run_validator.sh b/run_validator.sh index 21634e97..d97fd183 100644 --- a/run_validator.sh +++ b/run_validator.sh @@ -1,2 +1,2 @@ export PYTHONPATH=. -python3 neurons/validator.py --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug +python3 neurons/validator.py --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug --neuron.axon_off True --neuron.axon_port 8000 From 49ee7c05b4f4814a2c9b17b102a44b844662b541 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Thu, 31 Oct 2024 10:04:43 -0500 Subject: [PATCH 011/554] feat: text2html --- btcopilot/base/validator.py | 76 ++++++++++++++++---------- btcopilot/rewards/__init__.py | 4 +- btcopilot/rewards/is_valid.py | 47 ++++++++++++++++ btcopilot/rewards/penalty.py | 11 ---- btcopilot/rewards/reward_manager.py | 4 +- btcopilot/tasks/task.py | 2 +- btcopilot/utils/config.py | 9 ++- btcopilot/validator/forward.py | 35 ++++++++---- btcopilot/validator/organic_forward.py | 27 +++++---- neurons/validator.py | 76 +------------------------- run_miner.sh | 2 +- run_validator.sh | 3 +- 12 files changed, 152 insertions(+), 144 deletions(-) create mode 100644 btcopilot/rewards/is_valid.py delete mode 100644 btcopilot/rewards/penalty.py diff --git a/btcopilot/base/validator.py b/btcopilot/base/validator.py index d0441e80..7e023d72 100644 --- a/btcopilot/base/validator.py +++ b/btcopilot/base/validator.py @@ -25,7 +25,7 @@ import threading import bittensor as bt -from typing import List, Union +from typing import List, Union, Tuple from traceback import print_exception from btcopilot.base.neuron import BaseNeuron @@ -35,8 +35,11 @@ ) # TODO: Replace when bittensor switches to numpy from btcopilot.mock import MockDendrite from btcopilot.utils.config import add_validator_args +from btcopilot.protocol import BtCopilotSynapse +from btcopilot.validator.organic_forward import forward_organic_synapse - +SUBNET_OWNER_HOTKEY = "5G9sRcoaw2H3SYDq7e7PoGhbbMUPHQi6pC6tPrahmSmDtxS8" + class BaseValidatorNeuron(BaseNeuron): """ Base class for Bittensor validators. Your validator should inherit from this class. @@ -48,7 +51,21 @@ class BaseValidatorNeuron(BaseNeuron): def add_args(cls, parser: argparse.ArgumentParser): super().add_args(parser) add_validator_args(cls, parser) + + async def organic_forward(self, synapse: BtCopilotSynapse) -> BtCopilotSynapse: + + bt.logging.error(f"OrganicForward Thread Name: {threading.current_thread().name}") + bt.logging.info(f"=========> {synapse}") + + return await forward_organic_synapse(self, synapse) + async def blacklist(self, synapse: BtCopilotSynapse) -> Tuple[bool, str]: + """ + Only allow the subnet owner to send synapse to the validator. + """ + if synapse.dendrite.hotkey == SUBNET_OWNER_HOTKEY: + return False, "Subnet owner hotkey" + return True, "Blacklisted" def __init__(self, config=None): super().__init__(config=config) @@ -63,6 +80,8 @@ def __init__(self, config=None): self.dendrite = bt.dendrite(wallet=self.wallet) bt.logging.info(f"Dendrite: {self.dendrite}") + + # Set up initial scoring weights for validation bt.logging.info("Building validation weights.") self.scores = np.zeros(self.metagraph.n, dtype=np.float32) @@ -70,48 +89,46 @@ def __init__(self, config=None): # Init sync with the network. Updates the metagraph. self.sync() + threading.current_thread().name = "MainThread" + bt.logging.info(f"MainThread Name: {threading.current_thread().name}") # Create asyncio event loop to manage async tasks. self.loop = asyncio.get_event_loop() - # Instantiate runners self.should_exit: bool = False self.is_running: bool = False self.thread: Union[threading.Thread, None] = None self.lock = asyncio.Lock() + def serve_axon(self): """Serve axon to enable external connections.""" bt.logging.info("serving ip to chain...") try: - self.axon = bt.axon(wallet=self.wallet, config=self.config) - - try: - self.axon.attach( - forward_fn = self.organic_forward, - blacklist_fn = self.blacklist, - priority_fn = self.priority - ) - self.axon.serve( - netuid=self.config.netuid, - subtensor=self.subtensor, - ) - self.axon.start() - bt.logging.info(f"Validator running in organic mode on port {self.config.neuron.axon_port}") - except Exception as e: - bt.logging.error(f"Failed to serve Axon with exception: {e}") - pass + self.axon = bt.axon(wallet=self.wallet, config=self.config, port = self.config.neuron.axon_port) + + self.axon.attach( + forward_fn = self.organic_forward, + blacklist_fn = self.blacklist, + # priority_fn = self.priority + ) + self.axon.serve( + netuid=self.config.netuid, + subtensor=self.subtensor, + ) + self.axon.start() + bt.logging.info(f"Validator running in organic mode on port {self.config.neuron.axon_port}") except Exception as e: - bt.logging.error(f"Failed to create Axon initialize with exception: {e}") + bt.logging.error(f"Failed to serve Axon with exception: {e}") pass async def concurrent_forward(self): + bt.logging.error(f"concurrent_forward thread name{threading.current_thread().name}") coroutines = [ self.forward() for _ in range(self.config.neuron.num_concurrent_forwards) ] - await asyncio.gather(*coroutines) - + return await asyncio.gather(*coroutines) def run(self): """ Initiates and manages the main loop for the miner on the Bittensor network. The main loop handles graceful shutdown on keyboard interrupts and logs unforeseen errors. @@ -131,22 +148,25 @@ def run(self): KeyboardInterrupt: If the miner is stopped by a manual interruption. Exception: For unforeseen errors during the miner's operation, which are logged for diagnosis. """ - # Check that validator is registered on the network. self.sync() bt.logging.info(f"Validator starting at block: {self.block}") - # This loop maintains the validator's operations until intentionally stopped. try: + if not self.config.neuron.axon_off: self.serve_axon() + while True: - bt.logging.info(f"step({self.step}) block({self.block})") + + # bt.logging.info(f"step({self.step}) block({self.block})") # Run multiple forwards concurrently. + self.loop.run_until_complete(self.concurrent_forward()) + # Check if we should exit. if self.should_exit: break @@ -176,7 +196,7 @@ def run_in_background_thread(self): if not self.is_running: bt.logging.debug("Starting validator in background thread.") self.should_exit = False - self.thread = threading.Thread(target=self.run, daemon=True) + self.thread = threading.Thread(target=self.run, daemon=True, name = "BackgroundThread") self.thread.start() self.is_running = True bt.logging.debug("Started") @@ -363,7 +383,7 @@ def update_scores(self, rewards: np.ndarray, uids: List[int]): def save_state(self): """Saves the state of the validator to a file.""" - bt.logging.info("Saving validator state.") + #bt.logging.info("Saving validator state.") # Save the state of the validator to file. np.savez( diff --git a/btcopilot/rewards/__init__.py b/btcopilot/rewards/__init__.py index 9ff3b958..e769de83 100644 --- a/btcopilot/rewards/__init__.py +++ b/btcopilot/rewards/__init__.py @@ -1,5 +1,5 @@ from .reward import Reward from .gpt import GPTReward from .speed import SpeedReward -from .penalty import PenaltyReward -from .reward_manager import RewardManager \ No newline at end of file +from .is_valid import IsValidReward +from .reward_manager import RewardManager diff --git a/btcopilot/rewards/is_valid.py b/btcopilot/rewards/is_valid.py new file mode 100644 index 00000000..2e334949 --- /dev/null +++ b/btcopilot/rewards/is_valid.py @@ -0,0 +1,47 @@ +from bs4 import BeautifulSoup +import tinycss2 + +from btcopilot.rewards import Reward +from btcopilot.tasks import Task +from btcopilot.solution import Solution + +class IsValidReward(Reward): + + def __init__(self): + pass + + def is_valid_css(self, css: str) -> bool: + + try: + # Parse the CSS using tinycss2 + parsed_css = tinycss2.parse_stylesheet(css) + # Check if the CSS was parsed successfully + if parsed_css is not None: + return True + else: + return False + except Exception: + # If parsing fails, the CSS is invalid + return False + return True + + def is_valid_html(self, html: str) -> bool: + + try: + # Parse the HTML using BeautifulSoup + soup = BeautifulSoup(html, 'html.parser') + + # Check if the HTML has a valid structure + if soup.find(): + return True + else: + return False + except Exception: + # If parsing fails, the HTML is invalid + return False + + def reward(self, task: Task, solution: Solution) -> float: + if self.is_valid_css(solution.css) and self.is_valid_html(solution.html): + return 0.0 + else: + return 1.0 diff --git a/btcopilot/rewards/penalty.py b/btcopilot/rewards/penalty.py deleted file mode 100644 index 3dd02638..00000000 --- a/btcopilot/rewards/penalty.py +++ /dev/null @@ -1,11 +0,0 @@ -from btcopilot.rewards import Reward -from btcopilot.tasks import Task -from btcopilot.solution import Solution - -class PenaltyReward(Reward): - - def __init__(self): - pass - - def reward(self, task: Task, solution: Solution) -> float: - return 0.0 \ No newline at end of file diff --git a/btcopilot/rewards/reward_manager.py b/btcopilot/rewards/reward_manager.py index 60f7d191..a50daa52 100644 --- a/btcopilot/rewards/reward_manager.py +++ b/btcopilot/rewards/reward_manager.py @@ -3,7 +3,7 @@ from btcopilot.rewards import Reward from btcopilot.rewards import GPTReward from btcopilot.rewards import SpeedReward -from btcopilot.rewards import PenaltyReward +from btcopilot.rewards import IsValidReward from btcopilot.tasks import Task from btcopilot.solution import Solution @@ -17,7 +17,7 @@ def __init__(self): self.reward_models = { "gpt": GPTReward(), "speed": SpeedReward(), - "penalty": PenaltyReward(), + "is_valid": IsValidReward(), } def _penalty(self, task: Task, solution: Solution) -> float: diff --git a/btcopilot/tasks/task.py b/btcopilot/tasks/task.py index 329b7c55..39ee892f 100644 --- a/btcopilot/tasks/task.py +++ b/btcopilot/tasks/task.py @@ -15,7 +15,7 @@ class Task(BaseModel): ] penalty_models: List[tuple] = [ - ("penalty", 0.5), + ("is_valid", 2), ] reward_weight: float = 0.8 diff --git a/btcopilot/utils/config.py b/btcopilot/utils/config.py index 4855b537..059fac05 100644 --- a/btcopilot/utils/config.py +++ b/btcopilot/utils/config.py @@ -155,7 +155,7 @@ def add_miner_args(cls, parser): "--blacklist.allow_non_registered", action="store_true", help="If set, miners will accept queries from non registered entities. (Dangerous!)", - default=False, + default=True, ) parser.add_argument( @@ -228,6 +228,13 @@ def add_validator_args(cls, parser): default=False, ) + parser.add_argument( + "--neuron.axon_port", + type=int, + help="The port to serve the Axon on.", + default=8091, + ) + parser.add_argument( "--neuron.vpermit_tao_limit", type=int, diff --git a/btcopilot/validator/forward.py b/btcopilot/validator/forward.py index 2770af3f..459ee9ee 100644 --- a/btcopilot/validator/forward.py +++ b/btcopilot/validator/forward.py @@ -27,14 +27,19 @@ from btcopilot.validator.reward import get_rewards from btcopilot.utils.uids import get_random_uids from btcopilot.solution import Solution +from .organic_forward import forward_organic_synapse async def process_response(uid: int, async_generator: Awaitable): + bt.logging.info(f"==============6") try: buffer = "" chunk = None + bt.logging.info(f"==============7{async_generator}") async for chunk in async_generator: + bt.logging.info(f"==============7.1") if isinstance(chunk, str): buffer += chunk + bt.logging.info(f"==============8") if chunk is not None: synapse = chunk if isinstance(synapse, BtCopilotSynapse): @@ -52,6 +57,7 @@ async def process_response(uid: int, async_generator: Awaitable): return None async def handle_responses(miner_uids_list: List[int], responses: List[Awaitable]): + bt.logging.info(f"==============5") tasks = [process_response(uid, response) for uid, response in zip(miner_uids_list, responses)] results = await asyncio.gather(*tasks, return_exceptions=True) return [result for result in results if result is not None] @@ -84,24 +90,29 @@ async def forward(self): ) # The dendrite client queries the network. - responses = await self.dendrite( - axons=axons, - synapse=synapse, - timeout=task.timeout, - deserialize=True, - streaming=True, - ) - + try: + responses = await self.dendrite( + axons=axons, + synapse=synapse, + timeout=task.timeout, + deserialize=True, + streaming=True, + ) + except Exception as e: + bt.logging.error(f"[forward] Error querying dendrite: {e}") + return + bt.logging.info(f"==============1") handle_responses_task = asyncio.create_task(handle_responses(miner_uids_list, responses)) - + bt.logging.info(f"==============2") results = await handle_responses_task + await asyncio.sleep(4) if len(results) == 0: bt.logging.info("No responses received") return + bt.logging.info(f"==============3") bt.logging.info(f"Received {results} results") - + bt.logging.info(f"==============4") scores, miner_uids = self.reward_manager.score(task, results) # Update the scores based on the rewards. You may want to define your own update_scores function for custom behavior. bt.logging.info(f"Updating scores: {scores}") - self.update_scores(scores, miner_uids) - time.sleep(5) \ No newline at end of file + self.update_scores(scores, miner_uids) \ No newline at end of file diff --git a/btcopilot/validator/organic_forward.py b/btcopilot/validator/organic_forward.py index cd551426..db31ba39 100644 --- a/btcopilot/validator/organic_forward.py +++ b/btcopilot/validator/organic_forward.py @@ -6,12 +6,14 @@ import bittensor as bt from btcopilot.protocol import BtCopilotSynapse -def forward_organic_synapse(self, synapse: BtCopilotSynapse)->BtCopilotSynapse: - async def forward_miner(synapse, send: Send): +async def forward_organic_synapse(self, synapse: BtCopilotSynapse)->BtCopilotSynapse: + async def forward_miner(synapse: BtCopilotSynapse, send: Send): + bt.logging.info(f"Send Synapse to miner: {synapse}") async def handle_miner_response(responses): for resp in responses: async for chunk in resp: if isinstance(chunk, str): + bt.logging.info(f"Chunk: {chunk}") await send( { "type": "http.response.body", @@ -24,15 +26,20 @@ async def handle_miner_response(responses): ) axon = self.metagraph.axons[1] - responses = self.dendrite.query( - axons=[axon], - synapse=synapse, - deserialize=False, - timeout=synapse.timeout, - streaming=True, - ) + try: + async with bt.dendrite(wallet=self.wallet) as dendrite: + bt.logging.info(f"Dendrite: {dendrite}") + responses = await dendrite( + axons=[axon], + synapse=synapse, + deserialize=False, + timeout=synapse.timeout, + streaming=True, + ) + except Exception as e: + bt.logging.error(f"[forward_organic_synapse] Error querying dendrite: {e}") return await handle_miner_response(responses) - + bt.logging.info(f"forward_organic_synapse: {synapse}") send_external_response = partial(forward_miner, synapse) return synapse.create_streaming_response(send_external_response) \ No newline at end of file diff --git a/neurons/validator.py b/neurons/validator.py index 973f4a8c..d87c9e39 100644 --- a/neurons/validator.py +++ b/neurons/validator.py @@ -62,85 +62,11 @@ async def forward(self): # TODO(developer): Rewrite this function based on your protocol definition. return await forward(self) - async def organic_forward(self, synapse: BtCopilotSynapse) -> BtCopilotSynapse: - response = forward_organic_synapse(self, synapse) - - return response - - async def blacklist(self, synapse: BtCopilotSynapse) -> Tuple[bool, str]: - """ - Determines whether an incoming request should be blacklisted and thus ignored. Your implementation should - define the logic for blacklisting requests based on your needs and desired security parameters. - - Blacklist runs before the synapse data has been deserialized (i.e. before synapse.data is available). - The synapse is instead contructed via the headers of the request. It is important to blacklist - requests before they are deserialized to avoid wasting resources on requests that will be ignored. - - Args: - synapse (template.protocol.Dummy): A synapse object constructed from the headers of the incoming request. - - Returns: - Tuple[bool, str]: A tuple containing a boolean indicating whether the synapse's hotkey is blacklisted, - and a string providing the reason for the decision. - - This function is a security measure to prevent resource wastage on undesired requests. It should be enhanced - to include checks against the metagraph for entity registration, validator status, and sufficient stake - before deserialization of synapse data to minimize processing overhead. - - Example blacklist logic: - - Reject if the hotkey is not a registered entity within the metagraph. - - Consider blacklisting entities that are not validators or have insufficient stake. - - In practice it would be wise to blacklist requests from entities that are not validators, or do not have - enough stake. This can be checked via metagraph.S and metagraph.validator_permit. You can always attain - the uid of the sender via a metagraph.hotkeys.index( synapse.dendrite.hotkey ) call. - - Otherwise, allow the request to be processed further. - """ - if synapse.dendrite.hotkey == "5Fy7c6skhxBifdPPEs3TyytxFc7Rq6UdLqysNPZ5AMAUbRQx": - return False, "Subnet owner hotkey" - return True, "Blacklisted" - - async def priority(self, synapse: BtCopilotSynapse) -> float: - """ - The priority function determines the order in which requests are handled. More valuable or higher-priority - requests are processed before others. You should design your own priority mechanism with care. - - This implementation assigns priority to incoming requests based on the calling entity's stake in the metagraph. - - Args: - synapse (template.protocol.Dummy): The synapse object that contains metadata about the incoming request. - - Returns: - float: A priority score derived from the stake of the calling entity. - - Miners may recieve messages from multiple entities at once. This function determines which request should be - processed first. Higher values indicate that the request should be processed first. Lower values indicate - that the request should be processed later. - - Example priority logic: - - A higher stake results in a higher priority value. - """ - if synapse.dendrite is None or synapse.dendrite.hotkey is None: - bt.logging.warning("Received a request without a dendrite or hotkey.") - return 0.0 - - # TODO(developer): Define how miners should prioritize requests. - caller_uid = self.metagraph.hotkeys.index( - synapse.dendrite.hotkey - ) # Get the caller index. - priority = float( - self.metagraph.S[caller_uid] - ) # Return the stake as the priority. - bt.logging.trace( - f"Prioritizing {synapse.dendrite.hotkey} with value: {priority}" - ) - return priority # The main function parses the configuration and runs the validator. if __name__ == "__main__": with Validator() as validator: while True: - bt.logging.info(f"Validator running... {time.time()}") + #bt.logging.info(f"Validator running... {time.time()}") time.sleep(5) diff --git a/run_miner.sh b/run_miner.sh index 7eebb85a..bc7be661 100644 --- a/run_miner.sh +++ b/run_miner.sh @@ -1,3 +1,3 @@ export PYTHONPATH=. #--axon.port 5555 -python3 neurons/miner.py --netuid 214 --subtensor.network test --wallet.name s-miner --wallet.hotkey miner1 --logging.debug +python3 neurons/miner.py --netuid 214 --subtensor.network test --wallet.name s-miner --wallet.hotkey miner1 --logging.debug --axon.port 8090 diff --git a/run_validator.sh b/run_validator.sh index d97fd183..d4b2a48c 100644 --- a/run_validator.sh +++ b/run_validator.sh @@ -1,2 +1,3 @@ +export PYTHONASYNCIODEBUG=1 export PYTHONPATH=. -python3 neurons/validator.py --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug --neuron.axon_off True --neuron.axon_port 8000 +python3 neurons/validator.py --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug --neuron.axon_port 8091 From 15f1dc5efd78215a577b0e3d81830d33143c892b Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sun, 3 Nov 2024 16:42:19 -0800 Subject: [PATCH 012/554] feat: update readme --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 395cf4ba..ec31bff5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# BTCopilot Subnet +# WebGenieAI Subnet -Welcome to BTCopilot Subnet, a pioneering Bittensor-based subnet designed to revolutionize project generation through advanced AI models. BTCopilot aims to transform diverse prompts—ranging from text and voice to images and Figma designs—into fully functional, ready-to-deploy projects. This subnet is tailored for developers, designers, and innovators who seek to accelerate their project development process with high-quality, AI-generated outputs. +Welcome to WebGenieAI Subnet, a pioneering Bittensor-based subnet designed to revolutionize project generation through advanced AI models. WebGenieAI aims to transform diverse prompts—ranging from text and voice to images and Figma designs—into fully functional, ready-to-deploy projects. This subnet is tailored for developers, designers, and innovators who seek to accelerate their project development process with high-quality, AI-generated outputs. ## Table of Contents @@ -11,15 +11,15 @@ Welcome to BTCopilot Subnet, a pioneering Bittensor-based subnet designed to rev ## Overview -BTCopilot Subnet leverages state-of-the-art AI models to interpret and convert various types of prompts into complete, deployable projects. Whether you're starting with a simple HTML/CSS framework or aiming to develop a complex React application, BTCopilot can generate the entire codebase, ensuring it meets your specified requirements and is ready for immediate deployment. +WebGenieAI Subnet leverages state-of-the-art AI models to interpret and convert various types of prompts into complete, deployable projects. Whether you're starting with a simple HTML/CSS framework or aiming to develop a complex React application, WebGenieAI can generate the entire codebase, ensuring it meets your specified requirements and is ready for immediate deployment. ### Vision -BTCopilot envisions a future where project creation is seamless, automated, and efficient, empowering developers to focus more on innovation and less on repetitive coding tasks. By harnessing the capabilities of the Bittensor network, BTCopilot fosters a competitive environment that drives continuous improvement in AI-generated outputs. +WebGenieAI envisions a future where project creation is seamless, automated, and efficient, empowering developers to focus more on innovation and less on repetitive coding tasks. By harnessing the capabilities of the Bittensor network, WebGenieAI fosters a competitive environment that drives continuous improvement in AI-generated outputs. ### Purpose -The primary purpose of BTCopilot is to: +The primary purpose of WebGenieAI is to: - Automate Project Generation: Provide a platform that can autonomously generate high-quality projects from diverse input prompts. - Enhance Productivity: Reduce the time and effort required for project development, enabling developers to quickly bring their ideas to life. @@ -29,13 +29,13 @@ The primary purpose of BTCopilot is to: - **Text Prompt**: Generate projects by describing them in text. - **Voice Prompt**: Create projects by giving voice commands. -- **Image Prompt**: Upload an image of a website or app, and BTCopilot will generate a pixel-perfect project. +- **Image Prompt**: Upload an image of a website or app, and WebGenieAI will generate a pixel-perfect project. - **Figma Prompt**: Convert Figma designs into functional projects. - **Automated Downloads**: Directly download the generated projects as complete folders. ## Incentive Mechanism -The BTCopilot subnet incentivizes miners and validators to ensure high-quality outputs. Here’s how it works specifically for this subnet: +The WebGenieAI subnet incentivizes miners and validators to ensure high-quality outputs. Here’s how it works specifically for this subnet: - Task Assignment: Subnet miners are assigned tasks related to generating and improving machine learning models based on various prompts (text, voice, image, Figma). - Performance Evaluation: Validators evaluate the outputs produced by miners. The evaluation criteria include accuracy, efficiency, and innovation. @@ -45,7 +45,7 @@ The BTCopilot subnet incentivizes miners and validators to ensure high-quality o - For Miners: - Miners in the BTCopilot subnet are tasked with generating project outputs based on various types of prompts. Their outputs are evaluated based on the following criteria: + Miners in the WebGenieAI subnet are tasked with generating project outputs based on various types of prompts. Their outputs are evaluated based on the following criteria: 1. Accuracy: @@ -76,7 +76,7 @@ The BTCopilot subnet incentivizes miners and validators to ensure high-quality o ### Example Scenario -- Prompt: A miner receives a text prompt to create a React-based TodoList application. +- Prompt: A miner receives a prompt to create a front-end focus application. - Generation: The miner generates the code for the application and submits it. - Evaluation: Validators review the submission: - Accuracy: Does the application have all the features mentioned in the prompt? @@ -89,9 +89,9 @@ The BTCopilot subnet incentivizes miners and validators to ensure high-quality o Phase 1: Generate HTML/CSS projects from text prompts. -Phase 2: Enable voice prompts for project generation. +Phase 2: Generate HTML/CSS projects from image based prompts. -Phase 3: Support image prompts to generate pixel-perfect projects. +Phase 3: Enable voice prompts for project generation. Phase 4: Integrate Figma designs as input for project generation. From 4b38c2b86c3f8d9002e12a557b2ed563e1049227 Mon Sep 17 00:00:00 2001 From: cardoso-topdev Date: Fri, 15 Nov 2024 10:22:35 -0600 Subject: [PATCH 013/554] feat: add scoring mechanism for the imagetohtml feature --- README.md | 82 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index ec31bff5..83f8ea0b 100644 --- a/README.md +++ b/README.md @@ -33,46 +33,57 @@ The primary purpose of WebGenieAI is to: - **Figma Prompt**: Convert Figma designs into functional projects. - **Automated Downloads**: Directly download the generated projects as complete folders. -## Incentive Mechanism +## Incentive Mechanism v1 The WebGenieAI subnet incentivizes miners and validators to ensure high-quality outputs. Here’s how it works specifically for this subnet: -- Task Assignment: Subnet miners are assigned tasks related to generating and improving machine learning models based on various prompts (text, voice, image, Figma). +- Task Assignment: Subnet miners are assigned tasks related to generating and improving machine learning models based on various prompts (text and image). - Performance Evaluation: Validators evaluate the outputs produced by miners. The evaluation criteria include accuracy, efficiency, and innovation. - Ranking and Rewarding: Validators rank the miners according to their performance. The Bittensor blockchain’s Yuma Consensus mechanism determines the TAO rewards distribution based on these rankings. ## Evaluation Process -- For Miners: +### Automatic evaluation of ImageToHTML task for design-wise +We automatically evaluate generated webpages by calculating the similarity between the original input image and the rendered screenshot of generated webpage. +We break down the evaluation into both high-level visual similarity and low-level element matching. - Miners in the WebGenieAI subnet are tasked with generating project outputs based on various types of prompts. Their outputs are evaluated based on the following criteria: +#### High-level Visual Similarity - 1. Accuracy: +To evaluate the visual similarity of IR and IG, we use the similarity of their CLIP embedding, denoted as CLIP(IR, IG). Specifically, we extract features by CLIP-ViT-B/32 after resizing screenshots to squares. +To rule out the texts in the screenshots, we use the inpainting algorithm from [Telea](https://docs.opencv.org/4.3.0/df/d3d/tutorial_py_inpainting.html) to mask all detected text boxes using their bounding box coordinates. - Correctness: The generated code must accurately reflect the requirements stated in the prompt. +#### Low-level Element Matching - Functionality: The output should be fully functional with minimal to no errors. +Metrics like CLIP similarity only capture the similarity of the overall images rather than the matching of all the details like text. Moreover, the metric itself does not offer any fine-grained breakdown to help diagnose model weaknesses. - 2. Efficiency: +To complement that, we introduce a suite of element-matching metrics. Specifically, we consider whether the generated webpages manage to recall all visual elements, and whether the corresponding visual elements in the input image and generated webpages have aligned text content, position, and color. - Resource Utilization: The output should be produced using the least amount of computational resources without compromising on quality. +Given a reference webpage screenshot $I_R$ and a generated webpage screenshot $I_G$, we use a text detection module to output a set of detected visual element blocks for each: R = {$r_1$, $r_2$, ..., $r_m$} and G = {$g_1$, $g_2$, ..., $g_n$}, where each block contains its textual content and bounding box coordinates. - Speed: Faster generation times are favored, provided the output meets all other criteria. +Based on the two sets of detected blocks, we use the Jonker-Volgenant algorithm to get the optimal matching M between R and G based on text similarity, where (p, q) ∈ M indicates $r_p$ is matched with $g_q$. - 3. Innovation: +Given R, G, and matched pairs in M, we evaluate similarity along the following aspects: +- **Block-Match**: The first desideratum of the task is that all visual elements from the image should be reproduced in the generated webpage, and the generated webpage should not hallucinate non-existent new elements. We measure this by computing the total sizes of all matched blocks divided by the total sizes of all blocks, including unmatched ones (either because the generated webpages missed them or because the generated webpages contain hallucinated blocks): - Novelty: Unique and creative approaches to solving the prompt are rewarded. +$$ +\mathbf{match}_{\text{block}}(r_p, g_q) = \frac{S(r_p) + S(g_q)}{\sum_{(i,j) \in M} (S(r_i) + S(g_j)) + \left(\sum_{i \in U_R} S(r_i) + \sum_{j \in U_G} S(g_j)\right)} +$$ - Optimization: Innovative optimizations that improve the performance or usability of the generated project are highly valued. +$$ +\mathbf{match}_{\text{block}}(R, G) = \sum_{(p,q) \in M} \mathbf{match}_{\text{block}}(r_p, g_q) +$$ -- For Validators: +where S(·) returns the size of the blocks, $U_R$ and $U_G$ denotes the unmatched blocks in R +and G. The intuition here is that unmatched blocks will lower the score as they indicate +missing original blocks or generating hallucinated blocks, and the larger the unmatched +blocks are, the lower this score is. - Validators play a crucial role in ensuring the quality of outputs. They are responsible for evaluating and ranking the miners’ contributions. The evaluation process involves: +- **Text**: Given two strings from two matched blocks $r_p$ and $g_q$, the text similarity **sim**text($r_p$, $g_q$) is calculated as twice the number of overlapping characters divided by the total number of characters in the two strings (character-level Sørensen-Dice similarity). The overall score is averaged across all matched pairs. + +- Position: The positioning of the blocks largely impacts the overall layout. For each matched pair (p, q), we calculate the position similarity **sim**pos($r_p$, $g_q$) = 1 − max(abs($x_q$ − $x_p$), abs($y_q$ − $y_p$)), where ($x_p$, $y_p$) and ($x_q$, $y_q$) are normalized coordinates (in [0, 1]) of $r_p$ and $g_q$’s centors. The overall score is averaged across all matched pairs. + +- Color: We use the [CIEDE2000](https://en.wikipedia.org/wiki/Color_difference) color difference formula to assess the perceptual difference between the colors of the generated text in block $g_q$ and the reference text in block $r_p$, denoted as **sim**color(rp, gq), where the formula considers the complexities of human color vision. The overall score is averaged across all matched pairs. - 1. Initial Review: Validators perform an initial check to ensure that the submitted outputs meet basic functional requirements. - 2. Detailed Assessment: Each output is thoroughly reviewed against the criteria of accuracy, efficiency, and innovation. - 3. Feedback Provision: Validators provide detailed feedback to miners, highlighting strengths and areas for improvement. - 4. Ranking Submission: Validators rank the outputs from different miners. These rankings are submitted to the Bittensor blockchain. ### Example Scenario @@ -87,14 +98,25 @@ The WebGenieAI subnet incentivizes miners and validators to ensure high-quality ## Roadmap -Phase 1: Generate HTML/CSS projects from text prompts. - -Phase 2: Generate HTML/CSS projects from image based prompts. - -Phase 3: Enable voice prompts for project generation. - -Phase 4: Integrate Figma designs as input for project generation. - -Phase 5: Automate the downloading of fully functional project folders. - -Phase 6: Expand to generate full framework-based projects like React, Angular, etc. +### Phase 1: Foundation (Q4 2024) +- [x] Launch on testnet (214) +- [] Launch front-end application v1 (webgenieai.co) + - Enable Text & image inputs +- [] Incentive mechanism v1 + - Generate pure HTML/CSS web pages from text & image based prompts +- [] Begin marketing for brand awareness and interest +- [] Launch on mainnet + +### Phase 2: Upgrade (Q1 2025) +- [] Build dashboard to track miner performance and progress +- [] Upgrade front-end application to v2 + - Enable figma design inputs +- [] Upgrade incentive mechanism to v2 + - Generate full framework based on React, Vue, and Next.js projects from text, image, and figma prompts + +### Phase 3: Expand (Q2 2025) +- [] Add features to monetize the application + - Add payment gateways + - Automate the downloading of fully functional projects +- [] Market and B2B sales expansion +- [] Grow the team \ No newline at end of file From b76142480f4cae7d40de33d81ea19f98c3ce13cb Mon Sep 17 00:00:00 2001 From: cardoso-topdev Date: Fri, 15 Nov 2024 10:45:44 -0600 Subject: [PATCH 014/554] chore: readme update --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 83f8ea0b..9dcc6144 100644 --- a/README.md +++ b/README.md @@ -100,23 +100,23 @@ blocks are, the lower this score is. ### Phase 1: Foundation (Q4 2024) - [x] Launch on testnet (214) -- [] Launch front-end application v1 (webgenieai.co) +- [ ] Launch front-end application v1 (webgenieai.co) - Enable Text & image inputs -- [] Incentive mechanism v1 +- [ ] Incentive mechanism v1 - Generate pure HTML/CSS web pages from text & image based prompts -- [] Begin marketing for brand awareness and interest -- [] Launch on mainnet +- [ ] Begin marketing for brand awareness and interest +- [ ] Launch on mainnet ### Phase 2: Upgrade (Q1 2025) -- [] Build dashboard to track miner performance and progress -- [] Upgrade front-end application to v2 +- [ ] Build dashboard to track miner performance and progress +- [ ] Upgrade front-end application to v2 - Enable figma design inputs -- [] Upgrade incentive mechanism to v2 +- [ ] Upgrade incentive mechanism to v2 - Generate full framework based on React, Vue, and Next.js projects from text, image, and figma prompts ### Phase 3: Expand (Q2 2025) -- [] Add features to monetize the application +- [ ] Add features to monetize the application - Add payment gateways - Automate the downloading of fully functional projects -- [] Market and B2B sales expansion -- [] Grow the team \ No newline at end of file +- [ ] Market and B2B sales expansion +- [ ] Grow the team \ No newline at end of file From 9967324a958d1b9c8df80484847f020749f12833 Mon Sep 17 00:00:00 2001 From: cardoso-topdev Date: Fri, 15 Nov 2024 10:47:51 -0600 Subject: [PATCH 015/554] chore: readme update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9dcc6144..aedb193b 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ We break down the evaluation into both high-level visual similarity and low-leve #### High-level Visual Similarity -To evaluate the visual similarity of IR and IG, we use the similarity of their CLIP embedding, denoted as CLIP(IR, IG). Specifically, we extract features by CLIP-ViT-B/32 after resizing screenshots to squares. +To evaluate the visual similarity of IR and IG, we use the similarity of their CLIP embedding, denoted as CLIP(IR, IG). Specifically, we extract features by CLIP-ViT-B/32 after resizing screenshots to squares. To rule out the texts in the screenshots, we use the inpainting algorithm from [Telea](https://docs.opencv.org/4.3.0/df/d3d/tutorial_py_inpainting.html) to mask all detected text boxes using their bounding box coordinates. #### Low-level Element Matching @@ -58,7 +58,7 @@ Metrics like CLIP similarity only capture the similarity of the overall images r To complement that, we introduce a suite of element-matching metrics. Specifically, we consider whether the generated webpages manage to recall all visual elements, and whether the corresponding visual elements in the input image and generated webpages have aligned text content, position, and color. -Given a reference webpage screenshot $I_R$ and a generated webpage screenshot $I_G$, we use a text detection module to output a set of detected visual element blocks for each: R = {$r_1$, $r_2$, ..., $r_m$} and G = {$g_1$, $g_2$, ..., $g_n$}, where each block contains its textual content and bounding box coordinates. +Given a reference webpage screenshot $I_R$ and a generated webpage screenshot $I_G$, we use a text detection module to output a set of detected visual element blocks for each: R = { $r_1$, $r_2$, ..., $r_m$ } and G = { $g_1$, $g_2$, ..., $g_n$ }, where each block contains its textual content and bounding box coordinates. Based on the two sets of detected blocks, we use the Jonker-Volgenant algorithm to get the optimal matching M between R and G based on text similarity, where (p, q) ∈ M indicates $r_p$ is matched with $g_q$. From 355f557bf248ccd613dc32675bf3bcaef4066fa0 Mon Sep 17 00:00:00 2001 From: cardoso-topdev Date: Fri, 15 Nov 2024 10:49:07 -0600 Subject: [PATCH 016/554] chore: readme update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aedb193b..e6d99564 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ We break down the evaluation into both high-level visual similarity and low-leve #### High-level Visual Similarity -To evaluate the visual similarity of IR and IG, we use the similarity of their CLIP embedding, denoted as CLIP(IR, IG). Specifically, we extract features by CLIP-ViT-B/32 after resizing screenshots to squares. +To evaluate the visual similarity of $I_R$ and $I_G$, we use the similarity of their CLIP embedding, denoted as CLIP($I_R$, $I_G$). Specifically, we extract features by CLIP-ViT-B/32 after resizing screenshots to squares. To rule out the texts in the screenshots, we use the inpainting algorithm from [Telea](https://docs.opencv.org/4.3.0/df/d3d/tutorial_py_inpainting.html) to mask all detected text boxes using their bounding box coordinates. #### Low-level Element Matching From e28172e23bc32ee24f6dec6590efbb64178185ed Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Wed, 11 Dec 2024 06:39:34 -0700 Subject: [PATCH 017/554] chore: update documents --- README.md | 16 ++++++++++++++-- docs/running_on_mainnet.md | 18 +++++++++--------- docs/running_on_staging.md | 12 ++++++------ docs/running_on_testnet.md | 24 ++++++++++++------------ min_compute.yml | 8 ++++---- 5 files changed, 45 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index e6d99564..bc0f773b 100644 --- a/README.md +++ b/README.md @@ -96,13 +96,25 @@ blocks are, the lower this score is. - Ranking: Validators rank this submission against others. - Rewarding: Based on the ranking, the miner receives TAO rewards. +## Installation + +- See [Running on Staging](docs/running_on_staging.md) for instructions on how to run the subnet on staging. +- See [Running on Testnet](docs/running_on_testnet.md) for instructions on how to run the subnet on testnet. +- See [Running on Mainnet](docs/running_on_mainnet.md) for instructions on how to run the subnet on mainnet. + +## Requirements + +- Miners can use any port. +- Miners can use OpenAI API key or can use their own model. +- Validators need to use OpenAI API key to generate a task for miners. + ## Roadmap ### Phase 1: Foundation (Q4 2024) - [x] Launch on testnet (214) -- [ ] Launch front-end application v1 (webgenieai.co) +- [x] Launch front-end application v1 (webgenieai.co) - Enable Text & image inputs -- [ ] Incentive mechanism v1 +- [x] Incentive mechanism v1 - Generate pure HTML/CSS web pages from text & image based prompts - [ ] Begin marketing for brand awareness and interest - [ ] Launch on mainnet diff --git a/docs/running_on_mainnet.md b/docs/running_on_mainnet.md index 38be00a6..f95f363c 100644 --- a/docs/running_on_mainnet.md +++ b/docs/running_on_mainnet.md @@ -24,20 +24,20 @@ After installing `bittensor`, proceed as below: ## Steps -## 1. Install your subnet template +## 1. Install web-genie-ai **NOTE: Skip this step if** you already did this during local testing and development. In your project directory: ```bash -git clone https://github.com/opentensor/bittensor-subnet-template.git +git clone https://github.com/web-genie-ai/web-genie-ai.git ``` -Next, `cd` into `bittensor-subnet-template` repo directory: +Next, `cd` into `web-genie-ai` repo directory: ```bash -cd bittensor-subnet-template +cd web-genie-ai ``` Install the Bittensor subnet template package: @@ -132,7 +132,7 @@ This step registers your subnet validator and subnet miner keys to the subnet gi Register your miner key to the subnet: ```bash -btcli subnet recycle_register --netuid 1 --subtensor.network finney --wallet.name miner --wallet.hotkey default +btcli subnet recycle_register --netuid [mainnet_netuid] --subtensor.network finney --wallet.name miner --wallet.hotkey default ``` Follow the below prompts: @@ -149,13 +149,13 @@ Follow the below prompts: Next, register your validator key to the subnet: ```bash -btcli subnet recycle_register --netuid 1 --subtensor.network finney --wallet.name validator --wallet.hotkey default +btcli subnet recycle_register --netuid [mainnet_netuid] --subtensor.network finney --wallet.name validator --wallet.hotkey default ``` Follow the below prompts: ```bash ->> Enter netuid [1] (1): # Enter netuid 1 to specify the subnet you just created. +>> Enter netuid [1] (1): # Enter netuid [mainnet_netuid] to specify the subnet you just created. >> Continue Registration? hotkey: ... coldkey: ... @@ -202,7 +202,7 @@ miner default 1 True 0.00000 0.00000 0.00000 0.00000 0.00000 Run the subnet miner: ```bash -python neurons/miner.py --netuid 1 --wallet.name miner --wallet.hotkey default --logging.debug +python neurons/miner.py --netuid [mainnet_netuid] --wallet.name miner --wallet.hotkey default --logging.debug ``` You will see the below terminal output: @@ -214,7 +214,7 @@ You will see the below terminal output: Run the subnet validator: ```bash -python neurons/validator.py --netuid 1 --wallet.name validator --wallet.hotkey default --logging.debug +python neurons/validator.py --netuid [mainnet_netuid] --wallet.name validator --wallet.hotkey default --logging.debug ``` You will see the below terminal output: diff --git a/docs/running_on_staging.md b/docs/running_on_staging.md index e282dcfc..d93c9986 100644 --- a/docs/running_on_staging.md +++ b/docs/running_on_staging.md @@ -1,6 +1,6 @@ # Running Subnet Locally -This tutorial will guide you through: +This document will guide you through: - Setting up a local blockchain that is not connected to either Bittensor testchain or mainchain - Creating a subnet @@ -92,21 +92,21 @@ BUILD_BINARY=0 ./scripts/localnet.sh **NOTE**: Watch for any build or initialization outputs in this step. If you are building the project for the first time, this step will take a while to finish building, depending on your hardware. -## 6. Install subnet template +## 6. Install web-genie-ai subnet -`cd` to your project directory and clone the bittensor subnet template repository: +`cd` to your project directory and clone the web-genie-ai repository: ```bash -git clone https://github.com/opentensor/bittensor-subnet-template.git +git clone https://github.com/web-genie-ai/web-genie-ai.git ``` Navigate to the cloned repository: ```bash -cd bittensor-subnet-template +cd web-genie-ai ``` -Install the bittensor-subnet-template Python package: +Install the web-genie-ai Python package: ```bash python -m pip install -e . diff --git a/docs/running_on_testnet.md b/docs/running_on_testnet.md index 9203d3a5..be4e8553 100644 --- a/docs/running_on_testnet.md +++ b/docs/running_on_testnet.md @@ -18,23 +18,23 @@ Before proceeding further, make sure that you have installed Bittensor. See the After installing `bittensor`, proceed as below: -## 1. Install Bittensor subnet template +## 1. Install web-genie-ai **NOTE: Skip this step if** you already did this during local testing and development. -`cd` into your project directory and clone the bittensor-subnet-template repo: +`cd` into your project directory and clone the web-genie-ai repo: ```bash -git clone https://github.com/opentensor/bittensor-subnet-template.git +git clone https://github.com/web-genie-ai/web-genie-ai.git ``` -Next, `cd` into bittensor-subnet-template repo directory: +Next, `cd` into web-genie-ai repo directory: ```bash -cd bittensor-subnet-template # Enter the +cd web-genie-ai # Enter the ``` -Install the bittensor-subnet-template package: +Install the web-genie-ai package: ```bash python -m pip install -e . @@ -129,13 +129,13 @@ This step registers your subnet validator and subnet miner keys to the subnet, g Register your miner key to the subnet: ```bash -btcli subnet register --netuid 13 --subtensor.network test --wallet.name miner --wallet.hotkey default +btcli subnet register --netuid 214 --subtensor.network test --wallet.name miner --wallet.hotkey default ``` Follow the below prompts: ```bash ->> Enter netuid [1] (1): # Enter netuid 1 to specify the subnet you just created. +>> Enter netuid [1] (1): # Enter netuid 214 to specify web-genie-ai test subnet. >> Continue Registration? hotkey: ... coldkey: ... @@ -146,13 +146,13 @@ Follow the below prompts: Next, register your validator key to the subnet: ```bash -btcli subnet register --netuid 13 --subtensor.network test --wallet.name validator --wallet.hotkey default +btcli subnet register --netuid 214 --subtensor.network test --wallet.name validator --wallet.hotkey default ``` Follow the prompts: ```bash ->> Enter netuid [1] (1): # Enter netuid 1 to specify the subnet you just created. +>> Enter netuid [1] (1): # Enter netuid 214 to specify web-genie-ai test subnet. >> Continue Registration? hotkey: ... coldkey: ... @@ -201,7 +201,7 @@ miner default 1 True 0.00000 0.00000 0.00000 0.00000 0.00000 Run the subnet miner: ```bash -python neurons/miner.py --netuid 1 --subtensor.network test --wallet.name miner --wallet.hotkey default --logging.debug +python neurons/miner.py --netuid 214 --subtensor.network test --wallet.name miner --wallet.hotkey default --logging.debug ``` You will see the below terminal output: @@ -213,7 +213,7 @@ You will see the below terminal output: Next, run the subnet validator: ```bash -python neurons/validator.py --netuid 1 --subtensor.network test --wallet.name validator --wallet.hotkey default --logging.debug +python neurons/validator.py --netuid 214 --subtensor.network test --wallet.name validator --wallet.hotkey default --logging.debug ``` You will see the below terminal output: diff --git a/min_compute.yml b/min_compute.yml index 1da3bb04..3d8e0153 100644 --- a/min_compute.yml +++ b/min_compute.yml @@ -57,8 +57,8 @@ compute_spec: gpu: required: True # Does the application require a GPU? - min_vram: 8 # Minimum GPU VRAM (GB) - recommended_vram: 24 # Recommended GPU VRAM (GB) + min_vram: 80 # Minimum GPU VRAM (GB) + recommended_vram: 100 # Recommended GPU VRAM (GB) cuda_cores: 1024 # Minimum number of CUDA cores (if applicable) min_compute_capability: 6.0 # Minimum CUDA compute capability recommended_compute_capability: 7.0 # Recommended CUDA compute capability @@ -71,8 +71,8 @@ compute_spec: ram_type: "DDR4" # RAM type (e.g., DDR4, DDR3, etc.) storage: - min_space: 10 # Minimum free storage space (GB) - recommended_space: 100 # Recommended free storage space (GB) + min_space: 500 # Minimum free storage space (GB) + recommended_space: 1000 # Recommended free storage space (GB) type: "SSD" # Preferred storage type (e.g., SSD, HDD) min_iops: 1000 # Minimum I/O operations per second (if applicable) recommended_iops: 5000 # Recommended I/O operations per second From 287a02a6696b3e602826dcf519e6c4040b8aed8d Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Wed, 11 Dec 2024 06:43:59 -0700 Subject: [PATCH 018/554] chore: replaced incentive formula code with an image --- README.md | 5 +++-- docs/incentive-fomula.png | Bin 0 -> 90280 bytes 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 docs/incentive-fomula.png diff --git a/README.md b/README.md index bc0f773b..4aac8a23 100644 --- a/README.md +++ b/README.md @@ -65,13 +65,14 @@ Based on the two sets of detected blocks, we use the Jonker-Volgenant algorithm Given R, G, and matched pairs in M, we evaluate similarity along the following aspects: - **Block-Match**: The first desideratum of the task is that all visual elements from the image should be reproduced in the generated webpage, and the generated webpage should not hallucinate non-existent new elements. We measure this by computing the total sizes of all matched blocks divided by the total sizes of all blocks, including unmatched ones (either because the generated webpages missed them or because the generated webpages contain hallucinated blocks): -$$ +![Incentive Mechanism Fomula](docs/incentive-fomula.png) + where S(·) returns the size of the blocks, $U_R$ and $U_G$ denotes the unmatched blocks in R and G. The intuition here is that unmatched blocks will lower the score as they indicate diff --git a/docs/incentive-fomula.png b/docs/incentive-fomula.png new file mode 100644 index 0000000000000000000000000000000000000000..848b6fa99735521e91b0e79f057429b43a0c6c81 GIT binary patch literal 90280 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%0A7N@e3r)La^Vdk2%q@_ zpXJR)d~qTceqpE*4VCBP%XTP;V9URbp3lYHJdMeF%8h9J7mnEP#zKve4IB3ZyC0;7|hx3b$d)iaT? zgcut9N7|f5Gtz%r>h%f}(<~>1vPl(i`Nq>FBO|N1$D~9=`~t&LB7nQ^<^Z40=#=Ww zuJ=Z16mIVphYqLkyDR;pMltj>FsDTv4*wpQ0I%znyWXh2H*ekyXL9!gAR+)no7Gv8 zsd9f)sabZ#)TM#VYDVaOKEcnDU5+KyK5|n|CRJ&&4z9=%AZX6@%ii6ShkFh z)obZWlZmK-IQtif-YEE7sMfiY;`vx0O>L(YSUqa|kV1#mcI$gP#*G=KLYZXmyrp#Z znkvPJc0pkuGUlK2H?IH>S!ZrH5?`q7uZ>iWEG;$W0A0!hVztg{1(i2BGRw) zfGI_Iit6&2;$&YF-Z}cdz@3&^?^9t=b+!hZ6$-JLBV7^I1EFGdvzQR6NeJ%v8FpPuGyHs?!Dt@ZT}U7zFE;=AWqMr=aia zF$hvFRF^4k2nQT>0>yj(^;U$v6pW^dV+v_E3HMuvsQ@NvYz=p7z5I7oE!Jd!>yGY? z%1`mttg%YsbTBLI9im>NYgL^p;`R|zLWlOPceGT9AV*H9%3}_!p>bg~mJhX9YBK*m zCFd=a2Mi%*-5J0OC30Oi{&8O}<&{MB1nVr@=z-2aL?s2)^&1~8d)NWX#gk?TkA**l5MBj8pTv}2j161cSBx2}x^1nU%mBkz}JV{RW zK7s2AmqTD|0CeKfO;6^K`Xd+MW=cbxmqTt(BUy|~VWy=JdEHj{)ulf&d&&sIni*lzR- z0l||5M1A${ZF3I50muh$9)cI@Z*!Rpv02Z^LuRrv0Tf^NF}G8|SNqN{m^~-)9Z%>1 zagg;LOGul6v|F*=?qux2QnR6jygsOWqhC$5xG7F%!rEY?-x#P~x(FsR`vl{(O<2+# zGz0M+i=cLxr$F`ZG~W znG&xf341=N?oRIe3n^4JjG_gK1FY@K<``vTB|5zOdkWcDCOP8a>(k9I?>brO27V+K zwr?+a)QjH{AZnPE8pQx5JYfJx1Wa;$?1m?jRZ=-uJ|62oAXNKlf4*M-aQ_t^v08nH zi?QRK0$$kPSZWd&TC&}~cssT(@4^`uKP{>w920=&N4pLZ;OZglyKD1?eRZ)(fRp?n z79)A0QTxFEteI@OM3?rQkK}rv_4?50`b4%j@m=Wro38z3f*(aSZekB#N(6sryDHE3 zrjKEd522;Q!m7XAHtXa%9iv5$9rbb01e8LgR$Gm57OR!ZBOt5KOpp|VZ!g^8RDAwI z@!8pW)L4x|!+-GIGmVEIw;%D1UIJq!lA$^p3Raxj~FVH9TwM{@O z?Y=0;Ks*Lvy9 z|8N~3wcEEZEjj|CjIWM{CMtkD^yO538dZI`@zSCU!oY_8&gSwhU*M~(2@N&DvMYcH zldg1pz*DVvP%`)9EQ24=drPq)SD7a#tBi;Du|K&Qs9g;Ez5I+9#P?g+;6?GI#e1&8 z@%&)q&LDmP)O4kX;{I?AUvDs8&jEQKYV#nsP-#kWU6mhCkJD$%M<@|Zv-+c-{QPKV zvM^pAcda@%?034t*P;Zq7apQDX1)4#lZPkxz2t1YV%=GG>y9aDCg6}dDX(40H56fC z2Vz#`f}r)bP_gU$ZGJk5og*M1U>qRDmjXiwcnxyEyETAdB)9Ic={EZ2 z%s=3BHjB~mY0cf~kV^aa4fTjIk#bmq)j*Em7kGbxW@jm&6@YRM{EN zO@0L!U@3dBMEnap&cJ8KmWcTYnyr2B4fKfkBrXpYUQKGPRG7`^y`0)^QpVd%=P;N7 zQgK9;8}y+xt#dycQDtA zV{do+`1vI$Js+U*dd1IVgZ&eLp$>rQ7JYbqMFXtX`*jjDS-Aw1^xefz zK;g0l;3^9tsMhZqS|Lt4l3~P_{wbq~BM7~57gFK~tIAikoK1nTU0h`e`();+j=6854QNE*u}%UNXf z#k~TuZW*uE!)QISq!V*@G@ZSl)J_5o(`ik>wjg2obm7Vg&7pT|FIq7JE7i&k>}QDs z8)=U$Z5&)M#FRi=dDLs1usrqa#3=cMZsUGDp(Wp+xJU z(LBmcTfB&#(3*zlUW0UvyxrIQ^;ztz0p5p^UdC1SW0W6o$R^@3>_r1#77wI9>`rR+ zHooL#SS4uKyMcfHcWNHa>3x0rd3%5jG2gsoVyZw55~yb~*>2Kd9Cs{+XIBN%v)cT` z)E&Wi1@2w)gaJTESMK@?9M(8M*^zcrX4(tLqVi5o2WvhvTr^;*CMI{(6>2yl{$P(X z0hC+Ji9GJ|fP~?0E_m&kPUDSfkg^%Dwlm44+>jJX&*y@$6p-yThc%*SI$n>}FI1Ov z1+tbF5d*2CfY)5nyb6!Y**BBPpgK=`tuWZ%sE6+f^&F*@SE^2ULQkJuEXZMw=|umf zi8xcDLeZ5>6=jNrL&jFPJZPM7*}v<_f6+~<2DMSGfP()%WtY^IvDE5!=zxYS4`i+C z*F!Aaj6FNqN~kecL~K2)?kW#u&K+_v3MY{<`!b zYvhJKZ)>KUegXgz_SVmi)oI~=r)}Pw(h#lFw#_j9&0gz$g6kSOv#|?Wn68iaC><#A z+p{*vBJf~1T(Ptgg8^Ze?aY`7^?eVT+&{)7S3Wb%e9i&AIkVTl5E_TANCF6;;d@CX ze-9AqF#Pv5Qh;^|W;ItYW}(;_!PPN4TwEaD*cENVKAY6LYb?-Uk>g^tJ7-K7d+ca4}(fbV;M$HNOv_pXobV7$^)W<|N&KlQOtgKgc> zQ*gc6r=_JsF-Mk2t?LV~^d*LTOwDlz$lV zcYstwX+UB3D!fREt3Ev7H4%?=5~J4iB| zkileJl?q@-)OP_Dj`4B}PhJqxFD45Do>b`%VIvU{^~8x*X^Z^lRZ1`jEs4unU?7e~ z);bcentxX@k#lEtd}L zR@qNA~*UR%}Jb>mZc~LcmL)>ok#&{1K(k0fR5McHlzt4)$z-}r|F=7VdO@> z9Qs9PAlj2zTv(H&Ma|RJgytrS#&tS|+Ky^FiDk z$y4f6;wS}N0-XPqo)B_2c=vm{2nrB3EvZA1X)H?C2dd09klgWJ0aHe&Nyn<7m6WZY zpbo=SN?c;Bzz(QD)k4+}*ZNnmiVAlX|$8#bOaI zXHz)RkK^?W`3F}90VNg%;kvr~>@F_f3WURHZ$iCF zCG;8x%_h{P+8Us&Qn{S9GwrrVv7`YaM=g%sIzhsexxTX6X2gj7If8{0p-fUk`_cf1 z0d6)x#6NfZYM|4tE7$%HtS4{C*pKuX0NqMDq9IH3Cu&W76>JutF9P4SZ@OrGoHWZapH{2jNxLQ{8lTBj7LS zOgksLtqlXPnuYkFquywGl+@*ApMy%#0&;%%k|-*Td=vkr`SGro!#mc0?``qU5cl%5 zgoUM5DorSC_x&=f@s|QQt_e^QwOe4U#R&)~%Qm?c_Zd%5XpLo^ujwz=6d|syNvAmM z*Ntb(DE8NK*cuGqoTvKwt8ML%Z!@o0Z@3%(h+=oDpBpg`)D^3WWE=7Fr5byN( z;NEsM4HA!ei0llC&GEhb6R~FXI$${%_ZCT2R{T+T(YyatIQi^v1TjSR?W0dA8rkB@ zrc!3H$LZ~{?AP92%r6PXGS?nQd!8LJ8%U<+s}y4w(P>u-6^=taEKp>8{yVA9=H2ci5#cs$8%DpQb3I&faksf z7m%7L)sZjTW{O1!7j1S1kmMIdRLURJ$s=EuaqFybKd zglu%bttFQ3`Pv z7VzS8o{=FF@QCV=xotI*6r-0*0!k#SnM*%n63Y=3UN;dyL-`G#$IWOfRG<}od5HWr zcPcvsZ?5KNrx0)Sg3FP6&(s9n@qH49Eyy{PW5O)pP~7b&I!%7p zhP(;0i%g@^q|r^d4f>h`4$7B_UY$qFS4h_(Je;r^^us*W zIq7QOd?2DH{D4-9!(ZR-s@?37kBY>cS6#Pv!9~+x(w><<@&A#@pbUzBXNNm>zRr=Pt_LU9F9_N+lCK zsbz)__hy(|>#V+^mRqvh){#z7h=kw*$|cvQ;O!>Hr*Q{}xHG=S z1f2lYqda?wNboOt5#5Wc!6M~ve`mIY950uj-L!oTjc)#N1=|ZK&*tmM#(99^yMQyC zKRrJov0mtvR+O-ZAuf9fN+P>8S88;qb215JW1p=@G^6uL&1OxUVfIGNIBr)Edh^!i z8v1(py;|tjW0lE-a?SM~A)5cpQU`EhXEP6qK7J&z5Q1^4ZjocZLz~zlJOz^59DpyrD%1J!&Zm+QNcnbV`W zY6ADOQhi5{t>H3#1)>iqtO;H7v1{CK%~a=7^>dtq>SG!k;{(~8cx~T!E!En7z1W+f zTyrZe_IL=}+MJ@B1i6;#uKuBNqRjW@yd_aV=KC#ngn$W$hrCGnf&YR|yW!gh(4AWd zKz+S@Ec#eIq0Z^~kZ{y*Et+tNZEcn{O|H$GytX%!9KIf?U%DQS+jYzx35ff*2Z);H ziRya&b@Vn~1NhE8isw2b2z#&@%&v{EAyA}VsW}nPOtabqR7Ib*kG$7p#*O@4_M&-C zYO1>jXq{V4GE(>%R-Uyw9?XMK!W43okbY?0Cai$ z4wFeHp*)gbyfG$2dulfFMCcmdxAGeSx_sIu+>LVgtpQ~M^Vzqd8%#{4Ul>8iBBA(( zAIugpn8DO|+USopW#S==(of6h6_V%kNs0$1w_Aq{yGlUdt?nK{gcA4)Is}GxYYqL# z|KebQwy+_A+Zf=EI^wI7`wnp1?|?W14&Hu}uDpQ2rHh^_Y) zHI|qqLF@m;FaIXLJQzutuYl?02%!AzU-o$qL`R!@SjKVddA zL=$%ep1A5D919TFC+Jidd5q5ED(}m|T={(PSP9k9e%MTIUcaaDnDsj;ca77r`T6a& z|FwLwh!%ty=r2BiE0Ql9qw{Pkpd)r5o&+hH&l3@hoqI z(D31LoJ8pcHKm{=6%=nafC2|J2^5?U<_TBo(sT6LXfI-#N|-qovefP00}Z&X z-kdk4b?}@X8w=raUd7h;GSzDosFerkYe=?Y@~Up8Ozu65QpGM@ZQowqhBBy5Z_QP> z0eRFGfR&@u;wQUiNDym*y+FW2A2RfsKR=U zb0!0Yspwo^*JwN^wQRbfwiwm3&PZyf#%KhJD31fAu4Ks8U-C}i;ch#Egqj^R!Db#X z>=;9j2<%uJl#LmN^+31li^lUEE9vghGjz)W6m1EoD*mS}ChJX8v z|J-ct-A?k2<_lvL>hjlXWuRuR18ns3SBfYAq-l7S77L423%xE9ORP@+_f z#hD=^t$)N6I{o#30koIf>~!6EOM;uH5VJ_q9}#ua|q8=bF3)R#5Mcd(^lcerHL zYu{|;HpE^bK8dA9!*{&9V%%A3j+wxyvIm4T8GwQHU>eakeV1QIe`0V^1{=+~$Q*#} zh>0C8*y1r6dsza3a~&2UZ070&o9iW&_!|4$3ZeK~AFT}C6SV-YLrY%rWB!{e2A7+& zTs!JIvl;9*)S_ag{D3k(D#aWISU5PGn-UF~kV@2KWpQ70QY-Jvx0Z{<_Q6+nW~uF- z%umHOX9oxVp5%A#qZ*6byXc&jAkk~)j^x5C$-7$cF-MXAVYTPOOCC+XNA$uUnFu52a9cn?x+Ddqq`ONR zqy#~_ySt^k%OIq?ySuwfxL!H*zC#p7pUvi+-Kuo7`OKK zrIky$J+A?-b$!}NcTP74C)WMCqia_@gHE>@DEH^6Y1C^DK8ouVlJY?Q+Dh z+RV3eN6>fjjdH30>>wrQr*x&Nw-pz-zk3B8^edKa--L6KYT3Qzh}<`d87eOUIN1 z-1;l@LFX0jj-ocgm@2zu64c)xHdzc0DmDLS ztpxahRsS|(B<;jtRlW-Gs`nfR5Yx$YR=OYwg#^*hwHh`e5+Tf zWn&?7q<=a8@B44S-Lz*j0OFPKf@_w8d_F#vRICm$h6@>@gF6u%r-L

J&TrSBq) z%QqGkaW^9NmcI7HF#`1o9vGj-+GerBm^uaI`mEM6Cg@9&T`WG*t9O6A2VS7;JHxrd>&jF~OY=`R`sH?)Y>^B7**}vq@zpo2;0tD94gA3P$ z){9wnN1_h`m?F`8jxG2iJiUFxy7kiF8tBBD<-ZSnwK zBuV4Z$2VJb0{jo>+Pc!QA@}zUX2XH{?>kgVwH|^uj8(J$WQnHB2a9fNt&hckwWY%B ztK(#jbQVaL&(<`~uZrV>2y#kUP2rjixa6~d$TyiP*o@uq^NppR@wLV3xxK)DvqXIw z@>UvGKQ2B!UCws#yH6!kPhBY&o9TpxJAETw0Vzq%#LxZ>kaEgzqvsPA&g?BbyCM|4 zyF4skDk!x!2e;r`y2nh(VFy`eD{xyQY~kv&7|Xn{eK|uhy=(jW|FcA+r5P}Umk*YK zwAjMsM;?qGPAk;jW7}u~czwW@Jmt1T@%h{?-Cg(T_P;AHasjAScco587k_TfpxuHI zL16kIa0kK;z(t-Q$`hy5ZVMdz=6P7iy`?SO;+=t__3ws~c{s2rf8`w51LLipoOtT& zL`1;r#x86k3wR9e{x6J9!0J<={X3p4cIgKEyMlr~Re;DGOh2Itzc_kjSiU{d@qL-j z)Lk{a9Bs%q_+Q)Lri170ITs1aN@E~E)K?k6e}}jCRlc3R1-DVoz&X^(bR-FexhJ{B zqwn*h!_PkD+X9inM7osqg$9KMxE1?R8eUYKm{IdmPKN)-^8?lnQK)_`+_3&PV_=mH zkNI5Kp#p%bVG~tjV)vK3o;o=F$VEb+!=S%p%_~*=x_n5eoB-1T8~`~W2Au!6H5B;@ z9Yn%#?H!8-BLm78gm4QtZH{IU^bv9B(V=t&xj-3!TWEvhnf<5NVi75{;Y>J{*|HkY z==Mp}*d)M4x)0~|)%xtxihi-=W${m-(Go3YD%sa@RPJ|d<7v|ZJq#1OX0mL#4lXM& z#32BSX8I%G$sE~25*>S*&DTrdjQ<%7{x^Dp{3BhzMbtB!FHey#7B%9kHGW!xpf8_0 zlJS*nC5#x^7Y2{l$73Hq&)|7p1RDoeG)|3?g4;(qz%|Cv(to^A#i(y+(9};ouV~{q8AigU|73 zQ%9y8P%adJAT<;Blq!63H9gJLw~u!(gmmU5fW~I?iN3fTu=Rlct0|MAeX{y|jtzFV zj3nR^S*Q&%Uqb3VT)2`0Dw@I4U)2K4si4(h;5!rPQiG$^V(N!Y-U0g@sW`2g-YW7+ z0T#DQ2jF}wURxZ&{Pb#nCJ3*ghph$I>$Uk*0Y&LrdATB+zgXu>$MvnZR0um4i2zHeHM?e_DNppL(@wunc`K9n?$weYZlm311)7M}d}zYra;f$Qbsu0p zrv;X@E^Mm=qFkc;MBRPL$gm#0B+Yd$av7ww^=_$I$8;X&qu{&t#MwRZ)bkD%5F((0 zw!QO*mk*FVEpI6t`2Ba#wPwI#nSj{8`h9`v%_$lvD99TaF?z{iCHa)d8RI3o3F{qO?2MxqNY$$~{+!Lc)NSpn=Zw-+<1A;pwl4;OKH z?{Mth`O9PrKN#WU-Lj83?@lB)HAfsN>W4D)_#XUPOMMY$cH2d-Iag2xB2{`Hy1R|< zLeMm6_8Pmsdp!xJjtN#fIPys1!p0Pc7BE8@zs@9AJe-BQ z_5W6NTvS``?OKD0*}EztXaHYlde_E5u3Ka(Pk4H^Z^m^6+$_q$$gdO=xdLC*+7fyI zspDsOjx6SrM&PHkKN66Vc?cM`2zav-0%7FNUpkH&j-#9r3B+x^V*CFd1Mjxfi) zA92{4iz9mN#;MgDDg>q*N$7vr$tQD$$+2rV%W>E@lORVx93z~&P@c&yg1;%KTbQOrx#K(+SDWrCaM0FTrNNF*K9kMok)elo+T0JtjqyMBfG!blZOjdvx>@t?4AHZ0=W|{rWI>*PzvT@T z6lez+G~SG(*Th_LgC)zars(b#dU`2~K!D~)p4^K;w*bNp4l9iy;C8?-PWFN>Lu6^h z#B&KtV7DY9{HcBcbdrWojQkx15)FZ8mn9SnvzCO75VOOv$ zS87~>9Wl@8&}{sY;2UEsuwaAeStCYOQPI~YnUuC}I?tX?6~n^DxOcsOzFIvCq9<&v z`bXh*c1iNwD{TXVtU4U?K}Yl7I|1bYaY@KUEPV0+%?v+0Y>$^UsS-fzhE}cyqD&HJ z1mAsGwA0H_i9W?w(tseUm5Pg0V^bdks!+=Hj@e62o9}3yO*>ECg=`}n-DVTaJ&mRCE$A~nB5yKKF%%O-e21^{Eat#RXtFuUMpJXFjSH-3sr15m{U+> zIBQ6(9(TevN%0sC+E+%tDgxt=grK6mAg9?6S#E$@xsra9+h}kMcatky&6X*hbEtXs zkDxsM-H~unXS4R!@nBXh=3f)y9~&bL)WMe9(PF^f9i5x=D$c_H6a(PkRjE4Sn#gN^ zhL20w&EK8PN&!9$xVhgoyIv?ZAz;&D4l@gxzjZzekHtc5QSRdybiLRU6Af!2+rJTH zQIDJ@v%IH5x3-urf*ANh`aI$lkA&lBYHJz>qQYit{1Y2*wGtUk7CByNwe&KG1l9Rx z(22*b!#5={fH%IPeobVzL?5o6Yt3aOxYm7$!1qGyMPI*`y@v!&P3nV@5evbLYC_<4 z*3j;aOcs4#u&&1TaDRqn210t&`z#hQPZ2UAg`EYiQN6YrOWpIs+Lf`nO5?04^e}Yy z;q#DDe)mt&7$fVt)n=oMT&FHo{0O&9Nya$VI1DMuQx<~u8$HNtdsa1e<9+&Uh}2Uj zD@cf*7FeAjgqM|rJapxCZPw&=6&PQtJZZ1Xer0Bljm2~%m_#Z?_kQ;;*X?{KpDj6B zWc*3`KyQS4s(3+Q@$SLkMVO$wr3%s7kEaBr{C0d>zo9(=gS|UFjk5bY-GlgFJ$mpU zefFv2DTsesNrWi#*jYYudqr5}de%JP8%q-++6Ndv{J<`<7L`xeR;E6^Rw4k(`nRh^ z2JmAipOdh81*$atm;iI5$!LnEn%pa~@arUMg);0mSkxmN^TKlA_+?YMKY<@UD~Hh^ zP4S@9x6o|$j7Z?H&YI3Qoc(IOJ254BJ>f4@oE$}4P+3=!4WhH?y-W6F@d3SMWy9Jg_zVi4i*4xXA_fc;qhx5i> z>`wC5Ti?|mG63~1vM80{%Ma+Vzo6VJ$B5~kX>5=Hrd}ed;1N;&rgg159z@qmK!)x9 z@!q_8GcQ$hq22GfNmno>vF}}Z+EqV=;X$BC;WqEtGn-0$QIFp9-N{K%jA9u;32%phxInd0W4k=UoeyAIo+^trRf1g- zb0lic2UlH-xe7Fpzk&yncy^PEcFeTjy_1nhm(l}T?kVm2cOtk;$yJsM9TP%hRviN| znZ2|6g3t}1u0vg;mLe|UyY6G`~eU&Yh z@ySMEQSfo0E7rJ#{J~>T|EXAqcH}>L9GD=zu^>?d(Qt8#M3_9OBDJ-`B5L6ZZ{(i; z>+C*yxB76!p9S3JKkW6IFr#gh;X1y5v)GWw-H>OuKBCch?qnx%52j{9>FZgs;5=;B5BiurOfwQv`jT>Pq3eymFstCTC;o<*6WG&)t% z2vpLDH`=Q4st=TRC-JFvw@%hBY`S~^4reUe>6S0Hiy)21P;@+>f3&sVi)=I~u*IU^ zqrmoV@E7VQ>=$h5@`c-(F*?Q8QB#bt9d6oTm0XOkKSu{X!td>I$9kp|n*j5@LM-MH_wU|lx85U?MWIOzB7Z*-ZDPjg$&zgcs#3M=CH-HB}uoNSc9-8=p| z#a{cd3?yl|6Q+V)Ufaiy4ihdK4XXunD$iNa)y%H@!<54yZo%)D7`ri#X2VMisp=o> zyfkg%Qkn-&`si-lsHvXB(e#Kl($XbbaypgOCPtv~z6UWj564hYv<5%E`JLMQZVjQ5 zacg@1eF5zSp7%uT~fCUu($3X z#Aab1KzO}-<;um?pXi*=q%AA)9CeE%l&=os(l>*UEMh9o~EWC zp3+fa-K-Ryffc5lbhOk+LEDqk{bA$uGjY$gw9-2C)#L=|V~PgPUoqvzmy<=;t0X0C zKQnK?a8=yNFz&Zt`Vj82UDW#~8OPq!r#BOC5x*&^CpE~iOetVNRqO5&+)h0j<1*wi zH%!{-_S2wN%md>6k#emS@=1@`(3Kr1OzKy0f4jBXzC$ll&&T)e(S5$j2|Mc6!+r$} z@S-oeq+)JG;LhZ$^0QV4Itpn*=ytA;y<>+u%9W!2V~qmr*-!#oYA9A%sN3*K|dTQZwwz+PI=`7Osg5dDU^SR zPuCOPp|U-qnJ@8;GOYZ&2Da5gjk@Zy6GG7%1B1fO7LUxLe9!xns&Ld?>{Wwz4B=o} zmv4y&*NLe4){Kv>6I6!;2XH9AP=5`o0Ela%e|+WXieLZaXCYcZ7TMW-QgQCi##l>PkTIj5`bXE`xhNsM(G~H*`t7{I!Ljw9VNKvCOk8%! z%{rLK2;i_Cvr&E#76KLz8?&N0?y%9R=0@!*4#`g~K)fo<=6s=j04$bYAV;T&D40Qc zU5;c-2JJJyKdY>gaYN|;$LlNd zr79PD9?XI5HG|)wa$nx{zCYZXrUR4&@U4X&$CE|JEHpemJpUFq!$O0HJp{6`IVRE%AuFtuywO*3l_*=+0;3#?S>iI{oiF;OXmF<^EQj zG22Z}A&tAybTd2*@?2>AA5&EYZ5t~?x~qX%ow_#QZRCs%2umg7s+P{x_rpeZB20eETQAsyH zfYnLC9fi7|6Wvbe{=a)71k`T~IOL@_+5?l`v(0tc)f@kmR6#)XCquN(wg5}_g+=6* z`Q{;Yy}fwwgCMRHT)}r1RkSfgeG9V9L(LiH9L(oJKfQz10Pjr;16@2M*383Q$=GuU zni;Wdds9?DmH%4bn4Be2u_cuGtH_Oc^a0pPC6!D0Dg~%D=W#m+kAmLxdZ^biiu8CC z%4KF?%KY?SPX)3#rO(Kp!2mJy3>ZYAJe_t9#W}Oof^oqd#lo5n$s)f!TcC)pf0rMn z+1;?PJ*WWkH*W05YBS`QZ$H%)AfjjyttWEfY^1qeE&oUp)aQyVK31`CAd3g*=FA?q z@)PS#J60wcovdR$rKj=dQ3$SuRNkPl{V;`TP6Ond8Nl(e({G{<-c^FkS3%_H;I=b+ zwdI1G%J*uyCH+nTqqcAkoP9XA?`Hl`3ph zzjNyDoA%!7D#x7#6%v5Kq4cj$*n7FRz*&l;b`cxeq+OFWxgzDBT%h1WK9I0oPE+YN z3Od>bj9Rq8oid=ho{tdY{g-6%7C5DNs%KRLmcuo-<3EGcd$nSxx!$k-6-vF?AU$IT zmU||$rEwuObhM~a@1(Il*XPfPP zw$q_|42%*mKXjosF$VShW$~@}aC^R+ZPI;&!BviCdpK{sPS?M{+!Ed5`EWaCGR$7?U`tMV(4}U!ACH3~(Od2bowRH0O|avf~%gCg#h77~&tnjsMgI{h^<|7;7!+ z-UQNBFwVA_9rk~+6aoThc`D`1$b^ryNhr_Owx^^Mr*rW+th_(a0!liJj1x)f5t^05 z>8~8_u1u+9%$_9NBwQKH>_m3^_~L#!>MYnf2Kg24dnHq6sl$FRl#>eG229O=%y}>I zirXOv`1`2BsEZ@Eb=m`AV!yAegIXT!60rmX-K}_-1or1)WMYx-qMd=rKMMTW)FGa_ zk!to0(({0T+BGeeG~}j>9vo1M@=7hAE4e@XFW;` zYo50sJE+;!;~frt(oEpoYgK|o4hC_!);R^jmYx;QJ7mx?6jT6^4jm{Y6j5}FUb7|W zm5~D8{v2RnGZX!{SnP?>7rISLfBz!PQ7CPB(8lo! zYt8L8kD0jy9R%WDGMr!9Kmja`HqClG;dbmH0P;Ccgm5f2JG{?kva=+@RiJ2=z~vwV zKVtfN-P>H6ZOKITVzg9h$^G0+v8b`#NeWH8lvR(55<=B*U1z_MV6KyDMvrj1Load| z1k9|D*?h!K!hKC6DZCq}yCC-_3IdZ9qFpk{^P#-;TS9U?x`1c@QJi>oGV^%5&#=y^ zC7&z9TzCIt%iaN${o#N*RKZ1SQWQ7{l zd2n#Wd* zlnT7+_Qghg&;R&|n_gZH=s8acBp#wEoFKjerxogOeR!z4~wwk9d)zB!(Vy z!=c0LIVv8rP#0z)y_Ch_mW6xy=g(N{hcPpobwC)GPjIY+!T%dQ-}AezK$44&J8Fiu z`jX%>Lh3=FV=+;LzX_2VvF+!g7qlDU+D4KZ(S=9M3syyg6!rBn5RwxR?7F(!|Djer z#mmdG#LL^rQdyhwyrH0BeRnyfpy4V-3Jwj*AA&fI^fRd#3g|&mtFGzh)q6RHp20~h z@aO+~`jQXlU2Q%XtGR9=yLGfs8%Or0Dn_D#TfiGyAEhlL1N?+lUMQOyO?j6hm~X$cR-MUn@r#cs@AaK z?$tS*@DTDwBZ7v5{PT;?tZ>`FjBRJ2?EJ{$uSsOtXdS+Zs0rOJUAM$tGeksc0u*(Jb3O77GPT zq_g74J63`M-|5nof-UI5T69v_!m3vBj}s`wt4*#K1*@FPwZ|-8qExB>`B;!bM9)Ye z0t2$Ll93WdnY{~MAAr+Y{`$;+eknr#bUC+ugSVv8IgH9T+v$3FVAhu@VgoJCh)qCB zEeNitsdt++$5Pj-^5Mapw#Lmx1&fG96F#fS&`~`F1@IO6XIT2^Sb*Oh>4!fe&d5c0 zp|s?CE|i1~lD`9Up!`+2tg zY1*2r;pw0I_p*I1N6-71%?%80&5-_BG_iJu^&x?8CI9;^0&uwKvrSy_ATcCr`1o*z zrPSbn9+MNy3qHRQDh0JD_<>B`0&5+mWx!tlJV`=hF_D(^wu_nPR~6K5ZmOpNAePD- zL|sgS1*VHQDi7u=5oJtw?NNM(%}eT);M_o4U&8Nq-|S-ec7LJ`o5mksV5zMD5$n7` z)r{d83541VILxN}DW4_3tQG7EefqOLN?qLe#Q-c2OG0;U+A=Z)e8lEfytGjgCnQCl zefEKMez=&a3?lUOPv-wE2KZW+n-a)W5&Fw&|j=>(J}~)}3At-QOKxcYAR-mb-gheiSglcxtktpyo&$y3e#@8C2q-e-x4Ww(`uUeasDE1 z1(4H9S>jBchp%}X%0O^r<+E5w8{B6dT1GyHIPhfY!9iM(H#i-(qh3E6fuE;nL0dN4 zbkq(z%&he|DjPj?*W~-!k%PaX+1*#VaW-b|DZFpIEIe~khz5>KH@xrM?8XepHk zMxUndoxOx!eao&i#2MSqX!b$AHG~O+S4@Pz;4^F-c$V1hx1iD;d;%q7-Gx=ggMOHB zD_ov>TvQZ0c$2I*4t*sM5S_zeeWOH|H+XXQH(z=;or=)B9~cmydIHI~P?EfHUs+0QsZO1*Es z#?opehn090*T-am@Fr2`;{^SY+D})f%>k^~&ttJ`)^Cy^2SjC=Cjb}VCc5JKy89Wa zfNy!*^_b@0s|isCTAScR1jjP2oc<{X?u5!|5Ypy?AVe$}lt*QQo>dx2>xYCO9)jc} zGk`*?2ynw)vuP(wO)kPt2eaeB1cd}YlAV{ksRHAeR7Q#1Py5C2lzc?=FwB+VC}SD4 zDXJ!!YM(uC(E(^ae8AN8k4I0z7jH{&rbIK9%~bELY%=f&?|0g(;RoFK$erzmw)w+P zs0Wl6@^t*^3aKF7*B1K6HPg{6`;ZQqP|a;W))Ws{+~;uhbAt*(*tU5t+`&CP_$Q|;$z)^gADp*WQM$Zpw*gz z^eCv#B%ia-Y3yj^v|w3_xMH8?=_i14br1;5OJz=nYO{1Qn{bc;0$}%;1x;3U)0#$K zwyTw>P3Q;WO1pL`LhTZI78kiM)pVsn(*b`ufDJZlZJw7SJUm>*i!N+AL4@>epoWhA z5jGUdO>}c1q)qVj*P8y1l{Mh8nqBWcJ5-RqVm5lYu2>jr@3VRBK&V`Vvf7P<7-sDb z9x5AMMWqwE-y2aYTy%r*ms83?{x)_X(H6M7vE=Kxd3mTl3uYVSD_=6BQLVmJQp`0_ zl&lA$k@a#WoI&}=wWkgf7@{Wwdp zko;i36t%F1lxw2?Og)L0LcQp7W9;I0y9h zVxNlrXDkPAUzcya$@n_b^T4tlpK700fdv1@&*}F9a5|Hb(%rxgKpMJM)*>0hSzZsP zFgn6(@fd{isv8u&Q<~Vqf!bWFQIyyBLIkTL@s;8#GHYdpA|W%r({V6%KkNJu+m$E_ zY2Y~2YGuj^z8R7{nDIHhW-;Q-6;vnO?Tjoqd5SE78d%9@3LjY1709wa(Vg*3R2r?O ztl(1Z)<8gkOLiUJ&lIaO$Rw($VRjSaFAsJGwN}ZqWek4$<9!qHiw*RvtO(|GK1wR{ zg*U{lVn=}CEx41&Ux&8USV zoAC%z3tuQo_De2w%|5>iP{MSPIUSXqBwtsmQn>C0+aANEu{lk|X%d7OYy&e^xz-%e zfehX56HT%N=hn$)Wj4bLRmE=?m0VALPeZR>qg1@@b$6(I!f^*0gT9Bkehoa`3`W;S zwiMmanq>50l>s|I1Z3{f-`K%N79FDd1gm+8u9 z;~AC!g+rmGUftl2H4z8$8q*C#?z=q*rfW?nHB<(!i*?T49nN2LHJV*GpIxmeUqeFE zl0{&D+3^}liZ23L!9`zYQMh}6z&Y$K@M`jMWX{Ep^MILz=h;zB^v}P;%<<7 z8;nq=#~t5USy1>KCUkBl@L$?+TcOC(UvH<<4A^~a^#VSN8s}qUTKW>-fus3q(mPN< zs>m(-hQ5Skf0jwKF3=9R|F(W_K^I^sUNyOEH(SsY2p~=B6e<jyCn17h!brA_OFXnrdlG|G=gfF1O!fQz&2_^7koG$*tMsl)>( zA(1!>LKX&Jyp`|C+cK0(q|&NDo7kkcBgsk5&1c;GG_`7KeA-h|+r?d8@`F-K+& zDE4IsVF?U;CGlHq0mkZ}8?dS=QU&!lmjD!~EN2e&iFsd&E%TF7qM^QI)nIc!-Elg)0_+l{nvQmmZ* z;E{Qi1iSm;OEMt9J$2+ijA8ftfs=%^lEq>g@JmG3{%6vGd5J;AKM~tdr&Q!$?2^2Q{pQ+H@LB zOcutZP63XptkSsCSaoj~dZL~QBRu=;S=tf&-x$3X4hfWu@czCJi+yS(>3<-`8{VTs zbo~zRnq|8|UiqAG$lL@A+afI{T>uVR2*NWGXl`lji)F6R0eX4qAiL=MC~v);?Ciw! zt5mp7AIqkV&2paLXk(W=b)nhSQ6EhM8j#G>Ha|il0)+4hP z@|d7{0V9ylDtbWr8Tf4J&!rU-b^TmSqsc3xUWB zn8@_dW--(;Arglk3@geE3ka9tdJipFo9Qpidv&hI<|%Q799B@Eu9e8iOAN|Hq9GN$ zf8|39mbA*&{h~pFLaww!ZmG6zxoN=2x+#ou*UX3A1rhkNpFfL(-&wXjrpcyLOkIQG z_D-k6`Sc0Q6H$n1Um9hrUs5Y`*;3Ch=ZsUYm5}sN?hhkhvbC4-1!kz$+mUt5(y+Zk zfj05NL{W&a+=HT_ENPD_J!+j-nfEa333uVMf(pJ|=1X~lTN#BF^X8vd1p)H=NU2)2 zAg5H#OO@V8OMgG3LbtUc;21=4+er^n#F3D|=W-m9jmd!{)(L>v`I+K*tLkVz5DkAX zpc60I81lV3db|gK+qyCHk7HY)!H{e%>OI5GPaQDW&`B=fTXsQBA)z1u>t}jVkGEJ1 z^=8MOf?6+s?0Q;f^L9#-nPl1$h#O?kA-=WOWUa6_X3n+TEu25Y0W$)%IcTYFc(~bi zQw3&sMebM=<>6!xF zFse0Daj%=KRO2ycv^{U)!MxW0**K;Tcy_dNi{xwdM0X%oN;sQh+G#c^=Cq2mh9&iH z- zbJPXqd1qZOre^BN6blH^b02OK<=_0Ic;ldNI2fxe>~ld-v_nF+ie@?xJ$Ju!6USz$ zmYU3EPyR75r$?^UQy80YMI@V8$KNaa+4IZ7CINI4ix0`%3$jmqj>VN#W)!5b&u@%h zF3!ELoD0;Y6WMioqX^}9Rl@$<-#Kj}1Dy#wbl{vW&?4EOiX$BSsg5vx?r#XOS5%hv zXqar%%djgav8UV1FvoVgbzi~I88BB;r$&^@`7oDnFVdsUpuQ8?KMOVZ_m-9I8C+lRYQLlSeaIQ&8luO^uOS6Z%k+iE|^1pq_{Wntq0S!(@ z8Tzfts3d{@@9y^I!e%UCKuYADqIySB$W@ZGSSbVeP z#k+#4#Ne1;kO~Yk(A{as&;bcZPQ`Xx!|Fp|B-U2ahljVUkG;_XzZD#%Jh4sm^T64k z;CW-x`*a``L){5xN-CnrDq)VB#cyAoY+Ci=4Zqf>XDv2dSc-NA8|wsR?3ec<*o7|r zcs3ngX8OlKM|bgyH(aE%G3xkj%ayCQ4mOlp>9gX9rgy9xRU9s-NSA%2QTgz{Z}(NL z#?=M04@HJT+0s5PR~kFF6#%1|o`eKXj3`+ie)^kp0DGW|aOgOQfBCI`2t3>NGRC5L zNw@#A`MeJ0^;_TO^NGQX{R~n0l3ga0LO#&$*$wKByY{dXS&U(o#gnXE7?R>kTeYG= za7Na_$GKV|pQI*FsFRD_jED1S@sIoxM+V{#Lt1W!$WuQlQr){i5Hf2*OT^}=!f4n? zSpIs6QuP#B_%&G(I#K&~Hu~tl=q>>`5iBUB#R)qo8xz7lLSNeLi)!^+Eq6gBHCFsc z(X^S3?8^yMpW(!BF@`uOH#7>A-Bg?OfviMh;mcsOFNgpft)j_pJ`(;Hy>7{}6yCf1 zuTCg6cAEpkX;^fAiO<+nIX;#e%@2y--qFjGM>-xQtq)r6jQLpHUDBb$WX34v;%n1@ zs!QYAC8x&kiJDS9pv11+7lu&zQ{AqzVq`{e+;L%?r338WQ-%s`uVO9lJ0X8>!xXq^2m=zWAnmMFq_)h1yNGU!0O4~JtW%9i+ zJi>tN7qmdrmPAr%Mpz@5`=fnHHVl>~ZBRAzb9zP0P$@yvG4%^*-r1gJ+Mg`4-s^ky zgk*yf7u2}y7Tb4i%U*xM2iOFk0mmstd-*WIL756u1GzVx@nhm&qrDzno zfVT}TVdrwe$7jy+YTEMueyJlH#iR5PTL(Cfav*l;~~ zT?k5OCRh;Sap&o9nVt<8vT#oUT6{iRC_YlN!K~ta0E^puqEbl)=DB8}tbcC}^I*ok zrS4g8$KI;;xN8h_VK>JfjeI5LW@#8MifU_%rP>>*DWdcSp=Y{esS9|8MF}g=poNJJ z-q5O)MYs1p7Xs1K*&xCe!ZPln9UZwh!mi^71CNupdz*KAhU!}DXuHRJTV~ng8u=gb z0@)<%z&hU%U|~^Hh$!|G35!YY*CK#ln%Hf>7`jkBS_}>jK$d7e2BC$TSE|{P zii+FdjgF92Q^I>K0*gY>1M>HZ!csiW2D1pn|RX6R$Ph{NAz zD~&m}FF&N?vaDpX5`TUT_p|Sd-=tI#ivuu$N;~cSkPuFp6ai^mRd5U((v%Jte2L?= zU>Uvxb$3=)gdm%)`H#F|pj;LwwAyWk{;E%7)}w@K=+mgTE5s{St^BG!6QQ_!9twg8 z0oCSl9ZQ5EzD$*}Fr0H-tZW-p`8L0_He}If1RzIRpAJw6rB!2%x+9CvpD2HQ^2BS< zG1x|(JFo1yp-L_4b$_I@XzmiW4x4_*{Sp(^zwQ1;YPR`miwW~)HI`e)ji}_i4B=Hw>!1k^ux9HW-y>M&vN!=!-kHA z`$CP|IqPsMly$*q_fz&L*D5nb)K*AuoaPf*o@vyc z$C@C=ffP*vKoJ6`mTc^@``Iwkk54!Zfm?CI$2zoDPsf)xR1jhz{qLfh@;h>-h@0(R&3=G1n3eIs+{O`4ZV9 zP!svT?w2&m5YTqEE`!=zf0d*b0gUfq>ftlGIP~CEo!Y-X{IIN2OCRz(*7%!532T+VJobl=rW9YEsCB~V zYt5dO@2wC8;~=EZ>|R*@C|Rh}gUi(YrMtuDD-`azXe6qPu&JVLjPg0}`Rv#`z1zsX!oU6n==^deQ`d$naSgy$+yjBvy4Ld~%D==@+5Wj4(n( z&FEvvllrt;rZw@eX7|thA)X9xNvAfM`mHjqJW95l?^+LQ7N+QOpPS|dKP#5YBrj`n zIm>qfr{E%hGfBz#-$-p{1}PCihc%!Pc&#)#ks%J&2n6XCtf7$rF#*=k<{hjGEf#}S z*00Z36Q?hCJDxUg)vclYSaojpG(m8@T*7vAbi*d;ZD+e`2nJOwlI!pEF2Sg&G(pnK zt`{Hh-8DOo`$v-dLj#doMvhwUUc(YQs=@v-Xq{sCiF;iQ9}{n-#I_unSpM8xz;3hz zUfc*Q+UvSa==40m8jD`!Djro+3>48{)Q zKKdiSaQ+SelW<$!KPn&^GF&u`atg=;4cq1M-`l)1d7-wP^EX^QvAv-bAlX6zlU8^`o0F#+nX(l|%qW%oXhA$5H4WYV;hqUVL{Lms*V-_k5 zS+0heA`$U&DxT(UMUkI}OLtTtD%iYor?u$K&vuPCAzW*fJPTg3EaPmKCSV6hils6< z0#_H4HNccF7~)le$DS${49qsi3W5G{b8Vu26PJN#V2P%io#ZHUK_M8iD;pdO3mquY zP1-LHd4hp6P=}BSbe_IgdVA6(zmDLex;d~(AeR`(Ec54VZ78$V@a6Gnf8(1zztC!~w0phz0n3YwrDwD4k?HL%aYrnQfWWc-2 zj3maHq)|p6t^x11wV*sNP_JQW^t^N@vTUEknnx?Qui8zYB#!Vaqs$P`s)gZ;sQsxh zq@cuAl0ip~(KdfN5h`2fyV?e` zjw#o%@=e+5X$PJ&m{99KJB7AOVk8sr-DBOI4*DSlH5+O3nxVa&Xm&Hc1eTc{Row`; zCr&t4`CH_5-_pqf?uwiv39jdIsq}vnnu!h(Qc>_Uq3LH;rS(=#1F1AxoZaD4ncj^j~5zl}oj-Jexl&=Z$?USWNYL z>@TKYR@k)>Sv|(^WVjfsvu$?BGkKVi?J6t^vj8}{SM*^44Rpu5}CBm7UG&mpYqfM+cW`HAdI;aI} z;9a``tRKu;14le>vR?SD|A7Z#Wrf5=ZW~uX_kicB);MG5`7dd4bJ9-IG=CTVNDEupRKU+Gq7HD;gKh(vJu8MJ>huyr6_qzVXAbM za%BbXA8;lE?Ol2(rYF1{S9q4D7~!G|FEURs{?_DC(E3t;>mH%%)}$g&8VVZ1Jenf- z3#gGoNLH8YQ{Xkgpfk}p53HM^d=i-x}rI*SVo15@Ie-@ea*`pRK+R2RAGqY zr%i+hPz=>HA9M2S854l!bAh|d-mqjV_H*_O$Fv<|G#(S2$mC-m0&C%ge~a_P{@Y6x zL3Z1WQfAP=BUNWJT!%Wf@A9e@gh=KV>%)+TV`*<-J6&hPypO3`3b#OuCY7?qi!CQT zE@|I#70(7%j=x_nZ&C|n1!1wL!h%mN)W{KipR+i z6q&FPtmAVdf3{i2ookZt`(0r+MGbAT^a+H)Ba&`+l)iu=gP?Owak&Y#9K`zZ5Kp6J z(BZuFqxiM@a^I_|YzR!=_fyCdOzA^_@h9O-`$a7WG-ikkIy&4O@OBRq|DAIh|9 z_VkkWXRHCyh(oWTgGC*UaiLaaLQ2_l{w_`7N#hs9k8r2iH&l6yrIObu#g46uAF&+I za7h~Aj)~-^O@=jPRfEz0jvZ}@fXVpLdT@6nm0$f@`KxII29yAi+NIc5E(`~Hn#i

MR4eBEGyd1rVfr1t1)(2tUQ3xHDp9rWzFFL`|wW+xNYH-N-?1mS6-C=e2ql;Ey z*sC)f2)Hc|m-w(=uO6ZNnL%z7=j9%dfk(d>H7o<;hVX1*X-i4PYGyHj*3`+bzlh_Y zd}s1e;-`Il?RH=*b{?u;OKzJ`B)GNZK2he*cxUI+J=gB1eH~r>O%5C~G@bY5i|act zW55f_=R$fBMmXM^UaCz6=^(e{+vG9nc9_)IZnzfe37-9_rtP#@Vh5i)d(BtX(|SGL z2hM?MK>a$F)gVXLclw39Jg)ql6^zDX(9qySZXf&Zzn7tC zG_v;_H!t!8NA3NE%`vxR+^v0Xz1qBW57L8PV+hew;?yAz|M9^y?U zhLeKl*Fuz<4Z)H@G+#j{GprXucW5&eZL#kwhiZ$t?;Z~qYiXTKgtxB&n5gY}f5C;F z=kqappbTKgP?FpBn7Ly(gzr=s<@w+}Cs`@u9kRKGoKFG?PG(uITSOx>9+euwg$Yx} zNwLS1Uc#b}*~~JDCspoQh|i&v7SF!oNtBrkXU{8mFOMo5E<)&*0c8|Zw+MLvKiBHg) z{Yx@^2SnCuj9cYjO$#GIoaW^M@Y%1E=f5STdICqYj{_6-QeXesBn|IA_-ZM)z}`*c zBPgJvE<%pi9Ca`3F9U5lg7a8C4tzApM`1!4vOfiO{Q>e^OfGN&Syr}7aM<#%8KY~t z;aEiAox#^U7Px_rNT;R?v~y@czfosUQ_q{;&RfW>6LyxzyS~gbl{Gy@i^(ata@hK+ z?awXsWlCy$Jhh6^z76DE9EE_^(95g{0|_pxb|9wZYMaw5G@n|Jw)!|eJmihK_95+4 zJY+1JF=jLa{^m+V-u)YoJ4aJ^bXTVmG+pVx@45oD4QtzNWptQZDHW)j8U`WO3NQ=- z8Q2=jYtS|FlC-w?aw)U-t+IOx&R|3yK11xP7H$daJh!Fj`Na60M+3#>h<7?mUo1|8 z%f^om>|piMQFAXwBD?DNYqY&4aF;9m4fZlMz%El%n-r;&{-el9JHXF`O|!PJg=c7p zZ@Dzoe}=dT6*3?>dt@{moJiGCTAP!w8iIl&y1Sm^`Q`}@>g^LsT1X5%{r;KSFu2Ik zYqyh?5c7O5+x|uy8?4%|45_mTsV3@Kb^{&p2Iy%+@;D&0U>rj<)bIjB&%AWoS?Zj{ zu4J8wT{^gmDIk#B_rIgK9Tm=?`D_KfxjI#|^4(*(HS3`o1MLbA97r(npnL(SXl<=J z8?TC%szt*%vO0lDMcb?scSLocwV(@~Ae{WH2zIO=i_D z7lz*i%C{x8{}r^P9`y8e`$gxe^5?(`N#Y4n?&!|D_eXqD^j81YCm>-3o>sa#<`376 zlIqlCw?UF74wM2`qfSz|4Fqahd#u_2=$1K{b|hOFaVzB{XpI65-dlcff`t#Zi;b zW^4cv0?Sk|NCwm-h~ChQ)=r9y&P^AoXBj|!(?Quvl0;6gJ;$xqneN|KU*AtDiyT0y zv3QK30-e-E8aRSwg_-_UgTzt33&U-~`zl@mlWSvE=ChsrRVzfn?Pg;g98B0V{GB{X zGR!MTT$NPB@W-iw_sxXG-6VG;9lLw;jtMT$uSAOt7;#H5!xrPR(it$g$+PF3$V+(x zf%&5m@h47$lDFZ=Z}_2dLiNg^_SeNKS+F1Tck_*9{0^kP5OfkC#BS|Q2Ke?P3|w6< zm*}TWjPPhiFNtpBwz%yvBj|a$M_>tfoKX^MzY}_On9IkqRyllodpIC^?Tu9Vk>5Gv z^RIr>!TuABxOj5DXd!$fGb2HWo-n-xzKtjcPD1M{$)H?=z zFrj3Ysyr-a-8w{LG4ZWA$3eUQuoSQDT!YQccs3r?0~!OLlwYZLjExDZiYyF#6sYmW z3P8&Kf;BOm6<|p4@NEH7f^0M0Cj!9$+yB>7j@1W3hrdk$)9U@JW(ynyQ}ci~7Mtzs zH8mdGHAJ4>(ACXzPQBP!bNZ_J>1MZAzc({$NTa)+i4mB5&Z#0%0?s5^YF4F~|7_1k z5#nF~;JhMv4~txa6Wuh~`C;GFt4zNsBRjRlX%$AtCB^pWk_mA?-5Uz5&JFsAe=J(MGIZ>rS${FY0PVxcDm1IV?0bRla@a){X*a zfrCbh9R!)BPDj-Dd=CZ@UVsfPSdKvZ#eLN?3BSDyI98>Wc{;)bbNWIdwWjVUlW?=j zZ9fge5zKx*AXwl1=iUyKLICiVE-LSB6E*bAUm)sL1(LFYcJ#j8ZoBLipT@$?CioTM zCrMcs#s=`{C{EElGV|n9R*5u=GD%yI@3q#)u#qo3aYyr-}yrwWm17W=D*NV7KPD`64P6IF4XmnL$Cw$<) zp)~XXV!@`IzA((o+gRBzoHz(FN1e`W8MFXw zWnp<0Vgz9H`LVX1N#anjW$3pxED~O|k{RKcrQKooF5Dd-Z|=CY*V51pH)$WdEDY2v zO40wlES0j^FB3#RI+TBH7H~4AuD6}WN>M(P8~!NJ#50d)a*eM<1QDE^E!7!sa-R!6 z3m+Q|ct_VA%Faab-pFN$C)@`o9{j8l$~gv~-BUcgX3|iJYBa32RmWoMaZmT-HNyBb~o>B!a}?N3qN%>=R1J#3IP|O@;PC2A`=C& zp63Pd3cmq87kJCk4yL2A*gV@2a#k!FCdP&968CcWSaM1Ru^{z=hH)4+4uiZsnmqZ( z{rK*;E^iu-#-iPKfQI@0*>jD#Ym{~)q!Q5cFMWUJ5{A#a^SEV!^^U`}PKq8^);fW& z!Yn|-VeU7+`ez)?Y=TTi)p{%o%f7H^%=(P1adSoIXa6;Uhi(0YkE(4;)v&{n?t%8l zIRaDx@)7`M6aOq3X>@@YHhM@d{%%Jxfp*VBLO^dhp{?*l7H9$)z0yt@qs?Vu53TkO z;XrF!4d0ERQ7%u}qyMI;nRHa9}nyMYhvCP&CbHXOHb=rSZE zyO1}2_vF9aMV`&eSFk*kABiryO)r|FT+N5PpGpvWNHmm+p7GBqb-vwih;mGtyj1dE z2mdicf^J;pZsLgE^Y+XjYM!9oEnLyco0Pi_8j_>R?xpx)qf_qR5xT%Ru^2&MY$)$~o|(`70W{5v={BWSo#Tx%F$5XU=qvdrj) zt{vrR6jk3DZyHUtczCGX!M%#zsB_=H34V|85W}EIG6Yug?2biyiL_5REk=0e$YdqIjjUJcZ#Tudq|s?O zrZ|2)#Mi3>jI#Ec4ikmUC3<{yMlD1lT5&#pmFF{Nh_h*&NhC`~q|c3@PZ2&;R=SW| z45keLv}{E&wZKVTrMM>P4o=k5)xh#1bS99B-jQ!tZ*doSpsMG{MpZCcv1d%=VA^PBJc?+A3aq@;#wU}4)HB3V1Cnr!h1aE2*az5;3$| zWEOW1fvGCmgYC_p5`_IfUQxuhL=eP3;n7l7t0%#&kWa9G$i9&Ftoe~f?)li zfPc5(dIEL^mBXKwO|$)$s?3CpsLwpcW7h?z4Lm4`-Dlw8g2Cp1`fCV5@p3=sR%!^Z0h zj$t16%|TKrLnhRkHu+jYMNbm>fU4IY5X`Dho?27=cff$$TjencuU+ljjJNX-l58RE z1_s5n9yB6f{fWAgMU6^h+UJGxBHuj^TRn7uS<&G9umW$}9+b>}tvr4CDg^QD=3Eca z4fU=bwlcNFc&~jqz|YgBEvJ`m=6XyUoFCNvS^|kVKH`iz81*0s4t-m!Ww% z)QqS#ski?IUOHakfBq2n_Zf_dY8JWDtA3L*_8=>yT!uRoG2elpyWK^hTW>eOwq!$A z9f3{#{ZDujhL^YL#qY4+LC91SHE=h?10N1)SND*YUsFiaGd$lwt{$3LEuv&IlCsPX z{d?h058n|aeRT+(d}{2cQE%tfRd!|=Bi%K}#5N@m9+68wdU=1jJW}@TEe8|IPCwzJ z8-f2v&!I@E+R77o?CVo8R@Tbn`DZ&V&eY~6_BUW(kJ@1sO`mS~Vt>uz7h*#{CBGb- zPIWCAKb8@&D^ZTVmmu7Hhx??b8t5p7=9D5gJPqx(U&oZ`497Ttpu-qSv`syA<#oDg zp8moTw<`z+H(0u%!>&)*V$ew;??uCYOHl!OO72h7Qv3P%k4ufk88?m2U#_5U;^beN zN*CaURpxpgYmeKdWyAG0U{1MNkhwwZK_%gbHOH%?Rd!OShj$>|(}&iZ-901)wdC$O zvfH2HJ~XXmPCaJU0bfTD(p+REfYIqX$(hglomP6o~(C#(qvJa)x#V^DX->retf5Mas0I3u_ftenM@*T*Ug57lcP`LOfJl7CzO_5BOz3fb zy@|ze_uQ*6p;(g~%|=F{J1$*7*m?5yWaD_jarUjmPmd810U7lih>z)zdHqA7VJn@G z&jm4G*IB7DH+qjNEd$JKr3odukb9`*Oz0QdyMwMy86;JrsWLqShVkDvOE1Nna50G7 zgUcIntb^rFzj0()Em5c6;~~wpSPW&PpdV3x;e=M+@Y}eTLYIB9FXu*Zdjewj3ZtJ!g4p#`fDDyRC`v^WzTWtJH} zOh?S8L}0hBH|}^{zIuV3Wf@(8GjR(7Q#v4B>7Jo#J{U3Jzj^C|TJdbRd=h-5a-Rhy)^@-aWC9C#< z(yFHc@q2OjdS|nBi8|2-ac34XvRd=0V$2Cy{sWo!PVK&lvDf|ZvO+G%hzzXOZfGUq znce~RSirB=!6h3G0@rA|FVpK?hEmxw`JEc9}iJYvOOb!k$m zuJeIb7p`4n-GUC(Uo=yHy)PIvp=ps{J(g{rfo^)^GF{RgKt}=|I?R$alcAW@I$UwN z7&m2(N|PZ0vmRkh3|qO;&{RAq6!^XHCzGgk%_97KrAa$>EYpQ;LR<$bjR* z?h((Z$FPwU@Sq_Y&#ba1U1E$u@ZESMv26sj(yWXO%eDPxR;A)a z8qD%NVMa1Jsx%213%mGfhkJ-1HW~{O&sh}}6;39)xY+MdTZgDRX$X+R+-Ns9|2p2!-DG1@%jZ}(4;uLv@5-t z4(n@Z*<0av-azsEDoRR5{GXo~hYoI(0(1zXdQfCaW`=k9TSd!^cHJ8bnrVAF5)KCTv|4sppMX*S~J291#(tfJ6(n{nmu zyT%Ijlle+9ff};>6B)u@`DqDD)1jzlGk}eB#;WK12@i=7{MXX*Lh7IW(f(1Y7Wo^P zSnWWUymAJvc_#O(s?o$L)6p{|#E%$2dfm#{M+cwGI;U0bnQVz7N!$ixuXw>aHU8J3 z@LB}VNkktmR;1ve+;E5#$@Pv|*;uq{L~zUfDeK9jOsanOR8aa#f@Ih>M0dp1YOHdL z5+sOryI3b+RLXFo#0t9q?r&o;Keu^?rAs!-u8U}$K3bw(Jjvu64=xj0gwxn88u>&2 z_-c#b8dtQ;j-C2VRt&RVfzV+D9Tx1>tUj1 zoKXtp-;`eq(iFO88%URX%CJF-EK*L#$-CV~qimL;y1f=_~y zU~Ju(Gw=hRtiPpa6{k8hZ`6{P4Zzn@iB^gU>I`bwS$wa4)CUq5 z){YBFe=)`gJ_TGRrv6nBG0=gaNLu0S!D>W0ep5^Cu)$w$wk>~kj+!@e51Rf=d2BRr z`%S!|v6<=IfWYt_u}xuPKTuosd{yrVq$NRP`w(2J(q3j&y;$(tN(N8ncdwxDV}omO z^e2kQ_uchqJ$i<3t}?O-vKwzjObB5$*i|XB9K_9QG06 zQ2te~(}=MD4>l}b?9Y<_fe3tOg1bQ?jp+c0*`^aU0tZh}4$zOSy)MJ&fPlJ6l{J2G z4)0vxyWzAV`gVh#evtbzZYwg|$oU5p%+D@r1GU20!4?d)emP1^A(YLNKyWMwo`+*ol z!o*J-+PoS*SnU(UVpW{V-q1j+MffKMgCfo5IucZZ$G;Wqx%zW3*MdR9p>bvYQ-Sem zZ5`X)#Gc5bn9h-n1@cDqmF^Sw*)Zg)vQ5A056AK%Bw*H3Z(dPX5Fg9oVx{pU4!MNj zwS$xw|9&5D$PrPh!Ico3>I$wDA%%O8=>;$*hx-blep|Pgdsg5oxQ)EuZbZs$YaW;g zP{b9X`GQbCRo5fpBl5F8MrxfU5U^uxc=Mp(`|?eYZr$#QKtuAY=im2#Sbf;w9x6Cw$_v#StG&YAaRos4kKhYe!&S$jGCu=0) z)a$TrkR~9a0Zb44J=i;F9}MCbptW$K{Gff40(b1Z8zIY${v(#YiU}d zUM};15MoL_z7CM{JCVVSaJF3|#4#Z@aJ zOQf$Ojc>V5P4&^w^d(4mO#no;rq)BU-1JZW3&~M+_qZID)xlkcbtv2#^Zw?318?b{ub3X1od+_k$>I#E1}LAzUxbAa+3jXCW5ji{LuW?(?|HiE zo~Zf>U4oxv=4O%TMT+61fct?nD38|pRG4KDo&2yPB%&sQ${V6(3Ha|^6h|fGuyiw# z!E+;hu>Cg^BHs7*Jq*bQWJ7?Y6a+ zthBV^D+Kc|s&`Qty#k(2ZP{8AH=dn@3A`O?wY%_CS8-tbjH|6LME2ZstknRL2k_$( zSALzwN0(Qq+Vw%X%g5`z6o(R{NQD|EI`kn)bv+`NU#mH!gA;@WkoC=+1D8gN{j4PA&mkaj zSXP?mIe{TaD&cz0q2iEVD+jK!jUV{SV*@B**btd~(p>BttcRlZC zs-ASVg^>>=8lR@Pngjf%2{N|7$x&3DNtf`Au!g4*E4K7>moonpc z)EiCAB9`Pj*pL&7!NshbD>ZdTT<**`c zdso9r9_VzKL@ca38VadHDRq-X%?=B3QVa)bp>xT>8kaSYw&%%q55N5;AZpE%&r|(u zT1FLJ*EC!lc(KTY)938vm$Ii{o0$sd!*|Ltu7*W1)644xKlRgn ziskBgU>)qO)YUUY!j@0j>OVU?mI~FR?3t;MZ1k32W`D#l$^O@sH#oFxlg;VntdgZ& z`a8i@9~kFJJIWIoM_L!&az1HPlBfVj^k(PgRIxsj zEn!XG{Le2&=o0ld3n7u|bqGofjE=u<<23@}sa)}s9#=g7mugM(g)R`2(aQE|>i}rN zeT2y6p;HLTtbwS;h+mBBF)6Z@t~g~ROmQU{&$H`fPRl@{J765;dvmyuK6p=g&a=^^ z)E?z_xODIL@!}>kL{sc0{2XZS)gb4pM~Ls$+}>G67_r<_M}88BhCEZb5Ez!MyodAS zWg5koGgwPerpNF&=^dmIRZy!iV5QyvK(8pJEdS<$lBv`Q`1?AVR`}4dRG~imsuIM)^g($>WNseXWNy5>!|^Vcq|O#oQR=P zb#M8AxCCl38GAhEK<#GN;>7PplmXWkN*a8U*a7wt8Pv?3K_GIEqEHD7f5YJmf2InA z8vkvrkB_#&+wR68;~W?XHYm>vfv{A3lW$8<&2LU7UsM`F!(vsn`6tR|-8$P8%L|zh zE(-XG2KAiL`Qy5Yu5>G4$R2VBz3H^Av{;IjX@c zS`8z43x!q?K=GyKnCM~ZGRgE&FcmovfPP`cPG9B z>x2Gn52yJC9T;Iuz>pvUy{f89JuIW|^6wbs$O(f8uJK0qi2AL{8Mv3$f~;7m0-zu>_}x#So&&T-v&>-%%wfB;?E48}KmH52qQ zOI-81ZnqmNwLsai3&qdjpGk*;N=atqZjcZ$1=h18YwFIY{6f%lm+P)7ZY&vbGuRkb zQcyeHT{bfHVkp!}vKFb0;hX zjKmM~Es>tnzR&kW4~7rVcSGUD?OTo-!1;<+Y$`yegRaQ+&C7hwgM2KVZsN~iVRL$A`9Ii$ud6s%8h z{R^e(+5@sZVX#Hv5Rw6{rh#=4>IYXqQ%X@8Yc4~a{<=o3Koe$>loto(p?39gB1P0B z45h(Sp1~bNHV1dajfDr|ZV|pe>2(Xn^kH2MRv4ZMybg-I5ZF3kUj}Rin#Fwe5-kIy zpidYx6sAc`%cyqSRaFi6N0+S*i$wHq(9bUI3q&dgq(hwA_XOtcM_5O(p4Rd;ph_6! zD^AbiDZe6nHH(BbxMKVB)sejV^xx6pqp?YY9rw4}N(<}p{16lQ90&zDS+$(>{M?Uy zLB@}^sHYuO_yM^mD>FsEYP-J-;^qXjnhaImMytO@*%=KBE(|_&4XFn%? zZjyd1yq6G4*iuuU9wmmS?#Eo7Q-Let)P<5+cECH5xgRQ#@MDxb`eO3 zA+-CL<)^m`uO#2}r?iD(bM8Eah%pI){7Sh=`kGJYFn?+P1{ea3X%BZI*tys<%8#6Z zJoAl=%jyFF@)nO-Q@%SSy#eH2VyH}YWMYjyts8g#rW*HHv6md(ppk}m|5{u}-yz-O?k&9WeW^zWXq zh$#QzX6)2WX%Sdcj^S&0Z|vut|0Zs#MPQK;zpO5fq`zP^dQ!?U`0S%r-Oqa7w=Ki( zmOU$`@osdHu!o|=)LM{xJsF8uAr}0j<3>14Qme}#GSbI3)wW?9X04C2?q62WKpAxm zvcDX@9_DFc0rlBE$vd^s-DoVww$_j!sz&{$lZCl&nPwfa1WrE$zBY^{Qbc?Mb%HWi zY&Z9pE;|fP@69-ka?)TKu=H^&wTnOuldZ(nMK33xR<)DwCEE7r5&%jeo`z}8BMR{B zfCj4WWljXIw+8ubdqD_hCzJ^Di2m%~&@9YzMnrF=RTpNh+mt^c1*?5WRbpk00MUp2 zbu5_sAQR)E48l87_RWApg;2KursOU6%|t5mh2IH*IzV>nc0{;fy~2c4nMVCOa!bW; z4aA2P-JL8p*I;rT&<$+Et>pR9Mi|@Xx|8caJ$jS&>HE;F)kODlx;l#hqhHT!9lY4? zvGK1>|C0UQ$RP-N_{Z2L_Y^fD&wVVEG>LrEpZv;-tCKN=3Qo<+Rtb5Qvr}MN5C0vEQB#?`5 z!6~E>yF;TfI3aD z%HO2+`6$k-H~~Q}bFHB_2RQf$?bSuSMHuc6F;dfk9P@@hv0b*GIWGMqZ;uWS z1l@c9K%|p8yWa`tpLi^?dSp}XNrS#J>!{;b*7Z%YINK*tapP{vjZ=8d6%gSN@n!VW z7w?gKgOWp3kt4c6E(oXX13MG~;SO0Qg5XJ#ZZSMZ`2h}ScTn#Y8KSlT*aZxh3~7!B zTnuBIJm8-Ah94&J@aY_|$uNeRK-5<}U`1nB2WFHxM$l8GJV%%jUc%VQ8-HfeM(cuy zrXL^IHJ-=%p$)9G&aedCe_v>dk~hJrGbS{w-XNFH8C`g*gFan*6gKav2aI0-x+=CBSzBU^R8fOwi=7MGp@&8~@SM zt-nE;j4^IH2}g^_CAJo5C^LV-GD(Q~?7 zBSf)!zqq;Br{#?e z>YoOh<+uv(>9L1-{Xt+Ldhqj5KxvDz^xpV^>wooAl?U4!dxcJl>7n}#d1D68_?P?9 z@88nVr!C}qM~(CaS9|Wksw29M`pV6zdG+EqR(( zZ^)pvhR>*MnINL2s#Fm0X9>eL@(d*#zva_-G_A28$_*cr#P8+9!S}>xYuTh9Oa{|d z^@FTh6~}mB-~eRb;&Ic;6y(unI~^}6N0}YH)nI&#N4*@X-Zg!s=8t+Igq@WW#_vXB zg=0b*7U&^gP<(o6AGF4pOi`I7>~~XJ;w90DkyEY%t7h3I-lcru`MQue))ZUvISP*i zd?ZXyd=UJtZZ)?!Evq5&oYW=Qfg>#TvxP&K8yx)%PMkcjHXC7_~4t zn31DjUJ~bsFycu(bTO28ooX;P9u*K_4fO#6RLl@XIDAQ3V?`0wY1NOSy`g!SIHEeyUS|`Vk{2AVsQbDpdRO zzC)W5tK2D&UaAm0XF`qfB%D*?oOHckK1Ag=9-xqt=WY&>hf81XxE~x@R(c+zK?Mf# zGhi@PK-Vc@UR?7kWALS0cl5cu0!eT*;93<*VTnyL3BloVpko51VZ?aW7V8Qsbvs4j zZS7jxh@5i)&Lh8%{!&M1#RV}$l6)?i-nwE0?rWz~j$MCmduN;$6Ia<1T{^3svgW`d zfaD_cZs@Z(9=h!l7!!}bGlBzs6sIOIkllk!b0iX`J9fOelu|~mG3dL)@B98e40MoL zpQJVqe{M|_0kB!vfx=ETioO&UzJd?>#Z#A%0Ij0e%8=2#^V*}!&^l7AD|3GDGu!gy z>COw!(eSgD7+$#|Svt}?8!g#WMsc*I_bnii9c15Bk;^5?WIx$In)6^yP}cvX!uwoT zf9H7$-E7v|P#xxOBcF~F30nV)-|WRCcXg|dib#ynrZ{6#9q_ay-JcJ*XwoVbj51#t zwZHYPa<6L<3#KD34L^qwR2sF}?ND@MJ4AFDry1DMexZj!U;&hbBH}7S$JuH&ZLYGS ze!(g^jTAfCr(H3I-*kB_p@(P9q-@GyD82Y@4gF6q4r&*<+<3FH8?njSi7oaK*WYMx z&dE@PNRm}`(KzY0pQYkiF@L~4Lav%Lr5`OaV^^hgB|aX$HrJ>Z4TLRmzt*H6ww&_< zprx8r5PitZD*NS>m)M#E#lQEPDyG8b$$oeajTmw7LGH)2vOz+B&rt1g*gz7R!e!hQ zBhXL7cDT`Tc6aU4n7?l4rKe9nErw^Pez&rMIC2p$bU@$-E5yGOuN6SYzYv_j+sBq5 z^-!$2J|>ffU>8FikTk9)@`DED8X(yyoEOZfvLz)OA0fwO5pB$9KcfO2WFko+s57fu zNXeO-Fk&z6RMbaT)+j$rS$Ydx5k4AcSTdBnvg0qr3y}6b3p)Op9fqF}4Ro!wD_+AZ z5I|TX$V|=c5h#9_yL8s2a_G70Lw%#h-H98{BhRF^jFEe*B^CwQM4`Mw&TBqi3I@}q zvxC-zH8E$^=23PxXSJ4csv)Q@U*GhN-8trd*zGO~OD*1(Ku8NAg`2)yUS9tZQKeG_ zV|xMii#L!%PmaMf+gwN$Ua94Xzm4I)Oh64mecaM>W6p=c7b8&tS3OtgGZD$GJ6W&I_N|CU4fi5&ii+hoEx}#gl znP*T>oOd%`H@T|8X)SHix|F2~%5AL$@6b1dDxM*-W0{U#<93dcUzP?qw0-g~D zhUD2RX$cLpc9_)ktCp|Ej~levt|dt@yjELnmrE_dNH`&Pm&<=p zcVU!iXv%S(-yH_r5hN9qb@9;M0(`@2OtIn}ju`$lMyoOe(*!?OX&+Fhe_)F_z7FQ! zbB}GxZbjMThj@Gw1}GhWoZ9x8NE!K?f%gqySJYkiu-X6BQu{P#bfRd=->p<|Vh>68 zqdcuTKM(xs3_y<(t{DVYys$Gp&%RCIciqlD`V}H|`iMNUE!7^2K~u7~d;P}A$BJ!F z&%mUt{!O2a+e|#BJ%AgG%cQ;G5iRht*b7r-68LzruXzo2c}@yJHpNXKdVt=M(Vs1+ zL6=P}U4W9a-fa>hn%)uevhsuBNuB59!1giN#jd1=4d~zg2{MMCisOo<68VUJ`01zp zU+Qz}M|IgYQzcj<*J=Hv>I)buLnfW?V{<=t1Q{u8qu>y!%+>P;H7fQ%-a9lmx*r9H zRK%N3JKH-$?a-FBvDu)^TVKtcmy2jNuTHtpn>hKq&_iIBwQw%s(Znb730 z`>ujvHA?_@@ODpqB!WCxKBc(0^t-ezi>9V{j2sKY)o)53n>2Suo*Tfia1MYbNh4hZ znGX;V%bOR``jx$WBJ8CyAYee7BO4w8pX=##6d{e+t*TGoBl z!9Kv6Kg`TKpi6j~X{m)2_#Qb~q?54JYGm=jAlx?g-O_W^9=dzfpI84S@L=)bnlGAn zw4~`x(h<<}#7$-@HI_g-pte&MLD8J%!>)u24p}G)1V$=^RI(8itf4DBY0)xxnEP;T zLH49Ycck#oQ5WKONvdO@p+;fiu5Hq6VGnzaCWSrWwwXw#R_xY8YaXS>VxsS+y7LaI zrgPb3y@O$0inyqdiJAKN@YKzj`nfKORmpHhow(Yi^cRj-0jaYY*xQ%|dzVG^$;1 z2kfd84>w1DZS4W;LxtBzO>cpJ0%P1ma@?};%7`v?N>+JVU=^a!-b+~8YhJm>h%YIE z(r_HNJ(d^3W+c}fMrw5fP(V*x2(^7nj(qd7KF6L3y?I~&jemiD18fYrwA-D?rPBcN z-UmooIG9o@)0~pbxEg*q7cad@1SbUIa?6DyuXS`v_f8E#51%U1V;uyb+`Vf1CFqd9 zRI}Hs@>GSM#*-G0T5vH}zcqtY8hI@K!Qa9Lfd*XBrK1K$#4#LM z5IJhc{o2e0Q;G)gM%&RM9S#M+l4U6qLsr)GADFly3d1NK!Ho2QkOt=8=P4I8h)U!J zMKH;V#AUvI=o;zDxIHJB0m+pUrLS-PYON-q{iLzTmK20F=?mW2JmaOF35Dob20c{0 zO4^j1#~=%`5X20ijBj{#YNFW&TqFo#NC)8CT+zAKSNw6}M-Qx1BbJcGpxiO)=Ao*> z^0z+D8y_MId33%ryy^tw>8;N`L93N2RPD&~{K&TKt%qIO>VyY1sIVg~GBL8E!M$fx zUz!5$_KlN3U;A{7UY6fQ6#q8&hcB*YbWXtT6=}(ez4B4DC!EW6G2qGE&P)}iSY%_k z?X|MTP%y(#-)iDtakdCmO`28}sCW;V)35Vzoo~LK_g4X>qj?y$R~Ihg9e{0Uw{`Hl z{$3E^*ni>AA`XjcatowS#9!qPAMd*J;II!If!H!`H}XsJikuW{wCXeH^%>I-+Oja= z;W8Gdo`0~qKPR@ozL`Tu!vJsNi4P4mv&;B&2Z_4wZBH}-#X>AR@+r=HY41SQ+^^_l zGBu#Il?Ix)aYIV9!xYnMSO`0jeYKd_5uZz4ss)sAmW!k-Wl%UPsl})7MrMN&3M{7k zY0)F2#<7eWr`_D+HUUp(HJ;XjAH{pl?3y!`wb1Z{!2RPlN3bpU1vieCt22lQ%>&C9 zh8ENTaGiBqYk1A;54hweZgCIHGg2TmcHxx!`rlhtsJJu@P2DD)DF-YPZmframi=fp zE`Tvr$vwt&iQn?PYhbMqOu<4s+nY{$L~u(6p4^?Xb6NR3a-t{0dbEMcE(06YZQmW8 z7e@Hu*#=`Swc`GAn+nCk4fAdiKyv{aWfs>5z&8d6#eLuY9~i2kfzRkDbj@`H%qM{b zCN!+KJ;-kcOtxM2<$%y|JQ+vMoqSgR&%qgy0xn=W`WbRMx&*~6;*OU&}j)@|d?;S5bJ07o9g!{q0 z=T&k?@K#oB?9?Ufz)bn)`REfE;~|7aFu*fwa06l|Xq>`|bq zgUu#75vzISrevUu09M{TVrhf%B%*>ilR9K2)XzqF=ofklH9QM-Br;- z8-(^9NuHBbt1hoM7h}XU3Wl6lZEH+t?)#MOQ;o%f?|5h}7 zZ?-Rq5dzvP?#d&;<2-!HaxQ}p5DyHj$3y#SbCKe@hY7Cc7_kV0Pc26t2IZ6GW~U{y zqs}8K#g=Xqzp6N*6abXt_xXFpwtX*(0Q_xrZ0o$0{7ce(XMoSR#s7MY{|`Rn4HH7P z2s)c2>xKk<`wS0~E(_}!?uJQ{VvJ(upS}=BRy5o=3fqC|Ec8 zQ!*X7Q=?3Sn2^nd5YdTD@fj2eU!BFlFAy8C4z3_?^``_hrfQ*vh!>>wIrBW5%OQT` zx9UNCKmATUy;H$~HfYEuMmpGoB{Igrx-E2>3^D;i4q6oQQsmVxTNku?cqZ_dyCoGh zvT+%xHqx$Gd(Fq|FKYn&p5(LjK&t(^2W-I1S>mWBWiNnUu}$?<@WEE0o*pQ1;k z!_RbuhFr493EB7Z_?{2>u70K6qdjmbzR2=#hds%j|>GQW{k6h%H7V>5E%w5DmwF}f8ZL=VmI6-sN61ElNwAzW76Ix~aL2czL8gis@TeRQayC)IX-ZyrE z@0S*eH3z0|mTagGl^m<)(EwBTa&~O+zdKJ*p}4q>wn=5>CfbG~rEU$sc!vc}Cdnv# zXEDkS_)0s+b<&3mWESB$jbj-#dgbTz_G z6j7&#%kT#WbCkyhdHQB}22Q7{Qi1tQ$@`T|%Ot>j1$?Qf^ zZrh`?9PSX`imlXTQUQL0t!;=`Q)=S=Zu0bxkoWyBsr_h57n~vYqf*zi1|ExS(SR8o zb^d!+(`tF(rlb6*3trwb^vTP3r<@FDwx0zfFc&@K-QVx?mDXSVpi9jDgA%kY-CpXs z9cqdn?2WQgwH1Qn2Y4X9%MF7(WlYI0bH0Y0qrhDlfpZh}7O>yL&IO^4I0up|2{4;z zC?mo9$ZpgbT0ZKvDT$+Tw?E=vFnyGlU<~D^7m%S3JEBts(&RxeukyV=Kr_och_Yc8 z4w5b53OG_ubRX?8homT=dL@)UJfdC$?6$G~bIXdOe1(Mr@3$Euy6AInGSE73Xp=R84|vZIZ3?~5qa{5zD=?rE*(rbrfK0NzEirxtB6N-CGye5} zqZI;|+s6CG|Mmja*Dyr}4^Y4;nH;b6b4cL+3^l&5OTZRR#)Sz2Nrcn3pts?8apcox zk5M7o1iTLL*h8FR3laYNJ;hN%abZW1>=zmhAg)2DP`g>Ya%)aFOm(Vuknj9>I+1Xx zN03YwLtLSH`HAYgoG$zzVyZ>u_qXJaTl`eUk$ViXIGp#ZSq?>rN`2iiTS9Y{O_b=9 z|E|$#5WY#nbf!+0@R72(hn(*O3IGzh`8s-Q?}3KU97 zRRMZ(WZMfV270?kE{-0D8twn}`!m9U%h{TiS>M9Q)Hy3fgz4)e_lLW~1r=Ex&XmWF z1Ie(@8socQLYdZW3s*l^)bIjNI8c`I_`|f*Vd(^APz0i|VF>=W-GU#kGSUzre2jTy zpD3vI;Q6x(WDRXgM_tlr%3!C_P+Wp%I$quKu|zHr9nbb zkOpaxkPtx{q)R%an~@R`B}JsWyE}#urF+N$29#!iQDP{8v&Oym=X~}#=Q@AE_ZRi@ zdS~8w*IMsd&vQTbbKke5J4mpBe3NE-+C$f$OIV=QgHSS$Rg4N#p?`OC;XnPe*Ms<< zWP0xS&fj(G&n6z8bm3W(^Ii(N)`W+^e)6Qyo`w0r&h0nCv+_r~X?lxoIB`zA!cvZ7 z*)gqj2dBr&s+EB8@ZR-rZD71I8QlLw1`$zGpjIUkX-tjV1&Y$&0BZ#pDSV*2T5UZt zY}$$9z9U?jQRlR%xG+UBi?M5}ia!HpDy6_?c6+{QQA;EYvqxJn@;JZ^<0S+91*ndt z-_>CxdT;rt1(s?R8S2{XzbLqMvoHD3O3?eKv*W_)+f|#NVnRTFbhn0=MF(Cg-R=|r zP(7a->(~-^Ig2tRj{nU&K^%J3 zm50a%0?Fj+gNdTpW7O8|q+ENvr%RwVXAM|mdO%gby2d)-))dS7NXAIV`_T#fpvw`& zIh3FL9oC%yF2NgKe7r5SwD{k+LWS<(8x0n6Fz0?Lzgi^q;7P2-ZML^JY#$DWKj~{# z$Dw?ALitwc?)}1>7c=~KN%y8Kgmy$K6S#z;gS&>0480N4HT&Cs5-OpP5g+4qf*Ztb zgXe7$k8mcc5A==I8**}Fs2QT4iTasFB|u~@{c*9C$7j67j_B88f`i4!#MT6b+6^9@ z4TuNqwde<}u10HUx^-RUJZj-q&VKNUhgVzLP?{<53ifrzK&fBY`cBY#bp8vCs+{Gl z)Qk*DhLv`{T4FDQIlm9GPw%G;B{TVkAVx>oiz=KdDPB#PCn>?3VaVg-0w2S>k+NpS zTU(AFjL)ZMPa2o->!i*0sjl9Xx`K7J-zc-;sL9Mep{ufu70b*6*uScd?1dZETxjQ*`~!pBBUpfB#7;B64Ec zCZFSt#oQexw0z^Q@4xvlOG@sWfS$hMhxobK+1XERZ3Pcr3!Y`w{mzLGnh5w^d+_7p zBK^a`)5?O{GZ1He;6{2j*dS3 z<^5#^;_^#JI_p|}LJAL-5yI4LaFzSY_6}~(k?1xyb}kbuzlhp_|HXutsHQV(#G$M@ia@;5}4*k(GiD|JZ1XWZjr*T=7tSVtg8^^x|M@k4Ed$K& zdWH>z(uU(_Ybc)qzf&a#Ak|!1Ycr;XMqNI@x05ITd!ViaMUdLe0-L~%(LRw*U@gn# zcRG*c;3K6Cx^uPL45v^1p9}wU3r0a~fW)N_Ch|_>NA1iV#R549wJ16%N%(`&*x4)7$}+&-9hyw7EOKb{ZBx zpY1NMsYQ5;-1vJ^?#D>=eaf={c9Eahb+|Oy;o{V1Yd5I|bWNh7{`WHa`(L(XKvyn8 z2{@SGM=7U2elk_67uTIrMf=YLe@w*&;;*r0-@;P*q5H#g<2BX|E2TCRMdU$M9D7e) zssCOr|5*qYav1w4Q2Qk5S71rI4=a%50{+Q^VF`T#cmKLbN|0i-US+m=bp@Q04RCRs znkw1XTxlJ;uTw<`|GDpfzVENa`1dE@;9ie-a7?sx@PFR*zpuK2{p%JZ4+Vqu@SFeZ zasT^sA5F2a&GG$lgvI~=H+_Bedc?P$q?&)<XVY)k*o>%Yo_oBq$R{b%g{XW0I|`2J_L{d*tzpO^IC zTb}~%VkpDRn{*Pnk|9O(?DJ=mmTU-zh!^b-!Ur!fLdy=e*mvLc`6{VReAvH=IU9J`#zKk?6>xV7WKXqJq%Eud1`D^I11`~PkP<)xP2 zK)zV-Ou9+*zpw2b2Z2%b!Q8;Nzk`WC`<>KNe14lzT|h}rQV&(_UoJO>N`rRR#Lpf% z8vlAADSq(rZ1wDaLZ!bBjwL4G(Gu%=x@mp<3g7S$0YUYPAGzZjQhyr1D=!ApBh$uW1a#J}v84htmwUFWpxAAK(&@RbFsEv`%{=tr*W?xuw_Y?E-XJ8dHJ1 zLUxbJPA>GD-DL46_`<_~M+WyJuLM=)`mh&X|L-MeA&5m3#%bKD0j!LutF~bI;#?qz zi`3;Z##Kzq| zIxg_9cnbdKjQum9eH>Dz97&6GTkrn+o=MWC2OGaMs?74&g;QQ6vMp8Hy0Ln)b9YZfpOU5fqDr9wH_WvC;6=7m2yGNe?JLt#!>kVc+ z+4-!8;{dDrs?$L!;5j7%2zsbSP=rzd`UAL;kvbbCQ&el074ID_20XCND-g3TYeOD&$ANA~@Sn2a`6y=J-~MN%OXc4lb%}Z!NyC&3-&yL!(P;NC zQ+jc^tuf1KkRAtQPJ6$1h30vo==T6G(lX)s$lkPw->G5K&o6{b#_L{-e(a#k8l}sn zM?l7=HIo}Y7|Rk}G@d0BnrjT*>eCS43c7+l`waY$DeoFvLd(dbm=er@KFR^>ZSc5U z-TI#wmho+e9Hr4cKvQzpNFPcUH;U)bqsrzlA!X%pUb-gyw6TM4uj(Of@qOB5mT&dQp zHps8fMVCip3?42=ir`@8C$RlW5N`nG)St0o{Fryub$ysqE}SCnOGx;8iC-qU&u-bb zRg!V&_Ht0NX+7IpAK~V1YaIBUk;f%LYZTE|F_2JE<$E`8K4O~w8R$kHeu`byK9MTo z7A6trzdzIfEMklt{iA^T8+WrY3z3cA;iU0w{SNDFqf6RvFrrW58c)^?EH_4PWwUYu zLJ4&xs2-6sujZ9cR3!sq%)@KMcdkF|l3pV#RLjbapgYsmibG9TB(#DBtRAQQPMYK~ zkA`E@s-PyhGA_K_XuzV7!lQQ$yVj$+=Ne|zAsJxQp+}$Dr7@!(j2ZQxT#jiG(n}25&t}RtF{*^ELkt$b^sT&VV0U?#nJw=9y2)TiWG@FHR3z z7Pmiv_vn4g1(52=k7&yuq)%Axxddi<(Y-*x*8*4HOCKxea1VS^_ObRO@`uK)jqW?X z(ev&wPUZ)e{2nmISzvC*1{4^)!tB+Qg0I_cCQR+E#H!Z6NwSx7+Sob(wk0V`Gcio^ zspdSfKDfSzTP(naC)^v0h76$l>Ew^kIn8D*9%cr82mZ1KBzmc@ieb$jnkT^WJvPg3 z;w~-W=v3dSlYc2(RL_LX&FHQ}*5IMPx&e4@#**tGzhy(+p9V`4vo5Hx94SIN`|hQ< zD^&qgl1{@&ydrQqZ~%2=|KbYA-^YEflP`1%eA}Oi*qngHl-`4T#^yeo(B?OOP^D^qsA@Ok)S4Hyt#@O#;QClf#NCbe_Ej5gUgu*{ zm=vB}o5Q2O(Hcye{UJv&subSp6a4se?t#VpzjNb0r@)j+*UwTZoF8G}RC_UW+O0%n%XKw|x%JFVu=n`&`4XwI?Z)gh11 zXj3-u9(?+aL>2$D2s`Ox&1QFMc5wuw$l-*UMyc+vk9BwB%$_CZE;wJDQ1))m5~f}m zyf|-9Bnzu5b{Z!=ADHNwB_x`HI%3Bz}CYA`N9cqOjC_ZAqszu|-xu7*I$a z?ozpK@%f5FTEwT-#L$ec-GV`l9S{a(jMxV_(f_if_i5kDD|hU9t;B&wl12p zLcP(Kpbw4%l++wXeUHMxj;}D1?w6}yN3%O=Wej`liJW4!@oUwwiLUrSS|Dovx@n}s zoSBGze(`|Jt%5`daL!!Eq*A<2!CsK3UB-r(#2SE`m-*~#4>udN7=HxKkfH;F7LHiT z2|nkCLQh+-D=W`bzTBw{nt6XM>k2k6MP?bPxf*c-TTp}}GPn2<*{~WGT$;Lnpz{{y z2@OFIW=J*o_XbT71QdXteYpJ~%_vr!M>|Vej#>=OThO*kjcx82xsowG;fI_Oe;`T) zRROYv^}e<*D0UpkN`UoRlSckv2v#UiyKc{Y8rhG9{KDCoap94G9LuR`1)?LnFI>{N zP1@c7NyR6aaLUXIu6NQ2wbAnaH`xRg_3AVJ18Y`~9Rn(v(oW`US)1N#7eaf{!K*CS4f?xUIk zr&NCvO(RA=U-`03;bgt^edl02=ZGR8a$-k)h9iuDka`T*$nGvtHCMhqxlI~K0KXYZ zPR_3V2^8-q@Y&!66Q)`t=?kS&Qyhnw_p1G*|$1Uh&kpbBN!z0fuJfw`SrP*?^D2oHLAPai^e=yx@L8vDR{7d zpt6t3;Jvks6-pkE1(SKRmkNun1xP=iU|my1cgz483af5ug2ID#!mHiCwLW<*tPRaQ zFlqap|AuC?NT;G1w81;^mi$bzPJaLldI&@jsUm!qW7!=eerk88!@vn2L+jYsSI2CL zzPvzjZcYwJmRYNPrz*!BIUL5V{X%{}aut>>ci@woq6QVl=ea6rzF0jrSQo__t{N8E> z@eIo>jya{t>msG3M|_#XXzA+dg>se(v+kX(#ehngELMC0ftMTw@tkvtp~RibfdckZ zQvRSURj4WuaG@b`w(a(&zx^DY;=1T3{$|R0g+N)XsIv{GD#^6LZ&3fLdOSjctX86q zy_PaV;=7~jSOm9K0&uQ&qa0{jPhT%pv=|XBxIOq)ODfc1}!x6SDEJXj~MpiCWVka=I4;0yMP}Ogi4M{h4)mjde6oEGU2`mQS-+a5&}FzMdC(mv7k$ zc>igu&-j4z!6&g@n-ww)m8R^9rM-1h(jfOoP&c%1ts+v19&y$Io>!XM@R3{J!zzh_ zZjgfKdRco%m>|fz)+4(RPwqCOl)*!+jnvzi#b|}e4`*qISO4rTU!O`r+0gQ~>=5&)DVywuf*bo@yJ4H&tgjoNbzjW$uMGX>k`^q{Bg$li9MXUez` zF4L7BMK8_<=qKUOUVDmLKk?MMjHZC6)#^7&uMqYxf>+JBKt!;XdxwV{`VKX;2%sNXU03kuX)8m9l5Y>3&a<%_fWYx%he;A<7s#}>!#%1`mmExhA8Ufiu zQ^zRP)Z@a}XAVocEbueXa*bSx;2Zt~VEYeIA3z=XiO<=Nki{9gV5s`Y5bc^J9a5G8 zom@?I8?Hlha{277@Gfs`c(Vb%SxhU~8$8Ff+CeL^f@AA1E0L2G&0L$Y?Ckaa1n!KY z=&1dDFao+g0WTzum!Op%2kE-7N>C0~0K`8okRtCn3JysA{s|pS7hmw2qjhR!1_AHS z1q!=WJ?prSAo)~CdPFHYooyL#c`jsrKEU1HTW&Nm0RDamoYF}=sI2`!(O+3@MDKg@ zEanggH0-*kvXZo?5=E9^LtF(p)Ti^afEZv8H#c86$?u}{i@eaJJ!{@=#BW$3`i_WM zo_6b`UevdU3#D>HYO2?;ev~ zO59@DDh8sGug({Wto_$R*}6`9LD+MT))pTw3Kr;DuEnb&-3E_g$xCb)-n8JWLWMcB zTzL8`IP80W2wak{f?Kcp^N9-C$hG({K4TOF1zm!FUPxUK(}suK>-m#gH3j0;)B6j% zit@FveD+r#OYs5DszOg{)DkM5BxBmM@osF8I%_schlGs-M%yCo#ICt*zaZVd_1+5p z?&RHdRgqP#66<~@jrvzjAD^(JhT)pBI6lB%<(4uNmEE|#G1NDQoQLC;BUK0+rR{kl zZ_Y|lfuKB_RlSfCLJFs&JSib_Gdb^0j+AWdWCdGmnLZZPZLoKnSAbd1FX0i-eP2+;@VD zB0&c(?ecQ%J|+^VCkK&$!=f+O9k4?Q-&M;~6rkp}+4w+yca5K?-fq&>XCtc$%Oq2p z%uAa=)V=rg$K>GSydu`g^7f0T)&~51{?NqMGb8u02inf|iZhH+UJR%`2nuSC8*+Lg z5rbhF!GgP5RP7RS@FQ%uvK30Ecr+pQnF`LDQrOGniuQEQ5B!BKgfQvq1;dyh~1gJ7ACt}M2!Yc&o-)h z^52sS#nXw^=Wjx(d4Jq$`u;8@)pvt*KH2vZD-wT0hHfBUd^06^8d3+E%RaKCCw?~? zawP-rkj6(F7=%MgL&tZcwRn(Fe!kz+{57XXqXqlyiN zVlSwff!MI#l#t{6yd83pZsMuiv?}a5YUWoh^}ZIQ(w?>*WNT|#@?K35sRf*$!TCsw zIno0=s4_5rU%~o-wi*MJoi^Nqt@T+joZy{Q>fb890{e&vx1w;4;(LSOz**Aj@vV#tu4u`&Oy9>k3hZnHP)Fe zK?NA(6s?q#J=`dvRjy~z6b(tl#_ zfE-ujcu&AHy}90Nl+mZbkIEVfmXKC>k^5WevjG|x7m>5&Awt#?=>Ffp5 z6-;i=m@RVTF<%N(5i=SVL%rsk(z#E0ey=F#qe5)An5tSlm(f$u1Vp{h<1BT1=!Tvcpc+e7`>yYcr?d#Cuqc=8$#d_TV+RYI1>bK89Q?nI^Q z>cnBahbJ*_j?noow85*`_e!X+R z=8#540v8R+2)ED+lQWQq{Md4F2POvz3K>*(rRwLjCFX?y+3h_zM&m`L2VCgI7C+W| zJ+49G_spjMvmO>J{IFRdFSmYdTcO8n0;S-;SOVHIISDBS>%y&$?H_~0(EnEIUy-#f z2L0VJkoks*NWWgUakg4$=EPFf^tE>dKq32!jQsKAi53N*-D)N7;4O#AZb>9xFFyXY zYh17o*9092q$Y#&b6pE$Z3p86r7peF1@R!_NQ@(}NI7%PKZ3=oI_{m17vKt9IouoI z#@k*t2BgRg@~e9!k7v!Rng-JDTTHtdjiZqQR0`^7+P5tTD;KZDq)t&MWSB2KLVSzN<8hAg71pvhvj!GugHe= zlAeOd1!w#e!a1JH>QvXjqzMNQX>Q}#R9|9!)YjI>FCeA6HW#J1Q=lzb0ZJD)cP3*? zH96+Id?Pm0>e3pWqh^E-GoS$WrNX*98 zkGUI~we!Y94mK<`9>Ohmrl0M+ZZRx1K1W3BHiVeH(22qT){GxdFv()uQ3MSp=b+{S zHoLdY1sp zoD88fw(z~!jJ$W2e2(a<)mjrQ(a959g$GFcAh5XeHTQ=GNy|T^meVHIC!oPd@cHKP|&L9HWK!26Jo2=EoaVnl|m%kd-Xw zuL)oE#;ybLjltTG!R<-NqnBTF$BcWGY(Z8eW5A;AK({jq^gDuiZ8I*in&_Au7|=In*yDG^+PCr=%9iP2-XKEEZ)i!kMoz82dBkU9Y6EgUWdVRx zKcExCD~Ry_@gYfDjq|sRrT=sc2a;c)#(&yb^}BB;IjhE3y83|G#uT5y;1~^-vB?cm7r)W?8VHiGsB*Vm4r&zt|dIyN@T)j;1 zjz6?vKBTTe|N9rY`I1y0Wjc?g4lL_gaZ)yc{?6cb#q-k(96PVgab=KYe3yves|eyJ zDhx?BqoXpYwFwcp&{J51Z7wbO?stpXDn3&t%@MkQ!b&RIIB@U|&|ou;!8SkrS}Gwz zYnLqQ5kFb8b9-?JQMKfL2ZwH?Qw$U*WI%CZ$%xKVM2nGU7TY{aqAMBxhyt4LzRg7> zH5ZFsia3lUXg3am99^ga`*}N+l1_;o{DRKmH@Jk!Rcz(WTU(h+u~7^&wIF(a%L_E0Kd?3l&VwVPSPwyL^z|{ntU=FZO>A<# zN?~XUBH&V-{k--PN9n?v9F`*wL3@e? I3)S;wcOsb$2^0dc(+$BX?xr#XAM7+~3;|$~|1~`_{)s#5KfTm`5 zm`!6Lu9w=Q*KR#RUm`!Za90y4M-U-kt1;qAoQB^0)nncJAi;1gbwr%66Be9h*c6@% za^=z|gHEx8Tn~51l%Ja_EiMBYy%%g+jZe#=XvyKKPn%R- zR}Fm8+iMNa4W~}TJ<3^Bc9iQIwWi!|J4I7CEzN4Ce&e{GDXuCIr<<#wSpu*kMo_ys zyFNN~i%;-@16>A5XdRn?1KoLx53($7Xk?p6DD|0nT6{;MgtJDC6FB5 znz5Wb6a(nAMX`_R^-=XQ2dDy-T~n15K`(uWvleWX^@Mv>ChccsF0V@xx36M?+Q^`J z?iH^Ofx!LhinjLAv1PNwF;4naB4kvKJm+B7{|38#=# zIm(Cj99)8g_x=ykG7w;mUFG(8EJQaw+>TNY?h@DK+If#rmNjg#x3%f8=I-Q5(wZLYATFB8o*X-31W@@v_ zmQU0gDPZGMV+#w^H^y&MxxIMuG{ReUTgssHa;jw{HvL4m0kz$n+fe3)f6zc0%NtRLmP!Pd&f>He;yR%2o;c+igFq1foNlkrX*#ITMF~o`n{NO_FleFZaGB zj0mAk87$9SjKqb^0S3LrWN`<-09x=!x%U9C8|<_X;K51-7DN#~n#XddvsG#Lpu{u> zfm%hbEt=b4eFz4zSj&l8|8{(bVSzADs^!X@MJi;S{R2|l9^IJT_1l`4=zGq+Bd8iQ zHxOgIzc$1I1T)`j4SKcYa};Z=%5q~bN=vZsXsMy|!O8a?4Y%y4$A+8BLdtA11?I_y zaEQL6N6P0w30a6I-Uu$L`nXCB1JmrA#~}c^VOVS|{(#QA8n636+I+UHCV;^bY`H1S z`j>(LE8x@<%|n6IcojhTwR%Iy#ehpoOPazvmBTGdbo+V?7h|;xb`+=&^=hr(L>MrU zkRK)z$~a19mjff7O>y0;rqFT0PFuBfQJTKQ$L;b-iASia5{&z+a)H;G;_!w+L{%>BxK;ccDK@8V7~BHC~tvwO5YqL}ID zLGGJumB_Y7z7b6V*%@A!%6q$V1}t*9I^3X$GV+cq)E;RwVkYL{?hBsu^ahVD`C)3+ zg+WD6#N_8&K~(@t3p=r}zAo_2sT6eg3HzI~f)Zv4^W^Ko)00SUbQj6u^ZhjqGp{5NtMwDP!ZEZW_GiMQZ&QaLk-ixvycUe(6(Lp z;hP(DTaunhEve#*f76+uR0lLjgdq4*&;eKNJNJU5I2a<=*f9f46HED}!{@)H`TR8- zbdRAHeHv>sU`qByd%)>Z{KCL?u`PC)W`MW5mNFgPy{|IXX7Yznp4Kwc(n6i0MIgujoU2nW(_6Z-KQow9Ump9cK>s2^#%y&C19$o96mwY}} zPuVKzhFAr||9hMb$W5ttNBPY_wm53;*bUCyVtUtKGxBa13stA{aY90g?71vlx@uxY z_s?PBhy_Y{L~DP7!ic~#!tw-Jn9UtA&p}W}C8nf;8KRS667ytSJivWMtYrNg%afK+ zT1F#6)MERhBC26$q)WcB^37DuM`G{Rhew7OJO7 zQyBxgN)B`N4c$}7o9=~HLo~g>Z}Da=qUD~}N3ZS(R_13boD9*Hs58@y{a_MAED!~n z3%B(*s;I&I=hBuojlu%Bx}!W+muK|4!U#4*ct8m*n~7Do;7`%GThDf zisxi8QTB3F6U(_+9_u?^_Yzr2)J4k5LU_GO7(g~JJ9HcChfDSL%LGlRaS{>je6u1v z6v+Bv^JwL&?mKO4OY%^s#dKymua16>+M9N-d6jbtH_2p_0X2tAc@>$CN3_Kb+D)?| zFXq>lCn>__a9!^G0ikmNlRlo`377l{c!xg8GtLiX146{RN^&AFZGmdh^xvIi3j}#Q zW!@tgJs))CIVq`{au}X=fzq@dfTSICI^WJmM6YB8#8|F>jLnHzFr{0Y*CTIz) z^^OFOX9q#VBmrbw_iA^o&=5E?R{;eoiI)5{SHPPR6eKoBogxd)T!;H*#}!YAt$Lnp~5uErSCz4B9IzV?syR_$KBp)0P6W}qjy`e zl9-jZ`r7=?ltgxR@`Y*+@qdEh*Wd5C52UEINpinfEr8UR-u*E|4#0M0EU64NltcEp z83Tb|UQz&K0ZvLIcNeJ-Hr?5kUWpQZ<%j9UHr%2W)a0WsDmPztqrBjNg8=Ar9{0AX z{oR1DW7*NgZ01A^a9MIm)?jHK(XKRoGF$oG_K1x=%~FTGY*h>kIbM^QF$RQ$aVI6k zdz5zCKsRaq!S?2&n^0X(fAQ*JO(pOvL3(}t&@ltL=W}gg?_N4NTD^$YHA+}S;hxRJ z_!o@-2_Y_TeryRXIG0L9cekBx*#o=F@Um;HDskYEh97fOJ1wfB<%f?1>VkM3o==uR zLa8;`bjl6)Rue8`FVET=FqNUz^$bloLmP{08ORmny(Zg(J(m#YOEA`gRJIU8%6OoN z_06|%x6r3jWlj|!2mIDIbPB+3(7LQ;OUuD&9st)`9WZf-gck2KXIKC?9;_n7h-k@M ze*3BZ*6C)Wxt8zclM+^?CO}EUF)tBC@2u-!GF9XT=@3P|5%PFhyBxkL6F6;bpnw7P z*!g-wwP_(GasHPdMSeRpyoh%j7U7=U%sfPyJZ%VRogVp%HiO12P(OryIT^??-|I8x zI8w;*si@oGn1dQE#kuR2kga|P9+6$h_V@Vo4qX80Z!y&f&vN4f1bqSb1e7r#%+I##YWZC4M-kjG#OiWoA6Z&zpf8yxe) zAtJJcHqRYq_PF&v&#}O15Xx$adcLBq9EN5-5m0`xnSM|F9LYuNhyaK109*+DWyjiy z@UW$e_D20c57MJMLIhL7$%F~my8aNyhf{qE10F*98L*d69#C>7TjQo$;g+cm5un!P zk0wWEK(3vV-1U|d0x-ie{@M=;F19*KkTF3!t!d+xNv6yozjGh2%kqhXqA~*^Pwbs^ zBi#rM#t9Vrg1C4PFI<$>Ny-zzLOAA-BY#_5S%2bLmB}4U>r*XJE5m{fjyYUqEHqv^ z&v4Q+EIGgPf{`9z<}j zCB(a&q09C*a-w?^d4bkK{^kdvSGxA+P&!(*&z1mPWRY#uvy5l6W{%zF_1EET8pAT<_G=^Rx^h?o48EMfDeNrT?5idKv(T zh<|O+^bl-njDFZjn^t3ctM;#Zj?{y^7SAP{KrLFv>}_*VnV3yjv2zjGTm#BxA=hyo z^2$%NL8pRL)rRL_6|RV1^aF2=EUd8~Av_PD|r6Qj=agA|p|0LPBF<^&`}F~oH6V`DFVfb-kwJ6+$1 zv=lIG_m6yhexRw%v+acp_u)iZz2;+~qGtP;rJY>N2WkQNDj4Z11^bg>u}hYMRM)VG zJ0kG5&zXURM+2>5bl>~qkhzHhH(M9UHuH<|G!CsN#5b*9NY-p7wI0?*ueSABR_sHF zj+({`Vi%ssw4W~^-G58^jW=2JebOc=@)|m`lJwZ-qTFdk&$t(4)E>byVtCD;&W0EH z17)^|&B%>~Do}IT8xk^s=oHvBy-^+Mc(zQRw{`WBVro&<7sjIw1InH&AT?2(*o?hs zMTANo-~o2cqUHJUZ{ba3`Zjqk2Bey|<}ZUTF9h+P9Vh3XPU@J%fK>|1)hpF3XtRmt zO5@jcK3br|_PsNECPzxeuCsfOTJpkWtRr3p-Ui59MXy1K>$)5%Gyo90_}3>I^&xg9 zxdml8>+aXO|3;Q4C$*dRVzlRmC2wYvFi(~nO@7`#V~KZ212Q)H-A7kbL5sL@o5M<= zv;-mC6NaLP*Utk+Tby81e&4-JxkKngI#fQIzOMTxoGSpxnP^oQ4{Rzon0?duz>lyC z#)~Xt9ADL@{lQ*R*6_f{0iWpe*7}&Z4-DGUu=yG^PnV)~OxaGHm&1J+9?b*rw4~q% zrM6RZ^AiWcb0vg_4lRnci>s%EldS&stG`Gz)fnEww!uIM20F2&>{Sv9Pxm&`bcf(T5l@sWp}Vl*95Smd1(sZptAQGeiz0ZZ#hLcF>-UXI?@OgsdLix3u% zQ4uRH5@-;3Cd;E@8%5)dKjNNB?7ICXjCjbFPoI5CcjY_#)< zd2YK#xW`4=76^%r^M!2TZkBsBF6)$N%bXrDY#V%?JCk-=Qcf?$>SAGr0VFvv;0QmI)7sjj6cNmBuZST_Ra&P`fJ?)BKMErbuv|gY`CKkV^^PV zN7q~s0nfv_Nw?NQy}X@pc%2i;)&tZ9U1m{29np{U1Juarp{fW8>iAG`y!}Nu1*g|= zp83?)OdUEGEB&C><3Jeffe=H(vcD^>JuNIUV5=-gEQQ|e(}{$MGfpj%4zraj4Hw>}$hke|w?fhi4u zcw_ad7|z_}shCL*S`pdBbc%3Xq(46T0m{265jTbv4Ef-KX~HgcQfC@Mp)@8+bMVuj ziRtnMFS~XVD2=^`K|MnUrUPdc^jM8mEwAmrmX9qI()AlKsc#Uc{GlX{a{`E9;q4s| z`rYjH#q65u4UR|po$s03oZk3jIZPBr-FfWIW7CrV8&fiTg4`h&y-v)rDpc0s=!>f`}%_?RT^&1xbqi|uWCqWCz0^Fl(ZDX<}RQ~QA#Fmo(&h$%JSK#NcqJuKc~spmMF1W zYL4<@Ip@$We{J5e1t5=X5Lod%ftP+IykP|?|Vf`enu8YDy%Z!;kr#4MMaU_M_JDFOVPqwkb2qyq;K7S6C!HK15tu0W`d3REOGLOaI zQrecEPq&5`NjCw>jKGv0FJ!D0r7w@lb!X8<2-SE{P zMHD{4g8=X9s?CrblX~tp;nna8|C4ckrYvU2gm}E zP#@0$Iu(l?TYjTRt z+5@%W6fJ##U1k_g9u10bo`l|@UMd}5`o7apkn1+F4_H=B!-7WrR?w8a^e}J|3q)A}Dkv2cGZX(q*V*+^;WiS$=BJh8)T!MuTcm|CL`^j!GLBq8-it zg6`YIbbMXutwFqW3~XMzkEbFlSRyjeSH@GWn!Nti1bseHK_bqteX*Z`OxwehH{f;l zn;Zq6PowiBLp@c+A(s<(jxdB(Rhqf8W|IZ+?t_{dmu1-SNRtpz?!^t83SLyMQ1wB6 z#)V5+385+ahV7aLym=fCZpU~28_*~;RZUayWsQJNc7*ZrHLM_%s{7zUh2 zJ~I2tge4j6FaG4y5y7V#tEoK!+ACK?b1@3SwI070DdD1KzN24@%W^tH9$EI&yi&$r zD2}`AGSKg!tNev@xWmPR(HMa-^<1>ju{{qwFSj4L+hQG!{ya{vDGDlFIX%wTLMgaX zS;8qt5-@H4MH<#A>E&?5T(xBmh4Lg>czu*8*nGs7T5ah}lrAquQX0PEYsYa=3$GzT zAa~8E0G+780MT!DkD*2l8P}VH68ot+LMJbSpg~`wyK;Sj)t1ou8vc%Wc%L8rLPEWo z9WLu1>iAvhQTw#ME~r*Uq}6 zQtW5W69(vaI>&Kt3(tx&=T&KkxjH0n?3aG15f1*ekykL}#f%Tsis-F3>|wLlxYXch z)*5NN@_9>{A!H=g4pz?Q`nG~oHNv?|8ErOE;hPF_l8IciDyKr(mVE%Auq3}5NuL)| z`GVVB+uE`X8i7g=p0Jvf?wE%VMl`j*w?8>Ow7>coYR@2JBA^S9$`4Bmd?T-fip~!d zbk83-sv_*Grb8iz07rfI0kKluLvA}=9)f9UXq0(7pD#pnAiBdk&WQFD@f%7J%HS|; z9K34kwj$g&!7>ji$(RH6UVZ3Xlzvcqj8bDKE*;mj_rQ}ZiTnwg0!1;#D;6m`Mh z{DV7M77ZW)XF%PZ7;BSj=dj2JNcMydHM0Ak*)SRf7y9C;F1j7L&pE^mHCl_Nhx;*& zLl@GW#&g^73+D!G{SNhs=~FMB)*@iOHi2=HhFDUilDpUG)Q(Gq= zey6{213p&wrv4*gu-+f=ClQqM!bMoGVItH=8B$j5dbvNq=}@h4%~SEkPbApO;=t)? zi~CeCWE;z}J!Mxa6{>>c0AO^+hv%u#h4W%nO>ovvZr!S%fQqiX2FZ7sshUiQv5$28 zuenU|zSBb_`RBd+HMrgLs_G?1acn_P#Jzrgg}}T4oqR34Z6{~bmA`UX$To9rvmnDu z&^A*MNBi`k6p>rWvUAH51@)Q>SBC1i%(L_YKid1uMcR_G;i`|~ zGnE3$YaNjL5a}3c3CFuC&{#Tk+U_-O+JbmLL7Rbw%_PUG_mC){{$VescG(z7=b3sC z@WyW9)>8h+mSe~9N6odzK05#Qpc8j=Dyxii%baQ;G*E+-YCp=O1k2e0HWMqRn{9hK zZyvEJ+;Dm{p3ba9Wq+8jXU~lXwTs6QyOJHyUVEg%0T=bX=VdwwF*|SZ?x7RF@tuGS zywz7xHRzGZx2hY}`n22URBCdBwpsV!Fc5o@E1{b+y_wd!&cJI{=!A0jQ&WS?HqCjU ztA1loq3dmo>&i-sx+*})`s25{w5-^p-=}HP5T@pWB28<9X}_wPe(s?P31_g(2#0@- zk0X=nKgtaL>6w+n!v~yXMqT1?3^9ut>&z;~AH2J7+u$Z2=cltw;tvAOt8*$e^OZh> zl8Y{@rUpRykBI8pcS|mjo^)QlyS!EzFo}0(t)eLLhDAyaWu0uH7W>I!4Ha=nOa~-UnsExJ|j7Lzn{6Y}(`ZF|SIPc+J>hG+NhwEISgk+4c=n zN=;kshiJfReKaE)2abo3O{m74n7HDKybaA_PiU*2~<+1iaajume068nRW)?>SAYdawulvEy`1iZ_XR5g;C zXs%@AXPo1Y%6{hFNfn1Q1DYkzk1Vj@a^m1c4r)zm8P_-qQAH|f%{8mLUnPBiPnFGu z%p_Oq)47kHf(>}2!7!R0T6#qjni)8;(nmTd{Gd%N&o!@2@^q!a4+y+0)a_!>+sS>0 zQU$)^Gh0!Z|M_kLrd8(}Jr&tbmUR4n{_6>r7BMf0Us}Gsbh96Aacfx$E_Pin5ir^^ z&;~^^E5ipfe)Wb;#UQjk>U^wbj@7f}0Z5o9u6DN;VY4XHIEk0m!OQclBdR(O`@wj) zQHv{-sm{a}L*8>Ql9P2or67XP))u)TGQT5cgs%5620h>kIz@9DVtI9C<{1Sb8`E~m zzd)PKd|p$@dT0#<(7r#`rQD(sIQ$`^P4E;_Y|zj=$t-Tcjn}$`dZ=uxTWQ*PMaD@? z$h1IHYsbi9MQ!y_3C6x-zN1dfaX(ywYK$8;&o=ycl&4!N3=(Mh3&{`Y zZPFLx9vFYr;mI0>koTfJ3 z6xHJuel{AFAzLD3;e_KXk34RW(g|^b{uS+?s7CTi->wLCituG*!VE^`2C#$$gT7s! zqX_vVUKDcAR0&&iM1OB{xy{P6M!zqOT%ylWOz6VxKKNiDP+j26N_fazi{Pkcp;HAG z(s;~`pg{3~mK7AG+Z2Lr6T;UfX{lhm;!pL0%R!|Lfq%TQ4m81ip9qm#N zw9e!Vo&~64dgRNS*7Ql$CNs>Ba)sX2t|#WdnTMX?@^xq6&G5PI`M-lguRq$Gzaz$r zjYugtr3PK zoKIs)qEp3qxE|x7j_-)+sD>PExt1j|{BG{tLeVj)O3Li=&eh6iny8*!oL@@`eRrUn zh5ZWcBMlv=Ab8N_`{#hu1)EuT-2EO*aRb9Rti;}>WdTefU#3oCwe7`r6VL!a=e!+4 znD89*2o5%ba#>e%m0NVpf>@wWW9FAQo%jTNQhG0RT}J<;@!RC#v0YZnYlEq8)CXsv zcXzTqohz!XTf>1#k1wR zzB``k{_j7nD@l|PMYLpRCF4jT*|N8?H<{TqZL&AX=9tIsP*)*4j!l$(?2JR^?{!>V z*Twg~AHTov$Njkb!{hWh$LD;!$LswX&*$@17%TzhiiDF>#`#iFr|De}2^@>Ld&oPL z?19XqW&|$mcn<3I=7P%fV^^qiR^f@sy#)^)A9CHAP%~%2$cKGf)-4;jTdD>Li>I1p zLX(1vT)(TxxvxLVa_LK(E~;eUxsrj@U8l>1is}En)6n1DURY}%z~P$x@LiEZpf^d& zprfWCGu?W!_GepXl~*K}W?5dxwLj8<*h%J$tMsJJIHsyaKqr>g8tNk|7o4R(y!9t% zwnBU_X;r6eS=Za3Q-UlTQzmuk^R=5WdhIpogO zv{V;3AZ@0E{*lU?ShT?niiI=93bhr@kd{_s-&C{rz`82`vsRUf^>$Qof$<~ck&4faSPl4i@*fbvH z9E9a?wKA>Cjcbp>09;Lr*}fSDC(h~+suPdymUx#tpulxaU$Y)jy;l(O*w(lkHAcIv za!IEn{P`aww5OOR%kEw3?|5Bif3jz1WuQkVgK>fOD)D4wh1{vEk$1jDY0~_Y$~S z>a?(%3eDE5#V$D;#A~_Ec{bldOAJ;zka4laS1op=<%|ROh&u-YPHn8LRH^4Kd&=nR zDH*~Al*{UIE|0jpwV~efosA2IV_(`^+1@7lb*)4e8?|s@S$V`-fgs*26suy_7#`EQ z;~31L{dSZPsa@4T6?bnYo$*o)u$431xVeDNP}qtXW{mB6CO+RO#J>*A?W72XIrTE< z<spZT_f3 zMJbKeIk?w{XR8+n@d!;Hn&f1_yJBA@Bod7C?K9pjAFo3N1c@u>>l{(k8aTtM!EH<1 zS{VE`|H^&A%g`_|OfgR;E?u^Y32jM(m@2hozfDm2S5bWa=46NG3IV;l{FlV7Jj@Wq zG!NAP|NN8EtvX$BO)F19WuYSVFSHOtzj^B`QZC2oCJ?gJSa=dhnzp5CF86_uEL{!T z(-eI;c3%Dx-6rH;-JHJu(EIntIE#2J>@EqPxxG`b@GOW%FiZRe(uGj-@v%W~0DJKo-NUD^;~uockFFD6 z_ne3^j$zOw0)|=R_Tr~CmHtB95|rQhH%$x7HdYwoFYsJ)U-5Q0Qu(e}+xR#!KZgza z_;-E>GvhS2P447?HOBGZmEgJXc8*4F@{X(wisIw!cR0%5dt^cARS|Ky&|eXj$mUoe zua4be`}uPc8V{`R0k{0hQX@0_{cX|0`HYXe#DDf-fc1$?9x!v|P^_su5~XTKEOT-rRIj#u+GA< zi0xmV5E~}Y@_3M}o7m_>L-nx~f3{l;f-WnW_VaETRGQs+-laVDg9?`ZQ>h<|BUvvT zL(v16h8a45*m?9wC+(E;ICd#)l1}g-0d#vtDQ~b=McDI|otsPzuVJHp%BU&U9%@pW zXk12|^S1baW>X|v%D0_Z zwT_}OaadF8Ow{WSYfWenK2oZjoqE(T;fQE7%shcl%ihd$056$>OY-#JYB4{K&H~uU ztntn0)%J7jE@Z~_K^ZMxi+HaaLnIx%+{`tAq}3$3{TeutFC7#! z;E3ZUP8quR1&}`K-ErJ|z&&?6tycaS7Dlb|0`=Ve!xac2Uosg1lup;}d@|(GWPG!$7#N#TD^3ur$?X5A++tQ|uc2(|3l1!|tb1k_le+=oY z`^qxp&Zp}r-?19Z{Pf#{=eL7_=hTQE{VihSb*A^dbJbW=)bQZe_WCG^;nsS6OUn`V z;L|7b2{_OXXA@Vj>h69j*0->`^AfJwQ@bwuedwh0k*dkW&9TZEE1Sf@k~PeSNFg4C z(|oQE0VQ#VNq_NWF@?m&(%`qe$vPgt#JFz7pj&0zG6&v=32AQw2BFyHy$ml}Y%5Q! zDb+D~l|%pf7oHxinRr=i1$J%f+Y`@=ImnvBS=1#b+H|Lno<2?@#DUYip6L;rJvGM~ z6XCLxMtIeKKD8!{f=%m30CQ^8uUg6^sn@5GT;Is8dlyktGR_H5gr7|{YO<%i_wtO- zTd!}L^|Y6iF<9j+1=bt#Z%H~>)NV4|Kqm$fme`J07FZ_yhNvWHn0wuG&Fle+s-Vf3 zMS&42;VnU2q5h?lQxX$w`fVBMNc}Q_t$vPvT8wZz$Z_f+wopwMhX$-Lr9nal_C4B` zvlwx=k~ULHX(dhZOBMyLOQvzkuE)#@FbJ%OWWRuQeURmR*W<~qu?7ZSHOrPT89sL( z$>Fk2=?wvOPhQuIxq+n8x`+$&xvh{Lla9a5^N2!i}+CW-=lw|hG!Dfh0x6Yh?9eTBP>$2T*(fT(X93S-%SebX``P;$q0CkvJ4g4U3+N|uAspO+sNFci6v&|Bq#!kaN|j( z_iv^h$t@EW7Xn-ZSS_WdMb!BdRnsJUd~u~6rH?gSzM3^Q@jFb%gyC^!0dkXTAL5KT>8eVKdsx8s!k2LbK;2&$**cNN(95%BY zDF-;CVnR}(m9J_UCJRGrqNj@bUYZj1oqhio6I}|@y6;o@{t@E}@jfi(cdpQ?@(xyz zHP1AFnkRcE)wuD4V*S=vN4axo2~6@`q_i_}d&)UbVG1}C#%oPmEM=x3CFXj%@tTgX z>tU1Wtm0OtA4(JO*M{SZt@h@vjnX&YAv7XcT|2NZH$Rhc8U7sk6_Y%nbOthbRl+F) z{2O0`gx>4R;hTilNoiWMr_VSHmNx*!wkv&7*@f214Ehs636yKP(&YFtnkI|Eat;Fh zA7bu@R6pK*E*mhMa~Ew=4+doHp21(Sv=ywSk^3#azdb|{$|e8$5o6q@^L?W|k889#3@R+&mp1D>_9mQwdJa6SF7 zchec!96E%fyVWrWq3sn4X7|xXAI4h6AnISbGDosWrxO`%1B-_?Tvo>Tp1= z2mGfKpC2ac)k-b_MK%5$AAjA;ptQPhx5M)&3F=2vgX$Pvk61VIwENhz=eegoldZVW z$vE&o5?=87Ns4iJc6-hZ2nLOdKS=nzoe*>Q4kk{+-#BD>zH`HoE$k497r2bV0fqUhRQ?84_nsj)Kmpn{fMQ~O@5IO7BmNjj zO4WdBnoawPWtOuZZ);aGE&~qQ*Z5ZIpi^L0cf*&v*S++QrWc3$mkTWia0EP_vxVZw zxYy=~yRU6N(|NAr?*!y;ABm6S+I#4$kaoOmfr+N1=dLN!Ai$9LNoY+USwM`J{zvzp zFYWZZXPDl7({pyCxswmoHfYnVm!kh70bdRKu{OV*G&}g~8PBW5jHTIPf%w%SuuwW@ zBKJU|BiA7I)9Hx+V=pd;A3pEAhCcA^ZV}~PIGfY}IWfw5F1u`#V=LS3Yo=|9!3M+C z?(OsUE2J;WBQ=Pbzw_NrF70!uD8)Y=O@J{Kki2r`J-$Y&Aw5$5Ad5lid0as|%jGkrhLoT0ZHh*{^T?yENyc z$886D)%u>XMqM=0Dh4=AQ@YGd%Wb19J~qR>p(FKq4RinWhTUm=O5^YG`$BOTc&(uT zHIx8oV#3V!Y_VLLetl`HEoZAqWkD>DxzuP~%lK?@o3C1J5Y2$P{)qcoyvEJHw}AYJ z0bbI^V&TNWC=U5n@9&knH}dL7LBEF)ntYs- z5t&K^X*;~8M1g_~O__=&UwSTf{b#spF=_3c*hnTrN$0L)m7t4^%Nu6wGdbN?0O#0k z&W0DxILEA_k{l~!k&lxvTBJVI+ zehJ`7(fW&A8#^C^-XXj@ntI@Qy@Elc@zOaJe3kp}H-x)MZzyGkG{^Gj;z@u&l0req zDTL8h1JzH1xqepL^~J~7zT!BymG(bk=BYDp{`0GQ3ycN7z{V4jY5%?<{wk@R_c5YK z2`KtEL5YM#CbqK;g|35T&yE+*!UOO@lw_=Gp*paFe6P^?@5U>_ODoPUve^ahyP<)I zH`G)Z%LZ-Z2w#D^lv4Hft>xokc=H5ZTf!7&`NW{B>5Z0Ho)G+gHXZ7MOUi&BzXrVh zP`uP==S-64&KpREx3BaP^^(@!16Cnu2v$9poaP9o5x9=8oh1k)k@UBoJZM0|;c?_S zbNBaf@Rnx&TJV?*+dZeFxZ50iuiZZ%0eCab%lt#VaPF{L4&0K9rL`fZ=8go3F_)ZK z7I1a(S?xI%aV|vwq(dT1CRD&_KBTs|(e=X8;l6$I0YYw+kSefKIPLTABmuAU+zmWw z2-+H~Xl+1F2zf|7LsW$!ebQ>LKSGEtVtMioJv7|%#s0*&6m0!kZh{VbH^AAA0D;*r zfxJT(ZEnaAs3ZH41T&Xed$Cu(ji097Qtr;z)laM267$$`PJ+rT8^*3#`<3&-A&7?| zu;I55`R81NLrI$9CiH>C!>wh|RraRrt~j@NY#zN5u!(yCdUsVWQ4sW7U#B_FF-cIK zpYBMj;z`t8PL?X)#%Yd$_Uhf4Z50z6kf4;f8;56YN-|b(n;@9~T?g>$ztt^%;NpE* z%WE{U}(c&q24)$cwjaKByZV|N9i*J9r)- zVGug~9@6f=8mf{E_&7WZRIvuH8~}i(Sd@c|dF@GZCYG6QOOB5)7!C!@jY9u8WX8v+P{yrcx0oEl}D@wnA62F`}>?nU< z5obDGoy*^nP^H$2SQE5!-riUZgAT6mRp*0XL+;=F!YB$9b2q<`P2V9y|vP>1^tCfX2y`3|^*+$p2Y4nxpt5hRs ztp`doQch}V1&8!j>@V7m(=)Jo=Ov5%8KO97HL>vBlJULw;XT`0cz-7aIhY& z_6{({Qa{Xq#$PfUxH;1ak?rC~A358qob$eF&=2H;0)0emMVcwGu7`7(1WhL5o4w{t_ng7P5c_zavD13u zeZ^BjR9qh|il56@AKTSd2V_dc?#^Q)?AN!?Q_#&RR13_59NVQbF`SBcf0jY5?C~dz z0+<%Xm7IK>#@*rxkE$34ut23QzN_KC`HhTRM$K=E44e4IXS$JWHQSC~{>v)&hgFWs zEc_>4#v7)q2_Hskna6XBC8J%4sW^ol7lx=xgY@f#u7f7_sT*8+pXK|2%13p_qbT07 zA4@O*;?viI1v&3I&h}Ci2$c}^7bC@kH}L@^K^3jw(+`s5y^3`~2{%9(q2{IS?_K<6fwl*jTh(6juGcWqs#3A0$8CI(Y!<%}1Mtlq4wPj!kOu7EQ3<3RRE zx#Q5y_^>-bN~1!rNyVi6s5`+7XPI(+U?`|F+g8f(T$7)WzNcpA~N8&*T=)RaFA@?Ayr;quVwMW)EiVXD&^V=Co-^FmX z2}%Kn*RYNf^Ya^b$Ze}ZgB;_*u8C7FLrlt6ONfE;Ct=bgmDi*w8fW`3E$gQIL9qzz zd9(hezyrP|>4SWjrRQ0eng zi3g{iAHDhc5Iy}1iIDV97fDY@YQz}alqRI3^Eobm@4+Fdqhb`bnay1a8po6f?uowg z`Ra6OT4yL^EtImTaw*%xnALqPv8v6TVR*C#jq7umj_J2>B*CC*XZAtVRWTyK9o?#4vCAG6$XjCPmXFW2!1K(iVLuxw!d;WCc0N# zPha*f1nG-$SULs&02z=q|(A?bG|S^UQILqC2$KfkMjTlez|M;uL5 zZ<&gPsX+5XRo$YZNdC7P&S~Uod7tJz4ckn|73m^WSv7^&O-)DV#%1rd#uTtcvhT{9 z^;GvrV}~@#Ji1p4drHhn^7@L0-+&2puD!~z7RIKnhdjxg#S-Tt^@n^68ZkI$zC~=muM+97n`x`i!&_qO7?lh8C z*3G~m8i4!_6+QmAIo(k2Bf54Ipu%vyKG<_+aI@+wtu7zmjA)oJziiS@j{W@Lkh!n9 z-fXa^AREHvvrBEcY&orBx$U@Z48rx*jJ_ShvfqM`&{R*^YBdVs>gbGMZzF8m(c74Z z7K#nwjL@~M^Bw)F&jZ&}RH0@;g@Z-oPAVfdFA#Ixy1505@5hI`o$HS6U+#1^%!jYm zS&0Rg?DI0|uWk`r`yk-EAm5esGS@zG=^q`{brpxh88#CDPquXB%7>h`h(#^i>k|{=HU>)Ev`$P%+os?yy>+ z#2n)>>kKI|=e=#-ldsDV#vsSNwl=)$1)_VOvlpxMXbR8Iue`uGZ}{^WXdO7K_=rNz zGaJk69O!1YyL{`(}CPh{qsd2sq-bWM-c6ZQL9ErT2UPER_^As;e zv&`yE`@7f9ucA}KuV+{p&VH-X1gh)g8u`zk2;|8!(Xt&q+_PuTSY=BFA)7wlrK;Hc zE=}qVsZ6F!{O#`PpOMzDOBPs{7U@#_+3gNU+$qeJvvW^=j)?jCnOP3= zC*?`7$&^QQ>Pl0lXBaYd~;0@;;J9vs7{(y*Avu z1xE|R&!5_8JwXPe=BKKl&SjB~KZT&H#>%NT6?VmGcb~uF^z=x7zot{#v%C~rkTZgk zi^dVz(d2bGZVY^`a}14O?ujOyD*>v%vGymV{)IacOv>5O6_-3&!X4+VXdh!Kz7QYS zWAeud&gvTq>l5z3s>DKooao4Aqk$pganF-17J;Rw@2(tMfm`Y*|2bO!1)6}a7JY6< zYC2)Na>~dib84dJwKoH>YE`$y)1@11y4NeeG`h$^1(RTT{hO0-a*3Da1b>ECbVWmvKQ_@yU41#%foSgLa+ZXk z%tW95WGf9wIMHoJ%I#ZNq{81Lk39(Z#!1iq&tk&|CVq4UPevzg2Q15Cj>?!!Vbc0T z%{>dDE~4;?_`*o3Y?yLIMO^&Jbnrr?q@@MQqN^5mRP9)juTO$h9t++G3R-tDbxv=u zR@&*t?&v-LV-0WD_{`n3r_KbrD7`za+ra;AcVjiKg(GC)J(sf3*5ZBTgnXw+Lm@`S zT7FtUV-~ls5ud-Q2FcDboSClscLjyxYSp|scnus)TU(V1)dXdI59BnSh<|$`gsV6T8GZ>-GS;DiKn^?c=#D+| z8HUf(GYb9c;FQpj!XM&oat05E{m0sot2}CrKL&+>T`L@~)4YXxsfF(_+670+%)}I$ zr!DZM!fvvezwJe90F~m{yI>(3S7Q?sb1FXD+=0Q*zWRovb#BI1UD+im%7*;*=xXu$ zd=H0U7OnNf&di6O24{A6Rx%dGBUCGIuT`_HxNqptK!THEE^xvcn)oTkayv_V{2HeO zx!(pA^1=F@Kc(E#U9E=RzwBaX(V=x)q$kVY{-ERO9xyvlCf?aWOMF3yB_>84ha8&( zML*C`TyuGtrEtyMQ|8(g{aCxyu?S`{l~pxV+l!S^oHelnl7CFVO&I{)U9t zXElj?f(KGC$)sOT=8`kpHtP@(wP}h|m#$VV3=Xy^AT^Zt%wZO^k{)!POt3PCDvQN6 z>kfYeuI)4s=Q1NuzV;g-P|a5g4XqpqZplzl4ZILF|9NT*Tr#nmwRZ6fe1);im{c8! zPdsmxVyAC!y1Q?z6lWLcIELECp4ghN8nwmEs#m!ED#}{v7^M@-8;~++iM?1>Gc3ga zFj&-{rhi!Gbt?$~Oe8DkoM`j4tL{SAVm4FLD+U9lZ3)hZ`$Znxo6YELp(?rfZ6Wuk z8!Nhd{Ve#|{yo$1W~S&Q9N#$?XZBxB{*%yc>q&Lk;B+rX2OmYd-Md6|4NB5{dqZxl zu%0%mE>L6)w10|R*Vy=+mtIUNB(1?|pW9f#k$ySXdN@^Y)MGhrxRFdw(3Hkg?P`u{ zJ}0_NqwF+qq4x@9Q-iwkMyRPR>*W#WjKev-?5oir>Ho*oYldEYl?5)V0)mMy!_bb! z1EblpNyzo@_Jdc#i-aNPiK>SKX!ciXyhsRS)<1u?=aKWDF*IbCdhj5$yqp$?gj^F* zN|$08Cq57a>Pvjrruj?YbRd=THLABnva~mK$XrK0C#7kYMW5}#wm;Y4NY`JU2!xUk z{D1xA*BxDYdKsBer0sZP%5H^<8Maa;ZfSh+he)-~N5OrD0g{nQ;Oae_p42Pe|GeMj zgvkUncvQfzX}>}+P)f{N8q36G_m@J*$FmPBDlSRguS*S$Y}oqcKmPHLOPo&tj8hy$C8#|@eR%(T!CJOD5psT1_wfFQ z+7cmLnU1iymNC*YjCyo8okcx#Zgp*Z+W7_88O$&9%Bepuz<^o5A>>1 z$w}&!P%pDWa_ROHA%{Sx@J-ehHNqv@;#+0yqypj=2NP3&HcOQzux*{QAj}}kA=fu< zjgJJSmRKRf)+15=&CM)19dnP{hxEV`IWp}%u|&;wFzWEs0L#9Mx`Xh%z3*9f%MU{$ zBOIz|KGz#&8?(<`>A1==do0Nlg*%VY(f2+rC z>&C{%H4vY#Kdrrb9rvcUSG9R-@yG79filk@p)NAfCeNCyw zz?!>|9%}w`gr&mf$1{{E8NHa8>KO3#jH(sV8f`b{%Hobf+9^qMyK0DdJ>Rm6N$Jl| zR}4Edl`uWc!$cz^l}%8VGF=X98s zCW}fy3l|knRtZh)K|%kugvsHsiX{of&hLNhqe;Tj%}t@99mA6Mw9ABC8=%_yY(6+s zqa-_5CxN*uTbUQ}1w~19z*{C2PPTx1HBQvw_3r=qh7)JK7Z*#O3%jcT+*hv#wF4>a{TprX4blMo9{j1Q-}YbL zI`E4G5U>zM2ORtVf9fLy+brijd2j&-pXI=<{_oNLV-Wv0jM|4?0?ink)m0y_jzjQI NLQGaP@7|+l{|`XQYU}_2 literal 0 HcmV?d00001 From 3008c6c2062da414eaad2795872b1a111df2e590 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 11 Dec 2024 13:18:31 -0600 Subject: [PATCH 019/554] chore: rename folder name --- requirements.txt | 2 +- {btcopilot => webgenie}/__init__.py | 0 {btcopilot => webgenie}/api/__init__.py | 0 {btcopilot => webgenie}/api/dummy.py | 0 {btcopilot => webgenie}/api/get_query_axons.py | 0 {btcopilot => webgenie}/base/__init__.py | 0 {btcopilot => webgenie}/base/miner.py | 0 {btcopilot => webgenie}/base/neuron.py | 0 {btcopilot => webgenie}/base/utils/__init__.py | 0 {btcopilot => webgenie}/base/utils/weight_utils.py | 0 {btcopilot => webgenie}/base/validator.py | 0 {btcopilot => webgenie}/miners/dummy_miner.py | 0 {btcopilot => webgenie}/mock.py | 0 {btcopilot => webgenie}/protocol.py | 0 {btcopilot => webgenie}/rewards/__init__.py | 0 {btcopilot => webgenie}/rewards/gpt.py | 0 {btcopilot => webgenie}/rewards/is_valid.py | 0 {btcopilot => webgenie}/rewards/reward.py | 0 {btcopilot => webgenie}/rewards/reward_manager.py | 0 {btcopilot => webgenie}/rewards/speed.py | 0 {btcopilot => webgenie}/solution/__init__.py | 0 {btcopilot => webgenie}/solution/solution.py | 0 {btcopilot => webgenie}/subnet_links.py | 0 {btcopilot => webgenie}/task_generator/__init__.py | 0 {btcopilot => webgenie}/task_generator/task_generator.py | 0 {btcopilot => webgenie}/tasks/__init__.py | 0 {btcopilot => webgenie}/tasks/task.py | 0 {btcopilot => webgenie}/utils/__init__.py | 0 {btcopilot => webgenie}/utils/config.py | 0 {btcopilot => webgenie}/utils/logging.py | 0 {btcopilot => webgenie}/utils/misc.py | 0 {btcopilot => webgenie}/utils/uids.py | 0 {btcopilot => webgenie}/validator/__init__.py | 0 {btcopilot => webgenie}/validator/forward.py | 0 {btcopilot => webgenie}/validator/organic_forward.py | 0 {btcopilot => webgenie}/validator/reward.py | 0 36 files changed, 1 insertion(+), 1 deletion(-) rename {btcopilot => webgenie}/__init__.py (100%) rename {btcopilot => webgenie}/api/__init__.py (100%) rename {btcopilot => webgenie}/api/dummy.py (100%) rename {btcopilot => webgenie}/api/get_query_axons.py (100%) rename {btcopilot => webgenie}/base/__init__.py (100%) rename {btcopilot => webgenie}/base/miner.py (100%) rename {btcopilot => webgenie}/base/neuron.py (100%) rename {btcopilot => webgenie}/base/utils/__init__.py (100%) rename {btcopilot => webgenie}/base/utils/weight_utils.py (100%) rename {btcopilot => webgenie}/base/validator.py (100%) rename {btcopilot => webgenie}/miners/dummy_miner.py (100%) rename {btcopilot => webgenie}/mock.py (100%) rename {btcopilot => webgenie}/protocol.py (100%) rename {btcopilot => webgenie}/rewards/__init__.py (100%) rename {btcopilot => webgenie}/rewards/gpt.py (100%) rename {btcopilot => webgenie}/rewards/is_valid.py (100%) rename {btcopilot => webgenie}/rewards/reward.py (100%) rename {btcopilot => webgenie}/rewards/reward_manager.py (100%) rename {btcopilot => webgenie}/rewards/speed.py (100%) rename {btcopilot => webgenie}/solution/__init__.py (100%) rename {btcopilot => webgenie}/solution/solution.py (100%) rename {btcopilot => webgenie}/subnet_links.py (100%) rename {btcopilot => webgenie}/task_generator/__init__.py (100%) rename {btcopilot => webgenie}/task_generator/task_generator.py (100%) rename {btcopilot => webgenie}/tasks/__init__.py (100%) rename {btcopilot => webgenie}/tasks/task.py (100%) rename {btcopilot => webgenie}/utils/__init__.py (100%) rename {btcopilot => webgenie}/utils/config.py (100%) rename {btcopilot => webgenie}/utils/logging.py (100%) rename {btcopilot => webgenie}/utils/misc.py (100%) rename {btcopilot => webgenie}/utils/uids.py (100%) rename {btcopilot => webgenie}/validator/__init__.py (100%) rename {btcopilot => webgenie}/validator/forward.py (100%) rename {btcopilot => webgenie}/validator/organic_forward.py (100%) rename {btcopilot => webgenie}/validator/reward.py (100%) diff --git a/requirements.txt b/requirements.txt index 8e5192d3..ea719aa4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -bittensor>=7 +bittensor==6.9.3 starlette>=0.30.0 pydantic>=2 rich>=13 diff --git a/btcopilot/__init__.py b/webgenie/__init__.py similarity index 100% rename from btcopilot/__init__.py rename to webgenie/__init__.py diff --git a/btcopilot/api/__init__.py b/webgenie/api/__init__.py similarity index 100% rename from btcopilot/api/__init__.py rename to webgenie/api/__init__.py diff --git a/btcopilot/api/dummy.py b/webgenie/api/dummy.py similarity index 100% rename from btcopilot/api/dummy.py rename to webgenie/api/dummy.py diff --git a/btcopilot/api/get_query_axons.py b/webgenie/api/get_query_axons.py similarity index 100% rename from btcopilot/api/get_query_axons.py rename to webgenie/api/get_query_axons.py diff --git a/btcopilot/base/__init__.py b/webgenie/base/__init__.py similarity index 100% rename from btcopilot/base/__init__.py rename to webgenie/base/__init__.py diff --git a/btcopilot/base/miner.py b/webgenie/base/miner.py similarity index 100% rename from btcopilot/base/miner.py rename to webgenie/base/miner.py diff --git a/btcopilot/base/neuron.py b/webgenie/base/neuron.py similarity index 100% rename from btcopilot/base/neuron.py rename to webgenie/base/neuron.py diff --git a/btcopilot/base/utils/__init__.py b/webgenie/base/utils/__init__.py similarity index 100% rename from btcopilot/base/utils/__init__.py rename to webgenie/base/utils/__init__.py diff --git a/btcopilot/base/utils/weight_utils.py b/webgenie/base/utils/weight_utils.py similarity index 100% rename from btcopilot/base/utils/weight_utils.py rename to webgenie/base/utils/weight_utils.py diff --git a/btcopilot/base/validator.py b/webgenie/base/validator.py similarity index 100% rename from btcopilot/base/validator.py rename to webgenie/base/validator.py diff --git a/btcopilot/miners/dummy_miner.py b/webgenie/miners/dummy_miner.py similarity index 100% rename from btcopilot/miners/dummy_miner.py rename to webgenie/miners/dummy_miner.py diff --git a/btcopilot/mock.py b/webgenie/mock.py similarity index 100% rename from btcopilot/mock.py rename to webgenie/mock.py diff --git a/btcopilot/protocol.py b/webgenie/protocol.py similarity index 100% rename from btcopilot/protocol.py rename to webgenie/protocol.py diff --git a/btcopilot/rewards/__init__.py b/webgenie/rewards/__init__.py similarity index 100% rename from btcopilot/rewards/__init__.py rename to webgenie/rewards/__init__.py diff --git a/btcopilot/rewards/gpt.py b/webgenie/rewards/gpt.py similarity index 100% rename from btcopilot/rewards/gpt.py rename to webgenie/rewards/gpt.py diff --git a/btcopilot/rewards/is_valid.py b/webgenie/rewards/is_valid.py similarity index 100% rename from btcopilot/rewards/is_valid.py rename to webgenie/rewards/is_valid.py diff --git a/btcopilot/rewards/reward.py b/webgenie/rewards/reward.py similarity index 100% rename from btcopilot/rewards/reward.py rename to webgenie/rewards/reward.py diff --git a/btcopilot/rewards/reward_manager.py b/webgenie/rewards/reward_manager.py similarity index 100% rename from btcopilot/rewards/reward_manager.py rename to webgenie/rewards/reward_manager.py diff --git a/btcopilot/rewards/speed.py b/webgenie/rewards/speed.py similarity index 100% rename from btcopilot/rewards/speed.py rename to webgenie/rewards/speed.py diff --git a/btcopilot/solution/__init__.py b/webgenie/solution/__init__.py similarity index 100% rename from btcopilot/solution/__init__.py rename to webgenie/solution/__init__.py diff --git a/btcopilot/solution/solution.py b/webgenie/solution/solution.py similarity index 100% rename from btcopilot/solution/solution.py rename to webgenie/solution/solution.py diff --git a/btcopilot/subnet_links.py b/webgenie/subnet_links.py similarity index 100% rename from btcopilot/subnet_links.py rename to webgenie/subnet_links.py diff --git a/btcopilot/task_generator/__init__.py b/webgenie/task_generator/__init__.py similarity index 100% rename from btcopilot/task_generator/__init__.py rename to webgenie/task_generator/__init__.py diff --git a/btcopilot/task_generator/task_generator.py b/webgenie/task_generator/task_generator.py similarity index 100% rename from btcopilot/task_generator/task_generator.py rename to webgenie/task_generator/task_generator.py diff --git a/btcopilot/tasks/__init__.py b/webgenie/tasks/__init__.py similarity index 100% rename from btcopilot/tasks/__init__.py rename to webgenie/tasks/__init__.py diff --git a/btcopilot/tasks/task.py b/webgenie/tasks/task.py similarity index 100% rename from btcopilot/tasks/task.py rename to webgenie/tasks/task.py diff --git a/btcopilot/utils/__init__.py b/webgenie/utils/__init__.py similarity index 100% rename from btcopilot/utils/__init__.py rename to webgenie/utils/__init__.py diff --git a/btcopilot/utils/config.py b/webgenie/utils/config.py similarity index 100% rename from btcopilot/utils/config.py rename to webgenie/utils/config.py diff --git a/btcopilot/utils/logging.py b/webgenie/utils/logging.py similarity index 100% rename from btcopilot/utils/logging.py rename to webgenie/utils/logging.py diff --git a/btcopilot/utils/misc.py b/webgenie/utils/misc.py similarity index 100% rename from btcopilot/utils/misc.py rename to webgenie/utils/misc.py diff --git a/btcopilot/utils/uids.py b/webgenie/utils/uids.py similarity index 100% rename from btcopilot/utils/uids.py rename to webgenie/utils/uids.py diff --git a/btcopilot/validator/__init__.py b/webgenie/validator/__init__.py similarity index 100% rename from btcopilot/validator/__init__.py rename to webgenie/validator/__init__.py diff --git a/btcopilot/validator/forward.py b/webgenie/validator/forward.py similarity index 100% rename from btcopilot/validator/forward.py rename to webgenie/validator/forward.py diff --git a/btcopilot/validator/organic_forward.py b/webgenie/validator/organic_forward.py similarity index 100% rename from btcopilot/validator/organic_forward.py rename to webgenie/validator/organic_forward.py diff --git a/btcopilot/validator/reward.py b/webgenie/validator/reward.py similarity index 100% rename from btcopilot/validator/reward.py rename to webgenie/validator/reward.py From d541dc97502b3b5265f637c8a1669fb8f1554c84 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 11 Dec 2024 14:24:57 -0600 Subject: [PATCH 020/554] chore: refactor packagename --- contrib/CONTRIBUTING.md | 6 +++--- neurons/miner.py | 18 +++++++++--------- neurons/validator.py | 12 ++++++------ requirements.txt | 2 +- run_miner.sh | 3 ++- run_validator.sh | 2 +- setup.py | 10 +++++----- tests/test_template_validator.py | 8 ++++---- webgenie/__init__.py | 2 +- webgenie/api/dummy.py | 2 +- webgenie/base/miner.py | 4 ++-- webgenie/base/neuron.py | 8 ++++---- webgenie/base/validator.py | 16 ++++++++-------- webgenie/miners/dummy_miner.py | 4 ++-- webgenie/protocol.py | 8 ++++---- webgenie/rewards/gpt.py | 6 +++--- webgenie/rewards/is_valid.py | 6 +++--- webgenie/rewards/reward_manager.py | 12 ++++++------ webgenie/rewards/speed.py | 6 +++--- webgenie/task_generator/task_generator.py | 2 +- webgenie/tasks/task.py | 2 +- webgenie/validator/forward.py | 12 ++++++------ webgenie/validator/organic_forward.py | 6 +++--- 23 files changed, 79 insertions(+), 78 deletions(-) diff --git a/contrib/CONTRIBUTING.md b/contrib/CONTRIBUTING.md index 2393c621..9e98a31f 100644 --- a/contrib/CONTRIBUTING.md +++ b/contrib/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to BTCopilot +# Contributing to webgenie -The following is a set of guidelines for contributing to the BTCopilot Subnet. These are **HIGHLY RECOMMENDED** guidelines, but not hard-and-fast rules. Use your best judgment, and feel free to propose changes to this document in a pull request. +The following is a set of guidelines for contributing to the webgenie Subnet. These are **HIGHLY RECOMMENDED** guidelines, but not hard-and-fast rules. Use your best judgment, and feel free to propose changes to this document in a pull request. ## Table Of Contents 1. [How Can I Contribute?](#how-can-i-contribute) @@ -99,7 +99,7 @@ After you submit a pull request, it will be reviewed by the maintainers. They ma > Note: Be sure to merge the latest from "upstream" before making a pull request: ```bash -git remote add upstream https://github.com/BTCopilot/btcopilot.git +git remote add upstream https://github.com/webgenie/webgenie.git git fetch upstream git merge upstream/ git push origin diff --git a/neurons/miner.py b/neurons/miner.py index 363df6e9..9098a42f 100644 --- a/neurons/miner.py +++ b/neurons/miner.py @@ -23,10 +23,10 @@ import bittensor as bt # Bittensor Miner Template: -import btcopilot +import webgenie # import base miner class which takes care of most of the boilerplate -from btcopilot.base.miner import BaseMinerNeuron +from webgenie.base.miner import BaseMinerNeuron class Miner(BaseMinerNeuron): @@ -42,7 +42,7 @@ def __init__(self, config=None): super(Miner, self).__init__(config=config) miner_name = "dummy_miner" - miner_module = importlib.import_module(f"btcopilot.miners.{miner_name}") + miner_module = importlib.import_module(f"webgenie.miners.{miner_name}") self.miner_init = miner_module.miner_init self.miner_forward = miner_module.miner_forward @@ -50,14 +50,14 @@ def __init__(self, config=None): self.miner_init(self) async def forward( - self, synapse: btcopilot.protocol.BtCopilotSynapse - ) -> btcopilot.protocol.BtCopilotSynapse: + self, synapse: webgenie.protocol.webgenieSynapse + ) -> webgenie.protocol.webgenieSynapse: bt.logging.debug(f"Miner forward called with synapse: {synapse}") return self.miner_forward(self, synapse) async def blacklist( - self, synapse: btcopilot.protocol.BtCopilotSynapse + self, synapse: webgenie.protocol.webgenieSynapse ) -> typing.Tuple[bool, str]: """ Determines whether an incoming request should be blacklisted and thus ignored. Your implementation should @@ -68,7 +68,7 @@ async def blacklist( requests before they are deserialized to avoid wasting resources on requests that will be ignored. Args: - synapse (template.protocol.BtCopilotSynapse): A synapse object constructed from the headers of the incoming request. + synapse (template.protocol.webgenieSynapse): A synapse object constructed from the headers of the incoming request. Returns: Tuple[bool, str]: A tuple containing a boolean indicating whether the synapse's hotkey is blacklisted, @@ -118,7 +118,7 @@ async def blacklist( ) return False, "Hotkey recognized!" - async def priority(self, synapse: btcopilot.protocol.BtCopilotSynapse) -> float: + async def priority(self, synapse: webgenie.protocol.webgenieSynapse) -> float: """ The priority function determines the order in which requests are handled. More valuable or higher-priority requests are processed before others. You should design your own priority mechanism with care. @@ -126,7 +126,7 @@ async def priority(self, synapse: btcopilot.protocol.BtCopilotSynapse) -> float: This implementation assigns priority to incoming requests based on the calling entity's stake in the metagraph. Args: - synapse (template.protocol.BtCopilotSynapse): The synapse object that contains metadata about the incoming request. + synapse (template.protocol.webgenieSynapse): The synapse object that contains metadata about the incoming request. Returns: float: A priority score derived from the stake of the calling entity. diff --git a/neurons/validator.py b/neurons/validator.py index cdb97877..8e375b3b 100644 --- a/neurons/validator.py +++ b/neurons/validator.py @@ -23,14 +23,14 @@ import bittensor as bt # import base validator class which takes care of most of the boilerplate -from btcopilot.base.validator import BaseValidatorNeuron +from webgenie.base.validator import BaseValidatorNeuron # Bittensor Validator Template: -from btcopilot.validator import forward -from btcopilot.validator import forward_organic_synapse +from webgenie.validator import forward +from webgenie.validator import forward_organic_synapse -from btcopilot.task_generator import TaskGenerator -from btcopilot.rewards import RewardManager -from btcopilot.protocol import BtCopilotSynapse +from webgenie.task_generator import TaskGenerator +from webgenie.rewards import RewardManager +from webgenie.protocol import webgenieSynapse class Validator(BaseValidatorNeuron): """ diff --git a/requirements.txt b/requirements.txt index ea719aa4..c8d4ef70 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -bittensor==6.9.3 +bittensor==7.4.0 starlette>=0.30.0 pydantic>=2 rich>=13 diff --git a/run_miner.sh b/run_miner.sh index bc7be661..eb48a80a 100644 --- a/run_miner.sh +++ b/run_miner.sh @@ -1,3 +1,4 @@ export PYTHONPATH=. #--axon.port 5555 -python3 neurons/miner.py --netuid 214 --subtensor.network test --wallet.name s-miner --wallet.hotkey miner1 --logging.debug --axon.port 8090 +python3.12 neurons/miner.py --netuid 214 --subtensor.network test --wallet.name s-miner --wallet.hotkey miner1 --logging.debug --axon.port 8090 + diff --git a/run_validator.sh b/run_validator.sh index d4b2a48c..723b580d 100644 --- a/run_validator.sh +++ b/run_validator.sh @@ -1,3 +1,3 @@ export PYTHONASYNCIODEBUG=1 export PYTHONPATH=. -python3 neurons/validator.py --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug --neuron.axon_port 8091 +python3.12 neurons/validator.py --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug --neuron.axon_port 8091 diff --git a/setup.py b/setup.py index 18ca652c..440a4ffa 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ # The MIT License (MIT) -# Copyright © 2023 BTCopilot +# Copyright © 2023 webgenie # Sangar -# Copyright © 2023 +# Copyright © 2023 # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation @@ -63,12 +63,12 @@ def read_requirements(path): version_string = version_match.group(1) setup( - name="btcopilot", + name="webgenie", version=version_string, - description="BTCopilot aims to transform diverse prompts—ranging from text and voice to images and Figma designs—into fully functional, ready-to-deploy projects.", + description="webgenie aims to transform diverse prompts—ranging from text and voice to images and Figma designs—into fully functional, ready-to-deploy projects.", long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/BTCopilot/btcopilot", + url="https://github.com/webgenie/webgenie", author="Sangar", packages=find_packages(), include_package_data=True, diff --git a/tests/test_template_validator.py b/tests/test_template_validator.py index 92f82d36..021318e7 100644 --- a/tests/test_template_validator.py +++ b/tests/test_template_validator.py @@ -23,10 +23,10 @@ import torch from neurons.validator import Validator -from btcopilot.base.validator import BaseValidatorNeuron -from btcopilot.protocol import Dummy -from btcopilot.utils.uids import get_random_uids -from btcopilot.validator.reward import get_rewards +from webgenie.base.validator import BaseValidatorNeuron +from webgenie.protocol import Dummy +from webgenie.utils.uids import get_random_uids +from webgenie.validator.reward import get_rewards class TemplateValidatorNeuronTestCase(unittest.TestCase): diff --git a/webgenie/__init__.py b/webgenie/__init__.py index 4115dac0..41bb1b50 100644 --- a/webgenie/__init__.py +++ b/webgenie/__init__.py @@ -18,7 +18,7 @@ # DEALINGS IN THE SOFTWARE. # Change this value when updating your code base. -# Define the version of the btcopilot. +# Define the version of the webgenie. __version__ = "0.0.1" version_split = __version__.split(".") __spec_version__ = ( diff --git a/webgenie/api/dummy.py b/webgenie/api/dummy.py index b1d0e13b..2845f6b5 100644 --- a/webgenie/api/dummy.py +++ b/webgenie/api/dummy.py @@ -19,7 +19,7 @@ import bittensor as bt from typing import List, Optional, Union, Any, Dict -from btcopilot.protocol import Dummy +from webgenie.protocol import Dummy from bittensor.subnets import SubnetsAPI diff --git a/webgenie/base/miner.py b/webgenie/base/miner.py index 5f3e3402..999b489e 100644 --- a/webgenie/base/miner.py +++ b/webgenie/base/miner.py @@ -23,8 +23,8 @@ import bittensor as bt -from btcopilot.base.neuron import BaseNeuron -from btcopilot.utils.config import add_miner_args +from webgenie.base.neuron import BaseNeuron +from webgenie.utils.config import add_miner_args from typing import Union diff --git a/webgenie/base/neuron.py b/webgenie/base/neuron.py index 8c41320a..1eed9a77 100644 --- a/webgenie/base/neuron.py +++ b/webgenie/base/neuron.py @@ -23,10 +23,10 @@ from abc import ABC, abstractmethod # Sync calls set weights and also resyncs the metagraph. -from btcopilot.utils.config import check_config, add_args, config -from btcopilot.utils.misc import ttl_get_block -from btcopilot import __spec_version__ as spec_version -from btcopilot.mock import MockSubtensor, MockMetagraph +from webgenie.utils.config import check_config, add_args, config +from webgenie.utils.misc import ttl_get_block +from webgenie import __spec_version__ as spec_version +from webgenie.mock import MockSubtensor, MockMetagraph class BaseNeuron(ABC): diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index 70872c54..56f4cf6a 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -27,15 +27,15 @@ from typing import List, Union, Tuple from traceback import print_exception -from btcopilot.base.neuron import BaseNeuron -from btcopilot.base.utils.weight_utils import ( +from webgenie.base.neuron import BaseNeuron +from webgenie.base.utils.weight_utils import ( process_weights_for_netuid, convert_weights_and_uids_for_emit, ) # TODO: Replace when bittensor switches to numpy -from btcopilot.mock import MockDendrite -from btcopilot.utils.config import add_validator_args -from btcopilot.protocol import BtCopilotSynapse -from btcopilot.validator.organic_forward import forward_organic_synapse +from webgenie.mock import MockDendrite +from webgenie.utils.config import add_validator_args +from webgenie.protocol import webgenieSynapse +from webgenie.validator.organic_forward import forward_organic_synapse SUBNET_OWNER_HOTKEY = "5G9sRcoaw2H3SYDq7e7PoGhbbMUPHQi6pC6tPrahmSmDtxS8" @@ -51,14 +51,14 @@ def add_args(cls, parser: argparse.ArgumentParser): super().add_args(parser) add_validator_args(cls, parser) - async def organic_forward(self, synapse: BtCopilotSynapse) -> BtCopilotSynapse: + async def organic_forward(self, synapse: webgenieSynapse) -> webgenieSynapse: bt.logging.error(f"OrganicForward Thread Name: {threading.current_thread().name}") bt.logging.info(f"=========> {synapse}") return await forward_organic_synapse(self, synapse) - async def blacklist(self, synapse: BtCopilotSynapse) -> Tuple[bool, str]: + async def blacklist(self, synapse: webgenieSynapse) -> Tuple[bool, str]: """ Only allow the subnet owner to send synapse to the validator. """ diff --git a/webgenie/miners/dummy_miner.py b/webgenie/miners/dummy_miner.py index 3c9e2ba0..05d41bba 100644 --- a/webgenie/miners/dummy_miner.py +++ b/webgenie/miners/dummy_miner.py @@ -12,7 +12,7 @@ from langchain_core.runnables.base import RunnableSequence import bittensor as bt -import btcopilot +import webgenie import os def miner_init(self): @@ -25,7 +25,7 @@ def miner_init(self): model_name="gpt-4", ) -def miner_forward(self, synapse: btcopilot.protocol.BtCopilotSynapse)->Awaitable: +def miner_forward(self, synapse: webgenie.protocol.webgenieSynapse)->Awaitable: async def _forward(self, chain: RunnableSequence, chain_formatter: Dict[str, str], timeout_threshold: float, init_time: float, send: Send): try: diff --git a/webgenie/protocol.py b/webgenie/protocol.py index 8b55edf2..cac1a840 100644 --- a/webgenie/protocol.py +++ b/webgenie/protocol.py @@ -22,12 +22,12 @@ import bittensor as bt -from btcopilot.tasks import Task -from btcopilot.solution import Solution +from webgenie.tasks import Task +from webgenie.solution import Solution -class BtCopilotSynapse(bt.StreamingSynapse): +class webgenieSynapse(bt.StreamingSynapse): """ - A protocol for the BtCopilot. + A protocol for the webgenie. """ task: Union[Task, None] = pydantic.Field( diff --git a/webgenie/rewards/gpt.py b/webgenie/rewards/gpt.py index 9afe5971..056fc1c8 100644 --- a/webgenie/rewards/gpt.py +++ b/webgenie/rewards/gpt.py @@ -3,9 +3,9 @@ from langchain_openai import ChatOpenAI import bittensor as bt -from btcopilot.rewards import Reward -from btcopilot.tasks import Task -from btcopilot.solution import Solution +from webgenie.rewards import Reward +from webgenie.tasks import Task +from webgenie.solution import Solution class GPTReward(Reward): def __init__(self): diff --git a/webgenie/rewards/is_valid.py b/webgenie/rewards/is_valid.py index 2e334949..4a065508 100644 --- a/webgenie/rewards/is_valid.py +++ b/webgenie/rewards/is_valid.py @@ -1,9 +1,9 @@ from bs4 import BeautifulSoup import tinycss2 -from btcopilot.rewards import Reward -from btcopilot.tasks import Task -from btcopilot.solution import Solution +from webgenie.rewards import Reward +from webgenie.tasks import Task +from webgenie.solution import Solution class IsValidReward(Reward): diff --git a/webgenie/rewards/reward_manager.py b/webgenie/rewards/reward_manager.py index a50daa52..f68d255a 100644 --- a/webgenie/rewards/reward_manager.py +++ b/webgenie/rewards/reward_manager.py @@ -1,11 +1,11 @@ from typing import Dict, List, Tuple -from btcopilot.rewards import Reward -from btcopilot.rewards import GPTReward -from btcopilot.rewards import SpeedReward -from btcopilot.rewards import IsValidReward -from btcopilot.tasks import Task -from btcopilot.solution import Solution +from webgenie.rewards import Reward +from webgenie.rewards import GPTReward +from webgenie.rewards import SpeedReward +from webgenie.rewards import IsValidReward +from webgenie.tasks import Task +from webgenie.solution import Solution class RewardManager: """ diff --git a/webgenie/rewards/speed.py b/webgenie/rewards/speed.py index 0fd47655..d9e905ff 100644 --- a/webgenie/rewards/speed.py +++ b/webgenie/rewards/speed.py @@ -1,7 +1,7 @@ -from btcopilot.rewards import Reward -from btcopilot.tasks import Task -from btcopilot.solution import Solution +from webgenie.rewards import Reward +from webgenie.tasks import Task +from webgenie.solution import Solution class SpeedReward(Reward): diff --git a/webgenie/task_generator/task_generator.py b/webgenie/task_generator/task_generator.py index cee9aa7a..9f0c15a3 100644 --- a/webgenie/task_generator/task_generator.py +++ b/webgenie/task_generator/task_generator.py @@ -1,5 +1,5 @@ -from btcopilot.tasks import Task +from webgenie.tasks import Task class TaskGenerator: """ A singleton generator for tasks. diff --git a/webgenie/tasks/task.py b/webgenie/tasks/task.py index 39ee892f..3caa3274 100644 --- a/webgenie/tasks/task.py +++ b/webgenie/tasks/task.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from typing import List from pydantic import BaseModel -from btcopilot.solution import Solution +from webgenie.solution import Solution class Task(BaseModel): query: str diff --git a/webgenie/validator/forward.py b/webgenie/validator/forward.py index c8fd63eb..d73e9873 100644 --- a/webgenie/validator/forward.py +++ b/webgenie/validator/forward.py @@ -22,10 +22,10 @@ from typing import Awaitable, List from dataclasses import dataclass -from btcopilot.protocol import BtCopilotSynapse -from btcopilot.validator.reward import get_rewards -from btcopilot.utils.uids import get_random_uids -from btcopilot.solution import Solution +from webgenie.protocol import webgenieSynapse +from webgenie.validator.reward import get_rewards +from webgenie.utils.uids import get_random_uids +from webgenie.solution import Solution from .organic_forward import forward_organic_synapse async def process_response(uid: int, async_generator: Awaitable): @@ -41,7 +41,7 @@ async def process_response(uid: int, async_generator: Awaitable): bt.logging.info(f"==============8") if chunk is not None: synapse = chunk - if isinstance(synapse, BtCopilotSynapse): + if isinstance(synapse, webgenieSynapse): if synapse.dendrite.status_code == 200: synapse.solution.miner_uid = uid return synapse.solution @@ -84,7 +84,7 @@ async def forward(self): task = self.task_generator.next_task() - synapse = BtCopilotSynapse( + synapse = webgenieSynapse( task=task ) diff --git a/webgenie/validator/organic_forward.py b/webgenie/validator/organic_forward.py index db31ba39..c7965f81 100644 --- a/webgenie/validator/organic_forward.py +++ b/webgenie/validator/organic_forward.py @@ -4,10 +4,10 @@ from starlette.types import Send import bittensor as bt -from btcopilot.protocol import BtCopilotSynapse +from webgenie.protocol import webgenieSynapse -async def forward_organic_synapse(self, synapse: BtCopilotSynapse)->BtCopilotSynapse: - async def forward_miner(synapse: BtCopilotSynapse, send: Send): +async def forward_organic_synapse(self, synapse: webgenieSynapse)->webgenieSynapse: + async def forward_miner(synapse: webgenieSynapse, send: Send): bt.logging.info(f"Send Synapse to miner: {synapse}") async def handle_miner_response(responses): for resp in responses: From abd64fb3f53ae80b2177eadee9c81c90e4d9a3d3 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 11 Dec 2024 14:27:48 -0600 Subject: [PATCH 021/554] chore: refactor folder structure --- neurons/{ => miners}/miner.py | 0 neurons/{ => validators}/validator.py | 0 run_miner.sh | 2 +- run_validator.sh | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename neurons/{ => miners}/miner.py (100%) rename neurons/{ => validators}/validator.py (100%) diff --git a/neurons/miner.py b/neurons/miners/miner.py similarity index 100% rename from neurons/miner.py rename to neurons/miners/miner.py diff --git a/neurons/validator.py b/neurons/validators/validator.py similarity index 100% rename from neurons/validator.py rename to neurons/validators/validator.py diff --git a/run_miner.sh b/run_miner.sh index eb48a80a..0d2d9538 100644 --- a/run_miner.sh +++ b/run_miner.sh @@ -1,4 +1,4 @@ export PYTHONPATH=. #--axon.port 5555 -python3.12 neurons/miner.py --netuid 214 --subtensor.network test --wallet.name s-miner --wallet.hotkey miner1 --logging.debug --axon.port 8090 +python3.12 neurons/miners/miner.py --netuid 214 --subtensor.network test --wallet.name s-miner --wallet.hotkey miner1 --logging.debug --axon.port 8090 diff --git a/run_validator.sh b/run_validator.sh index 723b580d..508d7d76 100644 --- a/run_validator.sh +++ b/run_validator.sh @@ -1,3 +1,3 @@ export PYTHONASYNCIODEBUG=1 export PYTHONPATH=. -python3.12 neurons/validator.py --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug --neuron.axon_port 8091 +python3.12 neurons/validators/validator.py --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug --neuron.axon_port 8091 From 170271599a3d0c23358460171e4b87b9e2a5aae7 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 11 Dec 2024 20:27:15 -0600 Subject: [PATCH 022/554] feat: restructure architecture --- neurons/miners/genie_miner.py | 0 neurons/miners/miner.py | 8 +- neurons/validators/genie_validator.py | 95 +++++++++++ neurons/validators/validator.py | 105 +++++++------ webgenie/base/validator.py | 148 ++---------------- webgenie/miners/dummy_miner.py | 2 +- webgenie/protocol.py | 58 ++++--- webgenie/solution/solution.py | 1 - .../task_generator/image_task_generator.py | 21 +++ webgenie/task_generator/task_generator.py | 11 +- .../task_generator/text_task_generator.py | 21 +++ webgenie/tasks/task.py | 26 +-- webgenie/validator/__init__.py | 3 - webgenie/validator/forward.py | 117 -------------- webgenie/validator/organic_forward.py | 45 ------ webgenie/validator/reward.py | 54 ------- 16 files changed, 274 insertions(+), 441 deletions(-) create mode 100644 neurons/miners/genie_miner.py create mode 100644 neurons/validators/genie_validator.py create mode 100644 webgenie/task_generator/image_task_generator.py create mode 100644 webgenie/task_generator/text_task_generator.py delete mode 100644 webgenie/validator/__init__.py delete mode 100644 webgenie/validator/forward.py delete mode 100644 webgenie/validator/organic_forward.py delete mode 100644 webgenie/validator/reward.py diff --git a/neurons/miners/genie_miner.py b/neurons/miners/genie_miner.py new file mode 100644 index 00000000..e69de29b diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index 9098a42f..ae3d52ee 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -50,14 +50,14 @@ def __init__(self, config=None): self.miner_init(self) async def forward( - self, synapse: webgenie.protocol.webgenieSynapse - ) -> webgenie.protocol.webgenieSynapse: + self, synapse: webgenie.protocol.WebgenieStreamingSynapse + ) -> webgenie.protocol.WebgenieStreamingSynapse: bt.logging.debug(f"Miner forward called with synapse: {synapse}") return self.miner_forward(self, synapse) async def blacklist( - self, synapse: webgenie.protocol.webgenieSynapse + self, synapse: webgenie.protocol.WebgenieStreamingSynapse ) -> typing.Tuple[bool, str]: """ Determines whether an incoming request should be blacklisted and thus ignored. Your implementation should @@ -118,7 +118,7 @@ async def blacklist( ) return False, "Hotkey recognized!" - async def priority(self, synapse: webgenie.protocol.webgenieSynapse) -> float: + async def priority(self, synapse: webgenie.protocol.WebgenieStreamingSynapse) -> float: """ The priority function determines the order in which requests are handled. More valuable or higher-priority requests are processed before others. You should design your own priority mechanism with care. diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py new file mode 100644 index 00000000..f069b42b --- /dev/null +++ b/neurons/validators/genie_validator.py @@ -0,0 +1,95 @@ +import bittensor as bt +import random +import torch +from typing import List + +from webgenie.base.neuron import BaseNeuron +from webgenie.protocol import WebgenieStreamingSynapse, WebgenieTextSynapse +from webgenie.rewards import RewardManager +from webgenie.solution import Solution +from webgenie.task_generator.image_task_generator import ImageTaskGenerator +from webgenie.task_generator.text_task_generator import TextTaskGenerator +from webgenie.utils.uids import get_random_uids + + +MAX_SYNTHETIC_HISTORY_SIZE = 10 + +class GenieValidator: + def __init__(self, neuron: BaseNeuron): + self.neuron = neuron + self.config = neuron.config + self.synthetic_history = [] + + self.reward_manager = RewardManager() + self.task_generators = [ + TextTaskGenerator(), + ImageTaskGenerator(), + ] + + async def forward(self): + try: + if len(self.synthetic_history) > MAX_SYNTHETIC_HISTORY_SIZE: + return + + miner_uids = get_random_uids(self, k=self.config.neuron.sample_size) + + task, synapse = await random.choice(self.task_generators).generate_task() + + all_synapse_results = await self.neuron.dendrite( + axons = [self.metagraph.axons[uid] for uid in miner_uids], + synapse=synapse, + timeout=task.timeout + ) + + processed_synapses, processed_miner_uids = await self.process_synapses(all_synapse_results, miner_uids) + + if len(processed_synapses) == 0: + bt.logging.error(f"No valid synapses received") + return + bt.logging.debug(f"Processed synapses: {processed_synapses}") + + self.synthetic_history.append((task, processed_synapses, processed_miner_uids)) + except Exception as e: + bt.logging.error(f"Error in forward: {e}") + raise e + + async def process_synapses(self, synapses: List[WebgenieStreamingSynapse], miner_uids: List[int]): + processed_synapses = [] + processed_miner_uids = [] + + for synapse, miner_uid in zip(synapses, miner_uids): + if synapse.dendrite.status_code == 200: + processed_synapses.append(synapse) + processed_miner_uids.append(miner_uid) + + return processed_synapses, processed_miner_uids + + async def score(self): + if len(self.synthetic_history) == 0: + bt.logging.warning(f"No synthetic history to score") + return + + task, synapses, miner_uids = self.synthetic_history.pop(0) + + task_generator = task.generator + scores = await task_generator.reward(task, synapses) + self.neuron.update_scores(scores, miner_uids) + self.neuron.sync() + + async def organic_forward(self, synapse: WebgenieTextSynapse): + axon = self.metagraph.axons[1] + try: + async with bt.dendrite(wallet=self.wallet) as dendrite: + bt.logging.info(f"Dendrite: {dendrite}") + responses = await dendrite( + axons=[axon], + synapse=synapse, + timeout=synapse.timeout, + ) + return responses[0] + except Exception as e: + bt.logging.error(f"[forward_organic_synapse] Error querying dendrite: {e}") + synapse.solution = Solution( + html = "", + ) + return synapse \ No newline at end of file diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 8e375b3b..743a6c51 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -1,36 +1,16 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# Copyright © 2024 Sangar - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - - +# Copyright © 2024 pycorn +import asyncio +from dotenv import load_dotenv +load_dotenv() import time -from typing import Tuple -# Bittensor + import bittensor as bt -# import base validator class which takes care of most of the boilerplate from webgenie.base.validator import BaseValidatorNeuron -# Bittensor Validator Template: -from webgenie.validator import forward -from webgenie.validator import forward_organic_synapse - -from webgenie.task_generator import TaskGenerator -from webgenie.rewards import RewardManager -from webgenie.protocol import webgenieSynapse +from webgenie.protocol import WebgenieStreamingSynapse +from neurons.validators.genie_validator import GenieValidator class Validator(BaseValidatorNeuron): """ @@ -45,27 +25,64 @@ def __init__(self, config=None): super(Validator, self).__init__(config=config) bt.logging.info("load_state()") self.load_state() - self.reward_manager = RewardManager() - self.task_generator = TaskGenerator() + self.genie_validator = GenieValidator() + async def organic_forward(self, synapse: WebgenieStreamingSynapse): + return await self.genie_validator.organic_forward(synapse) async def forward(self): - """ - Validator forward pass. Consists of: - - Generating the query - - Querying the miners - - Getting the responses - - Rewarding the miners - - Updating the scores - """ - # TODO(developer): Rewrite this function based on your protocol definition. - return await forward(self) - + return await self.genie_validator.forward(self) + async def concurrent_forward(self): + coroutines = [ + self.forward() + for _ in range(self.config.neuron.num_concurrent_forwards) + ] + await asyncio.gather(*coroutines) + async def forward_loop(self): + self.sync() + bt.logging.info(f"Validator starting at block: {self.block}") + + while True: + try: + bt.logging.info(f"step({self.step}) block({self.block})") + self.loop.run_until_complete(self.concurrent_forward()) + self.sync() + self.step += 1 + except Exception as e: + bt.logging.error(f"Error during forward loop: {str(e)}") + + await asyncio.sleep(5) + + async def scoring_loop(self): + bt.logging.info(f"Scoring loop starting") + while True: + try: + await self.genie_validator.score() + except Exception as e: + bt.logging.error(f"Error during scoring: {str(e)}") + + await asyncio.sleep(5) + + async def __aenter__(self): + self.loop.create_task(self.forward_loop()) + self.loop.create_task(self.scoring_loop()) + self.is_running = True + bt.logging.debug("Starting validator in background thread") + return self + + async def __aexit__(self, exc_type, exc_value, traceback): + if self.is_running: + self.should_exit = True + self.is_running = False + bt.logging.debug("Stopping validator in background thread") + +async def main(): + async with Validator() as validator: + while validator.is_running and not validator.should_exit: + await asyncio.sleep(15) + # The main function parses the configuration and runs the validator. if __name__ == "__main__": - with Validator() as validator: - while True: - #bt.logging.info(f"Validator running... {time.time()}") - time.sleep(5) + asyncio.run(main()) diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index 56f4cf6a..437ac1a5 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -34,7 +34,7 @@ ) # TODO: Replace when bittensor switches to numpy from webgenie.mock import MockDendrite from webgenie.utils.config import add_validator_args -from webgenie.protocol import webgenieSynapse +from webgenie.protocol import WebgenieStreamingSynapse from webgenie.validator.organic_forward import forward_organic_synapse SUBNET_OWNER_HOTKEY = "5G9sRcoaw2H3SYDq7e7PoGhbbMUPHQi6pC6tPrahmSmDtxS8" @@ -50,27 +50,12 @@ class BaseValidatorNeuron(BaseNeuron): def add_args(cls, parser: argparse.ArgumentParser): super().add_args(parser) add_validator_args(cls, parser) - - async def organic_forward(self, synapse: webgenieSynapse) -> webgenieSynapse: - - bt.logging.error(f"OrganicForward Thread Name: {threading.current_thread().name}") - bt.logging.info(f"=========> {synapse}") - - return await forward_organic_synapse(self, synapse) - async def blacklist(self, synapse: webgenieSynapse) -> Tuple[bool, str]: - """ - Only allow the subnet owner to send synapse to the validator. - """ - if synapse.dendrite.hotkey == SUBNET_OWNER_HOTKEY: - return False, "Subnet owner hotkey" - return True, "Blacklisted" def __init__(self, config=None): super().__init__(config=config) # Save a copy of the hotkeys to local memory. self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) - print("=== self.hotkeys ===>", self.hotkeys) # Dendrite lets us send messages to other nodes (axons) in the network. if self.config.mock: @@ -79,8 +64,6 @@ def __init__(self, config=None): self.dendrite = bt.dendrite(wallet=self.wallet) bt.logging.info(f"Dendrite: {self.dendrite}") - - # Set up initial scoring weights for validation bt.logging.info("Building validation weights.") self.scores = np.zeros(self.metagraph.n, dtype=np.float32) @@ -88,29 +71,26 @@ def __init__(self, config=None): # Init sync with the network. Updates the metagraph. self.sync() - threading.current_thread().name = "MainThread" - bt.logging.info(f"MainThread Name: {threading.current_thread().name}") # Create asyncio event loop to manage async tasks. self.loop = asyncio.get_event_loop() + # Instantiate runners self.should_exit: bool = False self.is_running: bool = False self.thread: Union[threading.Thread, None] = None self.lock = asyncio.Lock() - def serve_axon(self): """Serve axon to enable external connections.""" bt.logging.info("serving ip to chain...") try: - self.axon = bt.axon(wallet=self.wallet, config=self.config, port = self.config.neuron.axon_port) + self.axon = bt.axon(wallet=self.wallet, config=self.config) self.axon.attach( forward_fn = self.organic_forward, - blacklist_fn = self.blacklist, - # priority_fn = self.priority + blacklist_fn = self.blacklist ) self.axon.serve( netuid=self.config.netuid, @@ -122,118 +102,22 @@ def serve_axon(self): bt.logging.error(f"Failed to serve Axon with exception: {e}") pass - async def concurrent_forward(self): - bt.logging.error(f"concurrent_forward thread name{threading.current_thread().name}") - coroutines = [ - self.forward() for _ in range(self.config.neuron.num_concurrent_forwards) - ] - return await asyncio.gather(*coroutines) def run(self): - """ - Initiates and manages the main loop for the miner on the Bittensor network. The main loop handles graceful shutdown on keyboard interrupts and logs unforeseen errors. - - This function performs the following primary tasks: - 1. Check for registration on the Bittensor network. - 2. Continuously forwards queries to the miners on the network, rewarding their responses and updating the scores accordingly. - 3. Periodically resynchronizes with the chain; updating the metagraph with the latest network state and setting weights. - - The essence of the validator's operations is in the forward function, which is called every step. The forward function is responsible for querying the network and scoring the responses. - - Note: - - The function leverages the global configurations set during the initialization of the miner. - - The miner's axon serves as its interface to the Bittensor network, handling incoming and outgoing requests. - - Raises: - KeyboardInterrupt: If the miner is stopped by a manual interruption. - Exception: For unforeseen errors during the miner's operation, which are logged for diagnosis. - """ - # Check that validator is registered on the network. - self.sync() - - bt.logging.info(f"Validator starting at block: {self.block}") - # This loop maintains the validator's operations until intentionally stopped. - try: - - if not self.config.neuron.axon_off: - self.serve_axon() - - while True: - - # bt.logging.info(f"step({self.step}) block({self.block})") - - # Run multiple forwards concurrently. - - self.loop.run_until_complete(self.concurrent_forward()) - + pass - # Check if we should exit. - if self.should_exit: - break - - # Sync metagraph and potentially set weights. - self.sync() - - self.step += 1 - - # If someone intentionally stops the validator, it'll safely terminate operations. - except KeyboardInterrupt: - if not self.config.neuron.axon_off: - self.axon.stop() - bt.logging.success("Validator killed by keyboard interrupt.") - exit() - - # In case of unforeseen errors, the validator will log the error and continue operations. - except Exception as err: - bt.logging.error(f"Error during validation: {str(err)}") - bt.logging.debug(str(print_exception(type(err), err, err.__traceback__))) - - def run_in_background_thread(self): - """ - Starts the validator's operations in a background thread upon entering the context. - This method facilitates the use of the validator in a 'with' statement. + async def blacklist(self, synapse: WebgenieStreamingSynapse) -> Tuple[bool, str]: """ - if not self.is_running: - bt.logging.debug("Starting validator in background thread.") - self.should_exit = False - self.thread = threading.Thread(target=self.run, daemon=True, name = "BackgroundThread") - self.thread.start() - self.is_running = True - bt.logging.debug("Started") - - def stop_run_thread(self): - """ - Stops the validator's operations that are running in the background thread. - """ - if self.is_running: - bt.logging.debug("Stopping validator in background thread.") - self.should_exit = True - self.thread.join(5) - self.is_running = False - bt.logging.debug("Stopped") - - def __enter__(self): - self.run_in_background_thread() - return self - - def __exit__(self, exc_type, exc_value, traceback): - """ - Stops the validator's background operations upon exiting the context. - This method facilitates the use of the validator in a 'with' statement. - - Args: - exc_type: The type of the exception that caused the context to be exited. - None if the context was exited without an exception. - exc_value: The instance of the exception that caused the context to be exited. - None if the context was exited without an exception. - traceback: A traceback object encoding the stack trace. - None if the context was exited without an exception. + Only allow the subnet owner to send synapse to the validator. """ - if self.is_running: - bt.logging.debug("Stopping validator in background thread.") - self.should_exit = True - self.thread.join(5) - self.is_running = False - bt.logging.debug("Stopped") + if synapse.dendrite.hotkey == SUBNET_OWNER_HOTKEY: + return False, "Subnet owner hotkey" + return True, "Blacklisted" + async def organic_forward(self, synapse: WebgenieStreamingSynapse) -> WebgenieStreamingSynapse: + + bt.logging.error(f"OrganicForward Thread Name: {threading.current_thread().name}") + bt.logging.info(f"=========> {synapse}") + + return await forward_organic_synapse(self, synapse) def set_weights(self): """ diff --git a/webgenie/miners/dummy_miner.py b/webgenie/miners/dummy_miner.py index 05d41bba..5b616403 100644 --- a/webgenie/miners/dummy_miner.py +++ b/webgenie/miners/dummy_miner.py @@ -25,7 +25,7 @@ def miner_init(self): model_name="gpt-4", ) -def miner_forward(self, synapse: webgenie.protocol.webgenieSynapse)->Awaitable: +def miner_forward(self, synapse: webgenie.protocol.WebgenieStreamingSynapse)->Awaitable: async def _forward(self, chain: RunnableSequence, chain_formatter: Dict[str, str], timeout_threshold: float, init_time: float, send: Send): try: diff --git a/webgenie/protocol.py b/webgenie/protocol.py index cac1a840..d6d7824e 100644 --- a/webgenie/protocol.py +++ b/webgenie/protocol.py @@ -1,33 +1,51 @@ # The MIT License (MIT) -# Copyright © 2024 Sangar - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. +# Copyright © 2024 pycorn +import bittensor as bt import pydantic import json from typing import AsyncIterator, Union, Any from starlette.responses import StreamingResponse -import bittensor as bt - -from webgenie.tasks import Task from webgenie.solution import Solution +from webgenie.tasks import Task + +class WebgenieTextSynapse(bt.Synapse): + """ + A protocol for the webgenie text task. + """ + prompt: str = pydantic.Field( + "", + title="Prompt", + description="The prompt to be sent to miners." + ) + + solution: Union[Solution, None] = pydantic.Field( + None, + title="Solution", + description="A solution received from miners." + ) + +class WebgenieImageSynapse(bt.Synapse): + """ + A protocol for the webgenie image task. + """ + base64_image: str = pydantic.Field( + "", + title="Base64 Image", + description="The base64 image to be sent to miners." + ) + + solution: Union[Solution, None] = pydantic.Field( + None, + title="Solution", + description="A solution received from miners." + ) + -class webgenieSynapse(bt.StreamingSynapse): +class WebgenieStreamingSynapse(bt.StreamingSynapse): """ - A protocol for the webgenie. + A protocol for the webgenie streaming task. """ task: Union[Task, None] = pydantic.Field( diff --git a/webgenie/solution/solution.py b/webgenie/solution/solution.py index 1a5ddec5..7a898cc6 100644 --- a/webgenie/solution/solution.py +++ b/webgenie/solution/solution.py @@ -1,7 +1,6 @@ from pydantic import BaseModel, Field class Solution(BaseModel): - css: str = Field("", description="The css solution") html: str = Field("", description="The html solution") process_time: float = Field(0, description="The time it took to process the solution") miner_uid: int = Field(0, description="The uid of the miner that processed the solution") \ No newline at end of file diff --git a/webgenie/task_generator/image_task_generator.py b/webgenie/task_generator/image_task_generator.py new file mode 100644 index 00000000..d8c4bac7 --- /dev/null +++ b/webgenie/task_generator/image_task_generator.py @@ -0,0 +1,21 @@ +import bittensor as bt +from typing import List, Tuple + +from webgenie.protocol import WebgenieImageSynapse +from webgenie.solution import Solution +from webgenie.tasks.task import Task, ImageTask +from webgenie.task_generator.task_generator import TaskGenerator + +class ImageTaskGenerator(TaskGenerator): + def __init__(self): + super().__init__() + + async def generate_task(self) -> Tuple[Task, bt.Synapse]: + return ImageTask( + base64_image="base64_image" , + timeout=50, + generator=self + ), WebgenieImageSynapse(base64_image="base64_image") + + async def reward(self, task: Task, responses: List[Solution]) -> List[float]: + pass \ No newline at end of file diff --git a/webgenie/task_generator/task_generator.py b/webgenie/task_generator/task_generator.py index 9f0c15a3..9bff3b40 100644 --- a/webgenie/task_generator/task_generator.py +++ b/webgenie/task_generator/task_generator.py @@ -1,5 +1,9 @@ +import bittensor as bt +from typing import List, Tuple from webgenie.tasks import Task +from webgenie.solution import Solution + class TaskGenerator: """ A singleton generator for tasks. @@ -7,6 +11,9 @@ class TaskGenerator: def __init__(self): pass - def next_task(self) -> Task: - return Task(query="CommingSoon Page with goback button, navHeader, and footer" , timeout=50) + async def generate_task(self) -> Tuple[Task, bt.Synapse]: + pass + + async def reward(self, task: Task, responses: List[Solution]) -> List[float]: + pass diff --git a/webgenie/task_generator/text_task_generator.py b/webgenie/task_generator/text_task_generator.py new file mode 100644 index 00000000..c300a175 --- /dev/null +++ b/webgenie/task_generator/text_task_generator.py @@ -0,0 +1,21 @@ +import bittensor as bt +from typing import List, Tuple + +from webgenie.protocol import WebgenieTextSynapse +from webgenie.solution import Solution +from webgenie.tasks.task import Task, TextTask +from webgenie.task_generator.task_generator import TaskGenerator + +class TextTaskGenerator(TaskGenerator): + def __init__(self): + super().__init__() + + async def generate_task(self) -> Tuple[Task, bt.Synapse]: + return TextTask( + prompt="CommingSoon Page with goback button, navHeader, and footer" , + timeout=50, + generator=self + ), WebgenieTextSynapse(prompt="CommingSoon Page with goback button, navHeader, and footer") + + async def reward(self, task: Task, responses: List[Solution]) -> List[float]: + pass \ No newline at end of file diff --git a/webgenie/tasks/task.py b/webgenie/tasks/task.py index 3caa3274..8d633538 100644 --- a/webgenie/tasks/task.py +++ b/webgenie/tasks/task.py @@ -1,22 +1,12 @@ -from abc import ABC, abstractmethod -from dataclasses import dataclass -from typing import List -from pydantic import BaseModel -from webgenie.solution import Solution +from typing import Any +from pydantic import BaseModel, Field class Task(BaseModel): - query: str - - timeout: float = 50 + timeout: float = Field(default=50) + generator: Any = Field(default=None) - reward_models: List[tuple] = [ - ("gpt", 0.5), - ("speed", 0.5), - ] +class ImageTask(Task): + base64_image: str = Field(default="") - penalty_models: List[tuple] = [ - ("is_valid", 2), - ] - - reward_weight: float = 0.8 - penalty_weight: float = 0.2 +class TextTask(Task): + prompt: str = Field(default="") diff --git a/webgenie/validator/__init__.py b/webgenie/validator/__init__.py deleted file mode 100644 index 445d768e..00000000 --- a/webgenie/validator/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .forward import forward -from .reward import reward -from .organic_forward import forward_organic_synapse diff --git a/webgenie/validator/forward.py b/webgenie/validator/forward.py deleted file mode 100644 index d73e9873..00000000 --- a/webgenie/validator/forward.py +++ /dev/null @@ -1,117 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao -# Copyright © 2024 Sangar - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - -import time -import asyncio -import bittensor as bt - -from typing import Awaitable, List -from dataclasses import dataclass -from webgenie.protocol import webgenieSynapse -from webgenie.validator.reward import get_rewards -from webgenie.utils.uids import get_random_uids -from webgenie.solution import Solution -from .organic_forward import forward_organic_synapse - -async def process_response(uid: int, async_generator: Awaitable): - bt.logging.info(f"==============6") - try: - buffer = "" - chunk = None - bt.logging.info(f"==============7{async_generator}") - async for chunk in async_generator: - bt.logging.info(f"==============7.1") - if isinstance(chunk, str): - buffer += chunk - bt.logging.info(f"==============8") - if chunk is not None: - synapse = chunk - if isinstance(synapse, webgenieSynapse): - if synapse.dendrite.status_code == 200: - synapse.solution.miner_uid = uid - return synapse.solution - else: - bt.logging.error(f"Received non-200 status code: {chunk.dendrite.status_code} for uid: {uid}") - return None - else: - bt.logging.error(f"Synapse is None for uid: {uid}") - return None - except Exception as e: - bt.logging.error(f"Error processing response for uid: {uid}: {e}") - return None - -async def handle_responses(miner_uids_list: List[int], responses: List[Awaitable]): - bt.logging.info(f"==============5") - tasks = [process_response(uid, response) for uid, response in zip(miner_uids_list, responses)] - results = await asyncio.gather(*tasks, return_exceptions=True) - return [result for result in results if result is not None] - - -async def forward(self): - """ - The forward function is called by the validator every time step. - - It is responsible for querying the network and scoring the responses. - - Args: - self (:obj:`bittensor.neuron.Neuron`): The neuron object which contains all the necessary state for the validator. - - """ - # TODO(developer): Define how the validator selects a miner to query, how often, etc. - # get_random_uids is an example method, but you can replace it with your own. - miner_uids = get_random_uids(self, k=self.config.neuron.sample_size) - miner_uids_list = miner_uids.tolist() - - bt.logging.info(f"Selected miners: {miner_uids}") - - # TODO(developer): Define how the validator selects a miner to query, how often, etc. - axons = [self.metagraph.axons[uid] for uid in miner_uids] - - task = self.task_generator.next_task() - - synapse = webgenieSynapse( - task=task - ) - - # The dendrite client queries the network. - try: - responses = await self.dendrite( - axons=axons, - synapse=synapse, - timeout=task.timeout, - deserialize=True, - streaming=True, - ) - except Exception as e: - bt.logging.error(f"[forward] Error querying dendrite: {e}") - return - bt.logging.info(f"==============1") - handle_responses_task = asyncio.create_task(handle_responses(miner_uids_list, responses)) - bt.logging.info(f"==============2") - results = await handle_responses_task - await asyncio.sleep(4) - if len(results) == 0: - bt.logging.info("No responses received") - return - bt.logging.info(f"==============3") - bt.logging.info(f"Received {results} results") - bt.logging.info(f"==============4") - scores, miner_uids = self.reward_manager.score(task, results) - # Update the scores based on the rewards. You may want to define your own update_scores function for custom behavior. - bt.logging.info(f"Updating scores: {scores}") - self.update_scores(scores, miner_uids) \ No newline at end of file diff --git a/webgenie/validator/organic_forward.py b/webgenie/validator/organic_forward.py deleted file mode 100644 index c7965f81..00000000 --- a/webgenie/validator/organic_forward.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import Callable -from functools import partial -from typing import Any, AsyncGenerator -from starlette.types import Send - -import bittensor as bt -from webgenie.protocol import webgenieSynapse - -async def forward_organic_synapse(self, synapse: webgenieSynapse)->webgenieSynapse: - async def forward_miner(synapse: webgenieSynapse, send: Send): - bt.logging.info(f"Send Synapse to miner: {synapse}") - async def handle_miner_response(responses): - for resp in responses: - async for chunk in resp: - if isinstance(chunk, str): - bt.logging.info(f"Chunk: {chunk}") - await send( - { - "type": "http.response.body", - "body": chunk.encode("utf-8"), - "more_body": True, - } - ) - await send( - {"type": "http.response.body", "body": b"", "more_body": False} - ) - - axon = self.metagraph.axons[1] - try: - async with bt.dendrite(wallet=self.wallet) as dendrite: - bt.logging.info(f"Dendrite: {dendrite}") - responses = await dendrite( - axons=[axon], - synapse=synapse, - deserialize=False, - timeout=synapse.timeout, - streaming=True, - ) - except Exception as e: - bt.logging.error(f"[forward_organic_synapse] Error querying dendrite: {e}") - return await handle_miner_response(responses) - bt.logging.info(f"forward_organic_synapse: {synapse}") - send_external_response = partial(forward_miner, synapse) - return synapse.create_streaming_response(send_external_response) - \ No newline at end of file diff --git a/webgenie/validator/reward.py b/webgenie/validator/reward.py deleted file mode 100644 index 58f920a4..00000000 --- a/webgenie/validator/reward.py +++ /dev/null @@ -1,54 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao -# Copyright © 2024 Sangar - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. -import numpy as np -from typing import List -import bittensor as bt - - -def reward(query: int, response: int) -> float: - """ - Reward the miner response to the dummy request. This method returns a reward - value for the miner, which is used to update the miner's score. - - Returns: - - float: The reward value for the miner. - """ - bt.logging.info(f"In rewards, query val: {query}, response val: {response}, rewards val: {1.0 if response == query * 2 else 0}") - return 1.0 if response == query * 2 else 0 - - -def get_rewards( - self, - query: int, - responses: List[float], -) -> np.ndarray: - """ - Returns an array of rewards for the given query and responses. - - Args: - - query (int): The query sent to the miner. - - responses (List[float]): A list of responses from the miner. - - Returns: - - np.ndarray: An array of rewards for the given query and responses. - """ - # Get all the reward results by iteratively calling your reward() function. - - return np.array( - [reward(query, response) for response in responses] - ) From 8945131980e281ae9df6e182232df348fcef804e Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 11 Dec 2024 20:48:16 -0600 Subject: [PATCH 023/554] chore: refactor rewarding structure --- neurons/validators/genie_validator.py | 56 +++++++++++-------- .../task_generator/image_task_generator.py | 4 +- webgenie/task_generator/task_generator.py | 2 +- .../task_generator/text_task_generator.py | 4 +- 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index f069b42b..6ee7279c 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -41,44 +41,41 @@ async def forward(self): timeout=task.timeout ) - processed_synapses, processed_miner_uids = await self.process_synapses(all_synapse_results, miner_uids) + solutions = [] - if len(processed_synapses) == 0: - bt.logging.error(f"No valid synapses received") + for synapse, miner_uid in zip(all_synapse_results, miner_uids): + processed_synapse = await self.process_synapse(synapse, miner_uid) + if processed_synapse is not None: + solutions.append(processed_synapse.solution) + + if len(solutions) == 0: + bt.logging.error(f"No valid solutions received") return - bt.logging.debug(f"Processed synapses: {processed_synapses}") + + bt.logging.debug(f"Processed solutions: {solutions}") - self.synthetic_history.append((task, processed_synapses, processed_miner_uids)) + self.synthetic_history.append((task, solutions)) except Exception as e: bt.logging.error(f"Error in forward: {e}") raise e - async def process_synapses(self, synapses: List[WebgenieStreamingSynapse], miner_uids: List[int]): - processed_synapses = [] - processed_miner_uids = [] - - for synapse, miner_uid in zip(synapses, miner_uids): - if synapse.dendrite.status_code == 200: - processed_synapses.append(synapse) - processed_miner_uids.append(miner_uid) - - return processed_synapses, processed_miner_uids - async def score(self): if len(self.synthetic_history) == 0: bt.logging.warning(f"No synthetic history to score") return - task, synapses, miner_uids = self.synthetic_history.pop(0) + task, solutions = self.synthetic_history.pop(0) task_generator = task.generator - scores = await task_generator.reward(task, synapses) - self.neuron.update_scores(scores, miner_uids) + scores = await task_generator.reward(task, solutions) + self.neuron.update_scores(scores, [solution.miner_uid for solution in solutions]) self.neuron.sync() async def organic_forward(self, synapse: WebgenieTextSynapse): - axon = self.metagraph.axons[1] + best_miner_uid = 1 try: + axon = self.metagraph.axons[best_miner_uid] + async with bt.dendrite(wallet=self.wallet) as dendrite: bt.logging.info(f"Dendrite: {dendrite}") responses = await dendrite( @@ -86,10 +83,23 @@ async def organic_forward(self, synapse: WebgenieTextSynapse): synapse=synapse, timeout=synapse.timeout, ) - return responses[0] + + processed_synapse = await self.process_synapse(responses[0], best_miner_uid) + if processed_synapse is None: + raise Exception(f"No valid solution received") + + return processed_synapse except Exception as e: bt.logging.error(f"[forward_organic_synapse] Error querying dendrite: {e}") synapse.solution = Solution( - html = "", + html = f"Error: {e}", + process_time = 0, + miner_uid = best_miner_uid, ) - return synapse \ No newline at end of file + return synapse + + async def process_synapse(self, synapse: bt.Synapse, miner_uid: int) -> bt.Synapse: + if synapse.dendrite.status_code == 200: + synapse.solution.miner_uid = miner_uid + return synapse + return None \ No newline at end of file diff --git a/webgenie/task_generator/image_task_generator.py b/webgenie/task_generator/image_task_generator.py index d8c4bac7..5a7218e2 100644 --- a/webgenie/task_generator/image_task_generator.py +++ b/webgenie/task_generator/image_task_generator.py @@ -17,5 +17,5 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: generator=self ), WebgenieImageSynapse(base64_image="base64_image") - async def reward(self, task: Task, responses: List[Solution]) -> List[float]: - pass \ No newline at end of file + async def reward(self, task: Task, solutions: List[Solution]) -> List[float]: + pass diff --git a/webgenie/task_generator/task_generator.py b/webgenie/task_generator/task_generator.py index 9bff3b40..a41b3a82 100644 --- a/webgenie/task_generator/task_generator.py +++ b/webgenie/task_generator/task_generator.py @@ -14,6 +14,6 @@ def __init__(self): async def generate_task(self) -> Tuple[Task, bt.Synapse]: pass - async def reward(self, task: Task, responses: List[Solution]) -> List[float]: + async def reward(self, task: Task, solutions: List[Solution]) -> List[float]: pass diff --git a/webgenie/task_generator/text_task_generator.py b/webgenie/task_generator/text_task_generator.py index c300a175..44fa9aa3 100644 --- a/webgenie/task_generator/text_task_generator.py +++ b/webgenie/task_generator/text_task_generator.py @@ -17,5 +17,5 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: generator=self ), WebgenieTextSynapse(prompt="CommingSoon Page with goback button, navHeader, and footer") - async def reward(self, task: Task, responses: List[Solution]) -> List[float]: - pass \ No newline at end of file + async def reward(self, task: Task, solutions: List[Solution]) -> List[float]: + pass From e799192709de8a6575a8de6467f598812870f832 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 12 Dec 2024 06:48:38 -0600 Subject: [PATCH 024/554] fix: fixed bugs for launch --- .gitignore | 8 +- lang_chain_test.py | 145 -------------------------- neurons/miners/miner.py | 2 +- neurons/validators/genie_validator.py | 6 +- neurons/validators/validator.py | 4 +- webgenie/__init__.py | 1 - webgenie/base/validator.py | 1 - 7 files changed, 13 insertions(+), 154 deletions(-) delete mode 100644 lang_chain_test.py diff --git a/.gitignore b/.gitignore index 6b79b60a..3c6f1d53 100644 --- a/.gitignore +++ b/.gitignore @@ -165,4 +165,10 @@ testing/ .vscode/settings.json .DS_Store -temp.txt \ No newline at end of file +temp.txt + +# env +.env + +# test +test.py \ No newline at end of file diff --git a/lang_chain_test.py b/lang_chain_test.py deleted file mode 100644 index c0f8d29d..00000000 --- a/lang_chain_test.py +++ /dev/null @@ -1,145 +0,0 @@ -from functools import partial -from starlette.types import Send -import time -from typing import Dict -import openai -import os - -from typing import Dict, Awaitable -from langchain_openai import ChatOpenAI,OpenAI - -from dotenv import load_dotenv, find_dotenv -from langchain.prompts import ChatPromptTemplate, PromptTemplate -from langchain_core.output_parsers import StrOutputParser -from langchain_core.runnables.base import RunnableSequence - -import bittensor as bt - -class DummySynapse: - query: str - timeout: float -class Self: - pass - -load_dotenv() -api_key = os.getenv("OPENAI_API_KEY") - -def miner_init(self): - bt.logging.debug(f"Dummy Miner initialized") - # Set openai key and other args - self.model = ChatOpenAI( - api_key=api_key, - model_name="gpt-4", - ) - -def miner_forward(self, synapse: DummySynapse) -> str: - def _forward(self, chain: RunnableSequence, chain_formatter: Dict[str, str], timeout_threshold: float, init_time: float) -> str: - buffer = [] - timeout_reached = False - is_in_code_block = True - is_first_line = True - generated_code = "" - try: - for token in chain.stream(chain_formatter): - - if is_first_line and token.startswith("```"): - is_in_code_block = False - - if is_in_code_block and not token.startswith("```"): - pass - buffer.append(token) - - if time.time() - init_time > timeout_threshold: - bt.logging.debug(f"⏰ Timeout reached, stopping streaming") - timeout_reached = True - break - - if len(buffer) == 10: - joined_buffer = "".join(buffer) - generated_code += joined_buffer - buffer = [] - - if is_first_line and token.endswith("\n"): - is_first_line = False - is_in_code_block = True - - if ( - buffer and not timeout_reached - ): # Don't send the last buffer of data if timeout. - joined_buffer = "".join(buffer) - generated_code += joined_buffer - return generated_code - except Exception as e: - bt.logging.error(f"Dummy Miner Error: {e}") - return "" - bt.logging.debug(f"Dummy Miner Query received, forwarding synapse: {synapse}") - - # prompt = PromptTemplate.from_template( - # "You are an expert programmer. Generate code based on the following request:\n\n{query}\n\nProvide only the code, without any explanations." - # ) - prompt = ChatPromptTemplate.from_messages([ - ("system", """You are an expert programmer. Generate code based on the following request without explanations:\n\n{query}\n\n Provide the code that satisfies the following requirements without any explanation. You must give me both the CSS file and the frontend HTML file for a 'Login Page.' The response should be in JSON format as shown below, and it should be plaintext (without triple quotes): - {{ 'CSS': 'css_code_here', 'HTML': 'html_code_here' }}"""), - ("user", "{query}"), - ]) - chain = prompt | self.model | StrOutputParser() - - query = synapse.query - bt.logging.info(f"Dummy Miner Query: {query}") - - chain_formatter = {"query": query} - - init_time = time.time() - timeout_threshold = synapse.timeout - token_streamer = partial( - _forward, - self, - chain, - chain_formatter, - timeout_threshold, - init_time, - ) - return token_streamer() - -def evaluate_code(task: str, generated_code: str) -> float: - # Use GPT to evaluate the code - query = f""" - Task: {task} - Generated Code: - {generated_code} - - Evaluate the generated code based on the following criteria: - 1. Correctness: Does the code implement the required functionality? - 2. Code quality: Is the code well-structured and following best practices? - 3. Completeness: Does the code address all aspects of the task? - - Provide a score between 0 and 1, where 1 is perfect and 0 is completely incorrect. - Only return the score, without any explanations. - """ - - try: - evaluation = ChatOpenAI( - api_key=api_key, - model="gpt-4", - - ) - messages=[ - {"role": "system", "content": "You are a code evaluation expert."}, - {"role": "user", "content": query} - ] - evaluation = evaluation.invoke(messages) - return float(evaluation.content) - except Exception as e: - bt.logging.error(f"Error evaluating code: {e}") - return 0.0 - -if __name__ == "__main__": - self = Self() - synapse = DummySynapse() - synapse.query = "Comming soon Page using Vue.js and css" - synapse.timeout = 50 - miner_init(self) - generated_code = miner_forward(self, synapse) - print(f"Generated Code: {generated_code}") - score = evaluate_code(synapse.query, generated_code) - print(f"Score: {score}") diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index ae3d52ee..d60606cf 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -1,6 +1,6 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# Copyright © 2023 Sangar +# Copyright © 2023 Sangar, pycorn0729 # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 6ee7279c..420d403c 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -31,12 +31,12 @@ async def forward(self): if len(self.synthetic_history) > MAX_SYNTHETIC_HISTORY_SIZE: return - miner_uids = get_random_uids(self, k=self.config.neuron.sample_size) + miner_uids = get_random_uids(self.neuron, k=self.config.neuron.sample_size) task, synapse = await random.choice(self.task_generators).generate_task() all_synapse_results = await self.neuron.dendrite( - axons = [self.metagraph.axons[uid] for uid in miner_uids], + axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], synapse=synapse, timeout=task.timeout ) @@ -49,7 +49,7 @@ async def forward(self): solutions.append(processed_synapse.solution) if len(solutions) == 0: - bt.logging.error(f"No valid solutions received") + bt.logging.warning(f"No valid solutions received") return bt.logging.debug(f"Processed solutions: {solutions}") diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 743a6c51..5620c940 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -25,13 +25,13 @@ def __init__(self, config=None): super(Validator, self).__init__(config=config) bt.logging.info("load_state()") self.load_state() - self.genie_validator = GenieValidator() + self.genie_validator = GenieValidator(neuron=self) async def organic_forward(self, synapse: WebgenieStreamingSynapse): return await self.genie_validator.organic_forward(synapse) async def forward(self): - return await self.genie_validator.forward(self) + return await self.genie_validator.forward() async def concurrent_forward(self): coroutines = [ diff --git a/webgenie/__init__.py b/webgenie/__init__.py index 41bb1b50..993ab636 100644 --- a/webgenie/__init__.py +++ b/webgenie/__init__.py @@ -30,6 +30,5 @@ # Import all submodules. from . import protocol from . import base -from . import validator from . import api from .subnet_links import SUBNET_LINKS diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index 437ac1a5..d777187d 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -35,7 +35,6 @@ from webgenie.mock import MockDendrite from webgenie.utils.config import add_validator_args from webgenie.protocol import WebgenieStreamingSynapse -from webgenie.validator.organic_forward import forward_organic_synapse SUBNET_OWNER_HOTKEY = "5G9sRcoaw2H3SYDq7e7PoGhbbMUPHQi6pC6tPrahmSmDtxS8" From 5cf0c922e756248e98d8a3885581aed3944ca900 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 12 Dec 2024 07:16:03 -0600 Subject: [PATCH 025/554] fix: fixed bugs in miner --- neurons/miners/miner.py | 65 ++++++++++++++++++--------- neurons/validators/genie_validator.py | 2 + webgenie/base/miner.py | 12 ++--- 3 files changed, 49 insertions(+), 30 deletions(-) diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index d60606cf..c2e5118d 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -18,16 +18,11 @@ import time import typing -import importlib import bittensor as bt - -# Bittensor Miner Template: -import webgenie - -# import base miner class which takes care of most of the boilerplate from webgenie.base.miner import BaseMinerNeuron - +from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse +from webgenie.solution import Solution class Miner(BaseMinerNeuron): """ @@ -41,23 +36,51 @@ class Miner(BaseMinerNeuron): def __init__(self, config=None): super(Miner, self).__init__(config=config) - miner_name = "dummy_miner" - miner_module = importlib.import_module(f"webgenie.miners.{miner_name}") - - self.miner_init = miner_module.miner_init - self.miner_forward = miner_module.miner_forward - - self.miner_init(self) + # Attach determiners which functions are called when servicing a request. + bt.logging.info(f"Attaching forward function to miner axon.") + self.axon.attach( + forward_fn=self.forward_text, + blacklist_fn=self.blacklist_text, + priority_fn=self.priority_text, + ).attach( + forward_fn = self.forward_image, + blacklist_fn=self.blacklist_image, + priority_fn=self.priority_image, + ) - async def forward( - self, synapse: webgenie.protocol.WebgenieStreamingSynapse - ) -> webgenie.protocol.WebgenieStreamingSynapse: + async def forward_text( + self, synapse: WebgenieTextSynapse + ) -> WebgenieTextSynapse: - bt.logging.debug(f"Miner forward called with synapse: {synapse}") - return self.miner_forward(self, synapse) + bt.logging.debug(f"Miner text forward called with synapse: {synapse}") + synapse.solution = Solution( + html = "

Hello, world!

" + ) + return synapse + + async def forward_image( + self, synapse: WebgenieImageSynapse + ) -> WebgenieImageSynapse: + bt.logging.debug(f"Miner image forward called with synapse: {synapse}") + synapse.solution = Solution( + html = "

Hello, Image!

" + ) + return synapse + + async def blacklist_text(self, synapse: WebgenieTextSynapse) -> typing.Tuple[bool, str]: + return await self.blacklist(synapse) + + async def blacklist_image(self, synapse: WebgenieImageSynapse) -> typing.Tuple[bool, str]: + return await self.blacklist(synapse) + + async def priority_text(self, synapse: WebgenieTextSynapse) -> float: + return await self.priority(synapse) + + async def priority_image(self, synapse: WebgenieImageSynapse) -> float: + return await self.priority(synapse) async def blacklist( - self, synapse: webgenie.protocol.WebgenieStreamingSynapse + self, synapse: bt.Synapse ) -> typing.Tuple[bool, str]: """ Determines whether an incoming request should be blacklisted and thus ignored. Your implementation should @@ -118,7 +141,7 @@ async def blacklist( ) return False, "Hotkey recognized!" - async def priority(self, synapse: webgenie.protocol.WebgenieStreamingSynapse) -> float: + async def priority(self, synapse: bt.Synapse) -> float: """ The priority function determines the order in which requests are handled. More valuable or higher-priority requests are processed before others. You should design your own priority mechanism with care. diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 420d403c..2ab46ceb 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -100,6 +100,8 @@ async def organic_forward(self, synapse: WebgenieTextSynapse): async def process_synapse(self, synapse: bt.Synapse, miner_uid: int) -> bt.Synapse: if synapse.dendrite.status_code == 200: + if synapse.solution is None: + return None synapse.solution.miner_uid = miner_uid return synapse return None \ No newline at end of file diff --git a/webgenie/base/miner.py b/webgenie/base/miner.py index 999b489e..0f567652 100644 --- a/webgenie/base/miner.py +++ b/webgenie/base/miner.py @@ -55,21 +55,15 @@ def __init__(self, config=None): # The axon handles request processing, allowing validators to send this miner requests. self.axon = bt.axon(wallet=self.wallet, config=self.config() if callable(self.config) else self.config) - # Attach determiners which functions are called when servicing a request. - bt.logging.info(f"Attaching forward function to miner axon.") - self.axon.attach( - forward_fn=self.forward, - blacklist_fn=self.blacklist, - priority_fn=self.priority, - ) - bt.logging.info(f"Axon created: {self.axon}") - # Instantiate runners self.should_exit: bool = False self.is_running: bool = False self.thread: Union[threading.Thread, None] = None self.lock = asyncio.Lock() + async def forward(self): + pass + def run(self): """ Initiates and manages the main loop for the miner on the Bittensor network. The main loop handles graceful shutdown on keyboard interrupts and logs unforeseen errors. From afcaf02e384375e0137a265996e98a4b2c193533 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 12 Dec 2024 08:04:33 -0600 Subject: [PATCH 026/554] feat: added wandb logging --- neurons/miners/miner.py | 5 +- neurons/validators/validator.py | 3 + wandb/latest-run | 1 + .../files/requirements.txt | 139 ++++++++++++++++++ .../files/wandb-metadata.json | 45 ++++++ .../run-fmmfb2d8.wandb | Bin 0 -> 32768 bytes .../files/requirements.txt | 139 ++++++++++++++++++ .../files/wandb-metadata.json | 45 ++++++ .../run-p5q8p631.wandb | 0 webgenie/__init__.py | 4 +- webgenie/helpers/weights.py | 45 ++++++ 11 files changed, 424 insertions(+), 2 deletions(-) create mode 120000 wandb/latest-run create mode 100644 wandb/run-20241212_080228-fmmfb2d8/files/requirements.txt create mode 100644 wandb/run-20241212_080228-fmmfb2d8/files/wandb-metadata.json create mode 100644 wandb/run-20241212_080228-fmmfb2d8/run-fmmfb2d8.wandb create mode 100644 wandb/run-20241212_080410-p5q8p631/files/requirements.txt create mode 100644 wandb/run-20241212_080410-p5q8p631/files/wandb-metadata.json create mode 100644 wandb/run-20241212_080410-p5q8p631/run-p5q8p631.wandb create mode 100644 webgenie/helpers/weights.py diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index c2e5118d..7c7ab172 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -1,6 +1,6 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# Copyright © 2023 Sangar, pycorn0729 +# Copyright © 2024 pycorn0729 # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation @@ -21,6 +21,7 @@ import bittensor as bt from webgenie.base.miner import BaseMinerNeuron +from webgenie.helpers.weights import init_wandb from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from webgenie.solution import Solution @@ -48,6 +49,8 @@ def __init__(self, config=None): priority_fn=self.priority_image, ) + init_wandb(self) + async def forward_text( self, synapse: WebgenieTextSynapse ) -> WebgenieTextSynapse: diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 5620c940..fbf3245f 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -9,6 +9,7 @@ import bittensor as bt from webgenie.base.validator import BaseValidatorNeuron +from webgenie.helpers.weights import init_wandb from webgenie.protocol import WebgenieStreamingSynapse from neurons.validators.genie_validator import GenieValidator @@ -27,6 +28,8 @@ def __init__(self, config=None): self.load_state() self.genie_validator = GenieValidator(neuron=self) + init_wandb(self) + async def organic_forward(self, synapse: WebgenieStreamingSynapse): return await self.genie_validator.organic_forward(synapse) diff --git a/wandb/latest-run b/wandb/latest-run new file mode 120000 index 00000000..3e4f5871 --- /dev/null +++ b/wandb/latest-run @@ -0,0 +1 @@ +run-20241212_080410-p5q8p631 \ No newline at end of file diff --git a/wandb/run-20241212_080228-fmmfb2d8/files/requirements.txt b/wandb/run-20241212_080228-fmmfb2d8/files/requirements.txt new file mode 100644 index 00000000..d0cdb471 --- /dev/null +++ b/wandb/run-20241212_080228-fmmfb2d8/files/requirements.txt @@ -0,0 +1,139 @@ +wandb==0.19.0 +charset-normalizer==3.4.0 +triton==3.1.0 +cryptography==42.0.8 +fsspec==2024.10.0 +backoff==2.2.1 +wheel==0.45.1 +GitPython==3.1.43 +Levenshtein==0.26.1 +urllib3==2.2.3 +munch==2.5.0 +typing_extensions==4.12.2 +distro==1.9.0 +rich==13.9.4 +langchain-openai==0.2.12 +scalecodec==1.2.11 +eth-utils==2.2.2 +attrs==24.2.0 +nvidia-nccl-cu12==2.21.5 +py==1.11.0 +sentry-sdk==2.19.2 +gitdb==4.0.11 +colorama==0.4.6 +multidict==6.1.0 +nvidia-nvjitlink-cu12==12.4.127 +nvidia-curand-cu12==10.3.5.147 +setproctitle==1.3.4 +setuptools==70.0.0 +httpx==0.28.1 +aiosignal==1.3.1 +python-statemachine==2.5.0 +packaging==24.2 +langchain-core==0.3.24 +substrate-interface==1.7.11 +eth-keys==0.6.0 +webencodings==0.5.1 +propcache==0.2.1 +pydantic_core==2.27.1 +xxhash==3.5.0 +platformdirs==4.3.6 +pip==24.2 +annotated-types==0.7.0 +sympy==1.13.1 +ddt==1.6.0 +openai==1.57.2 +tenacity==9.0.0 +nvidia-cusolver-cu12==11.6.1.9 +nvidia-nvtx-cu12==12.4.127 +Pygments==2.18.0 +fastapi==0.110.3 +eth-hash==0.7.0 +smmap==5.0.1 +regex==2024.11.6 +retry==0.9.2 +pluggy==1.5.0 +ansible-vault==2.1.0 +click==8.1.7 +RapidFuzz==3.10.1 +nvidia-cusparse-cu12==12.3.1.170 +jsonpointer==3.0.0 +shtab==1.6.5 +SQLAlchemy==2.0.36 +password-strength==0.0.3.post2 +py-sr25519-bindings==0.2.1 +ansible==8.5.0 +more-itertools==10.5.0 +pycparser==2.22 +msgpack==1.1.0 +PyYAML==6.0.2 +filelock==3.16.1 +soupsieve==2.6 +beautifulsoup4==4.12.3 +py-bip39-bindings==0.2.0 +jsonpatch==1.33 +nvidia-cuda-nvrtc-cu12==12.4.127 +fuzzywuzzy==0.18.0 +h11==0.14.0 +jiter==0.8.2 +protobuf==5.29.1 +eth-typing==5.0.1 +termcolor==2.5.0 +yarl==1.18.3 +aiohappyeyeballs==2.4.4 +nvidia-cuda-cupti-cu12==12.4.127 +docker-pycreds==0.4.0 +starlette==0.37.2 +ecdsa==0.19.0 +networkx==3.4.2 +sniffio==1.3.1 +ansible-core==2.15.13 +Jinja2==3.1.4 +certifi==2024.7.4 +torch==2.5.1 +toolz==1.0.0 +langchain==0.3.11 +netaddr==1.3.0 +MarkupSafe==3.0.2 +websocket-client==1.8.0 +langsmith==0.2.3 +python-dotenv==1.0.1 +greenlet==3.1.1 +orjson==3.10.12 +idna==3.10 +frozenlist==1.5.0 +decorator==5.1.1 +anyio==4.7.0 +nest-asyncio==1.6.0 +markdown-it-py==3.0.0 +psutil==6.1.0 +nvidia-cufft-cu12==11.2.1.3 +bittensor==7.4.0 +py-ed25519-zebra-bindings==1.2.0 +nvidia-cudnn-cu12==9.1.0.70 +requests-toolbelt==1.0.0 +uvicorn==0.32.1 +msgpack-numpy-opentensor==0.5.0 +python-Levenshtein==0.26.1 +tinycss2==1.4.0 +mdurl==0.1.2 +mpmath==1.3.0 +cffi==1.17.1 +numpy==1.26.4 +six==1.17.0 +pydantic==2.10.3 +tiktoken==0.8.0 +iniconfig==2.0.0 +langchain-text-splitters==0.3.2 +pycryptodome==3.21.0 +tqdm==4.67.1 +nvidia-cublas-cu12==12.4.5.8 +requests==2.32.3 +base58==2.1.1 +httpcore==1.0.7 +aiohttp==3.11.10 +PyNaCl==1.5.0 +cytoolz==1.0.0 +pytest==8.3.4 +resolvelib==1.0.1 +nvidia-cuda-runtime-cu12==12.4.127 diff --git a/wandb/run-20241212_080228-fmmfb2d8/files/wandb-metadata.json b/wandb/run-20241212_080228-fmmfb2d8/files/wandb-metadata.json new file mode 100644 index 00000000..4b201625 --- /dev/null +++ b/wandb/run-20241212_080228-fmmfb2d8/files/wandb-metadata.json @@ -0,0 +1,45 @@ +{ + "os": "Linux-5.4.0-105-generic-x86_64-with-glibc2.31", + "python": "CPython 3.12.7", + "startedAt": "2024-12-12T14:02:28.547811Z", + "args": [ + "--netuid", + "214", + "--subtensor.network", + "test", + "--wallet.name", + "sc-val1", + "--wallet.hotkey", + "sh-val1", + "--logging.debug", + "--neuron.axon_port", + "8091" + ], + "program": "/home/dev2user/workspace/sangar/web-genie-ai/neurons/validators/validator.py", + "codePath": "neurons/validators/validator.py", + "git": { + "remote": "https://github.com/web-genie-ai/web-genie-ai.git", + "commit": "5cf0c922e756248e98d8a3885581aed3944ca900" + }, + "email": "hayesdominique0729@gmail.com", + "root": "/home/dev2user/workspace/sangar/web-genie-ai", + "host": "vmi1162577.contaboserver.net", + "username": "root", + "executable": "/home/dev2user/workspace/sangar/web-genie-ai/venv/bin/python3.12", + "codePathLocal": "neurons/validators/validator.py", + "cpu_count": 6, + "cpu_count_logical": 6, + "disk": { + "/": { + "total": "630869753856", + "used": "33456181248" + } + }, + "memory": { + "total": "16786370560" + }, + "cpu": { + "count": 6, + "countLogical": 6 + } +} \ No newline at end of file diff --git a/wandb/run-20241212_080228-fmmfb2d8/run-fmmfb2d8.wandb b/wandb/run-20241212_080228-fmmfb2d8/run-fmmfb2d8.wandb new file mode 100644 index 0000000000000000000000000000000000000000..f20c3edc1d597796ebc65fe83ab9c295a3ca1097 GIT binary patch literal 32768 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 ReNs# Date: Thu, 12 Dec 2024 08:13:34 -0600 Subject: [PATCH 027/554] chore : added wandb to gitignore --- .gitignore | 5 ++++- wandb/latest-run | 2 +- .../run-fmmfb2d8.wandb | Bin 32768 -> 82089 bytes .../run-p5q8p631.wandb | Bin 0 -> 13718 bytes 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3c6f1d53..b9e1a14d 100644 --- a/.gitignore +++ b/.gitignore @@ -171,4 +171,7 @@ temp.txt .env # test -test.py \ No newline at end of file +test.py + +# wandb +wandb/ \ No newline at end of file diff --git a/wandb/latest-run b/wandb/latest-run index 3e4f5871..6f2f1da7 120000 --- a/wandb/latest-run +++ b/wandb/latest-run @@ -1 +1 @@ -run-20241212_080410-p5q8p631 \ No newline at end of file +run-20241212_080507-u6u37fmk \ No newline at end of file diff --git a/wandb/run-20241212_080228-fmmfb2d8/run-fmmfb2d8.wandb b/wandb/run-20241212_080228-fmmfb2d8/run-fmmfb2d8.wandb index f20c3edc1d597796ebc65fe83ab9c295a3ca1097..8a19372f048c24edb47da814bcba5f07509c6e8f 100644 GIT binary patch literal 82089 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 delta 8 PcmZ44$lB1rw4eb14?6== diff --git a/wandb/run-20241212_080410-p5q8p631/run-p5q8p631.wandb b/wandb/run-20241212_080410-p5q8p631/run-p5q8p631.wandb index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f768135d09c2656671e5383f57d5822d7eaacab6 100644 GIT binary patch literal 13718 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 Eze2+$8~^|S literal 0 HcmV?d00001 From 1cc9a2620f900119c18e1fccb90e3d164434a560 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 12 Dec 2024 08:24:38 -0600 Subject: [PATCH 028/554] chre: added pseudo rewards --- wandb/latest-run | 2 +- webgenie/task_generator/image_task_generator.py | 3 ++- webgenie/task_generator/text_task_generator.py | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/wandb/latest-run b/wandb/latest-run index 6f2f1da7..8f30dc87 120000 --- a/wandb/latest-run +++ b/wandb/latest-run @@ -1 +1 @@ -run-20241212_080507-u6u37fmk \ No newline at end of file +run-20241212_082229-ryemav9r \ No newline at end of file diff --git a/webgenie/task_generator/image_task_generator.py b/webgenie/task_generator/image_task_generator.py index 5a7218e2..cb7b5124 100644 --- a/webgenie/task_generator/image_task_generator.py +++ b/webgenie/task_generator/image_task_generator.py @@ -18,4 +18,5 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: ), WebgenieImageSynapse(base64_image="base64_image") async def reward(self, task: Task, solutions: List[Solution]) -> List[float]: - pass + bt.logging.debug(f"Rewarding image task {task} with solutions {solutions}") + return [1.0] * len(solutions) diff --git a/webgenie/task_generator/text_task_generator.py b/webgenie/task_generator/text_task_generator.py index 44fa9aa3..2629e3db 100644 --- a/webgenie/task_generator/text_task_generator.py +++ b/webgenie/task_generator/text_task_generator.py @@ -18,4 +18,5 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: ), WebgenieTextSynapse(prompt="CommingSoon Page with goback button, navHeader, and footer") async def reward(self, task: Task, solutions: List[Solution]) -> List[float]: - pass + bt.logging.debug(f"Rewarding text task {task} with solutions {solutions}") + return [1.0] * len(solutions) From 562b8c14265ba875814e600fe36110f1c74acac2 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 12 Dec 2024 08:30:00 -0600 Subject: [PATCH 029/554] chore: added sanity check --- webgenie/task_generator/image_task_generator.py | 3 +++ webgenie/task_generator/text_task_generator.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/webgenie/task_generator/image_task_generator.py b/webgenie/task_generator/image_task_generator.py index cb7b5124..6d86cc99 100644 --- a/webgenie/task_generator/image_task_generator.py +++ b/webgenie/task_generator/image_task_generator.py @@ -18,5 +18,8 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: ), WebgenieImageSynapse(base64_image="base64_image") async def reward(self, task: Task, solutions: List[Solution]) -> List[float]: + if not isinstance(task, ImageTask): + raise ValueError(f"Task is not a ImageTask: {type(task)}") + bt.logging.debug(task.base64_image) bt.logging.debug(f"Rewarding image task {task} with solutions {solutions}") return [1.0] * len(solutions) diff --git a/webgenie/task_generator/text_task_generator.py b/webgenie/task_generator/text_task_generator.py index 2629e3db..e46e88a2 100644 --- a/webgenie/task_generator/text_task_generator.py +++ b/webgenie/task_generator/text_task_generator.py @@ -18,5 +18,9 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: ), WebgenieTextSynapse(prompt="CommingSoon Page with goback button, navHeader, and footer") async def reward(self, task: Task, solutions: List[Solution]) -> List[float]: + if not isinstance(task, TextTask): + raise ValueError(f"Task is not a TextTask: {type(task)}") + bt.logging.debug(task.prompt) bt.logging.debug(f"Rewarding text task {task} with solutions {solutions}") + return [1.0] * len(solutions) From 4739278d5ae089ad14ea875d56d661cb5977a9f1 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 12 Dec 2024 09:15:50 -0600 Subject: [PATCH 030/554] feat: added openai text miner --- .gitignore | 2 +- neurons/miners/genie_miner.py | 0 neurons/miners/miner.py | 21 ++++++---- neurons/miners/openai_miner.py | 59 +++++++++++++++++++++++++++ neurons/validators/genie_validator.py | 1 + wandb/latest-run | 2 +- 6 files changed, 74 insertions(+), 11 deletions(-) delete mode 100644 neurons/miners/genie_miner.py create mode 100644 neurons/miners/openai_miner.py diff --git a/.gitignore b/.gitignore index b9e1a14d..26851170 100644 --- a/.gitignore +++ b/.gitignore @@ -173,5 +173,5 @@ temp.txt # test test.py -# wandb +# Wandb wandb/ \ No newline at end of file diff --git a/neurons/miners/genie_miner.py b/neurons/miners/genie_miner.py deleted file mode 100644 index e69de29b..00000000 diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index 7c7ab172..3b0dafa6 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -15,8 +15,12 @@ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +from dotenv import load_dotenv + +load_dotenv() import time + import typing import bittensor as bt @@ -24,6 +28,7 @@ from webgenie.helpers.weights import init_wandb from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from webgenie.solution import Solution +from neurons.miners.openai_miner import OpenaiMiner class Miner(BaseMinerNeuron): """ @@ -49,26 +54,24 @@ def __init__(self, config=None): priority_fn=self.priority_image, ) + self.genie_miner = OpenaiMiner(self) + init_wandb(self) + async def forward_text( self, synapse: WebgenieTextSynapse ) -> WebgenieTextSynapse: - bt.logging.debug(f"Miner text forward called with synapse: {synapse}") - synapse.solution = Solution( - html = "

Hello, world!

" - ) - return synapse + + return await self.genie_miner.forward_text(synapse) async def forward_image( self, synapse: WebgenieImageSynapse ) -> WebgenieImageSynapse: bt.logging.debug(f"Miner image forward called with synapse: {synapse}") - synapse.solution = Solution( - html = "

Hello, Image!

" - ) - return synapse + + return await self.genie_miner.forward_image(synapse) async def blacklist_text(self, synapse: WebgenieTextSynapse) -> typing.Tuple[bool, str]: return await self.blacklist(synapse) diff --git a/neurons/miners/openai_miner.py b/neurons/miners/openai_miner.py new file mode 100644 index 00000000..5e475dee --- /dev/null +++ b/neurons/miners/openai_miner.py @@ -0,0 +1,59 @@ +import bittensor as bt +import os + +from langchain_openai import ChatOpenAI +from langchain.prompts import ChatPromptTemplate +from langchain_core.output_parsers import StrOutputParser, JsonOutputParser +from langchain_core.pydantic_v1 import BaseModel, Field +from langchain_core.runnables.base import RunnableSequence + +from webgenie.base.neuron import BaseNeuron +from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse +from webgenie.solution.solution import Solution + +class HTMLResponse(BaseModel): + html: str = Field(default="", description="The HTML code for the webpage") + +class OpenaiMiner: + def __init__(self, neuron: BaseNeuron): + self.neuron = neuron + + self.model = ChatOpenAI( + api_key= os.getenv("OPENAI_API_KEY"), + model_name="gpt-4o", + ) + + self.html_response_parser = JsonOutputParser(pydantic_object=HTMLResponse) + + async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynapse: + try: + prompt = ChatPromptTemplate.from_messages([ + ("system", """You are an expert web developer who specializes in HTML and CSS. A user will provide you with the webpage requirements. You need to return a single html file that uses HTML and CSS to satisfy the requirements. + Include all CSS code in the HTML file itself. + If it involves any images, use "rick.jpg" as the placeholder. + Do not hallucinate any dependencies to external files. You do not need to include JavaScript scripts for dynamic interactions. + Pay attention to things like size, text, position, and color of all the elements, as well as the overall layout. + Respond with the content of the HTML+CSS file: + {instructions}"""), + ("user", "{query}"), + ]) + + chain = prompt | self.model | self.html_response_parser + html_response = chain.invoke({ + "query": synapse.prompt, + "instructions": self.html_response_parser.get_format_instructions() + }) + + synapse.solution = Solution(html=html_response["html"]) + return synapse + except: + bt.logging.error(f"Error in OpenaiMiner forward_text: {e}") + return synapse + + async def forward_image(self, synapse: WebgenieImageSynapse) -> WebgenieImageSynapse: + try: + synapse.solution = Solution(html = "Not implemented Yet") + return synapse + except: + bt.logging.error(f"Error in OpenaiMiner forward_image: {e}") + return synapse \ No newline at end of file diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 2ab46ceb..430ec11e 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -103,5 +103,6 @@ async def process_synapse(self, synapse: bt.Synapse, miner_uid: int) -> bt.Synap if synapse.solution is None: return None synapse.solution.miner_uid = miner_uid + synapse.solution.process_time = synapse.dendrite.process_time return synapse return None \ No newline at end of file diff --git a/wandb/latest-run b/wandb/latest-run index 8f30dc87..8f84c6d6 120000 --- a/wandb/latest-run +++ b/wandb/latest-run @@ -1 +1 @@ -run-20241212_082229-ryemav9r \ No newline at end of file +run-20241212_091353-r7n43ygp \ No newline at end of file From 95591081fd8b8f2339b5265dd30b32a496dc464a Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 12 Dec 2024 10:03:16 -0600 Subject: [PATCH 031/554] feat: added image task process --- .gitignore | 5 ++- neurons/miners/openai_miner.py | 38 ++++++++++++++++--- wandb/latest-run | 2 +- webgenie/helpers/images.py | 15 ++++++++ .../task_generator/image_task_generator.py | 6 ++- 5 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 webgenie/helpers/images.py diff --git a/.gitignore b/.gitignore index 26851170..73c0f3a3 100644 --- a/.gitignore +++ b/.gitignore @@ -174,4 +174,7 @@ temp.txt test.py # Wandb -wandb/ \ No newline at end of file +wandb/ + +# test images +original.jpg \ No newline at end of file diff --git a/neurons/miners/openai_miner.py b/neurons/miners/openai_miner.py index 5e475dee..6e663dd6 100644 --- a/neurons/miners/openai_miner.py +++ b/neurons/miners/openai_miner.py @@ -2,10 +2,9 @@ import os from langchain_openai import ChatOpenAI -from langchain.prompts import ChatPromptTemplate -from langchain_core.output_parsers import StrOutputParser, JsonOutputParser +from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate +from langchain_core.output_parsers import JsonOutputParser from langchain_core.pydantic_v1 import BaseModel, Field -from langchain_core.runnables.base import RunnableSequence from webgenie.base.neuron import BaseNeuron from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse @@ -52,8 +51,37 @@ async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynaps async def forward_image(self, synapse: WebgenieImageSynapse) -> WebgenieImageSynapse: try: - synapse.solution = Solution(html = "Not implemented Yet") + + prompt_messages = [ + SystemMessagePromptTemplate.from_template(""" + You are an expert web developer who specializes in HTML and CSS. + A user will provide you with a screenshot of a webpage, along with all texts that they want to put on the webpage. + You need to return a single html file that uses HTML and CSS to reproduce the given website. + Include all CSS code in the HTML file itself. + If it involves any images, use "rick.jpg" as the placeholder. + Some images on the webpage are replaced with a blue rectangle as the placeholder, use "rick.jpg" for those as well. + Do not hallucinate any dependencies to external files. You do not need to include JavaScript scripts for dynamic interactions. + Pay attention to things like size, text, position, and color of all the elements, as well as the overall layout. + Respond with the content of the HTML+CSS file: + {instructions}"""), + HumanMessagePromptTemplate.from_template( + template=[ + {"type": "image_url", "image_url": {"url": "{image_url}"}}, + ] + ) + ] + + prompt = ChatPromptTemplate(messages=prompt_messages) + + chain = prompt | self.model | self.html_response_parser + + html_response = chain.invoke({ + "instructions": self.html_response_parser.get_format_instructions(), + "image_url": f"data:image/jpeg;base64,{synapse.base64_image}", + }) + + synapse.solution = Solution(html = html_response["html"]) return synapse - except: + except Exception as e: bt.logging.error(f"Error in OpenaiMiner forward_image: {e}") return synapse \ No newline at end of file diff --git a/wandb/latest-run b/wandb/latest-run index 8f84c6d6..95dde8c5 120000 --- a/wandb/latest-run +++ b/wandb/latest-run @@ -1 +1 @@ -run-20241212_091353-r7n43ygp \ No newline at end of file +run-20241212_095851-fzlykewr \ No newline at end of file diff --git a/webgenie/helpers/images.py b/webgenie/helpers/images.py new file mode 100644 index 00000000..c0938be3 --- /dev/null +++ b/webgenie/helpers/images.py @@ -0,0 +1,15 @@ +from PIL import Image +import io +import base64 + +def pil_image_to_base64(img: Image.Image) -> str: + buffered = io.BytesIO() + img.save(buffered, format="jpeg") + img_bytes = buffered.getvalue() + base64_str = base64.b64encode(img_bytes).decode("utf-8") + + return base64_str + +def image_to_base64(image_path: str) -> str: + img = Image.open(image_path) + return pil_image_to_base64(img) diff --git a/webgenie/task_generator/image_task_generator.py b/webgenie/task_generator/image_task_generator.py index 6d86cc99..96053562 100644 --- a/webgenie/task_generator/image_task_generator.py +++ b/webgenie/task_generator/image_task_generator.py @@ -1,6 +1,7 @@ import bittensor as bt from typing import List, Tuple +from webgenie.helpers.images import image_to_base64 from webgenie.protocol import WebgenieImageSynapse from webgenie.solution import Solution from webgenie.tasks.task import Task, ImageTask @@ -11,11 +12,12 @@ def __init__(self): super().__init__() async def generate_task(self) -> Tuple[Task, bt.Synapse]: + base64_image = image_to_base64("original.jpg") return ImageTask( - base64_image="base64_image" , + base64_image=base64_image, timeout=50, generator=self - ), WebgenieImageSynapse(base64_image="base64_image") + ), WebgenieImageSynapse(base64_image=base64_image) async def reward(self, task: Task, solutions: List[Solution]) -> List[float]: if not isinstance(task, ImageTask): From f0305fe9987a09932c63aee32ee6fb4da999cadf Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 12 Dec 2024 10:08:16 -0600 Subject: [PATCH 032/554] feat: remove wandb --- .gitignore | 2 +- wandb/latest-run | 1 - .../files/requirements.txt | 139 ------------------ .../files/wandb-metadata.json | 45 ------ .../run-fmmfb2d8.wandb | Bin 82089 -> 0 bytes .../files/requirements.txt | 139 ------------------ .../files/wandb-metadata.json | 45 ------ .../run-p5q8p631.wandb | Bin 13718 -> 0 bytes 8 files changed, 1 insertion(+), 370 deletions(-) delete mode 120000 wandb/latest-run delete mode 100644 wandb/run-20241212_080228-fmmfb2d8/files/requirements.txt delete mode 100644 wandb/run-20241212_080228-fmmfb2d8/files/wandb-metadata.json delete mode 100644 wandb/run-20241212_080228-fmmfb2d8/run-fmmfb2d8.wandb delete mode 100644 wandb/run-20241212_080410-p5q8p631/files/requirements.txt delete mode 100644 wandb/run-20241212_080410-p5q8p631/files/wandb-metadata.json delete mode 100644 wandb/run-20241212_080410-p5q8p631/run-p5q8p631.wandb diff --git a/.gitignore b/.gitignore index 73c0f3a3..f50d3e89 100644 --- a/.gitignore +++ b/.gitignore @@ -174,7 +174,7 @@ temp.txt test.py # Wandb -wandb/ +# wandb/ # test images original.jpg \ No newline at end of file diff --git a/wandb/latest-run b/wandb/latest-run deleted file mode 120000 index 95dde8c5..00000000 --- a/wandb/latest-run +++ /dev/null @@ -1 +0,0 @@ -run-20241212_095851-fzlykewr \ No newline at end of file diff --git a/wandb/run-20241212_080228-fmmfb2d8/files/requirements.txt b/wandb/run-20241212_080228-fmmfb2d8/files/requirements.txt deleted file mode 100644 index d0cdb471..00000000 --- a/wandb/run-20241212_080228-fmmfb2d8/files/requirements.txt +++ /dev/null @@ -1,139 +0,0 @@ -wandb==0.19.0 -charset-normalizer==3.4.0 -triton==3.1.0 -cryptography==42.0.8 -fsspec==2024.10.0 -backoff==2.2.1 -wheel==0.45.1 -GitPython==3.1.43 -Levenshtein==0.26.1 -urllib3==2.2.3 -munch==2.5.0 -typing_extensions==4.12.2 -distro==1.9.0 -rich==13.9.4 -langchain-openai==0.2.12 -scalecodec==1.2.11 -eth-utils==2.2.2 -attrs==24.2.0 -nvidia-nccl-cu12==2.21.5 -py==1.11.0 -sentry-sdk==2.19.2 -gitdb==4.0.11 -colorama==0.4.6 -multidict==6.1.0 -nvidia-nvjitlink-cu12==12.4.127 -nvidia-curand-cu12==10.3.5.147 -setproctitle==1.3.4 -setuptools==70.0.0 -httpx==0.28.1 -aiosignal==1.3.1 -python-statemachine==2.5.0 -packaging==24.2 -langchain-core==0.3.24 -substrate-interface==1.7.11 -eth-keys==0.6.0 -webencodings==0.5.1 -propcache==0.2.1 -pydantic_core==2.27.1 -xxhash==3.5.0 -platformdirs==4.3.6 -pip==24.2 -annotated-types==0.7.0 -sympy==1.13.1 -ddt==1.6.0 -openai==1.57.2 -tenacity==9.0.0 -nvidia-cusolver-cu12==11.6.1.9 -nvidia-nvtx-cu12==12.4.127 -Pygments==2.18.0 -fastapi==0.110.3 -eth-hash==0.7.0 -smmap==5.0.1 -regex==2024.11.6 -retry==0.9.2 -pluggy==1.5.0 -ansible-vault==2.1.0 -click==8.1.7 -RapidFuzz==3.10.1 -nvidia-cusparse-cu12==12.3.1.170 -jsonpointer==3.0.0 -shtab==1.6.5 -SQLAlchemy==2.0.36 -password-strength==0.0.3.post2 -py-sr25519-bindings==0.2.1 -ansible==8.5.0 -more-itertools==10.5.0 -pycparser==2.22 -msgpack==1.1.0 -PyYAML==6.0.2 -filelock==3.16.1 -soupsieve==2.6 -beautifulsoup4==4.12.3 -py-bip39-bindings==0.2.0 -jsonpatch==1.33 -nvidia-cuda-nvrtc-cu12==12.4.127 -fuzzywuzzy==0.18.0 -h11==0.14.0 -jiter==0.8.2 -protobuf==5.29.1 -eth-typing==5.0.1 -termcolor==2.5.0 -yarl==1.18.3 -aiohappyeyeballs==2.4.4 -nvidia-cuda-cupti-cu12==12.4.127 -docker-pycreds==0.4.0 -starlette==0.37.2 -ecdsa==0.19.0 -networkx==3.4.2 -sniffio==1.3.1 -ansible-core==2.15.13 -Jinja2==3.1.4 -certifi==2024.7.4 -torch==2.5.1 -toolz==1.0.0 -langchain==0.3.11 -netaddr==1.3.0 -MarkupSafe==3.0.2 -websocket-client==1.8.0 -langsmith==0.2.3 -python-dotenv==1.0.1 -greenlet==3.1.1 -orjson==3.10.12 -idna==3.10 -frozenlist==1.5.0 -decorator==5.1.1 -anyio==4.7.0 -nest-asyncio==1.6.0 -markdown-it-py==3.0.0 -psutil==6.1.0 -nvidia-cufft-cu12==11.2.1.3 -bittensor==7.4.0 -py-ed25519-zebra-bindings==1.2.0 -nvidia-cudnn-cu12==9.1.0.70 -requests-toolbelt==1.0.0 -uvicorn==0.32.1 -msgpack-numpy-opentensor==0.5.0 -python-Levenshtein==0.26.1 -tinycss2==1.4.0 -mdurl==0.1.2 -mpmath==1.3.0 -cffi==1.17.1 -numpy==1.26.4 -six==1.17.0 -pydantic==2.10.3 -tiktoken==0.8.0 -iniconfig==2.0.0 -langchain-text-splitters==0.3.2 -pycryptodome==3.21.0 -tqdm==4.67.1 -nvidia-cublas-cu12==12.4.5.8 -requests==2.32.3 -base58==2.1.1 -httpcore==1.0.7 -aiohttp==3.11.10 -PyNaCl==1.5.0 -cytoolz==1.0.0 -pytest==8.3.4 -resolvelib==1.0.1 -nvidia-cuda-runtime-cu12==12.4.127 diff --git a/wandb/run-20241212_080228-fmmfb2d8/files/wandb-metadata.json b/wandb/run-20241212_080228-fmmfb2d8/files/wandb-metadata.json deleted file mode 100644 index 4b201625..00000000 --- a/wandb/run-20241212_080228-fmmfb2d8/files/wandb-metadata.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "os": "Linux-5.4.0-105-generic-x86_64-with-glibc2.31", - "python": "CPython 3.12.7", - "startedAt": "2024-12-12T14:02:28.547811Z", - "args": [ - "--netuid", - "214", - "--subtensor.network", - "test", - "--wallet.name", - "sc-val1", - "--wallet.hotkey", - "sh-val1", - "--logging.debug", - "--neuron.axon_port", - "8091" - ], - "program": "/home/dev2user/workspace/sangar/web-genie-ai/neurons/validators/validator.py", - "codePath": "neurons/validators/validator.py", - "git": { - "remote": "https://github.com/web-genie-ai/web-genie-ai.git", - "commit": "5cf0c922e756248e98d8a3885581aed3944ca900" - }, - "email": "hayesdominique0729@gmail.com", - "root": "/home/dev2user/workspace/sangar/web-genie-ai", - "host": "vmi1162577.contaboserver.net", - "username": "root", - "executable": "/home/dev2user/workspace/sangar/web-genie-ai/venv/bin/python3.12", - "codePathLocal": "neurons/validators/validator.py", - "cpu_count": 6, - "cpu_count_logical": 6, - "disk": { - "/": { - "total": "630869753856", - "used": "33456181248" - } - }, - "memory": { - "total": "16786370560" - }, - "cpu": { - "count": 6, - "countLogical": 6 - } -} \ No newline at end of file diff --git a/wandb/run-20241212_080228-fmmfb2d8/run-fmmfb2d8.wandb b/wandb/run-20241212_080228-fmmfb2d8/run-fmmfb2d8.wandb deleted file mode 100644 index 8a19372f048c24edb47da814bcba5f07509c6e8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82089 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 diff --git a/wandb/run-20241212_080410-p5q8p631/files/requirements.txt b/wandb/run-20241212_080410-p5q8p631/files/requirements.txt deleted file mode 100644 index d0cdb471..00000000 --- a/wandb/run-20241212_080410-p5q8p631/files/requirements.txt +++ /dev/null @@ -1,139 +0,0 @@ -wandb==0.19.0 -charset-normalizer==3.4.0 -triton==3.1.0 -cryptography==42.0.8 -fsspec==2024.10.0 -backoff==2.2.1 -wheel==0.45.1 -GitPython==3.1.43 -Levenshtein==0.26.1 -urllib3==2.2.3 -munch==2.5.0 -typing_extensions==4.12.2 -distro==1.9.0 -rich==13.9.4 -langchain-openai==0.2.12 -scalecodec==1.2.11 -eth-utils==2.2.2 -attrs==24.2.0 -nvidia-nccl-cu12==2.21.5 -py==1.11.0 -sentry-sdk==2.19.2 -gitdb==4.0.11 -colorama==0.4.6 -multidict==6.1.0 -nvidia-nvjitlink-cu12==12.4.127 -nvidia-curand-cu12==10.3.5.147 -setproctitle==1.3.4 -setuptools==70.0.0 -httpx==0.28.1 -aiosignal==1.3.1 -python-statemachine==2.5.0 -packaging==24.2 -langchain-core==0.3.24 -substrate-interface==1.7.11 -eth-keys==0.6.0 -webencodings==0.5.1 -propcache==0.2.1 -pydantic_core==2.27.1 -xxhash==3.5.0 -platformdirs==4.3.6 -pip==24.2 -annotated-types==0.7.0 -sympy==1.13.1 -ddt==1.6.0 -openai==1.57.2 -tenacity==9.0.0 -nvidia-cusolver-cu12==11.6.1.9 -nvidia-nvtx-cu12==12.4.127 -Pygments==2.18.0 -fastapi==0.110.3 -eth-hash==0.7.0 -smmap==5.0.1 -regex==2024.11.6 -retry==0.9.2 -pluggy==1.5.0 -ansible-vault==2.1.0 -click==8.1.7 -RapidFuzz==3.10.1 -nvidia-cusparse-cu12==12.3.1.170 -jsonpointer==3.0.0 -shtab==1.6.5 -SQLAlchemy==2.0.36 -password-strength==0.0.3.post2 -py-sr25519-bindings==0.2.1 -ansible==8.5.0 -more-itertools==10.5.0 -pycparser==2.22 -msgpack==1.1.0 -PyYAML==6.0.2 -filelock==3.16.1 -soupsieve==2.6 -beautifulsoup4==4.12.3 -py-bip39-bindings==0.2.0 -jsonpatch==1.33 -nvidia-cuda-nvrtc-cu12==12.4.127 -fuzzywuzzy==0.18.0 -h11==0.14.0 -jiter==0.8.2 -protobuf==5.29.1 -eth-typing==5.0.1 -termcolor==2.5.0 -yarl==1.18.3 -aiohappyeyeballs==2.4.4 -nvidia-cuda-cupti-cu12==12.4.127 -docker-pycreds==0.4.0 -starlette==0.37.2 -ecdsa==0.19.0 -networkx==3.4.2 -sniffio==1.3.1 -ansible-core==2.15.13 -Jinja2==3.1.4 -certifi==2024.7.4 -torch==2.5.1 -toolz==1.0.0 -langchain==0.3.11 -netaddr==1.3.0 -MarkupSafe==3.0.2 -websocket-client==1.8.0 -langsmith==0.2.3 -python-dotenv==1.0.1 -greenlet==3.1.1 -orjson==3.10.12 -idna==3.10 -frozenlist==1.5.0 -decorator==5.1.1 -anyio==4.7.0 -nest-asyncio==1.6.0 -markdown-it-py==3.0.0 -psutil==6.1.0 -nvidia-cufft-cu12==11.2.1.3 -bittensor==7.4.0 -py-ed25519-zebra-bindings==1.2.0 -nvidia-cudnn-cu12==9.1.0.70 -requests-toolbelt==1.0.0 -uvicorn==0.32.1 -msgpack-numpy-opentensor==0.5.0 -python-Levenshtein==0.26.1 -tinycss2==1.4.0 -mdurl==0.1.2 -mpmath==1.3.0 -cffi==1.17.1 -numpy==1.26.4 -six==1.17.0 -pydantic==2.10.3 -tiktoken==0.8.0 -iniconfig==2.0.0 -langchain-text-splitters==0.3.2 -pycryptodome==3.21.0 -tqdm==4.67.1 -nvidia-cublas-cu12==12.4.5.8 -requests==2.32.3 -base58==2.1.1 -httpcore==1.0.7 -aiohttp==3.11.10 -PyNaCl==1.5.0 -cytoolz==1.0.0 -pytest==8.3.4 -resolvelib==1.0.1 -nvidia-cuda-runtime-cu12==12.4.127 diff --git a/wandb/run-20241212_080410-p5q8p631/files/wandb-metadata.json b/wandb/run-20241212_080410-p5q8p631/files/wandb-metadata.json deleted file mode 100644 index c8e61208..00000000 --- a/wandb/run-20241212_080410-p5q8p631/files/wandb-metadata.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "os": "Linux-5.4.0-105-generic-x86_64-with-glibc2.31", - "python": "CPython 3.12.7", - "startedAt": "2024-12-12T14:04:10.946323Z", - "args": [ - "--netuid", - "214", - "--subtensor.network", - "test", - "--wallet.name", - "s-miner", - "--wallet.hotkey", - "miner1", - "--logging.debug", - "--axon.port", - "8090" - ], - "program": "/home/dev2user/workspace/sangar/web-genie-ai/neurons/miners/miner.py", - "codePath": "neurons/miners/miner.py", - "git": { - "remote": "https://github.com/web-genie-ai/web-genie-ai.git", - "commit": "5cf0c922e756248e98d8a3885581aed3944ca900" - }, - "email": "hayesdominique0729@gmail.com", - "root": "/home/dev2user/workspace/sangar/web-genie-ai", - "host": "vmi1162577.contaboserver.net", - "username": "root", - "executable": "/home/dev2user/workspace/sangar/web-genie-ai/venv/bin/python3.12", - "codePathLocal": "neurons/miners/miner.py", - "cpu_count": 6, - "cpu_count_logical": 6, - "disk": { - "/": { - "total": "630869753856", - "used": "33456324608" - } - }, - "memory": { - "total": "16786370560" - }, - "cpu": { - "count": 6, - "countLogical": 6 - } -} \ No newline at end of file diff --git a/wandb/run-20241212_080410-p5q8p631/run-p5q8p631.wandb b/wandb/run-20241212_080410-p5q8p631/run-p5q8p631.wandb deleted file mode 100644 index f768135d09c2656671e5383f57d5822d7eaacab6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13718 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 Eze2+$8~^|S From 7036a55d20e66006776bb0f446ede0387aafd677 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 12 Dec 2024 10:08:58 -0600 Subject: [PATCH 033/554] feat: add wandb to the gitignore list --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f50d3e89..73c0f3a3 100644 --- a/.gitignore +++ b/.gitignore @@ -174,7 +174,7 @@ temp.txt test.py # Wandb -# wandb/ +wandb/ # test images original.jpg \ No newline at end of file From ad8f1a59b25fb5a83c8f217b9f9742d45e82120d Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 12 Dec 2024 14:18:20 -0600 Subject: [PATCH 034/554] feat: implemented organic task --- neurons/validators/genie_validator.py | 13 +++--- neurons/validators/validator.py | 62 +++++++++++++++++++++++---- webgenie/base/validator.py | 38 +--------------- 3 files changed, 61 insertions(+), 52 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 430ec11e..0f8747c7 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -1,17 +1,15 @@ import bittensor as bt import random -import torch -from typing import List +from typing import Union from webgenie.base.neuron import BaseNeuron -from webgenie.protocol import WebgenieStreamingSynapse, WebgenieTextSynapse +from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.rewards import RewardManager from webgenie.solution import Solution from webgenie.task_generator.image_task_generator import ImageTaskGenerator from webgenie.task_generator.text_task_generator import TextTaskGenerator from webgenie.utils.uids import get_random_uids - MAX_SYNTHETIC_HISTORY_SIZE = 10 class GenieValidator: @@ -71,12 +69,13 @@ async def score(self): self.neuron.update_scores(scores, [solution.miner_uid for solution in solutions]) self.neuron.sync() - async def organic_forward(self, synapse: WebgenieTextSynapse): + async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImageSynapse]): + bt.logging.debug(f"Organic forward: {synapse}") best_miner_uid = 1 try: - axon = self.metagraph.axons[best_miner_uid] + axon = self.neuron.metagraph.axons[best_miner_uid] - async with bt.dendrite(wallet=self.wallet) as dendrite: + async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: bt.logging.info(f"Dendrite: {dendrite}") responses = await dendrite( axons=[axon], diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index fbf3245f..e5763f21 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -1,18 +1,19 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao # Copyright © 2024 pycorn +import bittensor as bt import asyncio from dotenv import load_dotenv load_dotenv() -import time - -import bittensor as bt +from typing import Tuple, Union from webgenie.base.validator import BaseValidatorNeuron from webgenie.helpers.weights import init_wandb -from webgenie.protocol import WebgenieStreamingSynapse +from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from neurons.validators.genie_validator import GenieValidator +API_HOTKEY = "5D72esHuc1DxD6PD8S6VyU24bTHGQjHHyodzTGsem1sejUYj" + class Validator(BaseValidatorNeuron): """ Your validator neuron class. You should use this class to define your validator's behavior. In particular, you should replace the forward function with your own logic. @@ -26,13 +27,58 @@ def __init__(self, config=None): super(Validator, self).__init__(config=config) bt.logging.info("load_state()") self.load_state() + init_wandb(self) + + if not self.config.axon_off: + self.serve_axon() + self.genie_validator = GenieValidator(neuron=self) - init_wandb(self) + async def blacklist_text(self, synapse: WebgenieTextSynapse) -> Tuple[bool, str]: + """ + Only allow the subnet owner to send synapse to the validator. + """ + if synapse.dendrite.hotkey == API_HOTKEY: + return False, "Subnet owner hotkey" + return True, "Blacklisted" + async def blacklist_image(self, synapse: WebgenieImageSynapse) -> Tuple[bool, str]: + """ + Only allow the subnet owner to send synapse to the validator. + """ + if synapse.dendrite.hotkey == API_HOTKEY: + return False, "Subnet owner hotkey" + return True, "Blacklisted" + + async def organic_forward_text(self, synapse: WebgenieTextSynapse): + return await self.genie_validator.organic_forward(synapse) - async def organic_forward(self, synapse: WebgenieStreamingSynapse): + async def organic_forward_image(self, synapse: WebgenieImageSynapse): return await self.genie_validator.organic_forward(synapse) + def serve_axon(self): + """Serve axon to enable external connections.""" + bt.logging.info("serving ip to chain...") + try: + self.axon = bt.axon(wallet=self.wallet, config=self.config) + + self.axon.attach( + forward_fn = self.organic_forward_text, + blacklist_fn = self.blacklist_text + ).attach( + forward_fn = self.organic_forward_image, + blacklist_fn = self.blacklist_image + ) + + self.axon.serve( + netuid=self.config.netuid, + subtensor=self.subtensor, + ) + self.axon.start() + bt.logging.info(f"Validator running in organic mode on port {self.config.neuron.axon_port}") + except Exception as e: + bt.logging.error(f"Failed to serve Axon with exception: {e}") + pass + async def forward(self): return await self.genie_validator.forward() @@ -69,8 +115,8 @@ async def scoring_loop(self): await asyncio.sleep(5) async def __aenter__(self): - self.loop.create_task(self.forward_loop()) - self.loop.create_task(self.scoring_loop()) + #self.loop.create_task(self.forward_loop()) + #self.loop.create_task(self.scoring_loop()) self.is_running = True bt.logging.debug("Starting validator in background thread") return self diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index d777187d..e7202051 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -36,7 +36,6 @@ from webgenie.utils.config import add_validator_args from webgenie.protocol import WebgenieStreamingSynapse -SUBNET_OWNER_HOTKEY = "5G9sRcoaw2H3SYDq7e7PoGhbbMUPHQi6pC6tPrahmSmDtxS8" class BaseValidatorNeuron(BaseNeuron): """ @@ -78,46 +77,11 @@ def __init__(self, config=None): self.is_running: bool = False self.thread: Union[threading.Thread, None] = None self.lock = asyncio.Lock() - - def serve_axon(self): - """Serve axon to enable external connections.""" - - bt.logging.info("serving ip to chain...") - try: - - self.axon = bt.axon(wallet=self.wallet, config=self.config) - - self.axon.attach( - forward_fn = self.organic_forward, - blacklist_fn = self.blacklist - ) - self.axon.serve( - netuid=self.config.netuid, - subtensor=self.subtensor, - ) - self.axon.start() - bt.logging.info(f"Validator running in organic mode on port {self.config.neuron.axon_port}") - except Exception as e: - bt.logging.error(f"Failed to serve Axon with exception: {e}") - pass + def run(self): pass - async def blacklist(self, synapse: WebgenieStreamingSynapse) -> Tuple[bool, str]: - """ - Only allow the subnet owner to send synapse to the validator. - """ - if synapse.dendrite.hotkey == SUBNET_OWNER_HOTKEY: - return False, "Subnet owner hotkey" - return True, "Blacklisted" - async def organic_forward(self, synapse: WebgenieStreamingSynapse) -> WebgenieStreamingSynapse: - - bt.logging.error(f"OrganicForward Thread Name: {threading.current_thread().name}") - bt.logging.info(f"=========> {synapse}") - - return await forward_organic_synapse(self, synapse) - def set_weights(self): """ Sets the validator weights to the metagraph hotkeys based on the scores it has received from the miners. The weights determine the trust and incentive level the validator assigns to miner nodes on the network. From 5d061cc1a4904dbab04011d91721f5b9d2094226 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 12 Dec 2024 14:27:36 -0600 Subject: [PATCH 035/554] chore: removed version in wandb run name --- webgenie/helpers/weights.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/helpers/weights.py b/webgenie/helpers/weights.py index 835c9449..0ea254b6 100644 --- a/webgenie/helpers/weights.py +++ b/webgenie/helpers/weights.py @@ -16,7 +16,7 @@ def init_wandb(self): wandb_on = True wandb.login(key=os.getenv("WANDB_API_KEY")) - run_name = f"{self.config.neuron.name}-{self.uid}--{webgenie.__version__}" + run_name = f"{self.config.neuron.name}-{self.uid}" run = wandb.init( project=webgenie.PROJECT_NAME, entity=os.getenv("WANDB_ENTITY_NAME"), From b8c8807b022ba84091c57dc90eead9ba4d339145 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 05:59:58 -0600 Subject: [PATCH 036/554] feat: implemented visual reward --- .gitignore | 5 +- neurons/miners/miner.py | 3 +- neurons/miners/openai_miner.py | 6 +- neurons/validators/genie_validator.py | 33 +- neurons/validators/validator.py | 4 +- requirements.txt | 169 ++++- run_miner.sh | 2 +- run_validator.sh | 2 +- webgenie/base/validator.py | 1 - webgenie/constants.py | 4 + webgenie/datasets/dataset.py | 152 +++++ webgenie/helpers/htmls.py | 14 + webgenie/protocol.py | 108 +--- webgenie/rewards/__init__.py | 5 +- webgenie/rewards/gpt.py | 47 -- webgenie/rewards/is_valid.py | 47 -- webgenie/rewards/metrics/__init__.py | 0 webgenie/rewards/metrics/dedup_post_gen.py | 69 +++ .../rewards/metrics/multi_processing_eval.py | 123 ++++ webgenie/rewards/metrics/ocr_free_utils.py | 259 ++++++++ webgenie/rewards/metrics/screenshot_single.py | 48 ++ webgenie/rewards/metrics/visual_score.py | 581 ++++++++++++++++++ webgenie/rewards/reward.py | 13 +- webgenie/rewards/reward_manager.py | 67 -- webgenie/rewards/speed.py | 15 - webgenie/rewards/visual_reward.py | 33 + webgenie/solution/__init__.py | 1 - webgenie/task_generator/__init__.py | 1 - .../task_generator/image_task_generator.py | 27 - .../task_generator/text_task_generator.py | 26 - webgenie/tasks/__init__.py | 6 +- webgenie/tasks/image_task_generator.py | 33 + webgenie/{solution => tasks}/solution.py | 0 webgenie/tasks/task.py | 6 +- .../task_generator.py | 13 +- webgenie/tasks/text_task_generator.py | 26 + 36 files changed, 1560 insertions(+), 389 deletions(-) create mode 100644 webgenie/constants.py create mode 100644 webgenie/datasets/dataset.py create mode 100644 webgenie/helpers/htmls.py delete mode 100644 webgenie/rewards/gpt.py delete mode 100644 webgenie/rewards/is_valid.py create mode 100644 webgenie/rewards/metrics/__init__.py create mode 100644 webgenie/rewards/metrics/dedup_post_gen.py create mode 100644 webgenie/rewards/metrics/multi_processing_eval.py create mode 100644 webgenie/rewards/metrics/ocr_free_utils.py create mode 100644 webgenie/rewards/metrics/screenshot_single.py create mode 100644 webgenie/rewards/metrics/visual_score.py delete mode 100644 webgenie/rewards/reward_manager.py delete mode 100644 webgenie/rewards/speed.py create mode 100644 webgenie/rewards/visual_reward.py delete mode 100644 webgenie/solution/__init__.py delete mode 100644 webgenie/task_generator/__init__.py delete mode 100644 webgenie/task_generator/image_task_generator.py delete mode 100644 webgenie/task_generator/text_task_generator.py create mode 100644 webgenie/tasks/image_task_generator.py rename webgenie/{solution => tasks}/solution.py (100%) rename webgenie/{task_generator => tasks}/task_generator.py (52%) create mode 100644 webgenie/tasks/text_task_generator.py diff --git a/.gitignore b/.gitignore index 73c0f3a3..a169e7c9 100644 --- a/.gitignore +++ b/.gitignore @@ -177,4 +177,7 @@ test.py wandb/ # test images -original.jpg \ No newline at end of file +original.jpg + +# work dir +work/ \ No newline at end of file diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index 3b0dafa6..5b09a02f 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -27,7 +27,7 @@ from webgenie.base.miner import BaseMinerNeuron from webgenie.helpers.weights import init_wandb from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse -from webgenie.solution import Solution +from webgenie.tasks import Solution from neurons.miners.openai_miner import OpenaiMiner class Miner(BaseMinerNeuron): @@ -188,5 +188,4 @@ async def priority(self, synapse: bt.Synapse) -> float: if __name__ == "__main__": with Miner() as miner: while True: - bt.logging.info(f"Miner running... {time.time()}") time.sleep(5) diff --git a/neurons/miners/openai_miner.py b/neurons/miners/openai_miner.py index 6e663dd6..c7d2f1f5 100644 --- a/neurons/miners/openai_miner.py +++ b/neurons/miners/openai_miner.py @@ -8,7 +8,7 @@ from webgenie.base.neuron import BaseNeuron from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse -from webgenie.solution.solution import Solution +from webgenie.tasks.solution import Solution class HTMLResponse(BaseModel): html: str = Field(default="", description="The HTML code for the webpage") @@ -43,7 +43,7 @@ async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynaps "instructions": self.html_response_parser.get_format_instructions() }) - synapse.solution = Solution(html=html_response["html"]) + synapse.html = html_response["html"] return synapse except: bt.logging.error(f"Error in OpenaiMiner forward_text: {e}") @@ -80,7 +80,7 @@ async def forward_image(self, synapse: WebgenieImageSynapse) -> WebgenieImageSyn "image_url": f"data:image/jpeg;base64,{synapse.base64_image}", }) - synapse.solution = Solution(html = html_response["html"]) + synapse.html = html_response["html"] return synapse except Exception as e: bt.logging.error(f"Error in OpenaiMiner forward_image: {e}") diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 0f8747c7..0a6ba728 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -3,22 +3,19 @@ from typing import Union from webgenie.base.neuron import BaseNeuron +from webgenie.constants import MAX_SYNTHETIC_HISTORY_SIZE from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse -from webgenie.rewards import RewardManager -from webgenie.solution import Solution -from webgenie.task_generator.image_task_generator import ImageTaskGenerator -from webgenie.task_generator.text_task_generator import TextTaskGenerator +from webgenie.tasks.solution import Solution +from webgenie.tasks.image_task_generator import ImageTaskGenerator +from webgenie.tasks.text_task_generator import TextTaskGenerator from webgenie.utils.uids import get_random_uids -MAX_SYNTHETIC_HISTORY_SIZE = 10 - class GenieValidator: def __init__(self, neuron: BaseNeuron): self.neuron = neuron self.config = neuron.config self.synthetic_history = [] - self.reward_manager = RewardManager() self.task_generators = [ TextTaskGenerator(), ImageTaskGenerator(), @@ -42,9 +39,9 @@ async def forward(self): solutions = [] for synapse, miner_uid in zip(all_synapse_results, miner_uids): - processed_synapse = await self.process_synapse(synapse, miner_uid) + processed_synapse = await self.process_synapse(synapse) if processed_synapse is not None: - solutions.append(processed_synapse.solution) + solutions.append(Solution(html = processed_synapse.html, miner_uid = miner_uid, process_time = processed_synapse.dendrite.process_time)) if len(solutions) == 0: bt.logging.warning(f"No valid solutions received") @@ -82,26 +79,18 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag synapse=synapse, timeout=synapse.timeout, ) - - processed_synapse = await self.process_synapse(responses[0], best_miner_uid) + + processed_synapse = await self.process_synapse(responses[0]) if processed_synapse is None: raise Exception(f"No valid solution received") - + return processed_synapse except Exception as e: bt.logging.error(f"[forward_organic_synapse] Error querying dendrite: {e}") - synapse.solution = Solution( - html = f"Error: {e}", - process_time = 0, - miner_uid = best_miner_uid, - ) + synapse.html = f"Error: {e}" return synapse - async def process_synapse(self, synapse: bt.Synapse, miner_uid: int) -> bt.Synapse: + async def process_synapse(self, synapse: bt.Synapse) -> bt.Synapse: if synapse.dendrite.status_code == 200: - if synapse.solution is None: - return None - synapse.solution.miner_uid = miner_uid - synapse.solution.process_time = synapse.dendrite.process_time return synapse return None \ No newline at end of file diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index e5763f21..f57e6e6c 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -115,8 +115,8 @@ async def scoring_loop(self): await asyncio.sleep(5) async def __aenter__(self): - #self.loop.create_task(self.forward_loop()) - #self.loop.create_task(self.scoring_loop()) + self.loop.create_task(self.forward_loop()) + self.loop.create_task(self.scoring_loop()) self.is_running = True bt.logging.debug("Starting validator in background thread") return self diff --git a/requirements.txt b/requirements.txt index c8d4ef70..4d9dca97 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,158 @@ -bittensor==7.4.0 -starlette>=0.30.0 -pydantic>=2 -rich>=13 -pytest>=8 -torch>=2 -numpy>=1 -setuptools>=68 -langchain -langchain-openai -python-dotenv \ No newline at end of file +aiohappyeyeballs==2.4.4 +aiosignal==1.3.1 +annotated-types==0.7.0 +ansible==8.5.0 +ansible-core==2.15.13 +ansible-vault==2.1.0 +anyio==4.7.0 +attrs==24.2.0 +backoff==2.2.1 +base58==2.1.1 +beautifulsoup4==4.12.3 +bittensor==8.4.5 +certifi==2024.7.4 +cffi==1.17.1 +charset-normalizer==3.4.0 +click==8.1.7 +colorama==0.4.6 +colormath==3.0.0 +contourpy==1.3.1 +cryptography~=43.0.1 +cycler==0.12.1 +cytoolz==1.0.0 +ddt==1.6.0 +decorator==5.1.1 +distro==1.9.0 +docker-pycreds==0.4.0 +ecdsa==0.19.0 +eth-hash==0.7.0 +eth-keys==0.6.0 +eth-typing==5.0.1 +eth-utils==2.2.2 +fastapi==0.110.3 +filelock==3.16.1 +fonttools==4.55.3 +frozenlist==1.5.0 +fsspec==2024.10.0 +ftfy==6.3.1 +fuzzywuzzy==0.18.0 +gitdb==4.0.11 +GitPython==3.1.43 +greenlet==3.1.1 +h11==0.14.0 +httpcore==1.0.7 +httpx==0.28.1 +idna==3.10 +iniconfig==2.0.0 +Jinja2==3.1.4 +jiter==0.8.2 +joblib==1.4.2 +jsonpatch==1.33 +jsonpointer==3.0.0 +kiwisolver==1.4.7 +langchain==0.3.11 +langchain-core==0.3.24 +langchain-openai==0.2.12 +langchain-text-splitters==0.3.2 +langsmith==0.2.3 +Levenshtein==0.26.1 +markdown-it-py==3.0.0 +MarkupSafe==3.0.2 +matplotlib==3.9.3 +matplotlib-inline==0.1.7 +mdurl==0.1.2 +more-itertools==10.5.0 +mpmath==1.3.0 +msgpack==1.1.0 +msgpack-numpy-opentensor==0.5.0 +multidict==6.1.0 +munch==2.5.0 +nest-asyncio==1.6.0 +netaddr==1.3.0 +networkx==3.4.2 +numpy~=2.0.1 +nvidia-cublas-cu12==12.4.5.8 +nvidia-cuda-cupti-cu12==12.4.127 +nvidia-cuda-nvrtc-cu12==12.4.127 +nvidia-cuda-runtime-cu12==12.4.127 +nvidia-cudnn-cu12==9.1.0.70 +nvidia-cufft-cu12==11.2.1.3 +nvidia-curand-cu12==10.3.5.147 +nvidia-cusolver-cu12==11.6.1.9 +nvidia-cusparse-cu12==12.3.1.170 +nvidia-nccl-cu12==2.21.5 +nvidia-nvjitlink-cu12==12.4.127 +nvidia-nvtx-cu12==12.4.127 +openai==1.57.2 +openai-clip==1.0.1 +opencv-python==4.10.0.84 +orjson==3.10.12 +packaging==24.2 +password-strength==0.0.3.post2 +pillow==11.0.0 +platformdirs==4.3.6 +playwright==1.49.1 +pluggy==1.5.0 +propcache==0.2.1 +protobuf==5.29.1 +psutil==6.1.0 +py==1.11.0 +py-ed25519-zebra-bindings==1.2.0 +py-sr25519-bindings==0.2.1 +pycparser==2.22 +pycryptodome==3.21.0 +pydantic==2.10.3 +pydantic_core==2.27.1 +pyee==12.0.0 +Pygments==2.18.0 +PyNaCl==1.5.0 +pyparsing==3.2.0 +pytest==8.3.4 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.1 +python-Levenshtein==0.26.1 +python-statemachine==2.5.0 +PyYAML==6.0.2 +RapidFuzz==3.10.1 +regex==2024.11.6 +requests==2.32.3 +requests-toolbelt==1.0.0 +resolvelib==1.0.1 +retry==0.9.2 +rich==13.9.4 +scalecodec==1.2.11 +scikit-learn==1.6.0 +scipy==1.14.1 +sentry-sdk==2.19.2 +setproctitle==1.3.4 +setuptools~=70.0.0 +shtab==1.6.5 +six==1.17.0 +smmap==5.0.1 +sniffio==1.3.1 +soupsieve==2.6 +SQLAlchemy==2.0.36 +starlette==0.37.2 +substrate-interface==1.7.11 +sympy==1.13.1 +tenacity==9.0.0 +termcolor==2.5.0 +threadpoolctl==3.5.0 +tiktoken==0.8.0 +tinycss2==1.4.0 +toolz==1.0.0 +torch==2.5.1 +torchvision==0.20.1 +tqdm==4.67.1 +traitlets==5.14.3 +triton==3.1.0 +typing_extensions==4.12.2 +urllib3==2.2.3 +uvicorn==0.32.1 +wandb==0.19.0 +wcwidth==0.2.13 +webencodings==0.5.1 +websocket-client==1.8.0 +wheel==0.45.1 +xxhash==3.5.0 +yarl==1.18.3 diff --git a/run_miner.sh b/run_miner.sh index 0d2d9538..e53ded1a 100644 --- a/run_miner.sh +++ b/run_miner.sh @@ -1,4 +1,4 @@ export PYTHONPATH=. #--axon.port 5555 -python3.12 neurons/miners/miner.py --netuid 214 --subtensor.network test --wallet.name s-miner --wallet.hotkey miner1 --logging.debug --axon.port 8090 +python neurons/miners/miner.py --netuid 214 --subtensor.network test --wallet.name s-miner --wallet.hotkey miner1 --logging.debug --axon.port 8090 diff --git a/run_validator.sh b/run_validator.sh index 508d7d76..277a7ac0 100644 --- a/run_validator.sh +++ b/run_validator.sh @@ -1,3 +1,3 @@ export PYTHONASYNCIODEBUG=1 export PYTHONPATH=. -python3.12 neurons/validators/validator.py --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug --neuron.axon_port 8091 +python neurons/validators/validator.py --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug --neuron.axon_port 8091 diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index e7202051..5d953236 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -34,7 +34,6 @@ ) # TODO: Replace when bittensor switches to numpy from webgenie.mock import MockDendrite from webgenie.utils.config import add_validator_args -from webgenie.protocol import WebgenieStreamingSynapse class BaseValidatorNeuron(BaseNeuron): diff --git a/webgenie/constants.py b/webgenie/constants.py new file mode 100644 index 00000000..49ff9e30 --- /dev/null +++ b/webgenie/constants.py @@ -0,0 +1,4 @@ +MAX_SYNTHETIC_HISTORY_SIZE = 10 +PLACE_HOLDER_IMAGE_URL = "https://picsum.photos/seed/picsum/800/600" +SCREENSHOT_SCRIPT_PATH = "webgenie/rewards/metrics/screenshot_single.py" +WORK_DIR = "work" diff --git a/webgenie/datasets/dataset.py b/webgenie/datasets/dataset.py new file mode 100644 index 00000000..ce3f4373 --- /dev/null +++ b/webgenie/datasets/dataset.py @@ -0,0 +1,152 @@ +from pydantic import Field, BaseModel + +class DatasetEntry(BaseModel): + src: str = Field(default="", description="The source of the dataset entry") + topic: str = Field(default="", description="The topic of the dataset entry") + ground_truth_html: str = Field(default="", description="The ground truth html") + prompt: str = Field(default="", description="The prompt for the text task") + base64_image: str = Field(default="", description="The base64 encoded image") + +class Dataset: + async def generate_context(self)->DatasetEntry: + pass + +class MockUpDataset(Dataset): + async def generate_context(self)->DatasetEntry: + html = """ + + + + + +Tech Company + + + + +
+
+ + +
+
+
+Hero Image +
+
+

Welcome to Tech Company

+

At Tech Company, we are dedicated to providing the best technology solutions for your needs. Our team of experts is always ready to help you with any questions or problems you may have.

+
+ + + + """ + return DatasetEntry( + src="mockup", + topic="tech company", + ground_truth_html=html, + prompt="", + base64_image="" + ) + +class MockUpPromptDataset(Dataset): + async def generate_context(self)->DatasetEntry: + return DatasetEntry( + src="mockup", + topic="tech company", + ground_truth_html="", + prompt="CommingSoon Page with goback button, navHeader, and footer", + base64_image="" + ) \ No newline at end of file diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py new file mode 100644 index 00000000..93fc6ff9 --- /dev/null +++ b/webgenie/helpers/htmls.py @@ -0,0 +1,14 @@ +import os +import time +import uuid +from webgenie.constants import SCREENSHOT_SCRIPT_PATH, WORK_DIR +from webgenie.helpers.images import image_to_base64 + +def html_to_screenshot(html: str) -> str: + html_path = f"{WORK_DIR}/screenshot_{uuid.uuid4()}.html" + with open(html_path, "w") as f: + f.write(html) + png_path = f"{WORK_DIR}/screenshot_{uuid.uuid4()}.png" + os.system(f"python3 {SCREENSHOT_SCRIPT_PATH} --html {html_path} --png {png_path}") + time.sleep(0.1) + return image_to_base64(png_path) \ No newline at end of file diff --git a/webgenie/protocol.py b/webgenie/protocol.py index d6d7824e..56ba95c7 100644 --- a/webgenie/protocol.py +++ b/webgenie/protocol.py @@ -3,12 +3,6 @@ import bittensor as bt import pydantic -import json -from typing import AsyncIterator, Union, Any -from starlette.responses import StreamingResponse - -from webgenie.solution import Solution -from webgenie.tasks import Task class WebgenieTextSynapse(bt.Synapse): """ @@ -20,10 +14,10 @@ class WebgenieTextSynapse(bt.Synapse): description="The prompt to be sent to miners." ) - solution: Union[Solution, None] = pydantic.Field( - None, - title="Solution", - description="A solution received from miners." + html: str = pydantic.Field( + "", + title="HTML", + description="The HTML received from miners." ) class WebgenieImageSynapse(bt.Synapse): @@ -36,96 +30,8 @@ class WebgenieImageSynapse(bt.Synapse): description="The base64 image to be sent to miners." ) - solution: Union[Solution, None] = pydantic.Field( - None, - title="Solution", - description="A solution received from miners." - ) - - -class WebgenieStreamingSynapse(bt.StreamingSynapse): - """ - A protocol for the webgenie streaming task. - """ - - task: Union[Task, None] = pydantic.Field( - None, - title="Task", - description="A task to be sent to miners." - ) - - solution: Union[Solution, None] = pydantic.Field( - None, - title="Solution", - description="A solution received from miners." - ) - - completion: str = pydantic.Field( + html: str = pydantic.Field( "", - title="Completion", - description="The completion response from miners." + title="HTML", + description="The HTML received from miners." ) - - async def process_streaming_response(self, response: StreamingResponse) -> AsyncIterator[str]: - """ - Processes a streaming response from a miner. - """ - if self.completion is None: - self.completion = "" - async for chunk in response.content.iter_any(): - tokens = chunk.decode("utf-8") - - for token in tokens: - if token: - self.completion += token - yield tokens - - def deserialize(self) -> Union[Any, None]: - """ - Deserializes the response. - """ - try: - json_response = json.loads(self.completion) - css = None - html = None - for key, value in json_response.items(): - if key.lower() == "css": - css = value - elif key.lower() == "html": - html = value - - if css is None and html is None: - bt.logging.error(f"Invalid response: {json_response}") - return None - - css = str(css) - html = str(html) - - process_time = self.dendrite.process_time - bt.logging.debug(f"css: {css}, html: {html}, process_time: {process_time}") - self.solution = Solution(css=css, html=html, process_time=process_time, miner_uid=0) - return self - except Exception as e: - bt.logging.error(f"Failed to parse completion: {e}") - return None - - - def extract_response_json(self, response: StreamingResponse) -> dict: - """ - Extracts the response JSON. - """ - headers = { - k.decode("utf-8"): v.decode("utf-8") - for k, v in response.__dict__["_raw_headers"] - } - - def extract_info(prefix:str) -> dict: - return { - key.split("_")[-1]: value - for key, value in headers.items() - if key.startswith(prefix) - } - return { - "completion": self.completion - } - \ No newline at end of file diff --git a/webgenie/rewards/__init__.py b/webgenie/rewards/__init__.py index e769de83..a6cba18e 100644 --- a/webgenie/rewards/__init__.py +++ b/webgenie/rewards/__init__.py @@ -1,5 +1,2 @@ from .reward import Reward -from .gpt import GPTReward -from .speed import SpeedReward -from .is_valid import IsValidReward -from .reward_manager import RewardManager +from .visual_reward import VisualReward diff --git a/webgenie/rewards/gpt.py b/webgenie/rewards/gpt.py deleted file mode 100644 index 056fc1c8..00000000 --- a/webgenie/rewards/gpt.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -from dotenv import load_dotenv - -from langchain_openai import ChatOpenAI -import bittensor as bt -from webgenie.rewards import Reward -from webgenie.tasks import Task -from webgenie.solution import Solution - -class GPTReward(Reward): - def __init__(self): - load_dotenv() - api_key = os.getenv("OPENAI_API_KEY") - self.model = ChatOpenAI( - api_key=api_key, - model="gpt-4", - ) - def reward(self, task: Task, solution: Solution) -> float: - # Use GPT to evaluate the code - query = f""" - Task: {task.query} - Generated Code: - "```CSS" - {solution.css} - "```HTML" - {solution.html} - "```" - Evaluate the generated code based on the following criteria: - 1. Correctness: Does the code implement the required functionality? - 2. Code quality: Is the code well-structured and following best practices? - 3. Completeness: Does the code address all aspects of the task? - - Provide a score between 0 and 1, where 1 is perfect and 0 is completely incorrect. - Only return the score, without any explanations. - """ - - try: - messages=[ - {"role": "system", "content": "You are a code evaluation expert."}, - {"role": "user", "content": query} - ] - evaluation = self.model.invoke(messages) - bt.logging.info(f"Evaluation: {evaluation.content}") - return float(evaluation.content) - except Exception as e: - bt.logging.error(f"Error evaluating code: {e}") - return 0.0 \ No newline at end of file diff --git a/webgenie/rewards/is_valid.py b/webgenie/rewards/is_valid.py deleted file mode 100644 index 4a065508..00000000 --- a/webgenie/rewards/is_valid.py +++ /dev/null @@ -1,47 +0,0 @@ -from bs4 import BeautifulSoup -import tinycss2 - -from webgenie.rewards import Reward -from webgenie.tasks import Task -from webgenie.solution import Solution - -class IsValidReward(Reward): - - def __init__(self): - pass - - def is_valid_css(self, css: str) -> bool: - - try: - # Parse the CSS using tinycss2 - parsed_css = tinycss2.parse_stylesheet(css) - # Check if the CSS was parsed successfully - if parsed_css is not None: - return True - else: - return False - except Exception: - # If parsing fails, the CSS is invalid - return False - return True - - def is_valid_html(self, html: str) -> bool: - - try: - # Parse the HTML using BeautifulSoup - soup = BeautifulSoup(html, 'html.parser') - - # Check if the HTML has a valid structure - if soup.find(): - return True - else: - return False - except Exception: - # If parsing fails, the HTML is invalid - return False - - def reward(self, task: Task, solution: Solution) -> float: - if self.is_valid_css(solution.css) and self.is_valid_html(solution.html): - return 0.0 - else: - return 1.0 diff --git a/webgenie/rewards/metrics/__init__.py b/webgenie/rewards/metrics/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/webgenie/rewards/metrics/dedup_post_gen.py b/webgenie/rewards/metrics/dedup_post_gen.py new file mode 100644 index 00000000..2af9bd43 --- /dev/null +++ b/webgenie/rewards/metrics/dedup_post_gen.py @@ -0,0 +1,69 @@ +import difflib +import os +import re + +def map_positions(clean_text, original_text): + """ + Maps the positions from the clean text back to the original text. + """ + map_clean_to_original = [] + original_idx = 0 + + for clean_char in clean_text: + while original_text[original_idx] != clean_char: + original_idx += 1 + map_clean_to_original.append(original_idx) + original_idx += 1 + + return map_clean_to_original + +def check_repetitive_content(file_path, chunk_size=100, repetition_threshold=5, similarity_threshold=0.8, debug=False): + """ + Checks for repetitive content in a text file, considering both exact and similar chunks, + ignoring HTML tags but keeping the original position reference. + + :param file_path: Path to the text file. + :param chunk_size: The size of each chunk for comparison. + :param repetition_threshold: Minimum number of repetitions to consider it as repetitive content. + :param similarity_threshold: The threshold for considering two chunks as similar (0 to 1). + :return: A tuple indicating if repetitive content was found and the position where it starts in the original file. + """ + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + + # Clean HTML content and keep a map of positions + content_no_html = re.sub('<.*?>', '', content) + position_map = map_positions(content_no_html, content) + + # Split content into chunks + chunks = [content_no_html[i:i + chunk_size] for i in range(0, len(content_no_html), chunk_size)] + + # Check for repetitive and similar chunks + seen = {} + repetitive_start = len(content_no_html) + for i, chunk in enumerate(chunks): + for seen_chunk, indexes in seen.items(): + similarity = difflib.SequenceMatcher(None, chunk, seen_chunk).ratio() + if similarity >= similarity_threshold: + indexes.append(i) + if len(indexes) >= repetition_threshold: + clean_start = min(repetitive_start, indexes[0] * chunk_size) + c_repetitive_start = position_map[clean_start] if clean_start < len(position_map) else len(content) + if c_repetitive_start < repetitive_start: + repetitive_start = c_repetitive_start + break + else: + seen[chunk] = [i] + + repetitive, start_position = repetitive_start != len(content_no_html), repetitive_start + + if repetitive: + print(f"[Warning] Repetitive content found in {file_path}, start at {start_position}") + print(f"[Warning] You might want to manually check whether the automatic repetition removal is correct.") + if not debug: + os.rename(file_path, file_path.replace(".html", "_old.txt")) + with open(file_path, 'w', encoding='utf-8') as file: + file.write(content[:start_position]) + else: + with open(file_path.replace(".html", "_new.html"), 'w', encoding='utf-8') as file: + file.write(content[:start_position]) diff --git a/webgenie/rewards/metrics/multi_processing_eval.py b/webgenie/rewards/metrics/multi_processing_eval.py new file mode 100644 index 00000000..a1f6b377 --- /dev/null +++ b/webgenie/rewards/metrics/multi_processing_eval.py @@ -0,0 +1,123 @@ +from metrics.visual_score import visual_eval_v3_multi +from multiprocessing import Pool +import contextlib, joblib +from joblib import Parallel, delayed +from tqdm import tqdm +import numpy as np +import json +import os +import shutil + +@contextlib.contextmanager +def tqdm_joblib(tqdm_object): + """Context manager to patch joblib to report into tqdm progress bar given as argument""" + class TqdmBatchCompletionCallback(joblib.parallel.BatchCompletionCallBack): + def __call__(self, *args, **kwargs): + tqdm_object.update(n=self.batch_size) + return super().__call__(*args, **kwargs) + + old_batch_callback = joblib.parallel.BatchCompletionCallBack + joblib.parallel.BatchCompletionCallBack = TqdmBatchCompletionCallback + try: + yield tqdm_object + finally: + joblib.parallel.BatchCompletionCallBack = old_batch_callback + tqdm_object.close() + + +def print_multi_score(multi_score): + _, final_size_score, final_matched_text_score, final_position_score, final_text_color_score, final_clip_score = multi_score + print() + print("Block-Match: ", final_size_score) + print("Text: ", final_matched_text_score) + print("Position: ", final_position_score) + print("Color: ", final_text_color_score) + print("CLIP: ", final_clip_score) + print("--------------------------------\n") + +if __name__ == "__main__": + debug = False + multiprocessing = True + + orig_reference_dir = "../testset_final" + eval_name = "testset_final" + + ## copy the original reference directory to a new directory + ## because we will be creating new screenshots + reference_dir = "../testset_final_" + eval_name + os.makedirs(reference_dir, exist_ok=True) + for filename in os.listdir(orig_reference_dir): + if filename.endswith(".html") or filename == "rick.jpg": + shutil.copy(os.path.join(orig_reference_dir, filename), os.path.join(reference_dir, filename)) + print ("copied original reference directory to ", reference_dir) + + test_dirs = { + "gpt4v_direct_prompting": "../predictions_final/gpt4v_direct_prompting", + "gemini_direct_prompting": "../predictions_final/gemini_direct_prompting" + } + + file_name_list = [] + + ## check if the file is in all prediction directories + for filename in os.listdir(reference_dir): + if filename.endswith(".html"): + if all([os.path.exists(os.path.join(test_dirs[key], filename)) for key in test_dirs]): + file_name_list.append(filename) + + print ("total #egs: ", len(file_name_list)) + + input_lists = [] + for filename in file_name_list: + + input_pred_list = [os.path.join(test_dirs[key], filename) for key in test_dirs] + original = os.path.join(reference_dir, filename) + + input_list = [input_pred_list, original] + input_lists.append(input_list) + + # print ("input_list: ", input_lists) + if multiprocessing: + with tqdm_joblib(tqdm(total=len(input_lists))) as progress_bar: + return_score_lists = list(tqdm(Parallel(n_jobs=8)(delayed(visual_eval_v3_multi)(input_list, debug=debug) for input_list in input_lists), total=len(input_lists))) + else: + return_score_lists = [] + for input_list in tqdm(input_lists): + return_score_list = visual_eval_v3_multi(input_list, debug=debug) + return_score_lists.append(return_score_list) + # print ("return lists: ", return_score_lists) + + res_dict = {} + for key in test_dirs: + res_dict[key] = {} + + for i, filename in enumerate(file_name_list): + idx = 0 + return_score_list = return_score_lists[i] + # print ("return score list: ", return_score_list) + if return_score_list: + for key in test_dirs: + if multiprocessing: + matched, final_score, multi_score = return_score_list[idx] + else: + matched = return_score_list[idx][0] + final_score = return_score_list[idx][1] + multi_score = return_score_list[idx][2] + idx += 1 + current_score = [final_score] + [item for item in multi_score] + res_dict[key][filename] = current_score + else: + print (filename + " didn't get a score") + for key in test_dirs: + res_dict[key][filename] = [0, 0, 0, 0, 0, 0] + + ## cache all scores + with open("metrics/res_dict_{}.json".format(eval_name), "w") as f: + json.dump(res_dict, f, indent=4) + + for key in test_dirs: + print(key) + values = list(res_dict[key].values()) + # print (values) + current_res = np.mean(np.array(values), axis=0) + # print(current_res) + print_multi_score(current_res) \ No newline at end of file diff --git a/webgenie/rewards/metrics/ocr_free_utils.py b/webgenie/rewards/metrics/ocr_free_utils.py new file mode 100644 index 00000000..ccd706a8 --- /dev/null +++ b/webgenie/rewards/metrics/ocr_free_utils.py @@ -0,0 +1,259 @@ +import cv2 +import numpy as np +from PIL import Image, ImageColor +import os +from bs4 import BeautifulSoup, NavigableString, Tag, Comment +from pathlib import Path + + +def rgb_to_hex(rgb): + """Convert an RGB tuple to hexadecimal format.""" + return '{:02X}{:02X}{:02X}'.format(*rgb) + + +class ColorPool: + def __init__(self, offset=0): + + color_values = list(range(10, 251, 16)) + color_list = [((r + offset) % 256, (g + offset) % 256, (b + offset) % 256) for r in color_values for g in color_values for b in color_values] + self.color_pool = [rgb_to_hex(color) for color in color_list] + + def pop_color(self): + if self.color_pool: + return self.color_pool.pop() + else: + raise NotImplementedError + + +def process_html(input_file_path, output_file_path, offset=0): + # Read the input HTML file + with open(input_file_path, 'r') as file: + soup = BeautifulSoup(file, 'html.parser') + + def update_style(element, property_name, value): + # Update the element's style attribute with the given property and value + # Adding !important to ensure the style overrides others + important_value = f"{value} !important" + styles = element.attrs.get('style', '').split(';') + updated_styles = [s for s in styles if not s.strip().startswith(property_name) and len(s.strip()) > 0] + updated_styles.append(f"{property_name}: {important_value}") + element['style'] = '; '.join(updated_styles).strip() + + # Set the background color of all elements to white + for element in soup.find_all(True): + update_style(element, 'background-color', 'rgba(255, 255, 255, 0.0)') + + color_pool = ColorPool(offset) + + # Assign a unique color to text within each text-containing element + text_tags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'span', 'a', 'b', 'li', 'table', 'td', 'th', 'button', 'footer', 'header', 'figcaption'] # Add more tags as needed + for tag in soup.find_all(text_tags): + color = f"#{color_pool.pop_color()}" + update_style(tag, 'color', color) + update_style(tag, 'opacity', 1.0) + + # Write the modified HTML to a new file + with open(output_file_path, 'w') as file: + file.write(str(soup)) + + +def similar(n1, n2): + if abs(n1 - n2) <= 8: + return True + else: + return False + + +def find_different_pixels(image1_path, image2_path): + # Open the images + img1 = Image.open(image1_path) + img2 = Image.open(image2_path) + + # Ensure both images are of the same size + if img1.size != img2.size: + print(f"[Warning] Images are not the same size, {image1_path}, {image2_path}") + return None + + # Convert images to RGB if they are not + img1 = img1.convert('RGB') + img2 = img2.convert('RGB') + + # Get pixel data + pixels1 = img1.load() + pixels2 = img2.load() + + # List to store coordinates of different pixels + different_pixels = [] + + # Iterate through each pixel + for x in range(img1.size[0]): + for y in range(img1.size[1]): + # Compare pixel colors + r1, g1, b1 = pixels1[x, y] + r2, g2, b2 = pixels2[x, y] + if similar((r1 + 50) % 256, r2) and similar((g1 + 50) % 256, g2) and similar((b1 + 50) % 256, b2): + different_pixels.append((y, x)) + + if len(different_pixels) > 0: + return np.stack(different_pixels) + else: + return None + + +def extract_text_with_color(html_file): + def get_color(tag): + if 'style' in tag.attrs: + styles = tag['style'].split(';') + color_style = [s for s in styles if 'color' in s and 'background-color' not in s] + if color_style: + color = color_style[-1].split(':')[1].strip().replace(" !important", "") + if color[0] == "#": + return color + else: + try: + if color.startswith('rgb'): + color = tuple(map(int, color[4:-1].split(','))) # Extract the RGB values + else: + color = ImageColor.getrgb(color) # Convert named color to RGB + return '#{:02x}{:02x}{:02x}'.format(*color) # Convert RGB to hexadecimal + except ValueError: + print(f"Warning: unable to identify or convert color in {html_file}...", color) + return None + return None + + def extract_text_recursive(element, parent_color='#000000'): + if isinstance(element, Comment): + return None + elif isinstance(element, NavigableString): + text = element.strip() + return (text, parent_color) if text else None + + elif isinstance(element, Tag): + current_color = get_color(element) or parent_color + children_texts = filter(None, [extract_text_recursive(child, current_color) for child in element.children]) + return list(children_texts) + + with open(html_file, 'r', encoding='utf-8') as file: + soup = BeautifulSoup(file, 'html.parser') + body = soup.body + return extract_text_recursive(body) if body else [] + + +def flatten_tree(tree): + flat_list = [] + + # Helper function to recursively flatten the tree + def flatten(node): + if isinstance(node, list): + for item in node: + flatten(item) + else: + flat_list.append(node) + + # Flatten the tree + flatten(tree) + return flat_list + + +def average_color(image_path, coordinates): + """ + Calculates the average color of the specified coordinates in the given image. + + :param image: A PIL Image object. + :param coordinates: A 2D numpy array of coordinates, where each row represents [x, y]. + :return: A tuple representing the average color (R, G, B). + """ + # Convert image to numpy array + image_array = np.array(Image.open(image_path).convert('RGB')) + + # Extract colors at the specified coordinates + colors = [image_array[x, y] for x, y in coordinates] + + # Calculate the average color + avg_color = np.mean(colors, axis=0) + + return tuple(avg_color.astype(int)) + + +def get_blocks_from_image_diff_pixels(image_path, html_text_color_tree, different_pixels): + image = cv2.imread(image_path) + x_w = image.shape[0] + y_w = image.shape[1] + + def hex_to_bgr(hex_color): + """ + Converts a hex color string to a BGR color tuple. + """ + hex_color = hex_color.lstrip('#') + rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) + return rgb[::-1] + + + def get_intersect(arr1, arr2): + # Reshape the arrays to 1D + arr1_reshaped = arr1.view([('', arr1.dtype)] * arr1.shape[1]) + arr2_reshaped = arr2.view([('', arr2.dtype)] * arr2.shape[1]) + + # Find the intersection + common_rows = np.intersect1d(arr1_reshaped, arr2_reshaped) + + # Reshape the result back to 2D, if needed + common_rows = common_rows.view(arr1.dtype).reshape(-1, arr1.shape[1]) + return common_rows + + + blocks = [] + for item in html_text_color_tree: + try: + color = np.array(hex_to_bgr(item[1]), dtype="uint8") + except: + continue + + lower = color - 4 + upper = color + 4 + + mask = cv2.inRange(image, lower, upper) + + coords = np.column_stack(np.where(mask > 0)) + + coords = get_intersect(coords, different_pixels) + + if coords.size == 0: + continue + + x_min, y_min = np.min(coords, axis=0) + x_max, y_max = np.max(coords, axis=0) + color = average_color(image_path.replace("_p.png", ".png"), coords) + + blocks.append({'text': item[0].lower(), 'bbox': (y_min / y_w, x_min / x_w, (y_max - y_min + 1) / y_w, (x_max - x_min + 1) / x_w), 'color': color}) + return blocks + + +def get_itermediate_names(name): + return name.replace(".png", ".html"), name.replace(".png", "_p.html"), name.replace(".png", "_p_1.html"), name.replace(".png", "_p.png"), name.replace(".png", "_p_1.png") + +def get_blocks_ocr_free(image_path): + html, p_html, p_html_1, p_png, p_png_1 = get_itermediate_names(image_path) + process_html(html, p_html) + process_html(html, p_html_1, offset=50) + + os.system(f"python3 {Path(__file__).parent}/screenshot_single.py --html {p_html} --png {p_png}") + os.system(f"python3 {Path(__file__).parent}/screenshot_single.py --html {p_html_1} --png {p_png_1}") + + different_pixels = find_different_pixels(p_png, p_png_1) + + if different_pixels is None: + print(f"[Warning] Unable to get pixels with different colors from {p_png}, {p_png_1}...") + os.system(f"rm {p_html} {p_png} {p_html_1} {p_png_1}") + return [] + + html_text_color_tree = flatten_tree(extract_text_with_color(p_html)) + try: + blocks = get_blocks_from_image_diff_pixels(p_png, html_text_color_tree, different_pixels) + except: + print(f"[Warning] Unable to get blocks from {p_png}...") + os.system(f"rm {p_html} {p_png} {p_html_1} {p_png_1}") + return [] + + os.system(f"rm {p_html} {p_png} {p_html_1} {p_png_1}") + return blocks \ No newline at end of file diff --git a/webgenie/rewards/metrics/screenshot_single.py b/webgenie/rewards/metrics/screenshot_single.py new file mode 100644 index 00000000..86b0b6b6 --- /dev/null +++ b/webgenie/rewards/metrics/screenshot_single.py @@ -0,0 +1,48 @@ +import os +from playwright.sync_api import sync_playwright +import argparse +from PIL import Image + + +def take_screenshot(url, output_file="screenshot.png", do_it_again=False): + # Convert local path to file:// URL if it's a file + if os.path.exists(url): + url = "file://" + os.path.abspath(url) + + if os.path.exists(output_file) and not do_it_again: + print(f"{output_file} exists!") + return + + try: + with sync_playwright() as p: + # Choose a browser, e.g., Chromium, Firefox, or WebKit + browser = p.chromium.launch() + page = browser.new_page() + + # Navigate to the URL + page.goto(url, timeout=60000) + + # Take the screenshot + page.screenshot(path=output_file, full_page=True, animations="disabled", timeout=60000) + + browser.close() + except Exception as e: + print(f"Failed to take screenshot due to: {e}. Generating a blank image.") + # Generate a blank image + img = Image.new('RGB', (1280, 960), color = 'white') + img.save(output_file) + + +if __name__ == "__main__": + + # Initialize the parser + parser = argparse.ArgumentParser(description='Process two path strings.') + + # Define the arguments + parser.add_argument('--html', type=str) + parser.add_argument('--png', type=str) + + # Parse the arguments + args = parser.parse_args() + + take_screenshot(args.html, args.png, do_it_again=True) diff --git a/webgenie/rewards/metrics/visual_score.py b/webgenie/rewards/metrics/visual_score.py new file mode 100644 index 00000000..7bc330fd --- /dev/null +++ b/webgenie/rewards/metrics/visual_score.py @@ -0,0 +1,581 @@ +import cv2 +import numpy as np + +import sys +import os +# Add the current file's directory to Python path +current_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(current_dir) + +# This is a patch for color map, which is not updated for newer version of numpy +def patch_asscalar(a): + return a.item() +setattr(np, "asscalar", patch_asscalar) + +import matplotlib.pyplot as plt +from scipy.optimize import linear_sum_assignment +import random +from sklearn.metrics.pairwise import cosine_similarity +from difflib import SequenceMatcher +from tqdm import tqdm +from pathlib import Path +from PIL import Image, ImageDraw +import torch +import clip +from copy import deepcopy +from collections import Counter +from bs4 import BeautifulSoup, NavigableString, Comment +import re +import math +from colormath.color_objects import sRGBColor, LabColor +from colormath.color_conversions import convert_color +from colormath.color_diff import delta_e_cie2000 + +from ocr_free_utils import get_blocks_ocr_free +from dedup_post_gen import check_repetitive_content +from webgenie.constants import SCREENSHOT_SCRIPT_PATH, WORK_DIR + +device = "cuda" if torch.cuda.is_available() else "cpu" +model, preprocess = clip.load("ViT-B/32", device=device) + +def calculate_similarity(block1, block2, max_distance=1.42): + text_similarity = SequenceMatcher(None, block1['text'], block2['text']).ratio() + return text_similarity + + +def adjust_cost_for_context(cost_matrix, consecutive_bonus=1.0, window_size=20): + if window_size <= 0: + return cost_matrix + + n, m = cost_matrix.shape + adjusted_cost_matrix = np.copy(cost_matrix) + + for i in range(n): + for j in range(m): + bonus = 0 + if adjusted_cost_matrix[i][j] >= -0.5: + continue + nearby_matrix = cost_matrix[max(0, i - window_size):min(n, i + window_size + 1), max(0, j - window_size):min(m, j + window_size + 1)] + flattened_array = nearby_matrix.flatten() + sorted_array = np.sort(flattened_array)[::-1] + sorted_array = np.delete(sorted_array, np.where(sorted_array == cost_matrix[i, j])[0][0]) + top_k_elements = sorted_array[- window_size * 2:] + sum_top_k = np.sum(top_k_elements) + bonus = consecutive_bonus * sum_top_k + adjusted_cost_matrix[i][j] += bonus + return adjusted_cost_matrix + +def create_cost_matrix(A, B): + n = len(A) + m = len(B) + cost_matrix = np.zeros((n, m)) + for i in range(n): + for j in range(m): + cost_matrix[i, j] = -calculate_similarity(A[i], B[j]) + return cost_matrix + + +def draw_matched_bboxes(img1, img2, matched_bboxes): + # Create copies of images to draw on + img1_drawn = img1.copy() + img2_drawn = img2.copy() + + h1, w1, _ = img1.shape + h2, w2, _ = img2.shape + + + # Iterate over matched bounding boxes + for bbox_pair in matched_bboxes: + # Random color for each pair + color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + + # Ensure that the bounding box coordinates are integers + bbox1 = [int(bbox_pair[0][0] * w1), int(bbox_pair[0][1] * h1), int(bbox_pair[0][2] * w1), int(bbox_pair[0][3] * h1)] + bbox2 = [int(bbox_pair[1][0] * w2), int(bbox_pair[1][1] * h2), int(bbox_pair[1][2] * w2), int(bbox_pair[1][3] * h2)] + + # Draw bbox on the first image + top_left_1 = (bbox1[0], bbox1[1]) + bottom_right_1 = (bbox1[0] + bbox1[2], bbox1[1] + bbox1[3]) + img1_drawn = cv2.rectangle(img1_drawn, top_left_1, bottom_right_1, color, 2) + + # Draw bbox on the second image + top_left_2 = (bbox2[0], bbox2[1]) + bottom_right_2 = (bbox2[0] + bbox2[2], bbox2[1] + bbox2[3]) + img2_drawn = cv2.rectangle(img2_drawn, top_left_2, bottom_right_2, color, 2) + + return img1_drawn, img2_drawn + + +def calculate_distance_max_1d(x1, y1, x2, y2): + distance = max(abs(x2 - x1), abs(y2 - y1)) + return distance + + +def calculate_ratio(h1, h2): + return max(h1, h2) / min(h1, h2) + + +def rgb_to_lab(rgb): + """ + Convert an RGB color to Lab color space. + RGB values should be in the range [0, 255]. + """ + # Create an sRGBColor object from RGB values + rgb_color = sRGBColor(rgb[0], rgb[1], rgb[2], is_upscaled=True) + + # Convert to Lab color space + lab_color = convert_color(rgb_color, LabColor) + + return lab_color + +def color_similarity_ciede2000(rgb1, rgb2): + """ + Calculate the color similarity between two RGB colors using the CIEDE2000 formula. + Returns a similarity score between 0 and 1, where 1 means identical. + """ + # Convert RGB colors to Lab + lab1 = rgb_to_lab(rgb1) + lab2 = rgb_to_lab(rgb2) + + # Calculate the Delta E (CIEDE2000) + delta_e = delta_e_cie2000(lab1, lab2) + + # Normalize the Delta E value to get a similarity score + # Note: The normalization method here is arbitrary and can be adjusted based on your needs. + # A delta_e of 0 means identical colors. Higher values indicate more difference. + # For visualization purposes, we consider a delta_e of 100 to be completely different. + similarity = max(0, 1 - (delta_e / 100)) + + return similarity + + +def calculate_current_cost(cost_matrix, row_ind, col_ind): + return cost_matrix[row_ind, col_ind].sum() + + +def merge_blocks_wo_check(block1, block2): + # Concatenate text + merged_text = block1['text'] + " " + block2['text'] + + # Calculate bounding box + x_min = min(block1['bbox'][0], block2['bbox'][0]) + y_min = min(block1['bbox'][1], block2['bbox'][1]) + x_max = max(block1['bbox'][0] + block1['bbox'][2], block2['bbox'][0] + block2['bbox'][2]) + y_max = max(block1['bbox'][1] + block1['bbox'][3], block2['bbox'][1] + block2['bbox'][3]) + merged_bbox = (x_min, y_min, x_max - x_min, y_max - y_min) + + # Average color + merged_color = tuple( + (color1 + color2) // 2 for color1, color2 in zip(block1['color'], block2['color']) + ) + + return {'text': merged_text, 'bbox': merged_bbox, 'color': merged_color} + + +def calculate_current_cost(cost_matrix, row_ind, col_ind): + return cost_matrix[row_ind, col_ind].tolist() + + +def find_maximum_matching(A, B, consecutive_bonus, window_size): + cost_matrix = create_cost_matrix(A, B) + cost_matrix = adjust_cost_for_context(cost_matrix, consecutive_bonus, window_size) + row_ind, col_ind = linear_sum_assignment(cost_matrix) + current_cost = calculate_current_cost(cost_matrix, row_ind, col_ind) + return list(zip(row_ind, col_ind)), current_cost, cost_matrix + + +def remove_indices(lst, indices): + for index in sorted(indices, reverse=True): + if index < len(lst): + lst.pop(index) + return lst + + +def merge_blocks_by_list(blocks, merge_list): + pop_list = [] + while True: + if len(merge_list) == 0: + remove_indices(blocks, pop_list) + return blocks + + i = merge_list[0][0] + j = merge_list[0][1] + + blocks[i] = merge_blocks_wo_check(blocks[i], blocks[j]) + pop_list.append(j) + + merge_list.pop(0) + if len(merge_list) > 0: + new_merge_list = [] + for k in range(len(merge_list)): + if merge_list[k][0] != i and merge_list[k][1] != i and merge_list[k][0] != j and merge_list[k][1] != j: + new_merge_list.append(merge_list[k]) + merge_list = new_merge_list + + +def print_matching(matching, blocks1, blocks2, cost_matrix): + for i, j in matching: + print(f"{blocks1[i]} matched with {blocks2[j]}, cost {cost_matrix[i][j]}") + + +def difference_of_means(list1, list2): + counter1 = Counter(list1) + counter2 = Counter(list2) + + for element in set(list1) & set(list2): + common_count = min(counter1[element], counter2[element]) + counter1[element] -= common_count + counter2[element] -= common_count + + unique_list1 = [item for item in counter1.elements()] + unique_list2 = [item for item in counter2.elements()] + + mean_list1 = sum(unique_list1) / len(unique_list1) if unique_list1 else 0 + mean_list2 = sum(unique_list2) / len(unique_list2) if unique_list2 else 0 + + if mean_list1 - mean_list2 > 0: + if min(unique_list1) > min(unique_list2): + return mean_list1 - mean_list2 + else: + return 0.0 + else: + return mean_list1 - mean_list2 + + +def find_possible_merge(A, B, consecutive_bonus, window_size, debug=False): + merge_bonus = 0.0 + merge_windows = 1 + + def sortFn(value): + return value[2] + + while True: + A_changed = False + B_changed = False + + matching, current_cost, cost_matrix = find_maximum_matching(A, B, merge_bonus, merge_windows) + if debug: + print("Current cost of the solution:", current_cost) + print_matching(matching, A, B, cost_matrix) + + if len(A) >= 2: + merge_list = [] + for i in range(len(A) - 1): + new_A = deepcopy(A) + new_A[i] = merge_blocks_wo_check(new_A[i], new_A[i + 1]) + new_A.pop(i + 1) + + updated_matching, updated_cost, cost_matrix = find_maximum_matching(new_A, B, merge_bonus, merge_windows) + diff = difference_of_means(current_cost, updated_cost) + if diff > 0.05: + merge_list.append([i, i + 1, diff]) + if debug: + print(new_A[i]['text'], diff) + + merge_list.sort(key=sortFn, reverse=True) + if len(merge_list) > 0: + A_changed = True + A = merge_blocks_by_list(A, merge_list) + matching, current_cost, cost_matrix = find_maximum_matching(A, B, merge_bonus, merge_windows) + if debug: + print("Cost after optimization A:", current_cost) + + if len(B) >= 2: + merge_list = [] + for i in range(len(B) - 1): + new_B = deepcopy(B) + new_B[i] = merge_blocks_wo_check(new_B[i], new_B[i + 1]) + new_B.pop(i + 1) + + updated_matching, updated_cost, cost_matrix = find_maximum_matching(A, new_B, merge_bonus, merge_windows) + diff = difference_of_means(current_cost, updated_cost) + if diff > 0.05: + merge_list.append([i, i + 1, diff]) + if debug: + print(new_B[i]['text'], diff) + + merge_list.sort(key=sortFn, reverse=True) + if len(merge_list) > 0: + B_changed = True + B = merge_blocks_by_list(B, merge_list) + matching, current_cost, cost_matrix = find_maximum_matching(A, B, merge_bonus, merge_windows) + if debug: + print("Cost after optimization B:", current_cost) + + if not A_changed and not B_changed: + break + matching, _, _ = find_maximum_matching(A, B, consecutive_bonus, window_size) + return A, B, matching + + +def merge_blocks_by_bbox(blocks): + merged_blocks = {} + + # Traverse and merge blocks + for block in blocks: + bbox = tuple(block['bbox']) # Convert bbox to tuple for hashability + if bbox in merged_blocks: + # Merge with existing block + existing_block = merged_blocks[bbox] + existing_block['text'] += ' ' + block['text'] + existing_block['color'] = [(ec + c) / 2 for ec, c in zip(existing_block['color'], block['color'])] + else: + # Add new block + merged_blocks[bbox] = block + + return list(merged_blocks.values()) + + +def mask_bounding_boxes_with_inpainting(image, bounding_boxes): + # Convert PIL image to OpenCV format + image_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) + + # Create a black mask + mask = np.zeros(image_cv.shape[:2], dtype=np.uint8) + + height, width = image_cv.shape[:2] + + # Draw white rectangles on the mask + for bbox in bounding_boxes: + x_ratio, y_ratio, w_ratio, h_ratio = bbox + x = int(x_ratio * width) + y = int(y_ratio * height) + w = int(w_ratio * width) + h = int(h_ratio * height) + mask[y:y+h, x:x+w] = 255 + + # Use inpainting + inpainted_image = cv2.inpaint(image_cv, mask, 3, cv2.INPAINT_TELEA) + + # Convert back to PIL format + inpainted_image_pil = Image.fromarray(cv2.cvtColor(inpainted_image, cv2.COLOR_BGR2RGB)) + + return inpainted_image_pil + + +def rescale_and_mask(image_path, blocks): + # Load the image + with Image.open(image_path) as img: + if len(blocks) > 0: + # use inpainting instead of simple mask + img = mask_bounding_boxes_with_inpainting(img, blocks) + + width, height = img.size + + # Determine which side is shorter + if width < height: + # Width is shorter, scale height to match the width + new_size = (width, width) + else: + # Height is shorter, scale width to match the height + new_size = (height, height) + + # Resize the image while maintaining aspect ratio + img_resized = img.resize(new_size, Image.LANCZOS) + + return img_resized + + +def calculate_clip_similarity_with_blocks(image_path1, image_path2, blocks1, blocks2): + # Load and preprocess images + image1 = preprocess(rescale_and_mask(image_path1, [block['bbox'] for block in blocks1])).unsqueeze(0).to(device) + image2 = preprocess(rescale_and_mask(image_path2, [block['bbox'] for block in blocks2])).unsqueeze(0).to(device) + + # Calculate features + with torch.no_grad(): + image_features1 = model.encode_image(image1) + image_features2 = model.encode_image(image2) + + # Normalize features + image_features1 /= image_features1.norm(dim=-1, keepdim=True) + image_features2 /= image_features2.norm(dim=-1, keepdim=True) + + # Calculate cosine similarity + similarity = (image_features1 @ image_features2.T).item() + + return similarity + + +def truncate_repeated_html_elements(soup, max_count=50): + content_counts = {} + + for element in soup.find_all(True): + if isinstance(element, (NavigableString, Comment)): + continue + + try: + element_html = str(element) + except: + element.decompose() + continue + content_counts[element_html] = content_counts.get(element_html, 0) + 1 + + if content_counts[element_html] > max_count: + element.decompose() + + return str(soup) + + +def make_html(filename): + with open(filename, 'r') as file: + content = file.read() + + if not re.search(r']*>', content, re.IGNORECASE): + new_content = f'

{content}

' + with open(filename, 'w') as file: + file.write(new_content) + + +def pre_process(html_file): + #check_repetitive_content(html_file) + make_html(html_file) + with open(html_file, 'r') as file: + soup = BeautifulSoup(file, 'html.parser') + soup_str = truncate_repeated_html_elements(soup) + with open(html_file, 'w') as file: + file.write(soup_str) + + +def visual_eval_v3_multi(input_list, debug=False): + predict_html_list, original_html = input_list[0], input_list[1] + predict_img_list = [html.replace(".html", ".png") for html in predict_html_list] + # try: + predict_blocks_list = [] + for predict_html in predict_html_list: + predict_img = predict_html.replace(".html", ".png") + # This will help fix some html syntax error + pre_process(predict_html) + os.system(f"python3 {SCREENSHOT_SCRIPT_PATH} --html {predict_html} --png {predict_img}") + predict_blocks = get_blocks_ocr_free(predict_img) + predict_blocks_list.append(predict_blocks) + + original_img = original_html.replace(".html", ".png") + os.system(f"python3 {SCREENSHOT_SCRIPT_PATH} --html {original_html} --png {original_img}") + original_blocks = get_blocks_ocr_free(original_img) + original_blocks = merge_blocks_by_bbox(original_blocks) + + # Consider context similarity for block matching + consecutive_bonus, window_size = 0.1, 1 + + return_score_list = [] + + for k, predict_blocks in enumerate(predict_blocks_list): + if len(predict_blocks) == 0: + print("[Warning] No detected blocks in: ", predict_img_list[k]) + final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) + return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) + continue + elif len(original_blocks) == 0: + print("[Warning] No detected blocks in: ", original_img) + final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) + return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) + continue + + if debug: + print(predict_blocks) + print(original_blocks) + + predict_blocks = merge_blocks_by_bbox(predict_blocks) + predict_blocks_m, original_blocks_m, matching = find_possible_merge(predict_blocks, deepcopy(original_blocks), consecutive_bonus, window_size, debug=debug) + + filtered_matching = [] + for i, j in matching: + text_similarity = SequenceMatcher(None, predict_blocks_m[i]['text'], original_blocks_m[j]['text']).ratio() + # Filter out matching with low similarity + if text_similarity < 0.5: + continue + filtered_matching.append([i, j, text_similarity]) + matching = filtered_matching + + indices1 = [item[0] for item in matching] + indices2 = [item[1] for item in matching] + + matched_list = [] + sum_areas = [] + matched_areas = [] + matched_text_scores = [] + position_scores = [] + text_color_scores = [] + + unmatched_area_1 = 0.0 + for i in range(len(predict_blocks_m)): + if i not in indices1: + unmatched_area_1 += predict_blocks_m[i]['bbox'][2] * predict_blocks_m[i]['bbox'][3] + unmatched_area_2 = 0.0 + for j in range(len(original_blocks_m)): + if j not in indices2: + unmatched_area_2 += original_blocks_m[j]['bbox'][2] * original_blocks_m[j]['bbox'][3] + sum_areas.append(unmatched_area_1 + unmatched_area_2) + + for i, j, text_similarity in matching: + sum_block_area = predict_blocks_m[i]['bbox'][2] * predict_blocks_m[i]['bbox'][3] + original_blocks_m[j]['bbox'][2] * original_blocks_m[j]['bbox'][3] + + # Consider the max postion shift, either horizontally or vertically + position_similarity = 1 - calculate_distance_max_1d(predict_blocks_m[i]['bbox'][0] + predict_blocks_m[i]['bbox'][2] / 2, \ + predict_blocks_m[i]['bbox'][1] + predict_blocks_m[i]['bbox'][3] / 2, \ + original_blocks_m[j]['bbox'][0] + original_blocks_m[j]['bbox'][2] / 2, \ + original_blocks_m[j]['bbox'][1] + original_blocks_m[j]['bbox'][3] / 2) + # Normalized ciede2000 formula + text_color_similarity = color_similarity_ciede2000(predict_blocks_m[i]['color'], original_blocks_m[j]['color']) + matched_list.append([predict_blocks_m[i]['bbox'], original_blocks_m[j]['bbox']]) + + # validation check + if min(predict_blocks_m[i]['bbox'][2], original_blocks_m[j]['bbox'][2], predict_blocks_m[i]['bbox'][3], original_blocks_m[j]['bbox'][3]) == 0: + print(f"{predict_blocks_m[i]} matched with {original_blocks_m[j]}") + assert calculate_ratio(predict_blocks_m[i]['bbox'][2], original_blocks_m[j]['bbox'][2]) > 0 and calculate_ratio(predict_blocks_m[i]['bbox'][3], original_blocks_m[j]['bbox'][3]) > 0, f"{predict_blocks_m[i]} matched with {original_blocks_m[j]}" + + sum_areas.append(sum_block_area) + matched_areas.append(sum_block_area) + matched_text_scores.append(text_similarity) + position_scores.append(position_similarity) + text_color_scores.append(text_color_similarity) + + if debug: + print(f"{predict_blocks_m[i]} matched with {original_blocks_m[j]}") + print(SequenceMatcher(None, predict_blocks_m[i]['text'], original_blocks_m[j]['text']).ratio()) + print("text similarity score", text_similarity) + print("position score", position_similarity) + print("color score", text_color_similarity) + print("----------------------------------") + pass + """ + if debug: + img1 = cv2.imread(predict_img_list[k]) + img2 = cv2.imread(original_img) + img1_with_boxes, img2_with_boxes = draw_matched_bboxes(img1, img2, matched_list) + + plt.figure(figsize=(20, 10)) + plt.subplot(1, 2, 1) + plt.imshow(cv2.cvtColor(img1_with_boxes, cv2.COLOR_BGR2RGB)) + plt.axis('off') + plt.subplot(1, 2, 2) + plt.imshow(cv2.cvtColor(img2_with_boxes, cv2.COLOR_BGR2RGB)) + plt.axis('off') + plt.show() + # """ + + if len(matched_areas) > 0: + sum_sum_areas = np.sum(sum_areas) + + final_size_score = np.sum(matched_areas) / np.sum(sum_areas) + final_matched_text_score = np.mean(matched_text_scores) + final_position_score = np.mean(position_scores) + final_text_color_score = np.mean(text_color_scores) + final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) + final_score = 0.2 * (final_size_score + final_matched_text_score + final_position_score + final_text_color_score + final_clip_score) + return_score_list.append([sum_sum_areas, final_score, (final_size_score, final_matched_text_score, final_position_score, final_text_color_score, final_clip_score)]) + else: + print("[Warning] No matched blocks in: ", predict_img_list[k]) + final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) + return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) + return return_score_list + + # except: + # print("[Warning] Error not handled in: ", input_list) + # return [[0.0, 0.0, (0.0, 0.0, 0.0, 0.0, 0.0)] for _ in range(len(predict_html_list))] + + +if __name__ == "__main__": + print(visual_eval_v3_multi([ + ["work/miner1.html", "work/miner2.html", "work/miner3.html"], + "work/original.html"], debug=True)) diff --git a/webgenie/rewards/reward.py b/webgenie/rewards/reward.py index 39f95bd1..b46e2b79 100644 --- a/webgenie/rewards/reward.py +++ b/webgenie/rewards/reward.py @@ -1,2 +1,11 @@ -class Reward: - pass \ No newline at end of file +from abc import ABC, abstractmethod +import numpy as np +from typing import List + +from webgenie.tasks import Task +from webgenie.tasks.solution import Solution + +class Reward(ABC): + @abstractmethod + async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: + pass \ No newline at end of file diff --git a/webgenie/rewards/reward_manager.py b/webgenie/rewards/reward_manager.py deleted file mode 100644 index f68d255a..00000000 --- a/webgenie/rewards/reward_manager.py +++ /dev/null @@ -1,67 +0,0 @@ -from typing import Dict, List, Tuple - -from webgenie.rewards import Reward -from webgenie.rewards import GPTReward -from webgenie.rewards import SpeedReward -from webgenie.rewards import IsValidReward -from webgenie.tasks import Task -from webgenie.solution import Solution - -class RewardManager: - """ - A singleton manager for the reward models. - """ - reward_models: Dict[str, Reward] = {} - - def __init__(self): - self.reward_models = { - "gpt": GPTReward(), - "speed": SpeedReward(), - "is_valid": IsValidReward(), - } - - def _penalty(self, task: Task, solution: Solution) -> float: - """ - Penalize the solution based on the penalty models. - """ - penalty = 0 - for penalty_model in task.penalty_models: - model = self.reward_models[penalty_model[0]] - weight = penalty_model[1] - - penalty += weight * model.reward(task, solution) - return penalty - - def _reward(self, task: Task, solution: Solution) -> float: - """ - Reward the solution based on the reward models. - """ - reward = 0 - for reward_model in task.reward_models: - model = self.reward_models[reward_model[0]] - weight = reward_model[1] - reward += weight * model.reward(task, solution) - - return reward - - def _score(self, task: Task, solution: Solution) -> float: - """ - Score the solution based on the reward and penalty models. - """ - score = task.reward_weight * self._reward(task, solution) - task.penalty_weight * self._penalty(task, solution) - if score < 0: - score = 0 - return score - - def score(self, task: Task, results: List[Solution]) -> Tuple[List[float], List[int]]: - """ - Score the solutions based on the reward and penalty models. - """ - scores = [] - miner_uids = [] - for solution in results: - score = self._score(task, solution) - scores.append(score) - miner_uids.append(solution.miner_uid) - - return scores, miner_uids \ No newline at end of file diff --git a/webgenie/rewards/speed.py b/webgenie/rewards/speed.py deleted file mode 100644 index d9e905ff..00000000 --- a/webgenie/rewards/speed.py +++ /dev/null @@ -1,15 +0,0 @@ - -from webgenie.rewards import Reward -from webgenie.tasks import Task -from webgenie.solution import Solution - -class SpeedReward(Reward): - - def __init__(self): - pass - - def reward(self, task: Task, solution: Solution) -> float: - if (task.timeout == 0): - return 1.0 - return 1.0 - (solution.process_time / task.timeout) - \ No newline at end of file diff --git a/webgenie/rewards/visual_reward.py b/webgenie/rewards/visual_reward.py new file mode 100644 index 00000000..d4eef431 --- /dev/null +++ b/webgenie/rewards/visual_reward.py @@ -0,0 +1,33 @@ +import bittensor as bt +import numpy as np +from typing import List + +from webgenie.constants import WORK_DIR +from webgenie.rewards import Reward +from webgenie.rewards.metrics.visual_score import visual_eval_v3_multi +from webgenie.tasks.task import Task, ImageTask +from webgenie.tasks.solution import Solution + +class VisualReward(Reward): + def __init__(self): + pass + + async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: + if not isinstance(task, ImageTask): + raise ValueError(f"Task is not a ImageTask: {type(task)}") + + bt.logging.debug(f"Rewarding image task in visual reward") + + original_html_path = f"{WORK_DIR}/original.html" + with open(original_html_path, "w") as f: + f.write(task.ground_truth_html) + + miner_html_paths = [] + for solution in solutions: + path = f"{WORK_DIR}/miner{solution.miner_uid}.html" + with open(path, "w") as f: + f.write(solution.html) + miner_html_paths.append(path) + + visual_scores = visual_eval_v3_multi([miner_html_paths, original_html_path]) + return np.array([score[1] for score in visual_scores]) diff --git a/webgenie/solution/__init__.py b/webgenie/solution/__init__.py deleted file mode 100644 index fb1bbb15..00000000 --- a/webgenie/solution/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .solution import Solution \ No newline at end of file diff --git a/webgenie/task_generator/__init__.py b/webgenie/task_generator/__init__.py deleted file mode 100644 index e4507b44..00000000 --- a/webgenie/task_generator/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .task_generator import TaskGenerator \ No newline at end of file diff --git a/webgenie/task_generator/image_task_generator.py b/webgenie/task_generator/image_task_generator.py deleted file mode 100644 index 96053562..00000000 --- a/webgenie/task_generator/image_task_generator.py +++ /dev/null @@ -1,27 +0,0 @@ -import bittensor as bt -from typing import List, Tuple - -from webgenie.helpers.images import image_to_base64 -from webgenie.protocol import WebgenieImageSynapse -from webgenie.solution import Solution -from webgenie.tasks.task import Task, ImageTask -from webgenie.task_generator.task_generator import TaskGenerator - -class ImageTaskGenerator(TaskGenerator): - def __init__(self): - super().__init__() - - async def generate_task(self) -> Tuple[Task, bt.Synapse]: - base64_image = image_to_base64("original.jpg") - return ImageTask( - base64_image=base64_image, - timeout=50, - generator=self - ), WebgenieImageSynapse(base64_image=base64_image) - - async def reward(self, task: Task, solutions: List[Solution]) -> List[float]: - if not isinstance(task, ImageTask): - raise ValueError(f"Task is not a ImageTask: {type(task)}") - bt.logging.debug(task.base64_image) - bt.logging.debug(f"Rewarding image task {task} with solutions {solutions}") - return [1.0] * len(solutions) diff --git a/webgenie/task_generator/text_task_generator.py b/webgenie/task_generator/text_task_generator.py deleted file mode 100644 index e46e88a2..00000000 --- a/webgenie/task_generator/text_task_generator.py +++ /dev/null @@ -1,26 +0,0 @@ -import bittensor as bt -from typing import List, Tuple - -from webgenie.protocol import WebgenieTextSynapse -from webgenie.solution import Solution -from webgenie.tasks.task import Task, TextTask -from webgenie.task_generator.task_generator import TaskGenerator - -class TextTaskGenerator(TaskGenerator): - def __init__(self): - super().__init__() - - async def generate_task(self) -> Tuple[Task, bt.Synapse]: - return TextTask( - prompt="CommingSoon Page with goback button, navHeader, and footer" , - timeout=50, - generator=self - ), WebgenieTextSynapse(prompt="CommingSoon Page with goback button, navHeader, and footer") - - async def reward(self, task: Task, solutions: List[Solution]) -> List[float]: - if not isinstance(task, TextTask): - raise ValueError(f"Task is not a TextTask: {type(task)}") - bt.logging.debug(task.prompt) - bt.logging.debug(f"Rewarding text task {task} with solutions {solutions}") - - return [1.0] * len(solutions) diff --git a/webgenie/tasks/__init__.py b/webgenie/tasks/__init__.py index 760a4128..0670b304 100644 --- a/webgenie/tasks/__init__.py +++ b/webgenie/tasks/__init__.py @@ -1 +1,5 @@ -from .task import Task \ No newline at end of file +from .solution import Solution +from .task import Task, ImageTask, TextTask +from .task_generator import TaskGenerator +from .image_task_generator import ImageTaskGenerator +from .text_task_generator import TextTaskGenerator \ No newline at end of file diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py new file mode 100644 index 00000000..d7e937d5 --- /dev/null +++ b/webgenie/tasks/image_task_generator.py @@ -0,0 +1,33 @@ +import bittensor as bt +import numpy as np +import random +from typing import List, Tuple + +from webgenie.helpers.htmls import html_to_screenshot +from webgenie.protocol import WebgenieImageSynapse +from webgenie.tasks.solution import Solution +from webgenie.tasks.task import Task, ImageTask +from webgenie.tasks.task_generator import TaskGenerator +from webgenie.rewards.visual_reward import VisualReward +from webgenie.datasets.dataset import MockUpDataset + +class ImageTaskGenerator(TaskGenerator): + def __init__(self): + super().__init__() + self.rewards = [ + (VisualReward(), 1.0) + ] + self.datasets = [ + MockUpDataset() + ] + + async def generate_task(self) -> Tuple[Task, bt.Synapse]: + dataset_entry = await random.choice(self.datasets).generate_context() + base64_image = html_to_screenshot(dataset_entry.ground_truth_html) + return ImageTask( + base64_image=base64_image, + ground_truth_html=dataset_entry.ground_truth_html, + timeout=50, + generator=self, + ), WebgenieImageSynapse(base64_image=base64_image) + diff --git a/webgenie/solution/solution.py b/webgenie/tasks/solution.py similarity index 100% rename from webgenie/solution/solution.py rename to webgenie/tasks/solution.py diff --git a/webgenie/tasks/task.py b/webgenie/tasks/task.py index 8d633538..3a85d86d 100644 --- a/webgenie/tasks/task.py +++ b/webgenie/tasks/task.py @@ -6,7 +6,9 @@ class Task(BaseModel): generator: Any = Field(default=None) class ImageTask(Task): - base64_image: str = Field(default="") + base64_image: str = Field(default="", description="The base64 encoded image") + ground_truth_html: str = Field(default="", description="The ground truth html") class TextTask(Task): - prompt: str = Field(default="") + prompt: str = Field(default="", description="The prompt for the text task") + ground_truth_html: str = Field(default="", description="The ground truth html") diff --git a/webgenie/task_generator/task_generator.py b/webgenie/tasks/task_generator.py similarity index 52% rename from webgenie/task_generator/task_generator.py rename to webgenie/tasks/task_generator.py index a41b3a82..e27668ed 100644 --- a/webgenie/task_generator/task_generator.py +++ b/webgenie/tasks/task_generator.py @@ -1,8 +1,9 @@ import bittensor as bt +import numpy as np from typing import List, Tuple from webgenie.tasks import Task -from webgenie.solution import Solution +from webgenie.tasks.solution import Solution class TaskGenerator: """ @@ -13,7 +14,11 @@ def __init__(self): async def generate_task(self) -> Tuple[Task, bt.Synapse]: pass - - async def reward(self, task: Task, solutions: List[Solution]) -> List[float]: - pass + + async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: + scores = np.zeros(len(solutions)) + for reward, weight in self.rewards: + reward_scores = await reward.reward(task, solutions) + scores += weight * np.array(reward_scores) + return scores diff --git a/webgenie/tasks/text_task_generator.py b/webgenie/tasks/text_task_generator.py new file mode 100644 index 00000000..415cee3b --- /dev/null +++ b/webgenie/tasks/text_task_generator.py @@ -0,0 +1,26 @@ +import bittensor as bt +import numpy as np +import random +from typing import List, Tuple + +from webgenie.datasets.dataset import MockUpPromptDataset +from webgenie.protocol import WebgenieTextSynapse +from webgenie.tasks.solution import Solution +from webgenie.tasks.task import Task, TextTask +from webgenie.tasks.task_generator import TaskGenerator + +class TextTaskGenerator(TaskGenerator): + def __init__(self): + super().__init__() + self.rewards = [] + self.datasets = [ + MockUpPromptDataset() + ] + + async def generate_task(self) -> Tuple[Task, bt.Synapse]: + dataset_entry = await random.choice(self.datasets).generate_context() + return TextTask( + prompt=dataset_entry.prompt, + timeout=50, + generator=self + ), WebgenieTextSynapse(prompt=dataset_entry.prompt) From ee552e4b348eba9557919038763e6990a0b30faa Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 06:02:50 -0600 Subject: [PATCH 037/554] chore: moved backend api hotkey to constants --- neurons/validators/validator.py | 7 +++---- webgenie/constants.py | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index f57e6e6c..389e27d0 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -8,12 +8,11 @@ from typing import Tuple, Union from webgenie.base.validator import BaseValidatorNeuron +from webgenie.constants import API_HOTKEY from webgenie.helpers.weights import init_wandb from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from neurons.validators.genie_validator import GenieValidator -API_HOTKEY = "5D72esHuc1DxD6PD8S6VyU24bTHGQjHHyodzTGsem1sejUYj" - class Validator(BaseValidatorNeuron): """ Your validator neuron class. You should use this class to define your validator's behavior. In particular, you should replace the forward function with your own logic. @@ -115,8 +114,8 @@ async def scoring_loop(self): await asyncio.sleep(5) async def __aenter__(self): - self.loop.create_task(self.forward_loop()) - self.loop.create_task(self.scoring_loop()) + #self.loop.create_task(self.forward_loop()) + #self.loop.create_task(self.scoring_loop()) self.is_running = True bt.logging.debug("Starting validator in background thread") return self diff --git a/webgenie/constants.py b/webgenie/constants.py index 49ff9e30..83553113 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -1,3 +1,4 @@ +API_HOTKEY = "5D72esHuc1DxD6PD8S6VyU24bTHGQjHHyodzTGsem1sejUYj" MAX_SYNTHETIC_HISTORY_SIZE = 10 PLACE_HOLDER_IMAGE_URL = "https://picsum.photos/seed/picsum/800/600" SCREENSHOT_SCRIPT_PATH = "webgenie/rewards/metrics/screenshot_single.py" From 7c90dd49cc783b39f4608280818134814db20468 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 06:04:26 -0600 Subject: [PATCH 038/554] docs: added comments in constants --- webgenie/constants.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/webgenie/constants.py b/webgenie/constants.py index 83553113..6041aba4 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -1,5 +1,14 @@ +# backend api hotkey API_HOTKEY = "5D72esHuc1DxD6PD8S6VyU24bTHGQjHHyodzTGsem1sejUYj" + +# max synthetic history size MAX_SYNTHETIC_HISTORY_SIZE = 10 + +# place holder image url PLACE_HOLDER_IMAGE_URL = "https://picsum.photos/seed/picsum/800/600" + +# screenshot script path SCREENSHOT_SCRIPT_PATH = "webgenie/rewards/metrics/screenshot_single.py" + +# work dir WORK_DIR = "work" From cb7fe17fb472a5eb100b852beb182a6a2e10cfe1 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 06:15:08 -0600 Subject: [PATCH 039/554] feat: added bert reward --- neurons/validators/validator.py | 4 +- webgenie/datasets/dataset.py | 125 +++++++++++++++++++++++++- webgenie/rewards/bert_reward.py | 28 ++++++ webgenie/tasks/text_task_generator.py | 7 +- 4 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 webgenie/rewards/bert_reward.py diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 389e27d0..2fc0abe2 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -114,8 +114,8 @@ async def scoring_loop(self): await asyncio.sleep(5) async def __aenter__(self): - #self.loop.create_task(self.forward_loop()) - #self.loop.create_task(self.scoring_loop()) + self.loop.create_task(self.forward_loop()) + self.loop.create_task(self.scoring_loop()) self.is_running = True bt.logging.debug("Starting validator in background thread") return self diff --git a/webgenie/datasets/dataset.py b/webgenie/datasets/dataset.py index ce3f4373..93c4ab12 100644 --- a/webgenie/datasets/dataset.py +++ b/webgenie/datasets/dataset.py @@ -143,10 +143,133 @@ async def generate_context(self)->DatasetEntry: class MockUpPromptDataset(Dataset): async def generate_context(self)->DatasetEntry: + html = """ + + + + + + Coming Soon + + + + + +
+ +
+ + +
+

Coming Soon!

+

We're working hard to launch something amazing. Stay tuned!

+ +
+ + + + + + + +""" return DatasetEntry( src="mockup", topic="tech company", - ground_truth_html="", + ground_truth_html=html, prompt="CommingSoon Page with goback button, navHeader, and footer", base64_image="" ) \ No newline at end of file diff --git a/webgenie/rewards/bert_reward.py b/webgenie/rewards/bert_reward.py new file mode 100644 index 00000000..264e5e47 --- /dev/null +++ b/webgenie/rewards/bert_reward.py @@ -0,0 +1,28 @@ +import bert_score +import bittensor as bt +import numpy as np +from typing import List + +from webgenie.rewards import Reward +from webgenie.tasks.task import Task +from webgenie.tasks.solution import Solution + +class BertReward(Reward): + def __init__(self): + pass + + async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: + bt.logging.debug(f"Rewarding task in bert reward") + + original_htmls= [] + miner_htmls = [] + + for solution in solutions: + original_htmls.append(task.ground_truth_html) + miner_htmls.append(solution.html) + + P, R, F1 = bert_score.score(original_htmls, miner_htmls, lang='en') + + return np.array(F1) + + \ No newline at end of file diff --git a/webgenie/tasks/text_task_generator.py b/webgenie/tasks/text_task_generator.py index 415cee3b..2298cb62 100644 --- a/webgenie/tasks/text_task_generator.py +++ b/webgenie/tasks/text_task_generator.py @@ -5,14 +5,16 @@ from webgenie.datasets.dataset import MockUpPromptDataset from webgenie.protocol import WebgenieTextSynapse -from webgenie.tasks.solution import Solution +from webgenie.rewards.bert_reward import BertReward from webgenie.tasks.task import Task, TextTask from webgenie.tasks.task_generator import TaskGenerator class TextTaskGenerator(TaskGenerator): def __init__(self): super().__init__() - self.rewards = [] + self.rewards = [ + BertReward(), + ] self.datasets = [ MockUpPromptDataset() ] @@ -21,6 +23,7 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: dataset_entry = await random.choice(self.datasets).generate_context() return TextTask( prompt=dataset_entry.prompt, + ground_truth_html=dataset_entry.ground_truth_html, timeout=50, generator=self ), WebgenieTextSynapse(prompt=dataset_entry.prompt) From 6b30f5ec2e6acdd7fa49a71bdf96a00416ef3cb2 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 06:19:14 -0600 Subject: [PATCH 040/554] fix: fixed bugs in bert --- webgenie/tasks/text_task_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/tasks/text_task_generator.py b/webgenie/tasks/text_task_generator.py index 2298cb62..374e227c 100644 --- a/webgenie/tasks/text_task_generator.py +++ b/webgenie/tasks/text_task_generator.py @@ -13,7 +13,7 @@ class TextTaskGenerator(TaskGenerator): def __init__(self): super().__init__() self.rewards = [ - BertReward(), + (BertReward(), 1.0) ] self.datasets = [ MockUpPromptDataset() From 25e5b1872eb8aeea9820f2cedcf1fe817a3e1eda Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 06:23:37 -0600 Subject: [PATCH 041/554] chore: implemented preprocess html pseudo code --- neurons/validators/genie_validator.py | 2 ++ webgenie/helpers/htmls.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 0a6ba728..64d2a4b2 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -4,6 +4,7 @@ from webgenie.base.neuron import BaseNeuron from webgenie.constants import MAX_SYNTHETIC_HISTORY_SIZE +from webgenie.helpers.htmls import preprocess_html from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.tasks.solution import Solution from webgenie.tasks.image_task_generator import ImageTaskGenerator @@ -92,5 +93,6 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag async def process_synapse(self, synapse: bt.Synapse) -> bt.Synapse: if synapse.dendrite.status_code == 200: + synapse.html = preprocess_html(synapse.html) return synapse return None \ No newline at end of file diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 93fc6ff9..4e04119c 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -11,4 +11,7 @@ def html_to_screenshot(html: str) -> str: png_path = f"{WORK_DIR}/screenshot_{uuid.uuid4()}.png" os.system(f"python3 {SCREENSHOT_SCRIPT_PATH} --html {html_path} --png {png_path}") time.sleep(0.1) - return image_to_base64(png_path) \ No newline at end of file + return image_to_base64(png_path) + +def preprocess_html(html: str) -> str: + return html \ No newline at end of file From 488b6f0436b0785714be5f0946516b4a7b961add Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 06:33:06 -0600 Subject: [PATCH 042/554] chore: implemented preprocess func --- webgenie/helpers/htmls.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 4e04119c..1cff25f0 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -1,7 +1,9 @@ import os +from bs4 import BeautifulSoup import time +import re import uuid -from webgenie.constants import SCREENSHOT_SCRIPT_PATH, WORK_DIR +from webgenie.constants import SCREENSHOT_SCRIPT_PATH, WORK_DIR, PLACE_HOLDER_IMAGE_URL from webgenie.helpers.images import image_to_base64 def html_to_screenshot(html: str) -> str: @@ -13,5 +15,34 @@ def html_to_screenshot(html: str) -> str: time.sleep(0.1) return image_to_base64(png_path) +def beautify_html(html: str) -> str: + soup = BeautifulSoup(html, 'html.parser') + return str(soup) + +def replace_image_sources(html_content, new_url = PLACE_HOLDER_IMAGE_URL): + soup = BeautifulSoup(html_content, 'html.parser') + + for img_tag in soup.find_all('img'): + img_tag['src'] = new_url + + for source_tag in soup.find_all('source'): + if 'srcset' in source_tag.attrs: + source_tag['srcset'] = new_url + + for tag in soup.find_all(style=True): + style = tag['style'] + updated_style = re.sub(r'background-image\s*:\s*url\([^)]+\)', f'background-image: url({new_url})', style) + tag['style'] = updated_style + + for style_tag in soup.find_all('style'): + style_content = style_tag.string + if style_content: + updated_content = re.sub(r'background-image\s*:\s*url\([^)]+\)', f'background-image: url({new_url})', style_content) + style_tag.string.replace_with(updated_content) + + return str(soup) + def preprocess_html(html: str) -> str: + html = beautify_html(html) + html = replace_image_sources(html) return html \ No newline at end of file From 03b268dc6882c362b9efbaf7f935676fc81bfa29 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 06:52:56 -0600 Subject: [PATCH 043/554] feat: implemented rtc reward --- webgenie/prompts.py | 12 +++++++ webgenie/rewards/rtc_reward.py | 52 +++++++++++++++++++++++++++ webgenie/tasks/text_task_generator.py | 4 ++- 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 webgenie/prompts.py create mode 100644 webgenie/rewards/rtc_reward.py diff --git a/webgenie/prompts.py b/webgenie/prompts.py new file mode 100644 index 00000000..35058ec7 --- /dev/null +++ b/webgenie/prompts.py @@ -0,0 +1,12 @@ +# prompt to make rounded trip correctness +PROMPT_RTC = """ +You are an HTML, CSS expert. And you are well versed in the AI, ML. +I have a model that converts the prompt to html. +I want you to analyze the html code and make a prompt that generate the given html code. +The following is the given html code: +{html} +The following is the example of prompt: +{prompt} + +{instructions} +""" \ No newline at end of file diff --git a/webgenie/rewards/rtc_reward.py b/webgenie/rewards/rtc_reward.py new file mode 100644 index 00000000..69045cb2 --- /dev/null +++ b/webgenie/rewards/rtc_reward.py @@ -0,0 +1,52 @@ +import bittensor as bt +import bert_score +import os +import numpy as np +from typing import List + +from langchain_openai import ChatOpenAI +from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate +from langchain_core.output_parsers import JsonOutputParser +from langchain_core.pydantic_v1 import BaseModel, Field + +from webgenie.prompts import PROMPT_RTC +from webgenie.rewards import Reward +from webgenie.tasks.task import Task +from webgenie.tasks.solution import Solution + +class PromptResponse(BaseModel): + prompt: str = Field(default="", description="The prompt that generates the given html code") + +class RtcReward(Reward): + def __init__(self): + self.model = ChatOpenAI( + api_key= os.getenv("OPENAI_API_KEY"), + model_name="gpt-4o", + ) + + self.prompt_response_parser = JsonOutputParser(pydantic_object=PromptResponse) + + def _get_prompt(self, task: Task, solutions: List[Solution]) -> str: + prompt = ChatPromptTemplate.from_messages([ + SystemMessagePromptTemplate.from_template(PROMPT_RTC) + ]) + + chain = prompt | self.model | self.prompt_response_parser + prompt_response = chain.invoke({ + "html": task.ground_truth_html, + "prompt": task.prompt, + "instructions": self.prompt_response_parser.get_format_instructions() + }) + + return prompt_response["prompt"] + + async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: + bt.logging.debug(f"Rewarding task in rtc reward") + original_prompts = [task.prompt for _ in solutions] + miner_prompts = [self._get_prompt(task, solution) for solution in solutions] + P, R, F1 = bert_score.score(original_prompts, miner_prompts, lang='en') + return np.array(R) + + + + \ No newline at end of file diff --git a/webgenie/tasks/text_task_generator.py b/webgenie/tasks/text_task_generator.py index 374e227c..65c89128 100644 --- a/webgenie/tasks/text_task_generator.py +++ b/webgenie/tasks/text_task_generator.py @@ -6,6 +6,7 @@ from webgenie.datasets.dataset import MockUpPromptDataset from webgenie.protocol import WebgenieTextSynapse from webgenie.rewards.bert_reward import BertReward +from webgenie.rewards.rtc_reward import RtcReward from webgenie.tasks.task import Task, TextTask from webgenie.tasks.task_generator import TaskGenerator @@ -13,7 +14,8 @@ class TextTaskGenerator(TaskGenerator): def __init__(self): super().__init__() self.rewards = [ - (BertReward(), 1.0) + (BertReward(), 0.5), + (RtcReward(), 0.5) ] self.datasets = [ MockUpPromptDataset() From b2b5d138e495505a6e5f2e5f11857d571decd1dd Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 06:58:36 -0600 Subject: [PATCH 044/554] feat: added auto update script --- auto_update.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 auto_update.sh diff --git a/auto_update.sh b/auto_update.sh new file mode 100644 index 00000000..99968193 --- /dev/null +++ b/auto_update.sh @@ -0,0 +1,32 @@ +#!/bin/bash + + +while true; do + # Log the start of the script execution + echo "$(date): Script started" + + # Save the current HEAD hash + current_head=$(git rev-parse HEAD) + + # Pull the latest changes from the repository + git stash + git pull -f + git reset --hard origin/main + + # Get the new HEAD hash + new_head=$(git rev-parse HEAD) + + # Check if the new HEAD is different from the current HEAD + if [ "$current_head" != "$new_head" ]; then + # The HEAD has changed, meaning there's a new version + echo "$(date): New version detected, installing requirements and restarting the validator." + pip install -e . + pm2 restart webgenie_validator + else + # No new version, no action needed + echo "$(date): No new version detected, no restart needed." + fi + + # Sleep until the beginning of the next hour + sleep 3600 +done From 46eb1e29e194a4256b643c4de3381c46cb9f4bdc Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 07:07:37 -0600 Subject: [PATCH 045/554] chore: removed hardcoded python cmd --- neurons/validators/validator.py | 4 ++-- webgenie/constants.py | 4 ++++ webgenie/helpers/htmls.py | 9 +++++++-- webgenie/rewards/metrics/visual_score.py | 6 +++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 2fc0abe2..389e27d0 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -114,8 +114,8 @@ async def scoring_loop(self): await asyncio.sleep(5) async def __aenter__(self): - self.loop.create_task(self.forward_loop()) - self.loop.create_task(self.scoring_loop()) + #self.loop.create_task(self.forward_loop()) + #self.loop.create_task(self.scoring_loop()) self.is_running = True bt.logging.debug("Starting validator in background thread") return self diff --git a/webgenie/constants.py b/webgenie/constants.py index 6041aba4..81fa8f6f 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -7,8 +7,12 @@ # place holder image url PLACE_HOLDER_IMAGE_URL = "https://picsum.photos/seed/picsum/800/600" +# python command +PYTHON_CMD = "python" + # screenshot script path SCREENSHOT_SCRIPT_PATH = "webgenie/rewards/metrics/screenshot_single.py" # work dir WORK_DIR = "work" + diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 1cff25f0..686a727d 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -3,7 +3,12 @@ import time import re import uuid -from webgenie.constants import SCREENSHOT_SCRIPT_PATH, WORK_DIR, PLACE_HOLDER_IMAGE_URL +from webgenie.constants import ( + SCREENSHOT_SCRIPT_PATH, + WORK_DIR, + PLACE_HOLDER_IMAGE_URL, + PYTHON_CMD +) from webgenie.helpers.images import image_to_base64 def html_to_screenshot(html: str) -> str: @@ -11,7 +16,7 @@ def html_to_screenshot(html: str) -> str: with open(html_path, "w") as f: f.write(html) png_path = f"{WORK_DIR}/screenshot_{uuid.uuid4()}.png" - os.system(f"python3 {SCREENSHOT_SCRIPT_PATH} --html {html_path} --png {png_path}") + os.system(f"{PYTHON_CMD} {SCREENSHOT_SCRIPT_PATH} --html {html_path} --png {png_path}") time.sleep(0.1) return image_to_base64(png_path) diff --git a/webgenie/rewards/metrics/visual_score.py b/webgenie/rewards/metrics/visual_score.py index 7bc330fd..bbc8f055 100644 --- a/webgenie/rewards/metrics/visual_score.py +++ b/webgenie/rewards/metrics/visual_score.py @@ -33,7 +33,7 @@ def patch_asscalar(a): from ocr_free_utils import get_blocks_ocr_free from dedup_post_gen import check_repetitive_content -from webgenie.constants import SCREENSHOT_SCRIPT_PATH, WORK_DIR +from webgenie.constants import SCREENSHOT_SCRIPT_PATH, WORK_DIR, PYTHON_CMD device = "cuda" if torch.cuda.is_available() else "cpu" model, preprocess = clip.load("ViT-B/32", device=device) @@ -445,12 +445,12 @@ def visual_eval_v3_multi(input_list, debug=False): predict_img = predict_html.replace(".html", ".png") # This will help fix some html syntax error pre_process(predict_html) - os.system(f"python3 {SCREENSHOT_SCRIPT_PATH} --html {predict_html} --png {predict_img}") + os.system(f"{PYTHON_CMD} {SCREENSHOT_SCRIPT_PATH} --html {predict_html} --png {predict_img}") predict_blocks = get_blocks_ocr_free(predict_img) predict_blocks_list.append(predict_blocks) original_img = original_html.replace(".html", ".png") - os.system(f"python3 {SCREENSHOT_SCRIPT_PATH} --html {original_html} --png {original_img}") + os.system(f"{PYTHON_CMD} {SCREENSHOT_SCRIPT_PATH} --html {original_html} --png {original_img}") original_blocks = get_blocks_ocr_free(original_img) original_blocks = merge_blocks_by_bbox(original_blocks) From 6c37c1b51eeec4d155497d23d46119feb70cbe35 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 07:09:51 -0600 Subject: [PATCH 046/554] chore: removed dummy miner --- webgenie/miners/dummy_miner.py | 96 ---------------------------------- 1 file changed, 96 deletions(-) delete mode 100644 webgenie/miners/dummy_miner.py diff --git a/webgenie/miners/dummy_miner.py b/webgenie/miners/dummy_miner.py deleted file mode 100644 index 5b616403..00000000 --- a/webgenie/miners/dummy_miner.py +++ /dev/null @@ -1,96 +0,0 @@ -import json -from functools import partial -from starlette.types import Send -import time -from typing import Dict - -from typing import Dict, Awaitable -from langchain_openai import ChatOpenAI -from dotenv import load_dotenv, find_dotenv -from langchain.prompts import ChatPromptTemplate -from langchain_core.output_parsers import StrOutputParser -from langchain_core.runnables.base import RunnableSequence - -import bittensor as bt -import webgenie -import os - -def miner_init(self): - bt.logging.debug(f"Dummy Miner initialized") - _ = load_dotenv(find_dotenv()) - api_key = os.getenv("OPENAI_API_KEY") - # Set openai key and other args - self.model = ChatOpenAI( - api_key=api_key, - model_name="gpt-4", - ) - -def miner_forward(self, synapse: webgenie.protocol.WebgenieStreamingSynapse)->Awaitable: - - async def _forward(self, chain: RunnableSequence, chain_formatter: Dict[str, str], timeout_threshold: float, init_time: float, send: Send): - try: - buffer = [] - - timeout_reached = False - - for token in chain.stream(chain_formatter): - buffer.append(token) - - if time.time() - init_time > timeout_threshold: - bt.logging.debug(f"⏰ Timeout reached, stopping streaming") - timeout_reached = True - break - - if len(buffer) == self.config.neuron.streaming_batch_size: - joined_buffer = "".join(buffer) - bt.logging.debug(f"Streamed tokens: {joined_buffer}") - - await send( - { - "type": "http.response.body", - "body": joined_buffer.encode("utf-8"), - "more_body": True, - } - ) - buffer = [] - - if ( - buffer and not timeout_reached - ): # Don't send the last buffer of data if timeout. - joined_buffer = "".join(buffer) - await send( - { - "type": "http.response.body", - "body": joined_buffer.encode("utf-8"), - "more_body": False, - } - ) - except Exception as e: - bt.logging.error(f"Dummy Miner Error: {e}") - - bt.logging.debug(f"Dummy Miner Query received, forwarding synapse: {synapse}") - - prompt = ChatPromptTemplate.from_messages([ - ("system", """You are an expert programmer. Generate code based on the following request without explanations:\n\n{query}\n\n Provide the code that satisfies the following requirements without any explanation. You must give me both the CSS file and the frontend HTML file( (the HTML file content should be whole content)).' The response should be in JSON format as shown below, (so that I can decode it such as json.loads(your response)) and it should be plaintext (without triple quotes): - {{ "CSS": "css_code_here", "HTML": "html_code_here" }}"""), - ("user", "{query}"), - ]) - chain = prompt | self.model | StrOutputParser() - - query = synapse.task.query - bt.logging.debug(f"Dummy Miner Query received: {synapse}") - time.sleep(2) - chain_formatter = {"query": query} - - init_time = time.time() - timeout_threshold = float(synapse.timeout) - - token_streamer = partial( - _forward, - self, - chain, - chain_formatter, - timeout_threshold, - init_time, - ) - return synapse.create_streaming_response(token_streamer) \ No newline at end of file From e93db2486b0f763169b7928d8423a35db69cdf69 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 07:16:50 -0600 Subject: [PATCH 047/554] docs: added developer info --- setup.py | 9 ++++----- webgenie/__init__.py | 3 +-- webgenie/base/validator.py | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index 440a4ffa..af66f894 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,6 @@ # The MIT License (MIT) -# Copyright © 2023 webgenie -# Sangar -# Copyright © 2023 +# Copyright © 2023 Yuma Rao +# Copyright © 2023 Sangar, pycorn0729 # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation @@ -69,10 +68,10 @@ def read_requirements(path): long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/webgenie/webgenie", - author="Sangar", + author="Sangar, pycorn0729", packages=find_packages(), include_package_data=True, - author_email="sangar.work1028@gmail.com", + author_email="sangar.work1028@gmail.com, hayesdominique0729@gmail.com", license="MIT", python_requires=">=3.8", install_requires=requirements, diff --git a/webgenie/__init__.py b/webgenie/__init__.py index 11da983c..5a811b92 100644 --- a/webgenie/__init__.py +++ b/webgenie/__init__.py @@ -1,7 +1,6 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# Sangar -# Copyright © 2023 +# Copyright © 2023 Sangar, pycorn0729 # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index 5d953236..89af5768 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -1,6 +1,6 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# Copyright © 2024 Sangar +# Copyright © 2024 Sangar, pycorn0729 # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation From 3580ebf6714068d5e15e1140ddd27b440c58eabf Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 07:23:33 -0600 Subject: [PATCH 048/554] docs: changed developer info --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index af66f894..a71f5056 100644 --- a/setup.py +++ b/setup.py @@ -67,13 +67,13 @@ def read_requirements(path): description="webgenie aims to transform diverse prompts—ranging from text and voice to images and Figma designs—into fully functional, ready-to-deploy projects.", long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/webgenie/webgenie", - author="Sangar, pycorn0729", + url="https://github.com/web-genie-ai/web-genie-ai", + author="Sangar, Dominique Hayes", packages=find_packages(), include_package_data=True, author_email="sangar.work1028@gmail.com, hayesdominique0729@gmail.com", license="MIT", - python_requires=">=3.8", + python_requires=">=3.12", install_requires=requirements, classifiers=[ "Development Status :: 3 - Alpha", From b3714a77119d6096007158699fdc6f084d4c0f36 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 07:56:02 -0600 Subject: [PATCH 049/554] chore: added code removing work file int htmls helper --- webgenie/helpers/htmls.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 686a727d..eef31b65 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -17,8 +17,14 @@ def html_to_screenshot(html: str) -> str: f.write(html) png_path = f"{WORK_DIR}/screenshot_{uuid.uuid4()}.png" os.system(f"{PYTHON_CMD} {SCREENSHOT_SCRIPT_PATH} --html {html_path} --png {png_path}") + + time.sleep(0.1) + base64_image = image_to_base64(png_path) + time.sleep(0.1) - return image_to_base64(png_path) + os.remove(html_path) + os.remove(png_path) + return base64_image def beautify_html(html: str) -> str: soup = BeautifulSoup(html, 'html.parser') From 60d407834ab71744cc60fbea59b667408d796e0a Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 07:59:46 -0600 Subject: [PATCH 050/554] chore:wrote code to make work dir --- neurons/validators/genie_validator.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 64d2a4b2..031eaf5e 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -22,6 +22,16 @@ def __init__(self, neuron: BaseNeuron): ImageTaskGenerator(), ] + self.make_work_dir() + + def make_work_dir(self): + import os + from webgenie.constants import WORK_DIR + + if not os.path.exists(WORK_DIR): + os.makedirs(WORK_DIR) + bt.logging.info(f"Created work directory at {WORK_DIR}") + async def forward(self): try: if len(self.synthetic_history) > MAX_SYNTHETIC_HISTORY_SIZE: From 513fdf11228f2829a23caf20a3ca1f36dbcf4f24 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 08:09:41 -0600 Subject: [PATCH 051/554] chore: added .env.example file --- .env.example | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..d4429700 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +OPENAI_API_KEY = your_openai_api_key +WANDB_API_KEY = your_wandb_api_key +WANDB_ENTITY_NAME = your_wandb_entity_name \ No newline at end of file From ca9a45af051d25aa545ccdb8a0768afbcbb8f89d Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 08:17:57 -0600 Subject: [PATCH 052/554] chore: reconfigured .env files --- .env.example => .env.miner.example | 0 .env.validator.example | 3 +++ .gitignore | 3 ++- neurons/miners/miner.py | 7 +++---- neurons/validators/validator.py | 8 +++++--- 5 files changed, 13 insertions(+), 8 deletions(-) rename .env.example => .env.miner.example (100%) create mode 100644 .env.validator.example diff --git a/.env.example b/.env.miner.example similarity index 100% rename from .env.example rename to .env.miner.example diff --git a/.env.validator.example b/.env.validator.example new file mode 100644 index 00000000..d4429700 --- /dev/null +++ b/.env.validator.example @@ -0,0 +1,3 @@ +OPENAI_API_KEY = your_openai_api_key +WANDB_API_KEY = your_wandb_api_key +WANDB_ENTITY_NAME = your_wandb_entity_name \ No newline at end of file diff --git a/.gitignore b/.gitignore index a169e7c9..8528596b 100644 --- a/.gitignore +++ b/.gitignore @@ -168,7 +168,8 @@ testing/ temp.txt # env -.env +.env.miner +.env.validator # test test.py diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index 5b09a02f..f113b64f 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -1,6 +1,6 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# Copyright © 2024 pycorn0729 +# Copyright © 2024 pycorn0729, Sangar # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation @@ -15,9 +15,8 @@ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -from dotenv import load_dotenv - -load_dotenv() +from dotenv import load_dotenv, find_dotenv +load_dotenv(find_dotenv(filename=".env.miner")) import time diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 389e27d0..3b83e259 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -1,10 +1,12 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao -# Copyright © 2024 pycorn +# Copyright © 2024 pycorn, Sangar import bittensor as bt import asyncio -from dotenv import load_dotenv -load_dotenv() + +from dotenv import load_dotenv, find_dotenv +load_dotenv(find_dotenv(filename=".env.validator")) + from typing import Tuple, Union from webgenie.base.validator import BaseValidatorNeuron From e7060d57e342ebe5783b91e07ac39029e71aa5af Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 08:28:07 -0600 Subject: [PATCH 053/554] docs: updated requirements.txt --- requirements.txt | 147 ++--------------------------------------------- 1 file changed, 4 insertions(+), 143 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4d9dca97..a86bab75 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,158 +1,19 @@ -aiohappyeyeballs==2.4.4 -aiosignal==1.3.1 -annotated-types==0.7.0 -ansible==8.5.0 -ansible-core==2.15.13 ansible-vault==2.1.0 -anyio==4.7.0 -attrs==24.2.0 -backoff==2.2.1 -base58==2.1.1 beautifulsoup4==4.12.3 +bert-score==0.3.13 bittensor==8.4.5 -certifi==2024.7.4 -cffi==1.17.1 -charset-normalizer==3.4.0 -click==8.1.7 -colorama==0.4.6 +clip==1.0 colormath==3.0.0 -contourpy==1.3.1 -cryptography~=43.0.1 -cycler==0.12.1 -cytoolz==1.0.0 ddt==1.6.0 -decorator==5.1.1 -distro==1.9.0 -docker-pycreds==0.4.0 -ecdsa==0.19.0 -eth-hash==0.7.0 -eth-keys==0.6.0 -eth-typing==5.0.1 -eth-utils==2.2.2 -fastapi==0.110.3 -filelock==3.16.1 -fonttools==4.55.3 -frozenlist==1.5.0 -fsspec==2024.10.0 -ftfy==6.3.1 -fuzzywuzzy==0.18.0 -gitdb==4.0.11 -GitPython==3.1.43 -greenlet==3.1.1 -h11==0.14.0 -httpcore==1.0.7 -httpx==0.28.1 -idna==3.10 -iniconfig==2.0.0 -Jinja2==3.1.4 -jiter==0.8.2 -joblib==1.4.2 -jsonpatch==1.33 -jsonpointer==3.0.0 -kiwisolver==1.4.7 langchain==0.3.11 -langchain-core==0.3.24 langchain-openai==0.2.12 -langchain-text-splitters==0.3.2 -langsmith==0.2.3 -Levenshtein==0.26.1 -markdown-it-py==3.0.0 -MarkupSafe==3.0.2 -matplotlib==3.9.3 matplotlib-inline==0.1.7 -mdurl==0.1.2 -more-itertools==10.5.0 -mpmath==1.3.0 -msgpack==1.1.0 -msgpack-numpy-opentensor==0.5.0 -multidict==6.1.0 -munch==2.5.0 -nest-asyncio==1.6.0 -netaddr==1.3.0 -networkx==3.4.2 -numpy~=2.0.1 -nvidia-cublas-cu12==12.4.5.8 -nvidia-cuda-cupti-cu12==12.4.127 -nvidia-cuda-nvrtc-cu12==12.4.127 -nvidia-cuda-runtime-cu12==12.4.127 -nvidia-cudnn-cu12==9.1.0.70 -nvidia-cufft-cu12==11.2.1.3 -nvidia-curand-cu12==10.3.5.147 -nvidia-cusolver-cu12==11.6.1.9 -nvidia-cusparse-cu12==12.3.1.170 -nvidia-nccl-cu12==2.21.5 -nvidia-nvjitlink-cu12==12.4.127 -nvidia-nvtx-cu12==12.4.127 -openai==1.57.2 -openai-clip==1.0.1 +open-clip-torch==2.29.0 opencv-python==4.10.0.84 -orjson==3.10.12 -packaging==24.2 -password-strength==0.0.3.post2 -pillow==11.0.0 -platformdirs==4.3.6 +pip-chill==1.0.3 playwright==1.49.1 -pluggy==1.5.0 -propcache==0.2.1 -protobuf==5.29.1 -psutil==6.1.0 -py==1.11.0 -py-ed25519-zebra-bindings==1.2.0 -py-sr25519-bindings==0.2.1 -pycparser==2.22 -pycryptodome==3.21.0 -pydantic==2.10.3 -pydantic_core==2.27.1 -pyee==12.0.0 -Pygments==2.18.0 -PyNaCl==1.5.0 -pyparsing==3.2.0 -pytest==8.3.4 -python-dateutil==2.9.0.post0 python-dotenv==1.0.1 -python-Levenshtein==0.26.1 -python-statemachine==2.5.0 -PyYAML==6.0.2 -RapidFuzz==3.10.1 -regex==2024.11.6 -requests==2.32.3 -requests-toolbelt==1.0.0 -resolvelib==1.0.1 -retry==0.9.2 -rich==13.9.4 -scalecodec==1.2.11 scikit-learn==1.6.0 -scipy==1.14.1 -sentry-sdk==2.19.2 -setproctitle==1.3.4 -setuptools~=70.0.0 shtab==1.6.5 -six==1.17.0 -smmap==5.0.1 -sniffio==1.3.1 -soupsieve==2.6 -SQLAlchemy==2.0.36 -starlette==0.37.2 -substrate-interface==1.7.11 -sympy==1.13.1 -tenacity==9.0.0 -termcolor==2.5.0 -threadpoolctl==3.5.0 -tiktoken==0.8.0 tinycss2==1.4.0 -toolz==1.0.0 -torch==2.5.1 -torchvision==0.20.1 -tqdm==4.67.1 -traitlets==5.14.3 -triton==3.1.0 -typing_extensions==4.12.2 -urllib3==2.2.3 -uvicorn==0.32.1 wandb==0.19.0 -wcwidth==0.2.13 -webencodings==0.5.1 -websocket-client==1.8.0 -wheel==0.45.1 -xxhash==3.5.0 -yarl==1.18.3 From d014476690397e39e69441672d0b098b89d7e80a Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 08:44:56 -0600 Subject: [PATCH 054/554] chore: updated beautify_html func --- webgenie/helpers/htmls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index eef31b65..24be1335 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -28,7 +28,7 @@ def html_to_screenshot(html: str) -> str: def beautify_html(html: str) -> str: soup = BeautifulSoup(html, 'html.parser') - return str(soup) + return soup.prettify() def replace_image_sources(html_content, new_url = PLACE_HOLDER_IMAGE_URL): soup = BeautifulSoup(html_content, 'html.parser') From 7a07cce175826b846b36fe022f65b682102bd9e6 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 09:00:57 -0600 Subject: [PATCH 055/554] chore: updated imagetaskgenerator --- webgenie/tasks/image_task_generator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index d7e937d5..990fcd8f 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -3,7 +3,7 @@ import random from typing import List, Tuple -from webgenie.helpers.htmls import html_to_screenshot +from webgenie.helpers.htmls import html_to_screenshot, preprocess_html from webgenie.protocol import WebgenieImageSynapse from webgenie.tasks.solution import Solution from webgenie.tasks.task import Task, ImageTask @@ -23,10 +23,11 @@ def __init__(self): async def generate_task(self) -> Tuple[Task, bt.Synapse]: dataset_entry = await random.choice(self.datasets).generate_context() - base64_image = html_to_screenshot(dataset_entry.ground_truth_html) + ground_truth_html = preprocess_html(dataset_entry.ground_truth_html) + base64_image = html_to_screenshot(ground_truth_html) return ImageTask( base64_image=base64_image, - ground_truth_html=dataset_entry.ground_truth_html, + ground_truth_html=ground_truth_html, timeout=50, generator=self, ), WebgenieImageSynapse(base64_image=base64_image) From 6e1818b0408051ab0075fea4fae63f12e372683a Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 09:28:45 -0600 Subject: [PATCH 056/554] chore: added exception handling --- neurons/miners/openai_miner.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/neurons/miners/openai_miner.py b/neurons/miners/openai_miner.py index c7d2f1f5..7402f77a 100644 --- a/neurons/miners/openai_miner.py +++ b/neurons/miners/openai_miner.py @@ -45,8 +45,9 @@ async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynaps synapse.html = html_response["html"] return synapse - except: + except Exception as e: bt.logging.error(f"Error in OpenaiMiner forward_text: {e}") + synapse.html = f"Error in OpenaiMiner forward_text: {e}" return synapse async def forward_image(self, synapse: WebgenieImageSynapse) -> WebgenieImageSynapse: @@ -84,4 +85,5 @@ async def forward_image(self, synapse: WebgenieImageSynapse) -> WebgenieImageSyn return synapse except Exception as e: bt.logging.error(f"Error in OpenaiMiner forward_image: {e}") + synapse.html = f"Error in OpenaiMiner forward_image: {e}" return synapse \ No newline at end of file From b2b18b37ffb715e343f267e590662cd0e86cf423 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 09:29:40 -0600 Subject: [PATCH 057/554] chore: removed logging --- neurons/validators/genie_validator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 031eaf5e..dae46aee 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -84,7 +84,6 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag axon = self.neuron.metagraph.axons[best_miner_uid] async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: - bt.logging.info(f"Dendrite: {dendrite}") responses = await dendrite( axons=[axon], synapse=synapse, From 6313c10593c0b658a5364b04c82b7a3c3152789a Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 09:38:56 -0600 Subject: [PATCH 058/554] chore: added seperate_html_css --- webgenie/helpers/htmls.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 24be1335..4517bcac 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -11,6 +11,25 @@ ) from webgenie.helpers.images import image_to_base64 +def seperate_html_css(html_content: str): + soup = BeautifulSoup(html_content, 'lxml') + + css = '' + for style_tag in soup.find_all('style'): + css += style_tag.get_text() + for style_tag in soup.find_all('style'): + style_tag.decompose() + + head = soup.head + if not head: + head = soup.new_tag('head') + soup.html.insert(0, head) + + link_tag = soup.new_tag('link', rel='stylesheet', href='styles.css') + head.append(link_tag) + cleaned_html = str(soup) + return cleaned_html, css + def html_to_screenshot(html: str) -> str: html_path = f"{WORK_DIR}/screenshot_{uuid.uuid4()}.html" with open(html_path, "w") as f: From 0a465bbd6374527486dc6f94f171786fceb017aa Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 09:53:36 -0600 Subject: [PATCH 059/554] chore: added html validity check --- neurons/validators/genie_validator.py | 4 +++- webgenie/helpers/htmls.py | 24 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index dae46aee..23f15fb5 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -103,5 +103,7 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag async def process_synapse(self, synapse: bt.Synapse) -> bt.Synapse: if synapse.dendrite.status_code == 200: synapse.html = preprocess_html(synapse.html) + if synapse.html == "": + return None return synapse - return None \ No newline at end of file + return None diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 4517bcac..d0fbbcfa 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -1,5 +1,6 @@ import os from bs4 import BeautifulSoup +from lxml import etree import time import re import uuid @@ -11,6 +12,14 @@ ) from webgenie.helpers.images import image_to_base64 +def is_valid_html(html_code: str): + try: + parser = etree.XMLParser(recover=False) + etree.fromstring(html_code, parser) + return True + except etree.XMLSyntaxError as e: + return False + def seperate_html_css(html_content: str): soup = BeautifulSoup(html_content, 'lxml') @@ -73,6 +82,19 @@ def replace_image_sources(html_content, new_url = PLACE_HOLDER_IMAGE_URL): return str(soup) def preprocess_html(html: str) -> str: + if not is_valid_html(html): + return "" html = beautify_html(html) html = replace_image_sources(html) - return html \ No newline at end of file + return html + +if __name__ == "__main__": + html = """ + + +

Hello, World!

+ + + """ + + print(preprocess_html(html)) \ No newline at end of file From 514a1e846ab7cd459d1ae9cb1464cbe9b66565e5 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 10:40:22 -0600 Subject: [PATCH 060/554] chore: removed unneccessary file --- .dependencies_installed | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .dependencies_installed diff --git a/.dependencies_installed b/.dependencies_installed deleted file mode 100644 index e69de29b..00000000 From ce4ffaf6a01e6113ef46113482375b8bab05c8f8 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 10:44:35 -0600 Subject: [PATCH 061/554] updated gitignore --- .gitignore | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 8528596b..2cda507a 100644 --- a/.gitignore +++ b/.gitignore @@ -177,8 +177,9 @@ test.py # Wandb wandb/ -# test images -original.jpg - # work dir -work/ \ No newline at end of file +work/ + +# scripts +run_miner.sh +run_validator.sh \ No newline at end of file From 45b3dd05264271d0876a55c22c80fe7e01930672 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 13 Dec 2024 10:49:11 -0600 Subject: [PATCH 062/554] removed run scripts --- run_miner.sh | 4 ---- run_validator.sh | 3 --- 2 files changed, 7 deletions(-) delete mode 100644 run_miner.sh delete mode 100644 run_validator.sh diff --git a/run_miner.sh b/run_miner.sh deleted file mode 100644 index e53ded1a..00000000 --- a/run_miner.sh +++ /dev/null @@ -1,4 +0,0 @@ -export PYTHONPATH=. -#--axon.port 5555 -python neurons/miners/miner.py --netuid 214 --subtensor.network test --wallet.name s-miner --wallet.hotkey miner1 --logging.debug --axon.port 8090 - diff --git a/run_validator.sh b/run_validator.sh deleted file mode 100644 index 277a7ac0..00000000 --- a/run_validator.sh +++ /dev/null @@ -1,3 +0,0 @@ -export PYTHONASYNCIODEBUG=1 -export PYTHONPATH=. -python neurons/validators/validator.py --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug --neuron.axon_port 8091 From 6f850031206b0338eb712b51fdc6de89028e6daa Mon Sep 17 00:00:00 2001 From: cardoso-topdev Date: Fri, 13 Dec 2024 11:59:24 -0600 Subject: [PATCH 063/554] feat: update readme for text prompted html generation model --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index e6d99564..c45dcfcd 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ The WebGenieAI subnet incentivizes miners and validators to ensure high-quality ## Evaluation Process +1) Image to HTML Model + ### Automatic evaluation of ImageToHTML task for design-wise We automatically evaluate generated webpages by calculating the similarity between the original input image and the rendered screenshot of generated webpage. We break down the evaluation into both high-level visual similarity and low-level element matching. @@ -84,6 +86,20 @@ blocks are, the lower this score is. - Color: We use the [CIEDE2000](https://en.wikipedia.org/wiki/Color_difference) color difference formula to assess the perceptual difference between the colors of the generated text in block $g_q$ and the reference text in block $r_p$, denoted as **sim**color(rp, gq), where the formula considers the complexities of human color vision. The overall score is averaged across all matched pairs. +2) Text Prompt to Html Model + +### Unsupervised Evaluation of Model by Round-Trip Correctness +We draw inspiration from a software testing technique known as property-based testing. It allows defining properties that must hold between inputs and outputs of a program (e.g., all items in the input list must also appear in the output list) Round-trip correctness is one such property (e.g., compressing and subsequently decompressing data must yield the original data). + +Consider two forms of data X and Y, such as text prompt and HTML and two (probabilistic) models whose task is to “translate” from one form of data to the other, i.e., a forward model M : X → Y and a backward model M-1: Y → X. These models could be a single LLM prompted differently. + +The central idea for unsupervised evaluation is the concept of round-trip correctness (RTC). Intuitively, for a “good” forward and backward model we expect ̂x =M-1 M(x) to be semantically equivalent to x. For example, we can describe the HTML code with text prompt in the forward pass and then generate back the code from the text prompt. To compute RTC we need some function sim(x, ̂x) that estimates the semantic equivalence between the original x and each predicted sample ̂x. Such functions may include discrete or continuous metrics such as exact match, BLEU and so on. + +### Supervised Evaluation of Model by CodeBERTScore +Let x is prompt, y is the ground truth html, ̂y is the generated html. +To evaluate the performance of the model, we can use [CodeBERTScore](https://github.com/neulab/code-bert-score). sim(y, ̂y ) = bert_score(y, ̂y) +CodeBERTScore is an evaluation metric for code generation, which builds on BERTScore. Instead of encoding only the generated tokens as in [BERTScore](https://huggingface.co/spaces/evaluate-metric/bertscore), CodeBERTScore also encodes the natural language input preceding the generated code, thus modeling the consistency between the generated code and its given natural language context as well. + ### Example Scenario From 5a2d2cfd0c91cf134de79fcea62ad5c23c74e086 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 14 Dec 2024 05:55:10 -0600 Subject: [PATCH 064/554] feat: added synthetic dataset --- webgenie/datasets/synthetic_dataset.py | 64 ++++++++++++++++++++++++++ webgenie/prompts.py | 24 +++++++++- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 webgenie/datasets/synthetic_dataset.py diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py new file mode 100644 index 00000000..6c807253 --- /dev/null +++ b/webgenie/datasets/synthetic_dataset.py @@ -0,0 +1,64 @@ +# The paper [Unlocking the conversion of Web Screenshots into HTML Code with the WebSight Dataset](https://arxiv.org/pdf/2403.09029v1#bib.bib5) is our inspiration. +# We should use Mistral-7B-Instruct to generate concepts and use Deepseek-Coder-33b-instruct to generate html, but now we are using openai models here. + +import bittensor as bt +import os +from typing import List + +from langchain_openai import ChatOpenAI +from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate +from langchain_core.output_parsers import JsonOutputParser +from langchain_core.pydantic_v1 import BaseModel, Field + +from webgenie.datasets.dataset import Dataset, DatasetEntry +from webgenie.prompts import PROMPT_GEN_CONCEPT, PROMPT_GEN_HTML + +class ConceptResponse(BaseModel): + concepts: List[str] = Field(description="The concept of the website") + +class HTMLResponse(BaseModel): + html: str = Field(description="The html code of the website") + +class SyntheticDataset(Dataset): + def __init__(self): + self.model = ChatOpenAI( + api_key= os.getenv("OPENAI_API_KEY"), + model_name="gpt-4o", + temperature=0.6, + ) + + self.concept_parser = JsonOutputParser(pydantic_object=ConceptResponse) + self.html_parser = JsonOutputParser(pydantic_object=HTMLResponse) + + async def _generate_concepts(self): + prompt = ChatPromptTemplate.from_messages([ + ("system", PROMPT_GEN_CONCEPT), + ]) + chain = prompt | self.model | self.concept_parser + response = chain.invoke({ + "instructions": self.concept_parser.get_format_instructions() + }) + return response["concepts"] + + async def _generate_html(self, concept: str): + prompt = ChatPromptTemplate.from_messages([ + ("system", PROMPT_GEN_HTML), + ]) + chain = prompt | self.model | self.html_parser + response = chain.invoke({ + "concept": concept, + "instructions": self.html_parser.get_format_instructions() + }) + return response["html"] + + async def generate_context(self)->DatasetEntry: + if len(self.concepts) == 0: + self.concepts = await self._generate_concepts() + + concept = self.concepts.pop(0) + ground_truth_html = await self._generate_html(concept) + return DatasetEntry( + src="synthetic", + prompt=concept, + ground_truth_html=ground_truth_html, + ) diff --git a/webgenie/prompts.py b/webgenie/prompts.py index 35058ec7..ed29915e 100644 --- a/webgenie/prompts.py +++ b/webgenie/prompts.py @@ -9,4 +9,26 @@ {prompt} {instructions} -""" \ No newline at end of file +""" + +PROMPT_GEN_CONCEPT = """ +Generate diverse website layout ideas for different companies, each with a unique design element. +Examples include: a car company site with a left column, a webpage footer with a centered logo. +Explore variations in colors, positions, and company fields. Don’t give any explanations or recognition that you have understood the request, just give the list of 10 ideas, with a line break between +each. +{instructions} +""" + +PROMPT_GEN_HTML = """ +Code a complete website with a good design in HTML and Tailwind CSS about this: {concept} +Write the code inside a tag . +Write real and long sentences about the business. NEVER USE sentences starting with Lorem +ipsum, NEVER. +You don’t have to include images, but if you do, use only this source +"https://source.unsplash.com/random/WxH/?keyword", by replacing ‘W‘ and ‘H‘ in the URL +by the desired width and height, and ‘?keyword‘ by a keyword describing the picture, for example +"https://source.unsplash.com/random/300x200/?gym" for an image about gym of size 300x200, or +"https://source.unsplash.com/random/100x200/?cake" for an image of a cake of size 100x200. + +{instructions} +""" From 6fbeeb5f0ba2cd159488cd505815d951c8d702c5 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 14 Dec 2024 05:58:20 -0600 Subject: [PATCH 065/554] refactor: moved mockup dataset to seperate file --- webgenie/datasets/dataset.py | 263 --------------------------- webgenie/datasets/mockup_dataset.py | 264 ++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+), 263 deletions(-) create mode 100644 webgenie/datasets/mockup_dataset.py diff --git a/webgenie/datasets/dataset.py b/webgenie/datasets/dataset.py index 93c4ab12..8b8cf31b 100644 --- a/webgenie/datasets/dataset.py +++ b/webgenie/datasets/dataset.py @@ -10,266 +10,3 @@ class DatasetEntry(BaseModel): class Dataset: async def generate_context(self)->DatasetEntry: pass - -class MockUpDataset(Dataset): - async def generate_context(self)->DatasetEntry: - html = """ - - - - - -Tech Company - - - - -
-
- - -
-
-
-Hero Image -
-
-

Welcome to Tech Company

-

At Tech Company, we are dedicated to providing the best technology solutions for your needs. Our team of experts is always ready to help you with any questions or problems you may have.

-
- - - - """ - return DatasetEntry( - src="mockup", - topic="tech company", - ground_truth_html=html, - prompt="", - base64_image="" - ) - -class MockUpPromptDataset(Dataset): - async def generate_context(self)->DatasetEntry: - html = """ - - - - - - Coming Soon - - - - - -
- -
- - -
-

Coming Soon!

-

We're working hard to launch something amazing. Stay tuned!

- -
- - - - - - - -""" - return DatasetEntry( - src="mockup", - topic="tech company", - ground_truth_html=html, - prompt="CommingSoon Page with goback button, navHeader, and footer", - base64_image="" - ) \ No newline at end of file diff --git a/webgenie/datasets/mockup_dataset.py b/webgenie/datasets/mockup_dataset.py new file mode 100644 index 00000000..09b53ef1 --- /dev/null +++ b/webgenie/datasets/mockup_dataset.py @@ -0,0 +1,264 @@ +from dataset import Dataset, DatasetEntry + +class MockUpDataset(Dataset): + async def generate_context(self)->DatasetEntry: + html = """ + + + + + +Tech Company + + + + +
+
+ + +
+
+
+Hero Image +
+
+

Welcome to Tech Company

+

At Tech Company, we are dedicated to providing the best technology solutions for your needs. Our team of experts is always ready to help you with any questions or problems you may have.

+
+ + + + """ + return DatasetEntry( + src="mockup", + topic="tech company", + ground_truth_html=html, + prompt="", + base64_image="" + ) + +class MockUpPromptDataset(Dataset): + async def generate_context(self)->DatasetEntry: + html = """ + + + + + + Coming Soon + + + + + +
+ +
+ + +
+

Coming Soon!

+

We're working hard to launch something amazing. Stay tuned!

+ +
+ + + + + + + +""" + return DatasetEntry( + src="mockup", + topic="tech company", + ground_truth_html=html, + prompt="CommingSoon Page with goback button, navHeader, and footer", + base64_image="" + ) \ No newline at end of file From 4b0b59a40a72b7b917d7ea49e1909e69a6f97621 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 14 Dec 2024 05:59:44 -0600 Subject: [PATCH 066/554] style: removed unneccessary imports --- webgenie/datasets/synthetic_dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index 6c807253..b3e6e74a 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -6,7 +6,7 @@ from typing import List from langchain_openai import ChatOpenAI -from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate +from langchain.prompts import ChatPromptTemplate from langchain_core.output_parsers import JsonOutputParser from langchain_core.pydantic_v1 import BaseModel, Field From 9b1a00615bd1641f6fac5b2f49addaa017600276 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 14 Dec 2024 06:05:41 -0600 Subject: [PATCH 067/554] feat: added synthetic dataset to image task generator --- webgenie/tasks/image_task_generator.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 990fcd8f..8b6590e7 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -9,7 +9,8 @@ from webgenie.tasks.task import Task, ImageTask from webgenie.tasks.task_generator import TaskGenerator from webgenie.rewards.visual_reward import VisualReward -from webgenie.datasets.dataset import MockUpDataset +from webgenie.datasets.mockup_dataset import MockUpDataset +from webgenie.datasets.synthetic_dataset import SyntheticDataset class ImageTaskGenerator(TaskGenerator): def __init__(self): @@ -18,12 +19,16 @@ def __init__(self): (VisualReward(), 1.0) ] self.datasets = [ - MockUpDataset() + MockUpDataset(), + SyntheticDataset() ] async def generate_task(self) -> Tuple[Task, bt.Synapse]: dataset_entry = await random.choice(self.datasets).generate_context() ground_truth_html = preprocess_html(dataset_entry.ground_truth_html) + if ground_truth_html == "": + raise ValueError("Invalid ground truth html") + base64_image = html_to_screenshot(ground_truth_html) return ImageTask( base64_image=base64_image, From 548dde3b15a8452a39c7608608bc4cd5991b2973 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 14 Dec 2024 06:28:17 -0600 Subject: [PATCH 068/554] feat: updated synthetic dataset --- .gitignore | 5 +++- webgenie/datasets/__init__.py | 3 +++ webgenie/datasets/synthetic_dataset.py | 11 +++++++-- webgenie/rewards/bert_reward.py | 4 ++- webgenie/tasks/text_task_generator.py | 34 ++++++++++++++++++-------- 5 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 webgenie/datasets/__init__.py diff --git a/.gitignore b/.gitignore index 2cda507a..b92ec6a6 100644 --- a/.gitignore +++ b/.gitignore @@ -182,4 +182,7 @@ work/ # scripts run_miner.sh -run_validator.sh \ No newline at end of file +run_validator.sh + +# developer doc +developer_doc.md \ No newline at end of file diff --git a/webgenie/datasets/__init__.py b/webgenie/datasets/__init__.py new file mode 100644 index 00000000..4e1069f2 --- /dev/null +++ b/webgenie/datasets/__init__.py @@ -0,0 +1,3 @@ +from .dataset import Dataset, DatasetEntry +from .synthetic_dataset import SyntheticDataset +from .mockup_dataset import MockUpDataset, MockUpPromptDataset diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index b3e6e74a..f3016ecb 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -20,7 +20,9 @@ class HTMLResponse(BaseModel): html: str = Field(description="The html code of the website") class SyntheticDataset(Dataset): - def __init__(self): + def __init__(self, has_ground_truth_html: bool = True): + self.has_ground_truth_html = has_ground_truth_html + self.model = ChatOpenAI( api_key= os.getenv("OPENAI_API_KEY"), model_name="gpt-4o", @@ -56,7 +58,12 @@ async def generate_context(self)->DatasetEntry: self.concepts = await self._generate_concepts() concept = self.concepts.pop(0) - ground_truth_html = await self._generate_html(concept) + + if self.has_ground_truth_html == True: + ground_truth_html = await self._generate_html(concept) + else: + ground_truth_html = "" + return DatasetEntry( src="synthetic", prompt=concept, diff --git a/webgenie/rewards/bert_reward.py b/webgenie/rewards/bert_reward.py index 264e5e47..6afc1013 100644 --- a/webgenie/rewards/bert_reward.py +++ b/webgenie/rewards/bert_reward.py @@ -13,7 +13,9 @@ def __init__(self): async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: bt.logging.debug(f"Rewarding task in bert reward") - + if task.ground_truth_html == "": + raise ValueError(f"Ground truth html is empty") + original_htmls= [] miner_htmls = [] diff --git a/webgenie/tasks/text_task_generator.py b/webgenie/tasks/text_task_generator.py index 65c89128..0d06cfcf 100644 --- a/webgenie/tasks/text_task_generator.py +++ b/webgenie/tasks/text_task_generator.py @@ -2,8 +2,10 @@ import numpy as np import random from typing import List, Tuple - -from webgenie.datasets.dataset import MockUpPromptDataset +from webgenie.datasets import ( + MockUpPromptDataset, + SyntheticDataset, +) from webgenie.protocol import WebgenieTextSynapse from webgenie.rewards.bert_reward import BertReward from webgenie.rewards.rtc_reward import RtcReward @@ -11,15 +13,27 @@ from webgenie.tasks.task_generator import TaskGenerator class TextTaskGenerator(TaskGenerator): - def __init__(self): + def __init__(self, has_ground_truth_html: bool = True): super().__init__() - self.rewards = [ - (BertReward(), 0.5), - (RtcReward(), 0.5) - ] - self.datasets = [ - MockUpPromptDataset() - ] + if has_ground_truth_html: + self.rewards = [ + (BertReward(), 0.5), + (RtcReward(), 0.5) + ] + + self.datasets = [ + MockUpPromptDataset(), + SyntheticDataset(has_ground_truth_html = True) + ] + else: + self.rewards = [ + (RtcReward(), 1.0) + ] + + self.datasets = [ + MockUpPromptDataset(), + SyntheticDataset(has_ground_truth_html = False) + ] async def generate_task(self) -> Tuple[Task, bt.Synapse]: dataset_entry = await random.choice(self.datasets).generate_context() From 7fd16092b1a9cf01c1dfe3634f78f41c23a0b41b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 14 Dec 2024 06:41:25 -0600 Subject: [PATCH 069/554] fix: fixed bugs in import --- webgenie/datasets/mockup_dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/datasets/mockup_dataset.py b/webgenie/datasets/mockup_dataset.py index 09b53ef1..f480a504 100644 --- a/webgenie/datasets/mockup_dataset.py +++ b/webgenie/datasets/mockup_dataset.py @@ -1,4 +1,4 @@ -from dataset import Dataset, DatasetEntry +from webgenie.datasets.dataset import Dataset, DatasetEntry class MockUpDataset(Dataset): async def generate_context(self)->DatasetEntry: From 78cb9adf9d7e7d8e659cfa7192834f37850363b3 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 14 Dec 2024 06:51:51 -0600 Subject: [PATCH 070/554] docs: added paper link --- neurons/validators/validator.py | 4 ++-- webgenie/rewards/rtc_reward.py | 3 +++ webgenie/rewards/visual_reward.py | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 3b83e259..e164e09a 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -116,8 +116,8 @@ async def scoring_loop(self): await asyncio.sleep(5) async def __aenter__(self): - #self.loop.create_task(self.forward_loop()) - #self.loop.create_task(self.scoring_loop()) + self.loop.create_task(self.forward_loop()) + self.loop.create_task(self.scoring_loop()) self.is_running = True bt.logging.debug("Starting validator in background thread") return self diff --git a/webgenie/rewards/rtc_reward.py b/webgenie/rewards/rtc_reward.py index 69045cb2..25b7ba9a 100644 --- a/webgenie/rewards/rtc_reward.py +++ b/webgenie/rewards/rtc_reward.py @@ -1,3 +1,6 @@ +# The paper [Unsupervised Evaluation of Code LLMs with Round-Trip Correctness] +# (https://arxiv.org/pdf/2402.08699#page=11&zoom=100,384,458) is our inspiration for this reward. + import bittensor as bt import bert_score import os diff --git a/webgenie/rewards/visual_reward.py b/webgenie/rewards/visual_reward.py index d4eef431..3a676ed1 100644 --- a/webgenie/rewards/visual_reward.py +++ b/webgenie/rewards/visual_reward.py @@ -1,3 +1,6 @@ +# The paper [Design2Code: Benchmarking Multimodal Code Generation for Automated Front-End Engineering] +# (https://arxiv.org/pdf/2403.03163) is our inspiration for this reward. + import bittensor as bt import numpy as np from typing import List From ad4cef93596eea6488758d977f1e4e7863256ca9 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 14 Dec 2024 06:55:26 -0600 Subject: [PATCH 071/554] docs: updated comment --- webgenie/datasets/synthetic_dataset.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index f3016ecb..2e517cd4 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -1,5 +1,7 @@ -# The paper [Unlocking the conversion of Web Screenshots into HTML Code with the WebSight Dataset](https://arxiv.org/pdf/2403.09029v1#bib.bib5) is our inspiration. -# We should use Mistral-7B-Instruct to generate concepts and use Deepseek-Coder-33b-instruct to generate html, but now we are using openai models here. +# The paper [Unlocking the conversion of Web Screenshots into HTML Code with the WebSight Dataset] +# (https://arxiv.org/pdf/2403.09029v1#bib.bib5) is our inspiration. +# The paper suggests using Mistral-7B-Instruct to generate concepts and use Deepseek-Coder-33b-instruct +# to generate html, but now we are using openai models here. We are going to use that models on the mainnet import bittensor as bt import os From a56f827b51755308df4126232371c8d07691d5e3 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 14 Dec 2024 08:40:48 -0600 Subject: [PATCH 072/554] fix: fixed synthetic data gen --- webgenie/helpers/htmls.py | 11 ++++++----- webgenie/prompts.py | 1 - 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index d0fbbcfa..b217a523 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -1,3 +1,4 @@ +import bittensor as bt import os from bs4 import BeautifulSoup from lxml import etree @@ -12,16 +13,16 @@ ) from webgenie.helpers.images import image_to_base64 -def is_valid_html(html_code: str): +def is_valid_html(html: str): try: - parser = etree.XMLParser(recover=False) - etree.fromstring(html_code, parser) + soup = BeautifulSoup(html, 'html.parser') return True - except etree.XMLSyntaxError as e: + except Exception as e: + bt.logging.debug(f"Error during HTML parsing: {e}") return False def seperate_html_css(html_content: str): - soup = BeautifulSoup(html_content, 'lxml') + soup = BeautifulSoup(html_content, 'html.parser') css = '' for style_tag in soup.find_all('style'): diff --git a/webgenie/prompts.py b/webgenie/prompts.py index ed29915e..f3e7dc69 100644 --- a/webgenie/prompts.py +++ b/webgenie/prompts.py @@ -21,7 +21,6 @@ PROMPT_GEN_HTML = """ Code a complete website with a good design in HTML and Tailwind CSS about this: {concept} -Write the code inside a tag . Write real and long sentences about the business. NEVER USE sentences starting with Lorem ipsum, NEVER. You don’t have to include images, but if you do, use only this source From 1ec8a85f1c1a2c3c30e642565344c2bbcb06f55b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 14 Dec 2024 09:09:57 -0600 Subject: [PATCH 073/554] fix: fixed bugs --- neurons/validators/validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index e164e09a..3b83e259 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -116,8 +116,8 @@ async def scoring_loop(self): await asyncio.sleep(5) async def __aenter__(self): - self.loop.create_task(self.forward_loop()) - self.loop.create_task(self.scoring_loop()) + #self.loop.create_task(self.forward_loop()) + #self.loop.create_task(self.scoring_loop()) self.is_running = True bt.logging.debug("Starting validator in background thread") return self From 2f5dd01ab3b3c5d29b0acd22e82b45af5293a94b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 14 Dec 2024 09:13:06 -0600 Subject: [PATCH 074/554] fix: fixed bugs in synthetic dataset --- neurons/validators/validator.py | 4 ++-- webgenie/datasets/synthetic_dataset.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 3b83e259..e164e09a 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -116,8 +116,8 @@ async def scoring_loop(self): await asyncio.sleep(5) async def __aenter__(self): - #self.loop.create_task(self.forward_loop()) - #self.loop.create_task(self.scoring_loop()) + self.loop.create_task(self.forward_loop()) + self.loop.create_task(self.scoring_loop()) self.is_running = True bt.logging.debug("Starting validator in background thread") return self diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index 2e517cd4..382fd719 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -33,6 +33,7 @@ def __init__(self, has_ground_truth_html: bool = True): self.concept_parser = JsonOutputParser(pydantic_object=ConceptResponse) self.html_parser = JsonOutputParser(pydantic_object=HTMLResponse) + self.concepts = [] async def _generate_concepts(self): prompt = ChatPromptTemplate.from_messages([ From b7edac0068a8acd77458426f79499145821fcd2c Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 14 Dec 2024 10:28:47 -0600 Subject: [PATCH 075/554] style: removed some empty lines --- neurons/miners/miner.py | 3 --- neurons/validators/validator.py | 7 ++++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index f113b64f..ff438169 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -57,19 +57,16 @@ def __init__(self, config=None): init_wandb(self) - async def forward_text( self, synapse: WebgenieTextSynapse ) -> WebgenieTextSynapse: bt.logging.debug(f"Miner text forward called with synapse: {synapse}") - return await self.genie_miner.forward_text(synapse) async def forward_image( self, synapse: WebgenieImageSynapse ) -> WebgenieImageSynapse: bt.logging.debug(f"Miner image forward called with synapse: {synapse}") - return await self.genie_miner.forward_image(synapse) async def blacklist_text(self, synapse: WebgenieTextSynapse) -> typing.Tuple[bool, str]: diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index e164e09a..167973b2 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -1,6 +1,7 @@ # The MIT License (MIT) # Copyright © 2023 Yuma Rao # Copyright © 2024 pycorn, Sangar + import bittensor as bt import asyncio @@ -42,6 +43,7 @@ async def blacklist_text(self, synapse: WebgenieTextSynapse) -> Tuple[bool, str] if synapse.dendrite.hotkey == API_HOTKEY: return False, "Subnet owner hotkey" return True, "Blacklisted" + async def blacklist_image(self, synapse: WebgenieImageSynapse) -> Tuple[bool, str]: """ Only allow the subnet owner to send synapse to the validator. @@ -93,7 +95,6 @@ async def concurrent_forward(self): async def forward_loop(self): self.sync() bt.logging.info(f"Validator starting at block: {self.block}") - while True: try: bt.logging.info(f"step({self.step}) block({self.block})") @@ -116,8 +117,8 @@ async def scoring_loop(self): await asyncio.sleep(5) async def __aenter__(self): - self.loop.create_task(self.forward_loop()) - self.loop.create_task(self.scoring_loop()) + #self.loop.create_task(self.forward_loop()) + #self.loop.create_task(self.scoring_loop()) self.is_running = True bt.logging.debug("Starting validator in background thread") return self From 14accdb0d626af5089fa82413237118c7a110222 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 14 Dec 2024 10:55:40 -0600 Subject: [PATCH 076/554] style: styled --- neurons/validators/genie_validator.py | 16 +++++----------- webgenie/datasets/synthetic_dataset.py | 2 +- webgenie/rewards/bert_reward.py | 2 +- webgenie/tasks/image_task_generator.py | 2 +- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 23f15fb5..3e09c94b 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -37,10 +37,8 @@ async def forward(self): if len(self.synthetic_history) > MAX_SYNTHETIC_HISTORY_SIZE: return - miner_uids = get_random_uids(self.neuron, k=self.config.neuron.sample_size) - + miner_uids = get_random_uids(self.neuron, k=self.config.neuron.sample_size) task, synapse = await random.choice(self.task_generators).generate_task() - all_synapse_results = await self.neuron.dendrite( axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], synapse=synapse, @@ -48,30 +46,27 @@ async def forward(self): ) solutions = [] - for synapse, miner_uid in zip(all_synapse_results, miner_uids): processed_synapse = await self.process_synapse(synapse) if processed_synapse is not None: solutions.append(Solution(html = processed_synapse.html, miner_uid = miner_uid, process_time = processed_synapse.dendrite.process_time)) - if len(solutions) == 0: + if not solutions: bt.logging.warning(f"No valid solutions received") return - - bt.logging.debug(f"Processed solutions: {solutions}") + bt.logging.debug(f"Processed solutions: {solutions}") self.synthetic_history.append((task, solutions)) except Exception as e: bt.logging.error(f"Error in forward: {e}") raise e async def score(self): - if len(self.synthetic_history) == 0: + if not self.synthetic_history: bt.logging.warning(f"No synthetic history to score") return task, solutions = self.synthetic_history.pop(0) - task_generator = task.generator scores = await task_generator.reward(task, solutions) self.neuron.update_scores(scores, [solution.miner_uid for solution in solutions]) @@ -82,7 +77,6 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag best_miner_uid = 1 try: axon = self.neuron.metagraph.axons[best_miner_uid] - async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: responses = await dendrite( axons=[axon], @@ -103,7 +97,7 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag async def process_synapse(self, synapse: bt.Synapse) -> bt.Synapse: if synapse.dendrite.status_code == 200: synapse.html = preprocess_html(synapse.html) - if synapse.html == "": + if not synapse.html: return None return synapse return None diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index 382fd719..19b1e68b 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -57,7 +57,7 @@ async def _generate_html(self, concept: str): return response["html"] async def generate_context(self)->DatasetEntry: - if len(self.concepts) == 0: + if not self.concepts: self.concepts = await self._generate_concepts() concept = self.concepts.pop(0) diff --git a/webgenie/rewards/bert_reward.py b/webgenie/rewards/bert_reward.py index 6afc1013..eda471a4 100644 --- a/webgenie/rewards/bert_reward.py +++ b/webgenie/rewards/bert_reward.py @@ -13,7 +13,7 @@ def __init__(self): async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: bt.logging.debug(f"Rewarding task in bert reward") - if task.ground_truth_html == "": + if not task.ground_truth_html: raise ValueError(f"Ground truth html is empty") original_htmls= [] diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 8b6590e7..ee3dc119 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -26,7 +26,7 @@ def __init__(self): async def generate_task(self) -> Tuple[Task, bt.Synapse]: dataset_entry = await random.choice(self.datasets).generate_context() ground_truth_html = preprocess_html(dataset_entry.ground_truth_html) - if ground_truth_html == "": + if not ground_truth_html : raise ValueError("Invalid ground truth html") base64_image = html_to_screenshot(ground_truth_html) From b79d74a9cb956a77f723fdf1b1c1300581bb026c Mon Sep 17 00:00:00 2001 From: cardoso-topdev Date: Mon, 16 Dec 2024 07:24:09 -0600 Subject: [PATCH 077/554] feat: add workflow diagram --- README.md | 11 +++-------- docs/webgenie-workflow.png | Bin 0 -> 334436 bytes 2 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 docs/webgenie-workflow.png diff --git a/README.md b/README.md index 90d29cb8..25d73d70 100644 --- a/README.md +++ b/README.md @@ -67,14 +67,7 @@ Based on the two sets of detected blocks, we use the Jonker-Volgenant algorithm Given R, G, and matched pairs in M, we evaluate similarity along the following aspects: - **Block-Match**: The first desideratum of the task is that all visual elements from the image should be reproduced in the generated webpage, and the generated webpage should not hallucinate non-existent new elements. We measure this by computing the total sizes of all matched blocks divided by the total sizes of all blocks, including unmatched ones (either because the generated webpages missed them or because the generated webpages contain hallucinated blocks): -![Incentive Mechanism Fomula](docs/incentive-fomula.png) - +![Incentive Mechanism Formula](docs/incentive-formula.png "WebGenieAI Incentive Formula") where S(·) returns the size of the blocks, $U_R$ and $U_G$ denotes the unmatched blocks in R and G. The intuition here is that unmatched blocks will lower the score as they indicate @@ -101,6 +94,8 @@ Let x is prompt, y is the ground truth html, ̂y is the generated html. To evaluate the performance of the model, we can use [CodeBERTScore](https://github.com/neulab/code-bert-score). sim(y, ̂y ) = bert_score(y, ̂y) CodeBERTScore is an evaluation metric for code generation, which builds on BERTScore. Instead of encoding only the generated tokens as in [BERTScore](https://huggingface.co/spaces/evaluate-metric/bertscore), CodeBERTScore also encodes the natural language input preceding the generated code, thus modeling the consistency between the generated code and its given natural language context as well. +![Webgenie Subnet workflow](docs/webgenie-workflow.png "WebGenieAI workflow") + ### Example Scenario diff --git a/docs/webgenie-workflow.png b/docs/webgenie-workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..2a74fd929f4b392171ebbf6dc2e470d7f6511ac4 GIT binary patch literal 334436 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 l*O2Yu-l6(g%h}5ZAR^ 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 Date: Mon, 16 Dec 2024 07:29:58 -0600 Subject: [PATCH 078/554] fix: incentive block match scoring formula --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 25d73d70..5944bce7 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Based on the two sets of detected blocks, we use the Jonker-Volgenant algorithm Given R, G, and matched pairs in M, we evaluate similarity along the following aspects: - **Block-Match**: The first desideratum of the task is that all visual elements from the image should be reproduced in the generated webpage, and the generated webpage should not hallucinate non-existent new elements. We measure this by computing the total sizes of all matched blocks divided by the total sizes of all blocks, including unmatched ones (either because the generated webpages missed them or because the generated webpages contain hallucinated blocks): -![Incentive Mechanism Formula](docs/incentive-formula.png "WebGenieAI Incentive Formula") +![Incentive Mechanism Formula](docs/incentive-fomula.png "WebGenieAI Incentive Formula") where S(·) returns the size of the blocks, $U_R$ and $U_G$ denotes the unmatched blocks in R and G. The intuition here is that unmatched blocks will lower the score as they indicate From ee527a2f11a1441b2b98b1ec8db2c75ac262a254 Mon Sep 17 00:00:00 2001 From: cardoso-topdev Date: Mon, 16 Dec 2024 07:33:00 -0600 Subject: [PATCH 079/554] hotfix: hyperlink anchor issue --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5944bce7..dd113af1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Welcome to WebGenieAI Subnet, a pioneering Bittensor-based subnet designed to re - [Overview](#overview) - [Features](#features) -- [Incentive Mechanism](#incentive-mechanism) +- [Incentive Mechanism](#incentive-mechanism-v1) - [Roadmap](#roadmap) ## Overview From 3453113f07385d62a1e200fa092ffe75ad6ae153 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 17 Dec 2024 06:02:54 -0600 Subject: [PATCH 080/554] chore: seperated making tasks loop --- neurons/validators/genie_validator.py | 18 ++++++++++++++++-- neurons/validators/validator.py | 11 +++++++++-- webgenie/constants.py | 3 +++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 3e09c94b..34216cd7 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -3,7 +3,7 @@ from typing import Union from webgenie.base.neuron import BaseNeuron -from webgenie.constants import MAX_SYNTHETIC_HISTORY_SIZE +from webgenie.constants import MAX_SYNTHETIC_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE from webgenie.helpers.htmls import preprocess_html from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.tasks.solution import Solution @@ -16,6 +16,7 @@ def __init__(self, neuron: BaseNeuron): self.neuron = neuron self.config = neuron.config self.synthetic_history = [] + self.synthetic_tasks = [] self.task_generators = [ TextTaskGenerator(), @@ -37,8 +38,11 @@ async def forward(self): if len(self.synthetic_history) > MAX_SYNTHETIC_HISTORY_SIZE: return + if not self.synthetic_tasks: + return + + task, synapse = self.synthetic_tasks.pop(0) miner_uids = get_random_uids(self.neuron, k=self.config.neuron.sample_size) - task, synapse = await random.choice(self.task_generators).generate_task() all_synapse_results = await self.neuron.dendrite( axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], synapse=synapse, @@ -72,6 +76,16 @@ async def score(self): self.neuron.update_scores(scores, [solution.miner_uid for solution in solutions]) self.neuron.sync() + async def synthensize_task(self): + try: + if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: + return + + task, synapse = await random.choice(self.task_generators).generate_task() + self.synthetic_tasks.append((task, synapse)) + except Exception as e: + bt.logging.error(f"Error in synthensize_task: {e}") + async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImageSynapse]): bt.logging.debug(f"Organic forward: {synapse}") best_miner_uid = 1 diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 167973b2..f05af3aa 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -116,9 +116,16 @@ async def scoring_loop(self): await asyncio.sleep(5) + async def synthensize_task_loop(self): + bt.logging.info(f"Synthensize task loop starting") + while True: + await self.genie_validator.synthensize_task() + await asyncio.sleep(5) + async def __aenter__(self): - #self.loop.create_task(self.forward_loop()) - #self.loop.create_task(self.scoring_loop()) + self.loop.create_task(self.synthensize_task_loop()) + self.loop.create_task(self.forward_loop()) + self.loop.create_task(self.scoring_loop()) self.is_running = True bt.logging.debug("Starting validator in background thread") return self diff --git a/webgenie/constants.py b/webgenie/constants.py index 81fa8f6f..898e50ea 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -4,6 +4,9 @@ # max synthetic history size MAX_SYNTHETIC_HISTORY_SIZE = 10 +# max synthensize task size +MAX_SYNTHETIC_TASK_SIZE = 10 + # place holder image url PLACE_HOLDER_IMAGE_URL = "https://picsum.photos/seed/picsum/800/600" From bcd0585db306e4618ee5fda2745022df915b0239 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 17 Dec 2024 06:08:33 -0600 Subject: [PATCH 081/554] chore: reduced sleep time --- neurons/validators/validator.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index f05af3aa..47c97f34 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -103,8 +103,7 @@ async def forward_loop(self): self.step += 1 except Exception as e: bt.logging.error(f"Error during forward loop: {str(e)}") - - await asyncio.sleep(5) + await asyncio.sleep(1) async def scoring_loop(self): bt.logging.info(f"Scoring loop starting") @@ -113,14 +112,16 @@ async def scoring_loop(self): await self.genie_validator.score() except Exception as e: bt.logging.error(f"Error during scoring: {str(e)}") - - await asyncio.sleep(5) + await asyncio.sleep(1) async def synthensize_task_loop(self): bt.logging.info(f"Synthensize task loop starting") while True: - await self.genie_validator.synthensize_task() - await asyncio.sleep(5) + try: + await self.genie_validator.synthensize_task() + except Exception as e: + bt.logging.error(f"Error during synthensize task: {str(e)}") + await asyncio.sleep(1) async def __aenter__(self): self.loop.create_task(self.synthensize_task_loop()) From af12c6f2e59861ec28220512221fa0de428dfcf0 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 17 Dec 2024 06:15:27 -0600 Subject: [PATCH 082/554] chore: updated logging info --- neurons/miners/miner.py | 5 +++-- neurons/miners/openai_miner.py | 1 - webgenie/constants.py | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index ff438169..350a9c70 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -24,6 +24,7 @@ import bittensor as bt from webgenie.base.miner import BaseMinerNeuron +from webgenie.constants import MAX_DEBUG_IMAGE_STRING_LENGTH from webgenie.helpers.weights import init_wandb from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from webgenie.tasks import Solution @@ -60,13 +61,13 @@ def __init__(self, config=None): async def forward_text( self, synapse: WebgenieTextSynapse ) -> WebgenieTextSynapse: - bt.logging.debug(f"Miner text forward called with synapse: {synapse}") + bt.logging.debug(f"Miner text forward called with prompt: {synapse.prompt}") return await self.genie_miner.forward_text(synapse) async def forward_image( self, synapse: WebgenieImageSynapse ) -> WebgenieImageSynapse: - bt.logging.debug(f"Miner image forward called with synapse: {synapse}") + bt.logging.debug(f"Miner image forward called with image: {synapse.base64_image[:MAX_DEBUG_IMAGE_STRING_LENGTH]}") return await self.genie_miner.forward_image(synapse) async def blacklist_text(self, synapse: WebgenieTextSynapse) -> typing.Tuple[bool, str]: diff --git a/neurons/miners/openai_miner.py b/neurons/miners/openai_miner.py index 7402f77a..1c4f4877 100644 --- a/neurons/miners/openai_miner.py +++ b/neurons/miners/openai_miner.py @@ -52,7 +52,6 @@ async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynaps async def forward_image(self, synapse: WebgenieImageSynapse) -> WebgenieImageSynapse: try: - prompt_messages = [ SystemMessagePromptTemplate.from_template(""" You are an expert web developer who specializes in HTML and CSS. diff --git a/webgenie/constants.py b/webgenie/constants.py index 898e50ea..18b47c15 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -7,6 +7,9 @@ # max synthensize task size MAX_SYNTHETIC_TASK_SIZE = 10 +# max debug image string length +MAX_DEBUG_IMAGE_STRING_LENGTH = 20 + # place holder image url PLACE_HOLDER_IMAGE_URL = "https://picsum.photos/seed/picsum/800/600" From d2202432bf7bcb41708808373767f24d2506a978 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 17 Dec 2024 06:20:17 -0600 Subject: [PATCH 083/554] chore: updated logging infos --- neurons/validators/genie_validator.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 34216cd7..72c67112 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -43,6 +43,8 @@ async def forward(self): task, synapse = self.synthetic_tasks.pop(0) miner_uids = get_random_uids(self.neuron, k=self.config.neuron.sample_size) + bt.logging.debug(f"Selected miner uids: {miner_uids}") + all_synapse_results = await self.neuron.dendrite( axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], synapse=synapse, @@ -73,7 +75,11 @@ async def score(self): task, solutions = self.synthetic_history.pop(0) task_generator = task.generator scores = await task_generator.reward(task, solutions) - self.neuron.update_scores(scores, [solution.miner_uid for solution in solutions]) + miner_uids = [solution.miner_uid for solution in solutions] + bt.logging.debug(f"Miner uids: {miner_uids}") + bt.logging.debug(f"Scores: {scores}") + + self.neuron.update_scores(scores, miner_uids) self.neuron.sync() async def synthensize_task(self): From aaf539330e5d355db7d1b5636f139fffe411134d Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 17 Dec 2024 06:22:48 -0600 Subject: [PATCH 084/554] chore: updated logging info --- neurons/miners/miner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index 350a9c70..bb673cee 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -67,7 +67,7 @@ async def forward_text( async def forward_image( self, synapse: WebgenieImageSynapse ) -> WebgenieImageSynapse: - bt.logging.debug(f"Miner image forward called with image: {synapse.base64_image[:MAX_DEBUG_IMAGE_STRING_LENGTH]}") + bt.logging.debug(f"Miner image forward called with image: {synapse.base64_image[:MAX_DEBUG_IMAGE_STRING_LENGTH]}...") return await self.genie_miner.forward_image(synapse) async def blacklist_text(self, synapse: WebgenieTextSynapse) -> typing.Tuple[bool, str]: From ac0d77935776ec7e07b862f52602da68f02cf586 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 17 Dec 2024 08:36:20 -0600 Subject: [PATCH 085/554] feat: added git design2code dataset --- neurons/validators/genie_validator.py | 7 +++- webgenie/datasets/huggingface_dataset.py | 47 ++++++++++++++++++++++++ webgenie/prompts.py | 9 +++++ webgenie/tasks/image_task_generator.py | 6 ++- 4 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 webgenie/datasets/huggingface_dataset.py diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 72c67112..4475d7b4 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -19,7 +19,7 @@ def __init__(self, neuron: BaseNeuron): self.synthetic_tasks = [] self.task_generators = [ - TextTaskGenerator(), + # TextTaskGenerator(), ImageTaskGenerator(), ] @@ -34,13 +34,15 @@ def make_work_dir(self): bt.logging.info(f"Created work directory at {WORK_DIR}") async def forward(self): + bt.logging.debug(f"Forward") try: if len(self.synthetic_history) > MAX_SYNTHETIC_HISTORY_SIZE: return if not self.synthetic_tasks: + bt.logging.debug(f"No synthetic tasks") return - + bt.logging.debug(f"Synthetic tasks: {self.synthetic_tasks}") task, synapse = self.synthetic_tasks.pop(0) miner_uids = get_random_uids(self.neuron, k=self.config.neuron.sample_size) bt.logging.debug(f"Selected miner uids: {miner_uids}") @@ -83,6 +85,7 @@ async def score(self): self.neuron.sync() async def synthensize_task(self): + bt.logging.debug(f"Synthensize task") try: if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: return diff --git a/webgenie/datasets/huggingface_dataset.py b/webgenie/datasets/huggingface_dataset.py new file mode 100644 index 00000000..459201db --- /dev/null +++ b/webgenie/datasets/huggingface_dataset.py @@ -0,0 +1,47 @@ +# https://huggingface.co/datasets/SALT-NLP/Design2Code_human_eval_pairwise + +import bittensor as bt +import random +from datasets import load_dataset + +from langchain_openai import ChatOpenAI +from langchain.prompts import ChatPromptTemplate +from langchain_core.output_parsers import JsonOutputParser +from langchain_core.pydantic_v1 import BaseModel, Field + +from webgenie.datasets.dataset import Dataset, DatasetEntry +from webgenie.prompts import PROMPT_MAKE_HTML_COMPLEX + + +class HTMLResponse(BaseModel): + complex_html: str = Field(description="the complex html code") + +class HuggingfaceDesign2CodeDataset(Dataset): + def __init__(self): + self.dataset = load_dataset("SALT-NLP/Design2Code_human_eval_pairwise", split="train") + self.model = ChatOpenAI(model="gpt-4o-mini") + self.output_parser = JsonOutputParser(pydantic_object=HTMLResponse) + + async def _make_html_complex(self, html: str)->str: + prompt = ChatPromptTemplate.from_messages([ + ("system", PROMPT_MAKE_HTML_COMPLEX), + ]) + chain = prompt | self.model | self.output_parser + response = chain.invoke({ + "html": html, + "instructions": self.output_parser.get_format_instructions() + }) + return response["complex_html"] + + async def generate_context(self)->DatasetEntry: + random_index = random.randint(0, len(self.dataset) - 1) + html = self.dataset[random_index]["ref_html"] + complex_html = await self._make_html_complex(html) + bt.logging.debug(f"Complex HTML: {complex_html}") + return DatasetEntry( + src="huggingface", + topic="design2code", + ground_truth_html=complex_html, + prompt="", + base64_image="" + ) diff --git a/webgenie/prompts.py b/webgenie/prompts.py index f3e7dc69..c4b89048 100644 --- a/webgenie/prompts.py +++ b/webgenie/prompts.py @@ -31,3 +31,12 @@ {instructions} """ + +PROMPT_MAKE_HTML_COMPLEX = """ +You are an HTML, CSS expert. I have an HTML code. +I want you to make the html code more complex. +The following is the given html code: +{html} + +{instructions} +""" diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index ee3dc119..2b3bcab8 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -11,6 +11,7 @@ from webgenie.rewards.visual_reward import VisualReward from webgenie.datasets.mockup_dataset import MockUpDataset from webgenie.datasets.synthetic_dataset import SyntheticDataset +from webgenie.datasets.huggingface_dataset import HuggingfaceDesign2CodeDataset class ImageTaskGenerator(TaskGenerator): def __init__(self): @@ -19,8 +20,9 @@ def __init__(self): (VisualReward(), 1.0) ] self.datasets = [ - MockUpDataset(), - SyntheticDataset() + # MockUpDataset(), + # SyntheticDataset(), + HuggingfaceDesign2CodeDataset() ] async def generate_task(self) -> Tuple[Task, bt.Synapse]: From 8b139f1534d1fe656012a0c0485715b0a884b5f4 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 17 Dec 2024 09:24:00 -0600 Subject: [PATCH 086/554] chore: added env variables for llm --- .env.validator.example | 5 +++- webgenie/constants.py | 4 +-- webgenie/datasets/huggingface_dataset.py | 35 ++++++++++++++++-------- webgenie/datasets/synthetic_dataset.py | 3 +- webgenie/rewards/rtc_reward.py | 3 +- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/.env.validator.example b/.env.validator.example index d4429700..0798cad9 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -1,3 +1,6 @@ OPENAI_API_KEY = your_openai_api_key WANDB_API_KEY = your_wandb_api_key -WANDB_ENTITY_NAME = your_wandb_entity_name \ No newline at end of file +WANDB_ENTITY_NAME = your_wandb_entity_name + +LLM_MODEL_ID = gpt-4o-mini +LLM_MODEL_URL = https://api.openai.com/v1/chat/completions \ No newline at end of file diff --git a/webgenie/constants.py b/webgenie/constants.py index 18b47c15..eb997497 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -2,10 +2,10 @@ API_HOTKEY = "5D72esHuc1DxD6PD8S6VyU24bTHGQjHHyodzTGsem1sejUYj" # max synthetic history size -MAX_SYNTHETIC_HISTORY_SIZE = 10 +MAX_SYNTHETIC_HISTORY_SIZE = 3 # max synthensize task size -MAX_SYNTHETIC_TASK_SIZE = 10 +MAX_SYNTHETIC_TASK_SIZE = 3 # max debug image string length MAX_DEBUG_IMAGE_STRING_LENGTH = 20 diff --git a/webgenie/datasets/huggingface_dataset.py b/webgenie/datasets/huggingface_dataset.py index 459201db..9100a933 100644 --- a/webgenie/datasets/huggingface_dataset.py +++ b/webgenie/datasets/huggingface_dataset.py @@ -1,6 +1,7 @@ # https://huggingface.co/datasets/SALT-NLP/Design2Code_human_eval_pairwise import bittensor as bt +import os import random from datasets import load_dataset @@ -19,7 +20,11 @@ class HTMLResponse(BaseModel): class HuggingfaceDesign2CodeDataset(Dataset): def __init__(self): self.dataset = load_dataset("SALT-NLP/Design2Code_human_eval_pairwise", split="train") - self.model = ChatOpenAI(model="gpt-4o-mini") + self.model = ChatOpenAI( + base_url=os.getenv("LLM_MODEL_URL"), + model=os.getenv("LLM_MODEL_ID"), + api_key=os.getenv("OPENAI_API_KEY") + ) self.output_parser = JsonOutputParser(pydantic_object=HTMLResponse) async def _make_html_complex(self, html: str)->str: @@ -34,14 +39,20 @@ async def _make_html_complex(self, html: str)->str: return response["complex_html"] async def generate_context(self)->DatasetEntry: - random_index = random.randint(0, len(self.dataset) - 1) - html = self.dataset[random_index]["ref_html"] - complex_html = await self._make_html_complex(html) - bt.logging.debug(f"Complex HTML: {complex_html}") - return DatasetEntry( - src="huggingface", - topic="design2code", - ground_truth_html=complex_html, - prompt="", - base64_image="" - ) + try: + random_index = random.randint(0, len(self.dataset) - 1) + html = self.dataset[random_index]["ref_html"] + bt.logging.debug(f"HTML: {html}") + complex_html = await self._make_html_complex(html) + bt.logging.debug(f"Complex HTML: {complex_html}") + return DatasetEntry( + src="huggingface", + topic="design2code", + ground_truth_html=complex_html, + prompt="", + base64_image="" + ) + except Exception as e: + bt.logging.error(f"Error in generate_context: {e}") + raise e + diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index 19b1e68b..e0e61f7b 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -27,7 +27,8 @@ def __init__(self, has_ground_truth_html: bool = True): self.model = ChatOpenAI( api_key= os.getenv("OPENAI_API_KEY"), - model_name="gpt-4o", + model_name=os.getenv("LLM_MODEL_ID"), + base_url=os.getenv("LLM_MODEL_URL"), temperature=0.6, ) diff --git a/webgenie/rewards/rtc_reward.py b/webgenie/rewards/rtc_reward.py index 25b7ba9a..6fbcc3f0 100644 --- a/webgenie/rewards/rtc_reward.py +++ b/webgenie/rewards/rtc_reward.py @@ -24,7 +24,8 @@ class RtcReward(Reward): def __init__(self): self.model = ChatOpenAI( api_key= os.getenv("OPENAI_API_KEY"), - model_name="gpt-4o", + model_name=os.getenv("LLM_MODEL_ID"), + base_url=os.getenv("LLM_MODEL_URL"), ) self.prompt_response_parser = JsonOutputParser(pydantic_object=PromptResponse) From 91869a42e14d268f37c4c6db9300bc862e42c8dc Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 17 Dec 2024 10:25:12 -0600 Subject: [PATCH 087/554] chore: implemented async langchain --- .env.validator.example | 2 +- neurons/miners/openai_miner.py | 2 +- neurons/validators/genie_validator.py | 2 +- webgenie/datasets/huggingface_dataset.py | 2 +- webgenie/datasets/synthetic_dataset.py | 4 ++-- webgenie/rewards/rtc_reward.py | 6 +++--- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.env.validator.example b/.env.validator.example index 0798cad9..8fac36d4 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -3,4 +3,4 @@ WANDB_API_KEY = your_wandb_api_key WANDB_ENTITY_NAME = your_wandb_entity_name LLM_MODEL_ID = gpt-4o-mini -LLM_MODEL_URL = https://api.openai.com/v1/chat/completions \ No newline at end of file +LLM_MODEL_URL = \ No newline at end of file diff --git a/neurons/miners/openai_miner.py b/neurons/miners/openai_miner.py index 1c4f4877..7bcda60d 100644 --- a/neurons/miners/openai_miner.py +++ b/neurons/miners/openai_miner.py @@ -38,7 +38,7 @@ async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynaps ]) chain = prompt | self.model | self.html_response_parser - html_response = chain.invoke({ + html_response = await chain.ainvoke({ "query": synapse.prompt, "instructions": self.html_response_parser.get_format_instructions() }) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 4475d7b4..e110c01b 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -40,7 +40,7 @@ async def forward(self): return if not self.synthetic_tasks: - bt.logging.debug(f"No synthetic tasks") + bt.logging.warning(f"No synthetic tasks") return bt.logging.debug(f"Synthetic tasks: {self.synthetic_tasks}") task, synapse = self.synthetic_tasks.pop(0) diff --git a/webgenie/datasets/huggingface_dataset.py b/webgenie/datasets/huggingface_dataset.py index 9100a933..f88a9173 100644 --- a/webgenie/datasets/huggingface_dataset.py +++ b/webgenie/datasets/huggingface_dataset.py @@ -32,7 +32,7 @@ async def _make_html_complex(self, html: str)->str: ("system", PROMPT_MAKE_HTML_COMPLEX), ]) chain = prompt | self.model | self.output_parser - response = chain.invoke({ + response = await chain.ainvoke({ "html": html, "instructions": self.output_parser.get_format_instructions() }) diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index e0e61f7b..aec88df1 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -41,7 +41,7 @@ async def _generate_concepts(self): ("system", PROMPT_GEN_CONCEPT), ]) chain = prompt | self.model | self.concept_parser - response = chain.invoke({ + response = await chain.ainvoke({ "instructions": self.concept_parser.get_format_instructions() }) return response["concepts"] @@ -51,7 +51,7 @@ async def _generate_html(self, concept: str): ("system", PROMPT_GEN_HTML), ]) chain = prompt | self.model | self.html_parser - response = chain.invoke({ + response = await chain.ainvoke({ "concept": concept, "instructions": self.html_parser.get_format_instructions() }) diff --git a/webgenie/rewards/rtc_reward.py b/webgenie/rewards/rtc_reward.py index 6fbcc3f0..e4b75f0b 100644 --- a/webgenie/rewards/rtc_reward.py +++ b/webgenie/rewards/rtc_reward.py @@ -30,13 +30,13 @@ def __init__(self): self.prompt_response_parser = JsonOutputParser(pydantic_object=PromptResponse) - def _get_prompt(self, task: Task, solutions: List[Solution]) -> str: + async def _get_prompt(self, task: Task, solutions: List[Solution]) -> str: prompt = ChatPromptTemplate.from_messages([ SystemMessagePromptTemplate.from_template(PROMPT_RTC) ]) chain = prompt | self.model | self.prompt_response_parser - prompt_response = chain.invoke({ + prompt_response = await chain.ainvoke({ "html": task.ground_truth_html, "prompt": task.prompt, "instructions": self.prompt_response_parser.get_format_instructions() @@ -47,7 +47,7 @@ def _get_prompt(self, task: Task, solutions: List[Solution]) -> str: async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: bt.logging.debug(f"Rewarding task in rtc reward") original_prompts = [task.prompt for _ in solutions] - miner_prompts = [self._get_prompt(task, solution) for solution in solutions] + miner_prompts = [await self._get_prompt(task, solution) for solution in solutions] P, R, F1 = bert_score.score(original_prompts, miner_prompts, lang='en') return np.array(R) From 757bc7ed8966930bd361f08b51da53aa2a51e55e Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 17 Dec 2024 12:33:59 -0600 Subject: [PATCH 088/554] chore: set weights for tasks --- neurons/validators/genie_validator.py | 11 ++++++++--- webgenie/tasks/image_task_generator.py | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index e110c01b..1c234f0b 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -19,8 +19,8 @@ def __init__(self, neuron: BaseNeuron): self.synthetic_tasks = [] self.task_generators = [ - # TextTaskGenerator(), - ImageTaskGenerator(), + (TextTaskGenerator(), 0.1), + (ImageTaskGenerator(), 0.9), ] self.make_work_dir() @@ -90,7 +90,12 @@ async def synthensize_task(self): if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: return - task, synapse = await random.choice(self.task_generators).generate_task() + task_generator, _ = random.choices( + self.task_generators, + weights=[weight for _, weight in self.task_generators] + )[0] + + task, synapse = await task_generator.generate_task() self.synthetic_tasks.append((task, synapse)) except Exception as e: bt.logging.error(f"Error in synthensize_task: {e}") diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 2b3bcab8..a8a5b2a1 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -21,7 +21,7 @@ def __init__(self): ] self.datasets = [ # MockUpDataset(), - # SyntheticDataset(), + SyntheticDataset(), HuggingfaceDesign2CodeDataset() ] From b44c4ba951d0db965a5ec215b053533b131893f7 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 17 Dec 2024 12:47:05 -0600 Subject: [PATCH 089/554] chore: updated hf-dataset --- webgenie/datasets/huggingface_dataset.py | 9 +++++---- webgenie/tasks/image_task_generator.py | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/webgenie/datasets/huggingface_dataset.py b/webgenie/datasets/huggingface_dataset.py index f88a9173..c9931108 100644 --- a/webgenie/datasets/huggingface_dataset.py +++ b/webgenie/datasets/huggingface_dataset.py @@ -17,9 +17,10 @@ class HTMLResponse(BaseModel): complex_html: str = Field(description="the complex html code") -class HuggingfaceDesign2CodeDataset(Dataset): - def __init__(self): - self.dataset = load_dataset("SALT-NLP/Design2Code_human_eval_pairwise", split="train") +class HuggingfaceDataset(Dataset): + def __init__(self , dataset_name: str, split: str, html_field: str): + self.dataset = load_dataset(dataset_name, split=split) + self.html_field = html_field self.model = ChatOpenAI( base_url=os.getenv("LLM_MODEL_URL"), model=os.getenv("LLM_MODEL_ID"), @@ -41,7 +42,7 @@ async def _make_html_complex(self, html: str)->str: async def generate_context(self)->DatasetEntry: try: random_index = random.randint(0, len(self.dataset) - 1) - html = self.dataset[random_index]["ref_html"] + html = self.dataset[random_index][self.html_field] bt.logging.debug(f"HTML: {html}") complex_html = await self._make_html_complex(html) bt.logging.debug(f"Complex HTML: {complex_html}") diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index a8a5b2a1..1b7dbb8c 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -11,7 +11,7 @@ from webgenie.rewards.visual_reward import VisualReward from webgenie.datasets.mockup_dataset import MockUpDataset from webgenie.datasets.synthetic_dataset import SyntheticDataset -from webgenie.datasets.huggingface_dataset import HuggingfaceDesign2CodeDataset +from webgenie.datasets.huggingface_dataset import HuggingfaceDataset class ImageTaskGenerator(TaskGenerator): def __init__(self): @@ -22,7 +22,7 @@ def __init__(self): self.datasets = [ # MockUpDataset(), SyntheticDataset(), - HuggingfaceDesign2CodeDataset() + HuggingfaceDataset("SALT-NLP/Design2Code-hf", "train", "text"), ] async def generate_task(self) -> Tuple[Task, bt.Synapse]: From 55b3c7d4708aa170456cc954b1b21282c3219673 Mon Sep 17 00:00:00 2001 From: donbusha Date: Tue, 17 Dec 2024 20:26:15 -0600 Subject: [PATCH 090/554] feat: updated incentive reward --- .gitignore | 1 + neurons/validators/genie_validator.py | 7 ++-- webgenie/rewards/__init__.py | 1 + webgenie/rewards/incentive_rewards.py | 47 +++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 webgenie/rewards/incentive_rewards.py diff --git a/.gitignore b/.gitignore index b92ec6a6..9307a961 100644 --- a/.gitignore +++ b/.gitignore @@ -182,6 +182,7 @@ work/ # scripts run_miner.sh +run_miner_2.sh run_validator.sh # developer doc diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 1c234f0b..8ba501bc 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -6,11 +6,11 @@ from webgenie.constants import MAX_SYNTHETIC_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE from webgenie.helpers.htmls import preprocess_html from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse +from webgenie.rewards import get_incentive_rewards from webgenie.tasks.solution import Solution from webgenie.tasks.image_task_generator import ImageTaskGenerator from webgenie.tasks.text_task_generator import TextTaskGenerator from webgenie.utils.uids import get_random_uids - class GenieValidator: def __init__(self, neuron: BaseNeuron): self.neuron = neuron @@ -80,8 +80,9 @@ async def score(self): miner_uids = [solution.miner_uid for solution in solutions] bt.logging.debug(f"Miner uids: {miner_uids}") bt.logging.debug(f"Scores: {scores}") - - self.neuron.update_scores(scores, miner_uids) + rewards = get_incentive_rewards(scores) + bt.logging.debug(f"Incentive rewards: {rewards}") + self.neuron.update_scores(rewards, miner_uids) self.neuron.sync() async def synthensize_task(self): diff --git a/webgenie/rewards/__init__.py b/webgenie/rewards/__init__.py index a6cba18e..53f7d6e2 100644 --- a/webgenie/rewards/__init__.py +++ b/webgenie/rewards/__init__.py @@ -1,2 +1,3 @@ from .reward import Reward from .visual_reward import VisualReward +from .incentive_rewards import get_incentive_rewards \ No newline at end of file diff --git a/webgenie/rewards/incentive_rewards.py b/webgenie/rewards/incentive_rewards.py new file mode 100644 index 00000000..f048d06b --- /dev/null +++ b/webgenie/rewards/incentive_rewards.py @@ -0,0 +1,47 @@ +import numpy as np + +def get_incentive_rewards(scores: np.ndarray, base_reward=100, alpha=1.5) -> np.ndarray: + """ + Calculate rewards based on the piecewise linear with exponential growth method, + preserving the original order of the scores, and returning rewards as a NumPy array. + + Parameters: + - scores: NumPy array of raw scores. + - base_reward: The minimum reward for the highest rank (rank 1). + - alpha: The exponential scaling factor for ranks above the threshold. + + Returns: + - rewards: NumPy array of rewards corresponding to the original order of scores. + """ + threshold = scores.shape[0] // 2 + + # Ensure input is a NumPy array + scores = np.array(scores) + + # Rank players based on scores (highest score gets better rank) + sorted_scores = np.sort(scores) # Sort in ascending order + score_to_rank = {score: idx + 1 for idx, score in enumerate(sorted_scores)} # Map each score to its rank + # Calculate rewards for each score based on its rank + rewards = np.zeros_like(scores, dtype=float) # Initialize the rewards array + + for idx, score in enumerate(scores): + rank = score_to_rank[score] + + if rank <= threshold: + # Linear reward scaling for ranks <= threshold + reward = base_reward + (rank - 1) * (base_reward / 2) # Linear scaling + else: + # Exponential reward scaling for ranks > threshold + reward = base_reward * (alpha ** (rank - threshold)) # Exponential scaling + + rewards[idx] = reward # Assign reward to the corresponding index + + return rewards + + +if __name__ == "__main__": + scores = np.array([500, 450, 400, 750, 300, 250, 200]) # Raw scores as a NumPy array + rewards = get_incentive_rewards(scores, base_reward=100, alpha=1.5) + + for score, reward in zip(scores, rewards): + print(f"Score: {score}, Reward = {reward:.2f}") \ No newline at end of file From ec04cb97d941d63d60e850fde8d45712d85055f9 Mon Sep 17 00:00:00 2001 From: donbusha Date: Tue, 17 Dec 2024 20:29:55 -0600 Subject: [PATCH 091/554] style: styled code --- neurons/validators/genie_validator.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 8ba501bc..3bff7dfe 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -11,6 +11,7 @@ from webgenie.tasks.image_task_generator import ImageTaskGenerator from webgenie.tasks.text_task_generator import TextTaskGenerator from webgenie.utils.uids import get_random_uids + class GenieValidator: def __init__(self, neuron: BaseNeuron): self.neuron = neuron @@ -76,12 +77,16 @@ async def score(self): task, solutions = self.synthetic_history.pop(0) task_generator = task.generator - scores = await task_generator.reward(task, solutions) + miner_uids = [solution.miner_uid for solution in solutions] bt.logging.debug(f"Miner uids: {miner_uids}") + + scores = await task_generator.reward(task, solutions) bt.logging.debug(f"Scores: {scores}") + rewards = get_incentive_rewards(scores) bt.logging.debug(f"Incentive rewards: {rewards}") + self.neuron.update_scores(rewards, miner_uids) self.neuron.sync() From 198d90b2279d3ecb669bf54890c701052845e1ec Mon Sep 17 00:00:00 2001 From: donbusha Date: Wed, 18 Dec 2024 05:31:05 -0600 Subject: [PATCH 092/554] fix: fixed circular import --- neurons/validators/genie_validator.py | 2 +- webgenie/rewards/__init__.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 3bff7dfe..04291c4c 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -6,7 +6,7 @@ from webgenie.constants import MAX_SYNTHETIC_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE from webgenie.helpers.htmls import preprocess_html from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse -from webgenie.rewards import get_incentive_rewards +from webgenie.rewards.incentive_rewards import get_incentive_rewards from webgenie.tasks.solution import Solution from webgenie.tasks.image_task_generator import ImageTaskGenerator from webgenie.tasks.text_task_generator import TextTaskGenerator diff --git a/webgenie/rewards/__init__.py b/webgenie/rewards/__init__.py index 53f7d6e2..a4073de3 100644 --- a/webgenie/rewards/__init__.py +++ b/webgenie/rewards/__init__.py @@ -1,3 +1,2 @@ from .reward import Reward -from .visual_reward import VisualReward -from .incentive_rewards import get_incentive_rewards \ No newline at end of file +from .visual_reward import VisualReward \ No newline at end of file From 36e05a39ad6dbbcba6a54e72a2d34fe960053e6a Mon Sep 17 00:00:00 2001 From: donbusha Date: Wed, 18 Dec 2024 06:35:37 -0600 Subject: [PATCH 093/554] fix: fixed circular import --- neurons/validators/genie_validator.py | 6 +----- webgenie/rewards/visual_reward.py | 2 +- webgenie/tasks/task_generator.py | 4 +++- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 04291c4c..29ee9afb 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -6,7 +6,6 @@ from webgenie.constants import MAX_SYNTHETIC_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE from webgenie.helpers.htmls import preprocess_html from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse -from webgenie.rewards.incentive_rewards import get_incentive_rewards from webgenie.tasks.solution import Solution from webgenie.tasks.image_task_generator import ImageTaskGenerator from webgenie.tasks.text_task_generator import TextTaskGenerator @@ -81,10 +80,7 @@ async def score(self): miner_uids = [solution.miner_uid for solution in solutions] bt.logging.debug(f"Miner uids: {miner_uids}") - scores = await task_generator.reward(task, solutions) - bt.logging.debug(f"Scores: {scores}") - - rewards = get_incentive_rewards(scores) + rewards = await task_generator.reward(task, solutions) bt.logging.debug(f"Incentive rewards: {rewards}") self.neuron.update_scores(rewards, miner_uids) diff --git a/webgenie/rewards/visual_reward.py b/webgenie/rewards/visual_reward.py index 3a676ed1..68824984 100644 --- a/webgenie/rewards/visual_reward.py +++ b/webgenie/rewards/visual_reward.py @@ -6,7 +6,7 @@ from typing import List from webgenie.constants import WORK_DIR -from webgenie.rewards import Reward +from webgenie.rewards.reward import Reward from webgenie.rewards.metrics.visual_score import visual_eval_v3_multi from webgenie.tasks.task import Task, ImageTask from webgenie.tasks.solution import Solution diff --git a/webgenie/tasks/task_generator.py b/webgenie/tasks/task_generator.py index e27668ed..6a546498 100644 --- a/webgenie/tasks/task_generator.py +++ b/webgenie/tasks/task_generator.py @@ -2,6 +2,7 @@ import numpy as np from typing import List, Tuple +from webgenie.rewards.incentive_rewards import get_incentive_rewards from webgenie.tasks import Task from webgenie.tasks.solution import Solution @@ -20,5 +21,6 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: for reward, weight in self.rewards: reward_scores = await reward.reward(task, solutions) scores += weight * np.array(reward_scores) - return scores + rewards = get_incentive_rewards(scores) + return rewards From 966ef049fb36ed7fe4c51d3b3449b1854c3ebfe7 Mon Sep 17 00:00:00 2001 From: donbusha Date: Wed, 18 Dec 2024 06:51:53 -0600 Subject: [PATCH 094/554] chore: updated dependencies --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index a86bab75..d398930e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,9 +4,11 @@ bert-score==0.3.13 bittensor==8.4.5 clip==1.0 colormath==3.0.0 +datasets==3.2.0 ddt==1.6.0 langchain==0.3.11 langchain-openai==0.2.12 +lxml==5.3.0 matplotlib-inline==0.1.7 open-clip-torch==2.29.0 opencv-python==4.10.0.84 From 3f1114bffa83d5b356d588882a33d12686f3394a Mon Sep 17 00:00:00 2001 From: donbusha Date: Wed, 18 Dec 2024 18:43:38 +0000 Subject: [PATCH 095/554] feat: added base image2html model --- .env.miner.example | 4 +- neurons/miners/hf_miner.py | 30 +++++++++ .../miners/hf_models/websight_finetuned.py | 66 +++++++++++++++++++ requirements.txt | 8 ++- webgenie/helpers/images.py | 5 ++ 5 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 neurons/miners/hf_miner.py create mode 100644 neurons/miners/hf_models/websight_finetuned.py diff --git a/.env.miner.example b/.env.miner.example index d4429700..4a124b0b 100644 --- a/.env.miner.example +++ b/.env.miner.example @@ -1,3 +1,5 @@ OPENAI_API_KEY = your_openai_api_key WANDB_API_KEY = your_wandb_api_key -WANDB_ENTITY_NAME = your_wandb_entity_name \ No newline at end of file +WANDB_ENTITY_NAME = your_wandb_entity_name + +HF_TOKEN = your_hf_token \ No newline at end of file diff --git a/neurons/miners/hf_miner.py b/neurons/miners/hf_miner.py new file mode 100644 index 00000000..d2ac6ef8 --- /dev/null +++ b/neurons/miners/hf_miner.py @@ -0,0 +1,30 @@ +import bittensor as bt +import os + +from webgenie.base.neuron import BaseNeuron +from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse +from webgenie.helpers.images import base64_to_image + +from neurons.miners.hf_models.websight_finetuned import generate_html_from_image + +class OpenaiMiner: + def __init__(self, neuron: BaseNeuron): + self.neuron = neuron + + async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynapse: + try: + synapse.html = "dummy text response" + return synapse + except Exception as e: + bt.logging.error(f"Error in OpenaiMiner forward_text: {e}") + synapse.html = f"Error in OpenaiMiner forward_text: {e}" + return synapse + + async def forward_image(self, synapse: WebgenieImageSynapse) -> WebgenieImageSynapse: + try: + synapse.html = generate_html_from_image(base64_to_image(synapse.base64_image)) + return synapse + except Exception as e: + bt.logging.error(f"Error in OpenaiMiner forward_image: {e}") + synapse.html = f"Error in OpenaiMiner forward_image: {e}" + return synapse \ No newline at end of file diff --git a/neurons/miners/hf_models/websight_finetuned.py b/neurons/miners/hf_models/websight_finetuned.py new file mode 100644 index 00000000..7590707d --- /dev/null +++ b/neurons/miners/hf_models/websight_finetuned.py @@ -0,0 +1,66 @@ +import torch + +from PIL import Image +from transformers import AutoModelForCausalLM, AutoProcessor + +from transformers.image_utils import to_numpy_array, PILImageResampling, ChannelDimension +from transformers.image_transforms import resize, to_channel_dimension_format\ + +API_TOKEN = "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" +DEVICE = torch.device("cuda") +PROCESSOR = AutoProcessor.from_pretrained( + "HuggingFaceM4/VLM_WebSight_finetuned", + token=API_TOKEN, +) +MODEL = AutoModelForCausalLM.from_pretrained( + "HuggingFaceM4/VLM_WebSight_finetuned", + token=API_TOKEN, + trust_remote_code=True, + torch_dtype=torch.bfloat16, +).to(DEVICE) +image_seq_len = MODEL.config.perceiver_config.resampler_n_latents +BOS_TOKEN = PROCESSOR.tokenizer.bos_token +BAD_WORDS_IDS = PROCESSOR.tokenizer(["", ""], add_special_tokens=False).input_ids + + +def convert_to_rgb(image): + # `image.convert("RGB")` would only work for .jpg images, as it creates a wrong background + # for transparent images. The call to `alpha_composite` handles this case + if image.mode == "RGB": + return image + + image_rgba = image.convert("RGBA") + background = Image.new("RGBA", image_rgba.size, (255, 255, 255)) + alpha_composite = Image.alpha_composite(background, image_rgba) + alpha_composite = alpha_composite.convert("RGB") + return alpha_composite + +# The processor is the same as the Idefics processor except for the BILINEAR interpolation, +# so this is a hack in order to redefine ONLY the transform method +def custom_transform(x): + x = convert_to_rgb(x) + x = to_numpy_array(x) + x = resize(x, (960, 960), resample=PILImageResampling.BILINEAR) + x = PROCESSOR.image_processor.rescale(x, scale=1 / 255) + x = PROCESSOR.image_processor.normalize( + x, + mean=PROCESSOR.image_processor.image_mean, + std=PROCESSOR.image_processor.image_std + ) + x = to_channel_dimension_format(x, ChannelDimension.FIRST) + x = torch.tensor(x) + return x + +def generate_html_from_image(image): + global MODEL, PROCESSOR, image_seq_len, BOS_TOKEN, BAD_WORDS_IDS, DEVICE + inputs = PROCESSOR.tokenizer( + f"{BOS_TOKEN}{'' * image_seq_len}", + return_tensors="pt", + add_special_tokens=False, + ) + inputs["pixel_values"] = PROCESSOR.image_processor([image], transform=custom_transform) + inputs = {k: v.to(DEVICE) for k, v in inputs.items()} + generated_ids = MODEL.generate(**inputs, bad_words_ids=BAD_WORDS_IDS, max_length=4096) + generated_text = PROCESSOR.batch_decode(generated_ids, skip_special_tokens=True)[0] + + return generated_text diff --git a/requirements.txt b/requirements.txt index a86bab75..ec98b5fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,16 @@ ansible-vault==2.1.0 beautifulsoup4==4.12.3 bert-score==0.3.13 -bittensor==8.4.5 -clip==1.0 +bittensor==7.4.0 colormath==3.0.0 ddt==1.6.0 +einops +flash-attn langchain==0.3.11 langchain-openai==0.2.12 matplotlib-inline==0.1.7 -open-clip-torch==2.29.0 opencv-python==4.10.0.84 +peft pip-chill==1.0.3 playwright==1.49.1 python-dotenv==1.0.1 @@ -17,3 +18,4 @@ scikit-learn==1.6.0 shtab==1.6.5 tinycss2==1.4.0 wandb==0.19.0 +git+https://github.com/openai/CLIP.git \ No newline at end of file diff --git a/webgenie/helpers/images.py b/webgenie/helpers/images.py index c0938be3..c2e7f57a 100644 --- a/webgenie/helpers/images.py +++ b/webgenie/helpers/images.py @@ -13,3 +13,8 @@ def pil_image_to_base64(img: Image.Image) -> str: def image_to_base64(image_path: str) -> str: img = Image.open(image_path) return pil_image_to_base64(img) + +def base64_to_image(base64_str: str) -> Image.Image: + img_bytes = base64.b64decode(base64_str) + img = Image.open(io.BytesIO(img_bytes)) + return img From 2704e499001b63b05c7d3d0f38ee15e9938a8a06 Mon Sep 17 00:00:00 2001 From: donbusha Date: Wed, 18 Dec 2024 19:43:30 +0000 Subject: [PATCH 096/554] fix: fixed bugs for backend hotkey --- neurons/miners/hf_miner.py | 2 +- neurons/miners/hf_models/websight_finetuned.py | 3 ++- neurons/miners/miner.py | 6 +++--- neurons/validators/genie_validator.py | 14 +++++++------- neurons/validators/validator.py | 8 ++++---- requirements.txt | 1 + webgenie/constants.py | 2 +- webgenie/datasets/huggingface_dataset.py | 2 -- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/neurons/miners/hf_miner.py b/neurons/miners/hf_miner.py index d2ac6ef8..cc5b1f9f 100644 --- a/neurons/miners/hf_miner.py +++ b/neurons/miners/hf_miner.py @@ -7,7 +7,7 @@ from neurons.miners.hf_models.websight_finetuned import generate_html_from_image -class OpenaiMiner: +class HfMiner: def __init__(self, neuron: BaseNeuron): self.neuron = neuron diff --git a/neurons/miners/hf_models/websight_finetuned.py b/neurons/miners/hf_models/websight_finetuned.py index 7590707d..3ff85a11 100644 --- a/neurons/miners/hf_models/websight_finetuned.py +++ b/neurons/miners/hf_models/websight_finetuned.py @@ -1,3 +1,4 @@ +import os import torch from PIL import Image @@ -6,7 +7,7 @@ from transformers.image_utils import to_numpy_array, PILImageResampling, ChannelDimension from transformers.image_transforms import resize, to_channel_dimension_format\ -API_TOKEN = "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" +API_TOKEN = os.getenv("HF_TOKEN") DEVICE = torch.device("cuda") PROCESSOR = AutoProcessor.from_pretrained( "HuggingFaceM4/VLM_WebSight_finetuned", diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index bb673cee..57795ee7 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -27,8 +27,8 @@ from webgenie.constants import MAX_DEBUG_IMAGE_STRING_LENGTH from webgenie.helpers.weights import init_wandb from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse -from webgenie.tasks import Solution -from neurons.miners.openai_miner import OpenaiMiner + +from neurons.miners.hf_miner import HfMiner class Miner(BaseMinerNeuron): """ @@ -54,7 +54,7 @@ def __init__(self, config=None): priority_fn=self.priority_image, ) - self.genie_miner = OpenaiMiner(self) + self.genie_miner = HfMiner(self) init_wandb(self) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index e110c01b..fb1467f2 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -3,7 +3,7 @@ from typing import Union from webgenie.base.neuron import BaseNeuron -from webgenie.constants import MAX_SYNTHETIC_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE +from webgenie.constants import MAX_SYNTHETIC_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE, MAX_DEBUG_IMAGE_STRING_LENGTH from webgenie.helpers.htmls import preprocess_html from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.tasks.solution import Solution @@ -34,15 +34,13 @@ def make_work_dir(self): bt.logging.info(f"Created work directory at {WORK_DIR}") async def forward(self): - bt.logging.debug(f"Forward") try: if len(self.synthetic_history) > MAX_SYNTHETIC_HISTORY_SIZE: return if not self.synthetic_tasks: - bt.logging.warning(f"No synthetic tasks") return - bt.logging.debug(f"Synthetic tasks: {self.synthetic_tasks}") + task, synapse = self.synthetic_tasks.pop(0) miner_uids = get_random_uids(self.neuron, k=self.config.neuron.sample_size) bt.logging.debug(f"Selected miner uids: {miner_uids}") @@ -63,7 +61,6 @@ async def forward(self): bt.logging.warning(f"No valid solutions received") return - bt.logging.debug(f"Processed solutions: {solutions}") self.synthetic_history.append((task, solutions)) except Exception as e: bt.logging.error(f"Error in forward: {e}") @@ -71,7 +68,6 @@ async def forward(self): async def score(self): if not self.synthetic_history: - bt.logging.warning(f"No synthetic history to score") return task, solutions = self.synthetic_history.pop(0) @@ -96,7 +92,11 @@ async def synthensize_task(self): bt.logging.error(f"Error in synthensize_task: {e}") async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImageSynapse]): - bt.logging.debug(f"Organic forward: {synapse}") + if isinstance(synapse, WebgenieTextSynapse): + bt.logging.debug(f"Organic text forward: {synapse.prompt}") + else: + bt.logging.debug(f"Organic image forward: {synapse.base64_image[:MAX_DEBUG_IMAGE_STRING_LENGTH]}...") + best_miner_uid = 1 try: axon = self.neuron.metagraph.axons[best_miner_uid] diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 47c97f34..acd0a573 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -38,18 +38,18 @@ def __init__(self, config=None): async def blacklist_text(self, synapse: WebgenieTextSynapse) -> Tuple[bool, str]: """ - Only allow the subnet owner to send synapse to the validator. + Only allow the backend owner to send synapse to the validator. """ if synapse.dendrite.hotkey == API_HOTKEY: - return False, "Subnet owner hotkey" + return False, "Backend hotkey" return True, "Blacklisted" async def blacklist_image(self, synapse: WebgenieImageSynapse) -> Tuple[bool, str]: """ - Only allow the subnet owner to send synapse to the validator. + Only allow the backend owner to send synapse to the validator. """ if synapse.dendrite.hotkey == API_HOTKEY: - return False, "Subnet owner hotkey" + return False, "Backend hotkey" return True, "Blacklisted" async def organic_forward_text(self, synapse: WebgenieTextSynapse): diff --git a/requirements.txt b/requirements.txt index ec98b5fd..5ea119cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ bert-score==0.3.13 bittensor==7.4.0 colormath==3.0.0 ddt==1.6.0 +datasets einops flash-attn langchain==0.3.11 diff --git a/webgenie/constants.py b/webgenie/constants.py index eb997497..df5f284d 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -1,5 +1,5 @@ # backend api hotkey -API_HOTKEY = "5D72esHuc1DxD6PD8S6VyU24bTHGQjHHyodzTGsem1sejUYj" +API_HOTKEY = "5DXDCYTuPfLqQXbxfvvnarG31SdTDtaubqpQrzjrcMgoP9dp" # max synthetic history size MAX_SYNTHETIC_HISTORY_SIZE = 3 diff --git a/webgenie/datasets/huggingface_dataset.py b/webgenie/datasets/huggingface_dataset.py index f88a9173..4c9f123d 100644 --- a/webgenie/datasets/huggingface_dataset.py +++ b/webgenie/datasets/huggingface_dataset.py @@ -42,9 +42,7 @@ async def generate_context(self)->DatasetEntry: try: random_index = random.randint(0, len(self.dataset) - 1) html = self.dataset[random_index]["ref_html"] - bt.logging.debug(f"HTML: {html}") complex_html = await self._make_html_complex(html) - bt.logging.debug(f"Complex HTML: {complex_html}") return DatasetEntry( src="huggingface", topic="design2code", From 7388fdddafe7fbaf20114b2ced3a71c90dca9e03 Mon Sep 17 00:00:00 2001 From: donbusha Date: Wed, 18 Dec 2024 19:50:30 +0000 Subject: [PATCH 097/554] fix: fixed bugs for empty html --- webgenie/helpers/htmls.py | 22 ++++++++++++++++++++++ webgenie/tasks/image_task_generator.py | 5 ++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index b217a523..2c7a224c 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -89,6 +89,28 @@ def preprocess_html(html: str) -> str: html = replace_image_sources(html) return html +def is_empty_html(html: str) -> bool: + """Check if HTML body is empty or missing. + + Args: + html (str): HTML string to check + + Returns: + bool: True if body is empty or missing, False otherwise + """ + soup = BeautifulSoup(html, 'html.parser') + body = soup.find('body') + + # Return True if no body tag exists + if not body: + return True + + # Return True if body has no content (whitespace is stripped) + if not body.get_text(strip=True): + return True + + return False + if __name__ == "__main__": html = """ diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 2b3bcab8..7021b8f2 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -3,7 +3,7 @@ import random from typing import List, Tuple -from webgenie.helpers.htmls import html_to_screenshot, preprocess_html +from webgenie.helpers.htmls import html_to_screenshot, preprocess_html, is_empty_html from webgenie.protocol import WebgenieImageSynapse from webgenie.tasks.solution import Solution from webgenie.tasks.task import Task, ImageTask @@ -30,6 +30,9 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: ground_truth_html = preprocess_html(dataset_entry.ground_truth_html) if not ground_truth_html : raise ValueError("Invalid ground truth html") + + if is_empty_html(ground_truth_html): + raise ValueError("Empty ground truth html") base64_image = html_to_screenshot(ground_truth_html) return ImageTask( From 1d5298d88f982b0c95def02d52ff81d2e905a449 Mon Sep 17 00:00:00 2001 From: donbusha Date: Wed, 18 Dec 2024 20:02:41 +0000 Subject: [PATCH 098/554] chore: added miner logs --- neurons/miners/hf_miner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/neurons/miners/hf_miner.py b/neurons/miners/hf_miner.py index cc5b1f9f..c8165c37 100644 --- a/neurons/miners/hf_miner.py +++ b/neurons/miners/hf_miner.py @@ -22,7 +22,9 @@ async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynaps async def forward_image(self, synapse: WebgenieImageSynapse) -> WebgenieImageSynapse: try: + bt.logging.debug(f"Generating HTML from image") synapse.html = generate_html_from_image(base64_to_image(synapse.base64_image)) + bt.logging.debug(f"Generated HTML: {synapse.html}") return synapse except Exception as e: bt.logging.error(f"Error in OpenaiMiner forward_image: {e}") From 57a6dca1893c94b32697f34db843a1f86e895e62 Mon Sep 17 00:00:00 2001 From: donbusha Date: Wed, 18 Dec 2024 20:17:58 +0000 Subject: [PATCH 099/554] fix: fixed bugs --- .gitignore | 1 + neurons/validators/genie_validator.py | 3 ++- webgenie/helpers/htmls.py | 17 ++++++++++++++--- webgenie/tasks/image_task_generator.py | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 9307a961..63e1eab1 100644 --- a/.gitignore +++ b/.gitignore @@ -179,6 +179,7 @@ wandb/ # work dir work/ +work_save/ # scripts run_miner.sh diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 25bf675a..74a08fa8 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -83,11 +83,12 @@ async def score(self): self.neuron.sync() async def synthensize_task(self): - bt.logging.debug(f"Synthensize task") try: if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: return + bt.logging.debug(f"Synthensize task") + task_generator, _ = random.choices( self.task_generators, weights=[weight for _, weight in self.task_generators] diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 2c7a224c..74014f66 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -59,25 +59,36 @@ def beautify_html(html: str) -> str: soup = BeautifulSoup(html, 'html.parser') return soup.prettify() -def replace_image_sources(html_content, new_url = PLACE_HOLDER_IMAGE_URL): +import re +from bs4 import BeautifulSoup + +def replace_image_sources(html_content, new_url="PLACE_HOLDER_IMAGE_URL"): soup = BeautifulSoup(html_content, 'html.parser') + # Replace 'src' attribute in tags for img_tag in soup.find_all('img'): img_tag['src'] = new_url + # Replace 'srcset' attribute in tags for source_tag in soup.find_all('source'): if 'srcset' in source_tag.attrs: source_tag['srcset'] = new_url + # Replace URLs in inline styles (background-image) in elements for tag in soup.find_all(style=True): style = tag['style'] - updated_style = re.sub(r'background-image\s*:\s*url\([^)]+\)', f'background-image: url({new_url})', style) + # Match both background-image and shorthand background property + updated_style = re.sub(r'background\s*:\s*[^;]*url\([^)]+\)', f'background: url({new_url})', style) + updated_style = re.sub(r'background-image\s*:\s*url\([^)]+\)', f'background-image: url({new_url})', updated_style) tag['style'] = updated_style + # Replace URLs in + + + +
+

+ Welcome to Fashion Brand +

+

+ Discover the latest trends and styles in our collection. +

+
+ + diff --git a/work _save/miner1.png b/work _save/miner1.png new file mode 100644 index 0000000000000000000000000000000000000000..964e31c8207afa29506d3d9d8c76b6a6e8d34e5e GIT binary patch literal 20679 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 + + + + + + + Fashion Brand + + + + + +
+

+ Welcome to Fashion Brand +

+

+ Discover the latest trends and styles in our collection. +

+
+ + diff --git a/work _save/miner3.png b/work _save/miner3.png new file mode 100644 index 0000000000000000000000000000000000000000..84cd31df58f466e037d9fb742adac7e490431dd7 GIT binary patch literal 21033 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 + + +
+ + +
+Fashion Brand +
+

+ Welcome to Fashion Brand +

+

+ Discover the latest trends and styles in our collection. +

+
+ + diff --git a/work _save/miner6.png b/work _save/miner6.png new file mode 100644 index 0000000000000000000000000000000000000000..8b6192c625aed336fca01c6585cfce009f026af0 GIT binary patch literal 25187 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 sfH!HwVyVlj<9`G=8Rq|81Z-{>eYVpN7i@bQ3WM + + + + + + + Fashion Brand + + + + +
+
+

+ Fashion Brand +

+ +
+
+
+Fashion Brand +
+
+
+

+ Welcome to Fashion Brand +

+

+ Discover the latest trends and styles in our collection. +

+
+
+ + diff --git a/work _save/original.png b/work _save/original.png new file mode 100644 index 0000000000000000000000000000000000000000..4f6490dbf9dd8a1d5e7a401f0af6a9f6551fd392 GIT binary patch literal 25237 zcmdqJ^;cDE7e9(ED1u0c2pmA91f&~u3({TEigb5}2#BD7h;#`^cS{S>vFYxvO~a<) zPR@D1Kit3I#u|>}9vt?H=b7<|x%8Kl5yi%OgoS~Dfh{g3tbl=W13q5uzI_$`dO!q3IS!{Z#P z6kj7iL&NW`$ai^RIJVLh={EoSh=Jj8^U0t8KL5o3hH>rRj~^6XT)q3>4<)W(KL76% z?T!D}{^HD##E0fU;`ql0vFDpxH9yIV@BD}sqt(lGuo<_#RcW$7djG%YN;=%TU`pUq zs8qo|QWg!0D!iC3NWdGglzoEl;byB-%Fo5c%qVcC)zRFd;plzfSM}vcj;!Z$qcAXN zlXbFg9R}0j8Yw+zu^7+a818vxDe8k>o2ag$`fp|B2v2wJO}kE1UKlBMTeuu8WN|H! z%3y9Coi2PaoTwt$KklsD3A&5|42wJoQ{s|mVMVN(GL*g8kMBO zMgF!X3+?sedp#5Fc>WEErKUDE^olY`6nB!shG8bK37~U^Tn*L zP9)0aS#X$g8yWlK;f3Z-{jPQ%i}wposx2CstkJTyu?dR5eTH<l44?Rhli~p$!UL?eA+j9D^g6m$h21bGvDdz z{%TrE`<3c0E#YxKyZ=|TX9?-ozH-&PXIy_i-^<>V&)CSQ%5C4^B=^h}P5MstA(z2n zvG|p}os_N58`Ynz)QzS}^~;#zR)=zo^^C-BA4UJbx4+niOCdjLE%x&}7>U{rJ>At$b}~SUGfv{(Y3KDo+ zjZLtc4o~dSCYEYFl++yit0gV^S)6W1-`@oE-MS2bL*vJ&Of7EL<@SgMX${9_j{vru zx4ro<r?Gub z!KvY6N>*s7LPrO^o!Qi4@5tZ5(tS48q8gg|NrB3zG-*Giubxh&O0Zpb8U6L)(ZKo$ z^{Bd=Ib(=4!CLMlDKfU2yW8^Xn`Z{4OtF0S!{0nm$4{GH!=E5@sa6%goqaLUGc~Pc zucz08B3a+D7pZ3%><=(3sumX0W(xX+B-RD}tksHU*CSF_DbCL}>KQ3I+m9)`bsQ)z zAwg%S!^qf^@ohr-(a?pXR%o%&#_Vcuy6VLY5wj?*jopouloX`MCVVBd%BIwU-K*m_VIT4RNb<6>vXS5S7eI3qI%)|Ipr$KI2{C#FI2bJp+BiB1zt2OVWqWf2ifG%(oyt z8g;F0Y)mXHv`{H*uCvCu-~u0p#J)Z zWmyA#;^`*nc{Dp>N2yL}U_f9plqp}S61bQvU236OXLo^zVA8Hp>DO*2?9?mNLEH|u z3YL^tGBHut))rJuN=jliJ$52mqU>^6=pB(lx2M0=<#*qCt;_tGWTxfAgkG5p{JM(*}ZnDKdaieY)uw0tl zfqpPthBGNxS#7iOI5E{A;}x|Jd%9|6ru2fhpX_6Pngm>jR8D8$5iQk?fN~i;Y@{&p|;Bx~K;g3Ew@+ji>1;!-$f&xl#Gm4l^bLADfjV89TiPIljhML4-*Yd#D*#M7CEcU@QaF%Q#$Suxo>2d zE7^_zuooKq9Tbrq>c))mEjOO~v_D#69;GPEnoW&IfRCR&RkfSo8DCN6u+}s2rF<}+ zbbP_i=A7j3&l(gFA1Sw;o9Y%tK+^I4%4C)(QANIXT(sx@W@|@cV`^$@__xFT%@U38 ziVByl>fIS{M`MP@*vh1L^G-05Wj;^v)%hxw23fJd;po1G$hy9s(LW(#$T%F&U8dBJ z1qI>ZpK%f5DMU7l!^vA>;Nag?&TIU`!7%mC?!w(y6_o|pJu1ku_0;B_2d`BX^>*RGkCw@ZmxmWo=b ziMQ~xcovT5PD{tPf|G1*ZAEX%l$Di4@zPC8A}HfHjQoWYn-5l&jTkk`XVxnZEIPGR zo<-%Prk2__Pb&Dh)2zNc$c-D!RnNEH%}wjlAyhY<8>L=i)+SvQ>glO-*m1h}+L6t( z8_^eY@498C;+SzyFNbVGwb?}B2#R=4TIy?Rmv#rL(qQ3~u8-(+fh*OZb5&!Id8ns{ zjQ?3azICsgdsT|U%FH)K$`GsZ$Qc2*swgp|E9bAsE_U@AmRnsac-c~Kk_lBMdr4NF zo?aC}Fe<%%Z7|VfzA;siZ?^Dh`Wl7Lw|UCfjcxMD71fD}=f~1x3;lHH2fb}M8Di-p zjz2wJUN?QE^Ez>`Oz%s#S5OF$wXCh?+a`;^$Onj3sOQi^%X-{q}=dZLJJglE?NHXZwwRN739uDZS5 z;zUDc0oTWMr*5;$Z#8gssd`5iQSnBBFEJY6TFUkt7)mJ&JODdQDGvt z{i_J5@i1La92f^yE&<{nOK7W9Hl;Fg_**P!+XL-aPw+4)=Ft-;2b5jG#5{VZKRgov z8TFa=lrjipDSExBavd98rs=vB93^p^$MM{zGkT!X$^Peq={nE-(hS{&#mR-%J|drQ zS@%N_(sjSA&&M3s(_D=t^p@hiWjG!mqgMyV%1qVOk8}7AWMc2}IUQaXk(Y{NTVg`S zm&M}ZIZ#wdEJ$co^K}~=W)bExNk0mD#Am+gg^Lh793Z*s)zn?*K4dLz5*4JN> z@%--Ab8Q(l)fJXw<+34icSPj-n}@r6zWwO$4<;81qcp4F3&q_zf0Z4}3pEp~VfN*# zYb)b=y?aN36Z~T(#^nbu#7-l8s!mU1V>KO|66R{Ie~m$A$Mcb&&u#}IwaNB=ZbJi2 zP0|1Dxcst=(0YzjOS&|px9S$a8)rw8GWYf=$J~Uot>eidS=rFJc$y}`cfpU}RuCo> zECx;fHfSyR(p;9U!|8#t3Ud-Un?d|n6e%q`+Ws6ZVP#KKjV#!V>QmDn(jAs8@ zIW-=Z-@Oe-y^FzS`I!Ps^6u)RB^h_u)T!Z?Z&CXJ?*YVieDDzszF^}avDF4}%i`NC z8<$+9qrO#hp(*}v?efMZ7syZ?^62oqcGmPfhMm2CZP0<&W9M1i7%oCrs1XRp0_xQK z(6GPSuOqgpOQ-6oPi(DI*vRo|Bg36LiZ12$cZMLER!|WRoT$RY#2WJf`jp!mlp$Y< zg^~@<2)!N{`hL>NTv2lWyV`imyDWb#R^Y%KF}_U0+#w~cq@;A40vWibh)-a1%3ic@ z)glfc*8!(*fq^S>L2~9kKUtnfwR+0uP~6#`(y-iIq}N}X<$-H{K`~L$&nMK26%Fen z-42i1hq|<@l0?(!@l1FW<{#`#zhe)L`IVBA_Uo5Qwvmdks}FoXccAr~R?k76hsOae zy7QX5`-F)yLtiZwH&?Fb{4!lC2M^ZD6(&0-{ZDd=yVH}MHt{(r{=ygN2<}Zwe z;c@<}D@7M~!{3s}>5TX2vxGK&@u@%FH|bGyzK^(zz0Z}cHeRST^L_(zpGo6Qj>$su zEywQK-z^k)CaH#cZQ(^-3=Ahi2{?;YU%%dss>iuFlcrrf6Fq9a>(k9@Gd;aG15Lb4i(|Bs zE~BLG*PKZWcHEC2*V=Z~#kt%*2C^Kk4VspJ~C4 zhXa;84zojgXeNJuaRY;=KDZ)?GiyQjAR3=H%9(GJZiom{5Jz2zzrkwI4ZQGtqE})$ z#_H63%s7UArd`8CN2f*~Mm9#cp@r?g$>sVRc%*86T)Xf+$#UKJU4Nw{r*E;& znY4)3F^Mxn2F4Y>ASO{22lT+_mMGhZjl0K=lxw3UCfviy40bKo&oXA;6xmz}J(Ec3 zjVaq`stRVLrS0zPTStwYYgAJjtm-DH%Fl_O2xy-NWuF&v!rZ5SH)3UMF zh6Y+5>XPNSfFH5nT`p#Xs4gzLDXGwk;|IRt%WhAMG^{QJ1xd~pZ{JoI+GM4C0!Wi-}& z*p>4dg}Nz%F2_ z&@|?^5)fdK@BJHfZHsh|ZyUqVcg3b+(-YjqC} zhWo0t8%l3VWxdrMPp>PZz`pkBx|74Y5U%)BYqLW@@cpTItk-Lh|gWwCW> zw^=&6lgv<1zX>5N?WLOLAXNekI*Px&E;rg+tAtM;CjdLlqb))}-!Am^lns-=FlC6? zbrHuhLP`3t+==`gj|uhIg9$uD6>rG{6BCamnkPa+2?FBW6v9vNQVN^!i;V}qM*X4_ zs-0fUNr$T!8dfu9X5bxQsJ^Wh_7m{7mgI^+=+OPj%#^@6Nu`Ct5z&D=zOHeOKEH4; zDa>~A#uqfTUU38YVwdxi^WEQ$UAfIN@%p9cchb9OrwgWN`%|9Pft*%ttd0{VwV zsv|?S<|JkHXxhLNoQn+QBHn7$K*9Rg5y{A@gySD>pX@Fd_>hWGy<)NDJVz95IZ^RG zPcEIuIM!1}My6oXgsy+_=Y&?3fDHMa$$aPLqNO|_NTteI>=v3+WnFSN(2LOzxeMQU z^ZEtFx}i@~Ko-b}9=Lea&Blq|ZS7kMm^j!8Tf4+UTI_#Yx~ErmiE`9`-$N}`zS~7I zV@~l7YsQi=MRV<+HfDkTBNg?~YERf`#o*7*`o`J$D^$?LtzqAgvZU9+kH`C(W8y~x zaFa4J$}JC?*7*+BR#v1E*q@^NmIO}j5s{emSBsJhdPH-WYyLST#uqulAu&>H3~>KR z#5`rU+Kt{g69oi8F4)8>Ysz4;-ek^U$_vUuNlMJxNWNq%U4}vG(>VTd5{_(*k=!~* z4@7*e(?D{JD&Il)whrF+P(?wvQ@bOHhFIbU;~E^bE&_$x6Y4;8b-R?=Z8v(mAGk8d z3KVFIw_)k16huyM5dU<MC|vZ@X9K%F|fxzEAIAuieFw7+WJg6X{ZC4|bz)e49}^F8xV z_g2#Ax<7xB<+hujFAO7LWi7%Vj@d);xgGyt`Fnay{Ak(p>lwf6AB>Slp7K=$pQ21o4)eaOU z9aYp4nfMEHSx&?~*JnDr_v9!spm8?d-H%a&eRnZTab+dS^}x?;gb>BGJQkxyBNbPv zcfOZ=t284cczto=cxye2YRCr?w2EMTj?K>4D4nWKLp5DNJ)bwln zJhbVk-GbhP$m_)%%HLf|)*x|1=(PE77SZk%-v5b`tZe24>-{|w9id&Z(!O|@zT{Km z5juW^E*lC7UQ|&74Z}1Y|6Dn^Cy=yZE2=20$@{XlHy}JBqHAuue_)`{)@FEfy0p~v zTYS8{Sxl>SSCl?F)w@n(wAkb+iMfZzmCA&v#vH{JkBxtI>%;ZmWe$A#hKf`}XjFOK z^nt>x;fILa@vX)6GRLbKnVA7T2!CO`=N_+Lzh+h+yA#m7Q`6#>Oii>SZhc2$HYW0vGk~@D1zPdxxki6phBJ}8}7t$cT{JG zeg$bAgXl7lBbjz=Z_;ad0BSHG`DAF6J*53Y#3J%)BO}|}n8C>jar~qXJ9clxH9+o8 zSFW^p(0s&YYn+{v6ZY+!(@NXEKEu%3>W|Jbe`9fRx|}h4a=y{Lq6kruJG;euduv>q z&veRXUKvmQ!2b7f>{o#L~eq8Mw4gQ6p5fpS-=jLqkI=DvrCbF#mg`pQvrR0Cw}q(eB@fcN!Tj zYcn|Hf*oybCjIFlpFTBp1>^kh_oTMy0@~V=)9w6)>5HVp+8BPC3}$EKSy~<0IDH|s zxa#%Ot^Qy25&EJaJwA0Ok>bdal0mLgoNQ*)HUn{n!J-74Z3<7hg1--?k5Z0_Vw%i) zUz)6;p<#sMpZ_j`kx~~SK}RP-8^AiC$kMm;05k_Dd-^O<&z9udA!1KL{yltf$$}lX zrW3uc-TL9!@xO0Ury}I>DH__|1R_0%73f;qiH3~(>&q@a*LVh^xQu?uc3rVsuwNV8 z){EtH%(c7!m0W1DzrQ#=XLqS{SV0Q6*(3db|v!uv zhbF6%r_C7Rwx)a!3(LmF2G3vE#l@w>bm-o_dj^{)^R1!bj2Z#LNtw!7xoSn9=$7t! zAv-!A3%C^f`ubK>xRjU-gfnTi&CjRFCM<3{5iw~l_ojR)ErnLGvNiLUAOT4I+{(%d zNSn`Z&2Bp}sOCQAF!=$Ea1~M^y>nrTnKKc0-;aDGTy}Nbmme-5vik8uhKU9^VMp!!)D7_uZIm^?f@yu7?9C@8L6 zxiXNcY&r$Tu1Y`>uLBBb?L@il5-$%=oPeuC?=SJ~?QM8sz16#9{7&bmM{7fQo_ot7 zd3kw3LCEjak^#KV1#e=bqQq~D%gPGbEz2n>^}|XJ6D%U*U|0WaZCPa~WhN)vo}ky; z!WbMkCnvhQwss8}{a}Jiix1yH2z}&d7NEaAS`z#E5r>Iz>zfjju*5`)xVN$~oSnVB3Z?f% zBB~chxgXUBDCYUEaSzVBt^g;OWqzSH76`r_I#8}AvSXjP4Low|( zgV#4L=`|%P})x!((;e0RLorU)47MA1xGYb&G z^1ewaPg_e>RrMcSsaS3ny34~KxA*5uZ;;N=n^PBiD}AUMooI;ZN{97G;r`avmA`&C zh2_VPNlyIQ@c2>R1>KLuo_r_avUpD28N+25Zf<5K=y~2j_NlW6cIWfIM}B^O6q{kL zPOV414<-sLN2AOjj}Tk|&GPRub~moRgcZi5VAigBOW&WTqw@r>r~UZ&_~y-WVSaR?+u1%<`V7(+|Tyo?OuM~`?d#($5GYa%{JL=3?NxXefA*VjAe zw#O$XF3wLjAwzpk0E!eWMf7%e|2&u#laU!57yySrPh$CirpG;pos^W+Kuua&nlhw& zSCznJ2M~;O3};Hj#KZ&?G$LkgezW1{BqSsi-3<*sTAs5F{xMvZ+Qmllp--iYOG-k& zf7jH~8c8e+mbvx9Qd&j^+1~zMHdP6CKmRZ?GSbla(fJ1l)A(53WQ$M_e8fQ;5D*l^l!>si+FI^OzIOHMBTh3h zS;LgK?XVG#9zBBCKp`9^|5w+AqOnB8E!}yxnAju`3q@Sn=C1v9>Gvp}%pa91J2uyL zR#~s_-ow04G=M7*^CLc(=g(8xc_iUWwul*Mp7wh-IEEJ$d}FxVTQix>tVp=!RK?n@#)Ed!U#_eUFTcj9}LB zaBTTs%F}wt$ON@f$y4`!qw5_QAmwOF&=z#w=`U*D9uU8U5aVQ4ef26i{S!2Yj*bq( zXK(V|PaJ?sSj`|DK0<1~k&~NVT?M1{(Srv?#(k;KAu=;E!e~{Xm_po~oSe`+eR^2~ z`5Y~F7dziK2XU~mF$|Q4b|nZU2)ak(evgYIB_=Mnn5ejk{is_O-3E=pe6$!73u|X@ zFIy?2x6b<()ViF*0)wvj2}q6`cL)#RtWTcYeatPleNXSd2RZaOrBi&`K7v{qY3(Zt zrNo9t5lSO_^ZNBSYHG%&rmn88d2b4IyMMmi8+uYf4_IaK@$t4xNJisq zezHv{Lw%{zCnuY=wH{|C`uZh$t>3rjnj?xyZwg!^q!9iG7tSXg`TQNkB`Ph1htqPB z-=v?Gh=>SmielrwZ{Khi@6YO%81>Bl@w&!lh`fXuDJctw+jAlR8|68b;zw7h{CC?J zo|%-ceE)$8jPob|KSiDYzk<;J_lNoErdjI|cc7zN@Vmis~<=g^{Z2NUz% z(9G@;U{Ik2{ulB^7Yt9IrfSw(+1k$0nQhNDF5o@Ew-*Jay12L)5;B_Im{qUk8yu&n zFz7^xfzgInTLn1ikOEjI38$IU`Y1>06G}?m!>yTElc7AFr60n=!k4MzQKDXA0+|tE z-?F8(*b((t+uqi;sI-)+)@r&;SYAox-^0!A6l!Ft#e|8uc{OASHL_Im&TaqFNuoTdYAXS00ix_{#}|R! z$cEc;c6OHX&Ch=xkOZCksPQ%ihW@ozKjBI#$;p3L!e;MU8srr|c+WF%n?gBLiG_`A z?lFUqkSFwb_v5{SqN2W`AwUXwa)}>{igv#evrX1|Uc}Mhk&skE0mZ?=VPIf@e(3e$ zt-k&M;N1@&K6H0?BW#@lk`S+;c9W_X8A!i>|DJ<`1K^sg>xswN@%mJ4ZTgW1U~FVO z?q}pK5IMt+$eHD3T70a|&Q3o2RlAj5Y6BuD(uQ4RTwGl6{X{tUxg(H1%Tw?Lv9|Pz{fnStgMB z&r!*Vh6-I#Q337;ket`SWSt(uIw4IEU4Gak2Jhb|LmAJ@+gx3JgpdCuL=2yYxpCX6 zib<iVQW#-6 zT5OCmVPax}-dK{K|IPdzq%gh+H4ROfao=l)tdNi%?^`%n)l{+xf;l?1HL=66-9LW( zP#Osrf=YK40Kr#P4t4M=2A^%qn# zGYw61S65!Q`%-5NjjJ4hc89n-L`*G%BW0Ee1qB7M>Cv3#$LMtpkw-ekMqs~0o82KJ zBZKq_(A49Up{jO0SX5e`suild8rIEAL z_1go^OeBK~=G{sdIj@`_?`I_^zb|04#hZqt_j)0R(x#`U=W{?!Yck1P)l88f;2DAW zNU`>C#xJAlIqDoT^)KZ6f#}~kBSgvr|Ml^?e#6q54sUw1T)6Q#5%c+(9^aC!&1?gv zh{H3^D;tA3F|iZOS``ic<-^w_*_a*W;pWH$T%OcgAB8^e6Kei(JMD(dRky_sVt~jF zH)v>RWbPCaSwTw1;z6aTD5S)Eei<%}WMbz7->Matka2UGvd9eO3U7FB;;}0ayt8`czR}r1<(;QGqcQ+HY^%JRU?2v$b4-`WNy*2 zl9HTeRsi3uuv!YI?mPtaN^jA+c+(PL^v+IBccFic|F#CqxDRFEO#!80vsR@;LGUmX zZnO&U?rf=D=#rIQHef<3*16LrszR~&gcTsbQ=R9JS<TFdDEC1w!RYr#dUJa`bgyliB! z2E~FqPX%bHoxcS;P5#!_79z`iiJD$z1#cRl1u>gJmCL?F>C1vQS5K&qM{INY+9=54VtWRAInx_LkWg6h3G(IV`&2<>0?;}tEGBp%$|`nXt+*`4p|8=7$>{1*8nSy7)cy!t zmH`kGRAiYZ6SpiRjOVX+aeh{qOaa67e-f}Si^t6I{#x_7#SlbWMHQ4xsTmM?=mKpP=0nzc`Ls1c1<;SqFu-K*}w{sAmqH=(o z#!_5^-lV*x+lyZ%>hle~K1OsbD~rCGnUy6fEc}D^?r3j{5$C1mxNs!_cd|o zlMP4ydPh48u@gXPU;&F>T>C>Fw0j0^-*wMQ5mc0IC+JLjZqKD0>_CWdaTtLt&381! zFfM{dXx-P(MKZBu1ReGmV( zK`1i7R=abxDlZ%R>MAQ61((_IH!Vg!K0ZdqK(&&hqW9r2d;g6&f>VO zDJ!E<-XxC$rsxXZbVTu2K`)P;`fz@{##}XtiSYw#@)BZmL*tQ?vpRu+5{mv($Om4H1qv|@0!(`(?m!$yysYuq9Q1hu4a0e8 zWaO1V3SUT&fl}|{Qi0m7m`d(omM@rxX|6cG&yBNx{`|ov;iNrONa*iZS~S6V#Qyor zRvdw-PkiMUshlVWtIrFvH1wDw=X+_AD(EZnXzZ|H+c*G^Q3=Km3sukI7GjG_(}(r` zD)N^i_BSSCoN=sdY%bF|O)9G1uIdYTk^-(EBoug>ert5L#nV6_5P%!>`I_#8MMu-u zew=lr2O)0vOE5}*2taFb@v~KGDGUrxvP;V42WW$hlM}duo&+T&WsTcWeRiSkD6kjS z&8O8KXS``yK<)EsG;|N@>1gTb#6wI9r#|rXi4sU~k|7mK*g^bpaG+Tlb4UzD6g+r~ zeB5KhOCo8Vv)ry+ifI+4lcd|Td4;J|{q5U-PDC^tb(dPN-MkBP$=a~2iBzs|`M*%s z>g!7_r#yD%#YcpH$Y&@}qfHVwA1mN4@e;X&=-*k;G?X!$B93yYr!sLL~O#76P`w zVia=Oqop~qcXY(T#4Pu`5Onu6?2J}U8pf|xER4$ecyJYC8s`()Ta3e94)f8y;ez)a zCVF~$nwpP`+GJef;^F{_u2ikTv&L?)%20N$_!&dy!f!-fH#0HGOiZjJ8*pA|Be;w` z*H-j*0guy2Hk}Pn6o=hod^h%~TF`3L)YNCE9%(wec)nmOBzUSQDpo@F`1@lje*gAO z&Wwt~bWpRzgh1eQA9y4S3yW+#|45Dd$rmE#NM6yP$5GGU)#GqqV?q+R9_{E`STGs# zH$prwY$|VX3sKV^o_@>*qo6=_tv}!7o69cD0(=Im$lb;+P7q0#K z^~uPqs%S}Z!m*9Dx7{LHzc>4N{X1zT<@8UzgeEIS|{z0lW-dm(_ zCx9>K6Le1ObD)-8t=R!d@XS1e5Fo=*cS&~FOT|N<#y)-z%{aELkQ&CdJaM5>IG90| z^R1x?4~dW0?qyzx-B?DU%tm-?7pZlFyV7*R+mRkCOu~&Cecj#ZfP#RDK9z}WS>a`} z$5K^@6&-{(ia_e_$|wRg5So0Q|O0g@+daj8C4dt~k0fRzCB&EY*L~wTK zf=SJ3)crxL&~Z~6saUsneL{#9W+ilR!Aw2e9mr4=eS$|&A?`3Zn6zlEEH6LPAutEh zqN!YY4jbdG&Ni69(#{Y$p+%*r`vO)XxCsUJbF3{ZHr4wT<@FiE^?8U z!i5V|d6-SktwEaR*}Ou=X3zmyvI24h3c`J&$~jc)-Si(XAsSARDz~HF=4L+uMGOHA z?G^R8@hM0mwjy@PlwRw(Cc1McD>|#x#pA6R@8DorH#h#X&vg)Msj>;@b3ykow(it} zU46K_eRSFsNJ11XXU*%k@*R;zl_{TmbLmSq%mDzW8D_=`f>$D}tc)hB%7LDjmO4N! z1?`wz(CtkCoe4Cp!91NVj~hmsnwn7CVfVmwYtY>WV6sAV=f=&OtoDFN?NP%lXnbLi zQ9*tW2@6w(w4*tL^w22O6Bob6Py!8&RKVp&)G{p_Tao!#DG(q4ZmcXU;9v#&`}+q3 z(C>c*R3n4ZsDfTaB^neN6Vq31?utew>dfuk(WXRNGU1CreszqEjg60=&iwUjtnpca zJAL``CH$9*gTvm&rm(bhu&1ZBz1?+t_J*MjGJ8VkUP6~ng`IH(lUC&Sy*)cnQrOto zsHl=byl=b5N)C*z%RfgBxOW!NhU(bw-xZd(u8eT;(6tN<*aK(kFflXB_V#`Szu}9R zY5YQzAmFMpnoiIQ`V7QQp-zt7YCqkb%i{|)HU6_Z7!tPuDAM{u%OH%#Xh$Ekr8bBR zJn?aIabZ#~HfsApC*`k1kBx(Kan9Ae<5YF2bej&MKw?1c08v_AUQR;8g9zF!e@jcF z;aiZAlniyBR1gsXq|O*S6C$LPA`u2)b~N$TE%Y5Pasiiym6T^CkjOR`)(FJa-Mwd_ z)@L6hTAG?tA>N?;>q!777|sHk4P>IR^Z_}!>;Z~E0^|fR8};w!A$T1(_zjVdk$W=M z*4C)zO=EC#X=LM{P5FO@eso9Lg&2uMg3gd)S_RenQk@r)-Ck%XGWb;uucuX3o`8KR zG2~PLg&Nw{b7^?fu7cir}YDpP+DU@9xg+N^ZSRsjPH`nhhT8LVt!* zhQgaS!ym3(!?!GzDmLn&q^Fk%`B4az-*@r`&rKXM&HiAb$F zDslQz?oxnll)!DBBooIwb^pD8XDp8`Bz_@tpRTR7HFOt`9Q6_pT}|r0bKA#zD-Q?> zRZzc{Meg6eez_`pvOX{V87|bnP0X_jo z1e)pq^x4`PW(rDDQuM|D68Z~6KLSF231LgF~qtEjt@q78F0| zJg)nz%Kefq9+#cO9@-V`3Ak^y&<5ZEL3Dy1)EGdZrKwo~0JzF|H-S9N4InaQNDClK zBr+FT7&zBIZ*_U%(9onozsyrFS#9ut00N`DynI1^zENLlU~O$}b@dr!XB#b=^62o8 z-K5_L&I(ZFQlEqo4-jDY_enX;B4brwUBfsHaf&?tpEdmB3V^16{h0r+y=DFX$eZAv zKfn;%b*k#`@87vM-;8zd-sP|dv-Y#UoWPvH3l1U|(*9(Ao&nCYJh*5(L9;?0Hevt` z_{l6gB3Wv+=4u}yYbz7Bx^ORgue@lb0CQmK&*{;w^F>;2u7#dnfBw6_z{I9%J!`98 zoy>stdWkp_5_BfLtU$K`*na}Qtv{Ulk){&*3Si|QGuB~`bC(iUq22;K^#CB|eCsN- zOYjjeY6B@=eaZ&61N;*ZpFsh>jfDk(brR%*UkY@JhVXNco1r=`JTg|p!p4pRMeL45g9^EjgjMl+_dm;m3XdOPyf#>0C>+JU$^spo0^0Hrc32a{DS~LKeRx8 z0fBaACX5d|4a}PmSojMoDjS0tf{+`OcA>V8VsQxjty3XYSq? zH}v)NjKGs|#FvwRQL3s})8ECOfQpJp%&)q3bSXyGPA}YmcZ@IHms$oz#XjgWD{E`u zZBoJr1k*hKwZ}w6Ky;tO029~>W@h71K48Rvjd6Zqfl<5aH}rKl;8}sP=~;phy#}cd z>;|M#K$+Zbhg%H|!Y+LPU*IKk!1CM_9LBv3&;y?lvcYt)n-jDtV2I!}oSmNH;o(KL zfpi1`0Li$$zYp#XA(k&B_#FE%xS@cTA0WWAU%5XGl~Au#0UsbjCYZP;Mn=P6sQ^w ztNoYi4hl;j4^I?O>c<;*9+Q%Sa{^Hh%}+G&5%rP_NVPGMk-)X`NieaoK}vyY{cM#s zrE;^7y9=YasR_UZL^=3WfZR0x!3BX6{Ae$C@;jH(9vIQEnIU5HD3sgYvOHSF>qU2( zEV)A~OvyR%PeMmuRz4O#0ZbV-phxmo zcprd~UKsNUhg{}1kInq;YM2}0M!?uHR<(MT3Zs_R1dRJ^jF;PXMsolHf%7MFEi5dQ zS5)93MoUb~+>UlG=gx>(e}Uq=HwdK$;s#8em)^B9FQecZdKtLN*?H3WWU&W`G2d28W3QN}6(E)&{#9@6DR6_8Sa1odCjtl&g4ykwkmkj1LAV&^)MO!;_QoxP=Ctk5WIvm=(nDNF@r??Q`cZ+9?-dYU=8xrb8Q0 zJ|IKB1O|H&>c7az#qde8tb)rm+f7;%MaZ@bu$sJe393EJjPm-~E6$leES2a2<>{<=B}s1r;_hug_e zoAUCQt6erH1rVRXd2|Kvi38G_jN)D7&@A{gA3t7o@P)dQm7Wge-Ag;=O_g9na`g6+aNK|Pe6I2YB`}+5#99y^br#j1Y=kN0$L6a1iqu| zeecB1Bo_k#fY#cl#_&pHVX8I&>I+DS=-u7-oS>#Er_0S;CbI zGS&cS#%&6?OXxyqG@6r(tLv;5s@mA-sP=CwFLz;HY$j5$+aM2I%5@uIf-DFOu_V=% zf@pA+%b!6(Yinuw?S4{983=WF(Q6(Y7$|B$D>9TG5fD%t=II8*;C@UB|DXBl~bF8G+=omt_9!Nr9IWvFc`IqI=7J8=}PFdXI%jxlIC1pePH-ys@- zX9g%WJp86Y53U1(l8C#eXTAI8a6xiSjbPCzOhg_Y9>S0cfDSUr#>eeyN z4N@|;dbO9=Zrz*TcCw*&ThhTh`snQq*2*}L%*9Ns>mQ->ycQM)%tGL5Cqn!19oULd z`ViFMmM!LCWBl2?acSF3UJeMeNB$iizMQB9Qc;$lpRQ51;e+{rl8P!S2bQ(4tZb;e zo3?laxW&lGNI}}K+1(@96{v0aB0YIpIf`jW@GVxD_b!WtzkgzpE}{iU(JzI|ipPGj z24i!m<_{}gzA1tSU?t?MVq;^&5aRLz;L!UBXbDY%yk`wmG#EVa@&%0lrNcA=5b}%+ z@nBaeaq*BM$%5v%=kGoS21YwG=cO!sC14m}uJ;sprs>bZ;qUD5PAJI3B!I z8So`3$vc|=tvmO{2(8ds{*riYR|#hZEUy|7d%OzHC~TCxe(?fpO!}2CI8XTU?K0|n>s?? zt93cs9|8owWGHkp@(M?wzqc0}Ms%0w`EDbo2aw3k(?2q<1mtfZYAza?J**+UiL~r` zpfbYaK<`KGa0u;Z$TDjF&Yz7@>Zl!!lg(XE`^scK1Gxi~cK^>c9GGh^(~X-onF&1} z9jbAdupa_EV+tN0&zjrDGNhK|jMu0BNsGk)A*NQ9HPCa)liPP3dw*5#~IgmZ(=bSvQJfO^p9S1M&xpPgPU1 z8c+s&(H6L7tVJ0N?f6cYZ}C zaeG|FC!0^=@_3wg=6C2yB%qC=k6&-j{w9#rUJHKwCng-@=48FL+jb-13+%_-TOaQ* zarf}TM7>%SfT+eVI?fAWvTm~vrq0o2c{RtY5Fa3Y3LMRcLV}Ze{K?77^IXkH12IfJ zphH#1eLo{D{t=&p*^2ixm=Ag7i_xG%$i#52LmWrJ5E#(70OmOBCSuq%w{s{10te$3 zg3Ut*E2W8v??8Y6lK?nFp5-`j9w``^g9*PGn*Bv&@W94tr~q%0hO>VpN3mw~rf z)Eq6&>g_NIjxgm8c6ZB-=l$l(L%^Jb_5ao0wLdj=rs1%5%2ZT#Mx(VT>2#-KHC$>% zt$~`|s+(;mW43zd)CnP>;c?EYfttmLI;c*geD9pFJ=hw}R(N+a z-QAa&3sZ^nf9fgB#5^X~i5;-_e=^4etUOQ-)Ea`p_`CPu5yEtYx`B%d415*KLO=PO< zI*1wP#c73CyxXI2E!xxTD{sf*xL2|HT;zI&(DiG3rS_f#cE0{}tfj5lUTYCJd#A@dR(93bGbXw>z@D&-CY4bhd8LVq|41Pv=IlKmqx88}sHSY8j8uBmz>eGkxs zJ?khuE6MGV(qqx%Jc@Qx%ScYbM$w0z>S*PNe%JU~yo5-XJ^7fc+5IZKK<3-mq0R-ub{+PmR!GE1J9iFhBaa6UQEDdmob7u&mgl71r<8BtYP z()j0DAje+2o$vfIj8p%ww4p}0sqfhJ9L~S>gGZ{!k&v+bZHsOaC4akllXa)vq4i9B zn-mTofDsuSpM`{4HJTfrb^jEW)z;YYj8-sl4l5RW$7$eG@G~<>Vt@b7tgSFVUb`_> zWmmJngn~D?Ml2XbQL=irQlRuNSXe9;VHL5-pgojvZNXYfv|-V^G=dMXIw{V2yOO7M zmGIiC%Qyad2;mc}w7RFSW*j?q?=pM3yZY`-i%tM@B1{Kq^0|9-kv88wb(wJYRtcVN zs=89%Sa(8o5P4zFV7i#Pdq9>mIJ<4~`(~uR*fe16kWU~gF(y=Uv zNT9MQFzP_#&)zz~FwRFw$3AaqpUFg`3CZ|k4&43wki$`^<^}o?cD||c?gmYSa_NT|8AfE$ zT@2%YfxRtx@%2y9mL*xx%6>D`@9L`H$fl4*m{7bzp^LP(ORyiY!Yx*M4GmW%jG61b zB# z;Vlw8Dgcl;uBeCaMU9ppg8msY(r_yx(R%hAc>wqgNElwQ`kilN&L<*^bi8Ew(vP_( z%J6GcdNLThbzpZX6u!C&PN7qOMpV6{_Dr15IgUW<5gOlBpD`aAu@qa1(D5T@X&V|C z&-y-s>rRsy@1MPNya?FT_sG4WbfQHK*a6)f9)t0Qc{-U2fY1e&h2&{OtvRp5RGFMWwTF*wIL&+~6N$4()+LPRv7OM1%8m~<0YLzAiYNaD!3#hPOk##l ztQ2OLU|646zaG*BBjgpGUY2$0I!cteT%qHOxxJZL04*osH!#}}yyV4fbL(XCh{lP6 zwnbZL1PuTb&{#|e#hRtn9m6mG(|aMeJ{Gxx?)fQPLmE3e(_kp~;dV@~%znM;VL7nY zXu_qADiJtFc~!ZZPgJY>(E77vfAz5C7rJGOip^&f>iLKmz|y0(w8i)N{*vpXzu>vW?<*CGM2G I{psib0~w8X?EnA( literal 0 HcmV?d00001 From 03f28d7b4ad9ef9ac51d58f8b5a50d7e6896c1f5 Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 19 Dec 2024 01:01:48 +0000 Subject: [PATCH 101/554] feat: added text2html basemodel --- .gitignore | 1 + neurons/miners/hf_miner.py | 7 ++- neurons/miners/hf_models/falcon7b.py | 33 ++++++++++ work _save/miner1.html | 86 --------------------------- work _save/miner1.png | Bin 20679 -> 0 bytes work _save/miner3.html | 82 ------------------------- work _save/miner3.png | Bin 21033 -> 0 bytes work _save/miner6.html | 75 ----------------------- work _save/miner6.png | Bin 25187 -> 0 bytes work _save/original.html | 58 ------------------ work _save/original.png | Bin 25237 -> 0 bytes 11 files changed, 38 insertions(+), 304 deletions(-) create mode 100644 neurons/miners/hf_models/falcon7b.py delete mode 100644 work _save/miner1.html delete mode 100644 work _save/miner1.png delete mode 100644 work _save/miner3.html delete mode 100644 work _save/miner3.png delete mode 100644 work _save/miner6.html delete mode 100644 work _save/miner6.png delete mode 100644 work _save/original.html delete mode 100644 work _save/original.png diff --git a/.gitignore b/.gitignore index 63e1eab1..b064338d 100644 --- a/.gitignore +++ b/.gitignore @@ -180,6 +180,7 @@ wandb/ # work dir work/ work_save/ +*.png # scripts run_miner.sh diff --git a/neurons/miners/hf_miner.py b/neurons/miners/hf_miner.py index c8165c37..f29e7f45 100644 --- a/neurons/miners/hf_miner.py +++ b/neurons/miners/hf_miner.py @@ -5,6 +5,7 @@ from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from webgenie.helpers.images import base64_to_image +from neurons.miners.hf_models.falcon7b import generate_html_from_text from neurons.miners.hf_models.websight_finetuned import generate_html_from_image class HfMiner: @@ -13,11 +14,11 @@ def __init__(self, neuron: BaseNeuron): async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynapse: try: - synapse.html = "dummy text response" + synapse.html = generate_html_from_text(synapse.prompt) return synapse except Exception as e: - bt.logging.error(f"Error in OpenaiMiner forward_text: {e}") - synapse.html = f"Error in OpenaiMiner forward_text: {e}" + bt.logging.error(f"Error in HfMiner forward_text: {e}") + synapse.html = f"Error in HfMiner forward_text: {e}" return synapse async def forward_image(self, synapse: WebgenieImageSynapse) -> WebgenieImageSynapse: diff --git a/neurons/miners/hf_models/falcon7b.py b/neurons/miners/hf_models/falcon7b.py new file mode 100644 index 00000000..25d92372 --- /dev/null +++ b/neurons/miners/hf_models/falcon7b.py @@ -0,0 +1,33 @@ +from peft import PeftModel +from transformers import AutoModelForCausalLM + +base_model = AutoModelForCausalLM.from_pretrained("ybelkada/falcon-7b-sharded-bf16") +model = PeftModel.from_pretrained(base_model, "kasperius/falcon-7b-sharded-bf16-finetuned-html-code-generation-the-css-v2") + +def generate_html_from_text(prompt, max_length=1024 * 10, num_return_sequences=1): + """ + Generate text from a prompt using the Falcon-7B model + + Args: + prompt (str): Input text prompt + max_length (int): Maximum length of generated text + num_return_sequences (int): Number of sequences to generate + + Returns: + str: Generated text response + """ + inputs = model.tokenizer(prompt, return_tensors="pt", padding=True) + + outputs = model.generate( + **inputs, + max_length=max_length, + num_return_sequences=num_return_sequences, + pad_token_id=model.config.eos_token_id, + do_sample=True + ) + + response = model.tokenizer.decode(outputs[0], skip_special_tokens=True) + return response + +if __name__ == "__main__": + print(generate_html_from_text("Write a simple HTML page with a header, a paragraph, and a footer.")) \ No newline at end of file diff --git a/work _save/miner1.html b/work _save/miner1.html deleted file mode 100644 index b320b6ac..00000000 --- a/work _save/miner1.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - Fashion Brand - - - - -
- - -
-
-

- Welcome to Fashion Brand -

-

- Discover the latest trends and styles in our collection. -

-
- - diff --git a/work _save/miner1.png b/work _save/miner1.png deleted file mode 100644 index 964e31c8207afa29506d3d9d8c76b6a6e8d34e5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20679 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 - - - - - - - Fashion Brand - - - - - -
-

- Welcome to Fashion Brand -

-

- Discover the latest trends and styles in our collection. -

-
- - diff --git a/work _save/miner3.png b/work _save/miner3.png deleted file mode 100644 index 84cd31df58f466e037d9fb742adac7e490431dd7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21033 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 - - -
- - -
-Fashion Brand -
-

- Welcome to Fashion Brand -

-

- Discover the latest trends and styles in our collection. -

-
- - diff --git a/work _save/miner6.png b/work _save/miner6.png deleted file mode 100644 index 8b6192c625aed336fca01c6585cfce009f026af0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25187 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 sfH!HwVyVlj<9`G=8Rq|81Z-{>eYVpN7i@bQ3WM - - - - - - - Fashion Brand - - - - -
-
-

- Fashion Brand -

- -
-
-
-Fashion Brand -
-
-
-

- Welcome to Fashion Brand -

-

- Discover the latest trends and styles in our collection. -

-
-
- - diff --git a/work _save/original.png b/work _save/original.png deleted file mode 100644 index 4f6490dbf9dd8a1d5e7a401f0af6a9f6551fd392..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25237 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 I{psib0~w8X?EnA( From a5dc545c8932daead8d709546722b3177d4ac8e7 Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 19 Dec 2024 02:59:29 +0000 Subject: [PATCH 102/554] wip: ext2html --- neurons/miners/hf_models/falcon7b.py | 31 ++++++++++++------- .../miners/hf_models/websight_finetuned.py | 2 +- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/neurons/miners/hf_models/falcon7b.py b/neurons/miners/hf_models/falcon7b.py index 25d92372..65f5c55f 100644 --- a/neurons/miners/hf_models/falcon7b.py +++ b/neurons/miners/hf_models/falcon7b.py @@ -1,10 +1,19 @@ +import torch from peft import PeftModel -from transformers import AutoModelForCausalLM +from transformers import AutoModelForCausalLM, AutoTokenizer -base_model = AutoModelForCausalLM.from_pretrained("ybelkada/falcon-7b-sharded-bf16") -model = PeftModel.from_pretrained(base_model, "kasperius/falcon-7b-sharded-bf16-finetuned-html-code-generation-the-css-v2") +DEVICE = "cuda" if torch.cuda.is_available() else "cpu" -def generate_html_from_text(prompt, max_length=1024 * 10, num_return_sequences=1): +BASE_MODEL = AutoModelForCausalLM.from_pretrained("ybelkada/falcon-7b-sharded-bf16") +MODEL = PeftModel.from_pretrained( + BASE_MODEL, + "PrincySinghal991/falcon-7b-sharded-bf16-finetuned-html-code-generation" +).to(DEVICE) + +TOKENIZER = AutoTokenizer.from_pretrained("ybelkada/falcon-7b-sharded-bf16") +TOKENIZER.pad_token = TOKENIZER.eos_token + +def generate_html_from_text(prompt, max_length=4096, num_return_sequences=1): """ Generate text from a prompt using the Falcon-7B model @@ -15,18 +24,18 @@ def generate_html_from_text(prompt, max_length=1024 * 10, num_return_sequences=1 Returns: str: Generated text response - """ - inputs = model.tokenizer(prompt, return_tensors="pt", padding=True) - - outputs = model.generate( - **inputs, + """ + input_ids = TOKENIZER(prompt, return_tensors="pt").input_ids.to(DEVICE) + + outputs = MODEL.generate( + input_ids=input_ids, max_length=max_length, num_return_sequences=num_return_sequences, - pad_token_id=model.config.eos_token_id, + pad_token_id=MODEL.config.eos_token_id, do_sample=True ) - response = model.tokenizer.decode(outputs[0], skip_special_tokens=True) + response = TOKENIZER.decode(outputs[0], skip_special_tokens=True) return response if __name__ == "__main__": diff --git a/neurons/miners/hf_models/websight_finetuned.py b/neurons/miners/hf_models/websight_finetuned.py index 3ff85a11..24769dae 100644 --- a/neurons/miners/hf_models/websight_finetuned.py +++ b/neurons/miners/hf_models/websight_finetuned.py @@ -5,7 +5,7 @@ from transformers import AutoModelForCausalLM, AutoProcessor from transformers.image_utils import to_numpy_array, PILImageResampling, ChannelDimension -from transformers.image_transforms import resize, to_channel_dimension_format\ +from transformers.image_transforms import resize, to_channel_dimension_format API_TOKEN = os.getenv("HF_TOKEN") DEVICE = torch.device("cuda") From 0f76a0906806a7ce2754bb59b3f1e68b5c7720dc Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 19 Dec 2024 05:27:05 +0000 Subject: [PATCH 103/554] feat: added text2html model --- neurons/miners/hf_models/falcon7b.py | 84 +++++++++++++++++----------- 1 file changed, 51 insertions(+), 33 deletions(-) diff --git a/neurons/miners/hf_models/falcon7b.py b/neurons/miners/hf_models/falcon7b.py index 65f5c55f..9df0b024 100644 --- a/neurons/miners/hf_models/falcon7b.py +++ b/neurons/miners/hf_models/falcon7b.py @@ -1,42 +1,60 @@ import torch -from peft import PeftModel -from transformers import AutoModelForCausalLM, AutoTokenizer +from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig -DEVICE = "cuda" if torch.cuda.is_available() else "cpu" +# Loading original model +model_name = "ybelkada/falcon-7b-sharded-bf16" -BASE_MODEL = AutoModelForCausalLM.from_pretrained("ybelkada/falcon-7b-sharded-bf16") -MODEL = PeftModel.from_pretrained( - BASE_MODEL, - "PrincySinghal991/falcon-7b-sharded-bf16-finetuned-html-code-generation" -).to(DEVICE) +bnb_config = BitsAndBytesConfig( + load_in_4bit=True, + bnb_4bit_quant_type="nf4", + bnb_4bit_use_double_quant=True, + bnb_4bit_compute_dtype=torch.float16, +) -TOKENIZER = AutoTokenizer.from_pretrained("ybelkada/falcon-7b-sharded-bf16") -TOKENIZER.pad_token = TOKENIZER.eos_token +model = AutoModelForCausalLM.from_pretrained( + model_name, + quantization_config=bnb_config, + device_map="auto", + trust_remote_code=True, +) -def generate_html_from_text(prompt, max_length=4096, num_return_sequences=1): - """ - Generate text from a prompt using the Falcon-7B model - - Args: - prompt (str): Input text prompt - max_length (int): Maximum length of generated text - num_return_sequences (int): Number of sequences to generate - - Returns: - str: Generated text response - """ - input_ids = TOKENIZER(prompt, return_tensors="pt").input_ids.to(DEVICE) +tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True) +tokenizer.pad_token = tokenizer.eos_token +PEFT_MODEL = "PrincySinghal991/falcon-7b-sharded-bf16-finetuned-html-code-generation" +# PEFT_MODEL = "kasperius/falcon-7b-sharded-bf16-finetuned-html-code-generation-the-css-only" + +peft_model = AutoModelForCausalLM.from_pretrained( + PEFT_MODEL, + quantization_config=bnb_config, + device_map="auto", # Let the transformers library handle device placement + trust_remote_code=True, + torch_dtype=torch.float16, # Use mixed precision to reduce memory usage + low_cpu_mem_usage=True +) - outputs = MODEL.generate( - input_ids=input_ids, - max_length=max_length, - num_return_sequences=num_return_sequences, - pad_token_id=MODEL.config.eos_token_id, - do_sample=True +# Load tokenizer +peft_tokenizer = AutoTokenizer.from_pretrained(PEFT_MODEL, trust_remote_code=True) +peft_tokenizer.pad_token = peft_tokenizer.eos_token + +def generate_html_from_text(prompt): + # Tokenize and generate with the PEFT model + peft_encoding = peft_tokenizer(prompt, return_tensors="pt") + peft_outputs = peft_model.generate( + input_ids=peft_encoding["input_ids"].to(peft_model.device), + attention_mask=peft_encoding["attention_mask"].to(peft_model.device), + max_length=2048, + pad_token_id=peft_tokenizer.eos_token_id, + eos_token_id=peft_tokenizer.eos_token_id ) + peft_model_html = peft_tokenizer.decode(peft_outputs[0], skip_special_tokens=True) + return peft_model_html[len(prompt):] - response = TOKENIZER.decode(outputs[0], skip_special_tokens=True) - return response - if __name__ == "__main__": - print(generate_html_from_text("Write a simple HTML page with a header, a paragraph, and a footer.")) \ No newline at end of file + + # Example usage + prompt="create a simple login page with html and css" + print("=================") + html = generate_html_from_text(prompt) + print("=================") + print(html) + From 7ee25c2865eda60b78d61a5713e629fa86311647 Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 19 Dec 2024 11:17:12 +0000 Subject: [PATCH 104/554] chore: added time measure --- neurons/miners/hf_models/falcon7b.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/neurons/miners/hf_models/falcon7b.py b/neurons/miners/hf_models/falcon7b.py index 9df0b024..7e47e25d 100644 --- a/neurons/miners/hf_models/falcon7b.py +++ b/neurons/miners/hf_models/falcon7b.py @@ -54,7 +54,12 @@ def generate_html_from_text(prompt): # Example usage prompt="create a simple login page with html and css" print("=================") + import time + start_time = time.time() + print(f"Prompt: {prompt}") html = generate_html_from_text(prompt) + end_time = time.time() + print(f"Time taken: {end_time - start_time} seconds") print("=================") print(html) From 84182ab7d00b05dbbabd3ab5854ec4f0cdeeb1a4 Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 19 Dec 2024 12:21:40 +0000 Subject: [PATCH 105/554] feat: checked gpu --- neurons/miners/hf_miner.py | 20 +++++++++++++++++--- webgenie/utils/gpus.py | 13 +++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 webgenie/utils/gpus.py diff --git a/neurons/miners/hf_miner.py b/neurons/miners/hf_miner.py index f29e7f45..bbc3f5cd 100644 --- a/neurons/miners/hf_miner.py +++ b/neurons/miners/hf_miner.py @@ -5,16 +5,30 @@ from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from webgenie.helpers.images import base64_to_image -from neurons.miners.hf_models.falcon7b import generate_html_from_text +from webgenie.utils.gpus import get_gpu_info +total_memory_mb, _, _ = get_gpu_info() + +if total_memory_mb is None: + raise ValueError("No GPU detected. HfMiner requires a GPU.") + +if total_memory_mb < 1024 * 25: + raise ValueError("Insufficient GPU memory. HfMiner requires at least 25GB of GPU memory.") + from neurons.miners.hf_models.websight_finetuned import generate_html_from_image +if total_memory_mb > 1024 * 50: + from neurons.miners.hf_models.falcon7b import generate_html_from_text + class HfMiner: def __init__(self, neuron: BaseNeuron): self.neuron = neuron - + async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynapse: try: - synapse.html = generate_html_from_text(synapse.prompt) + if total_memory_mb > 1024 * 50: + synapse.html = generate_html_from_text(synapse.prompt) + else: + synapse.html = "you don't have enough memory to generate html from text" return synapse except Exception as e: bt.logging.error(f"Error in HfMiner forward_text: {e}") diff --git a/webgenie/utils/gpus.py b/webgenie/utils/gpus.py new file mode 100644 index 00000000..8d615a30 --- /dev/null +++ b/webgenie/utils/gpus.py @@ -0,0 +1,13 @@ +import torch + +def get_gpu_info(): + if torch.cuda.is_available(): + total_memory = torch.cuda.get_device_properties(0).total_memory + allocated_memory = torch.cuda.memory_allocated(0) + cached_memory = torch.cuda.memory_reserved(0) + total_memory_mb = total_memory / (1024 ** 2) + allocated_memory_mb = allocated_memory / (1024 ** 2) + cached_memory_mb = cached_memory / (1024 ** 2) + return total_memory_mb, allocated_memory_mb, cached_memory_mb + else: + return None, None, None \ No newline at end of file From 612d783227fb9097b2770ef24f10e90dbc337806 Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 19 Dec 2024 13:53:57 +0000 Subject: [PATCH 106/554] chore: added logs --- neurons/validators/genie_validator.py | 2 +- webgenie/datasets/huggingface_dataset.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 74a08fa8..36af4c0e 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -60,7 +60,7 @@ async def forward(self): if not solutions: bt.logging.warning(f"No valid solutions received") return - + bt.logging.info(f"Received {len(solutions)} solutions") self.synthetic_history.append((task, solutions)) except Exception as e: bt.logging.error(f"Error in forward: {e}") diff --git a/webgenie/datasets/huggingface_dataset.py b/webgenie/datasets/huggingface_dataset.py index 832fa1e4..cfbb1dbb 100644 --- a/webgenie/datasets/huggingface_dataset.py +++ b/webgenie/datasets/huggingface_dataset.py @@ -29,6 +29,7 @@ def __init__(self , dataset_name: str, split: str, html_field: str): self.output_parser = JsonOutputParser(pydantic_object=HTMLResponse) async def _make_html_complex(self, html: str)->str: + bt.logging.info("Making HTML complex") prompt = ChatPromptTemplate.from_messages([ ("system", PROMPT_MAKE_HTML_COMPLEX), ]) @@ -41,9 +42,9 @@ async def _make_html_complex(self, html: str)->str: async def generate_context(self)->DatasetEntry: try: + bt.logging.info("Generating context") random_index = random.randint(0, len(self.dataset) - 1) html = self.dataset[random_index][self.html_field] - bt.logging.debug(f"HTML: {html}") complex_html = await self._make_html_complex(html) return DatasetEntry( src="huggingface", From 7b1573eae939dfb11c1ae8a7d954b4aa1ce244b1 Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 19 Dec 2024 13:59:19 +0000 Subject: [PATCH 107/554] chore: added logs --- webgenie/datasets/huggingface_dataset.py | 2 +- webgenie/datasets/synthetic_dataset.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/webgenie/datasets/huggingface_dataset.py b/webgenie/datasets/huggingface_dataset.py index cfbb1dbb..6ef1fb80 100644 --- a/webgenie/datasets/huggingface_dataset.py +++ b/webgenie/datasets/huggingface_dataset.py @@ -42,7 +42,7 @@ async def _make_html_complex(self, html: str)->str: async def generate_context(self)->DatasetEntry: try: - bt.logging.info("Generating context") + bt.logging.info("Generating Huggingface context") random_index = random.randint(0, len(self.dataset) - 1) html = self.dataset[random_index][self.html_field] complex_html = await self._make_html_complex(html) diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index aec88df1..58e57124 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -37,6 +37,7 @@ def __init__(self, has_ground_truth_html: bool = True): self.concepts = [] async def _generate_concepts(self): + bt.logging.info("Generating concepts") prompt = ChatPromptTemplate.from_messages([ ("system", PROMPT_GEN_CONCEPT), ]) @@ -47,6 +48,7 @@ async def _generate_concepts(self): return response["concepts"] async def _generate_html(self, concept: str): + bt.logging.info("Generating HTML from concept") prompt = ChatPromptTemplate.from_messages([ ("system", PROMPT_GEN_HTML), ]) @@ -58,6 +60,7 @@ async def _generate_html(self, concept: str): return response["html"] async def generate_context(self)->DatasetEntry: + bt.logging.info("Generating Synthetic context") if not self.concepts: self.concepts = await self._generate_concepts() From 1552a2322a08536414c45f57a0d21148d33b6715 Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 19 Dec 2024 14:04:57 +0000 Subject: [PATCH 108/554] chore: added logs --- neurons/miners/hf_miner.py | 4 +++- webgenie/tasks/image_task_generator.py | 1 + webgenie/tasks/text_task_generator.py | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/neurons/miners/hf_miner.py b/neurons/miners/hf_miner.py index bbc3f5cd..93ee3ab7 100644 --- a/neurons/miners/hf_miner.py +++ b/neurons/miners/hf_miner.py @@ -11,7 +11,9 @@ if total_memory_mb is None: raise ValueError("No GPU detected. HfMiner requires a GPU.") -if total_memory_mb < 1024 * 25: +bt.logging.info(f"Total memory: {total_memory_mb}") + +if total_memory_mb < 1024 * 23: raise ValueError("Insufficient GPU memory. HfMiner requires at least 25GB of GPU memory.") from neurons.miners.hf_models.websight_finetuned import generate_html_from_image diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index c7dc3613..9e5507ba 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -26,6 +26,7 @@ def __init__(self): ] async def generate_task(self) -> Tuple[Task, bt.Synapse]: + bt.logging.info("Generating Image task") dataset_entry = await random.choice(self.datasets).generate_context() ground_truth_html = preprocess_html(dataset_entry.ground_truth_html) if not ground_truth_html : diff --git a/webgenie/tasks/text_task_generator.py b/webgenie/tasks/text_task_generator.py index 0d06cfcf..9387b83a 100644 --- a/webgenie/tasks/text_task_generator.py +++ b/webgenie/tasks/text_task_generator.py @@ -36,6 +36,7 @@ def __init__(self, has_ground_truth_html: bool = True): ] async def generate_task(self) -> Tuple[Task, bt.Synapse]: + bt.logging.info("Generating Text task") dataset_entry = await random.choice(self.datasets).generate_context() return TextTask( prompt=dataset_entry.prompt, From 5dca432b0bfc0585dfbdb4e6ab3ad9e265280a6f Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 19 Dec 2024 14:43:14 +0000 Subject: [PATCH 109/554] chore: added logs --- neurons/validators/genie_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 36af4c0e..4a4bb175 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -40,7 +40,7 @@ async def forward(self): if not self.synthetic_tasks: return - + bt.logging.info("Popping synthetic task and sending it to miners") task, synapse = self.synthetic_tasks.pop(0) miner_uids = get_random_uids(self.neuron, k=self.config.neuron.sample_size) bt.logging.debug(f"Selected miner uids: {miner_uids}") From fd0108f3f0aea0480069f3a6f520635b60e2988d Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 19 Dec 2024 10:43:20 -0600 Subject: [PATCH 110/554] build(bittensor): upgraded version --- neurons/miners/miner.py | 4 ++-- neurons/validators/genie_validator.py | 2 +- requirements.txt | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index 57795ee7..61d17ec6 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -28,7 +28,7 @@ from webgenie.helpers.weights import init_wandb from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse -from neurons.miners.hf_miner import HfMiner +from neurons.miners.openai_miner import OpenaiMiner class Miner(BaseMinerNeuron): """ @@ -54,7 +54,7 @@ def __init__(self, config=None): priority_fn=self.priority_image, ) - self.genie_miner = HfMiner(self) + self.genie_miner = OpenaiMiner(self) init_wandb(self) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 4a4bb175..14f67f2d 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -105,7 +105,7 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag else: bt.logging.debug(f"Organic image forward: {synapse.base64_image[:MAX_DEBUG_IMAGE_STRING_LENGTH]}...") - best_miner_uid = 1 + best_miner_uid = 3 try: axon = self.neuron.metagraph.axons[best_miner_uid] async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: diff --git a/requirements.txt b/requirements.txt index c3a133ef..151b2c3e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,12 @@ ansible-vault==2.1.0 beautifulsoup4==4.12.3 bert-score==0.3.13 -bittensor==7.4.0 +bittensor colormath==3.0.0 datasets==3.2.0 ddt==1.6.0 datasets einops -flash-attn langchain==0.3.11 langchain-openai==0.2.12 lxml==5.3.0 From 2dffbd44e98d0140d40ce89b976e36b6689bc4ff Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 19 Dec 2024 12:26:16 -0600 Subject: [PATCH 111/554] chore: added default llm config --- .env.validator.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.validator.example b/.env.validator.example index 8fac36d4..810ecffc 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -2,5 +2,5 @@ OPENAI_API_KEY = your_openai_api_key WANDB_API_KEY = your_wandb_api_key WANDB_ENTITY_NAME = your_wandb_entity_name -LLM_MODEL_ID = gpt-4o-mini -LLM_MODEL_URL = \ No newline at end of file +LLM_MODEL_ID = gpt-3.5-turbo +LLM_MODEL_URL = https://api.openai.com/v1/ \ No newline at end of file From d5c313a11cd18f6bb400c0c65502aeb5db3893d1 Mon Sep 17 00:00:00 2001 From: Sangar <8627971+sangar-1028@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:31:43 -0700 Subject: [PATCH 112/554] Update README.md Update Readme file for updating the running scripts for miners, validators, and update validator code script --- README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dd113af1..40d4afff 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,30 @@ CodeBERTScore is an evaluation metric for code generation, which builds on BERTS - See [Running on Testnet](docs/running_on_testnet.md) for instructions on how to run the subnet on testnet. - See [Running on Mainnet](docs/running_on_mainnet.md) for instructions on how to run the subnet on mainnet. +#### Scripts for running miners and validators on the test network +```bash +npm install pm2 -g +git clone https://github.com/web-genie-ai/web-genie-ai.git +cd web-genie-ai +conda create -name venv python=3.12 +conda activate venv +pip install -r requirements.txt +``` +- miner +```bash +pm2 start neurons/miners/miner.py --name "webgenie_miner" --interpreter python -- --netuid 214 --subtensor.network test --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port] +``` +- validator +```bash +playwright install-deps +playwright install +pm2 start neurons/validators/validator.py --name "webgenie_validator" --interpreter python -- --netuid 214 --subtensor.network test --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port] +``` +- running auto_update script for validators +```bash +pm2 start --name auto_update auto_update.sh +``` + ## Requirements - Miners can use any port. @@ -143,4 +167,4 @@ CodeBERTScore is an evaluation metric for code generation, which builds on BERTS - Add payment gateways - Automate the downloading of fully functional projects - [ ] Market and B2B sales expansion -- [ ] Grow the team \ No newline at end of file +- [ ] Grow the team From 136691753c57f6cf2f798c877b6231b75b65020a Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 19 Dec 2024 18:18:54 -0600 Subject: [PATCH 113/554] fix: fixed clearing state --- neurons/validators/validator.py | 5 ----- webgenie/base/validator.py | 28 ++++++++++++++++++++------- webgenie/rewards/incentive_rewards.py | 5 ++++- webgenie/rewards/visual_reward.py | 1 + 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index acd0a573..a3cbe5f6 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -12,7 +12,6 @@ from webgenie.base.validator import BaseValidatorNeuron from webgenie.constants import API_HOTKEY -from webgenie.helpers.weights import init_wandb from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from neurons.validators.genie_validator import GenieValidator @@ -27,10 +26,6 @@ class Validator(BaseValidatorNeuron): def __init__(self, config=None): super(Validator, self).__init__(config=config) - bt.logging.info("load_state()") - self.load_state() - init_wandb(self) - if not self.config.axon_off: self.serve_axon() diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index 89af5768..b7bc27be 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -32,10 +32,10 @@ process_weights_for_netuid, convert_weights_and_uids_for_emit, ) # TODO: Replace when bittensor switches to numpy +from webgenie.helpers.weights import init_wandb from webgenie.mock import MockDendrite from webgenie.utils.config import add_validator_args - class BaseValidatorNeuron(BaseNeuron): """ Base class for Bittensor validators. Your validator should inherit from this class. @@ -49,7 +49,8 @@ def add_args(cls, parser: argparse.ArgumentParser): add_validator_args(cls, parser) def __init__(self, config=None): - super().__init__(config=config) + super().__init__(config=config) + init_wandb(self) # Save a copy of the hotkeys to local memory. self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) @@ -64,7 +65,10 @@ def __init__(self, config=None): # Set up initial scoring weights for validation bt.logging.info("Building validation weights.") self.scores = np.zeros(self.metagraph.n, dtype=np.float32) - + + bt.logging.info("load_state()") + self.load_state() + # Init sync with the network. Updates the metagraph. self.sync() @@ -228,7 +232,7 @@ def update_scores(self, rewards: np.ndarray, uids: List[int]): def save_state(self): """Saves the state of the validator to a file.""" - #bt.logging.info("Saving validator state.") + bt.logging.info("Saving validator state.") # Save the state of the validator to file. np.savez( @@ -237,6 +241,8 @@ def save_state(self): scores=self.scores, hotkeys=self.hotkeys, ) + + bt.logging.debug(f"Saved state: step={self.step}, scores={self.scores}, hotkeys={self.hotkeys}") def load_state(self): """Loads the state of the validator from a file.""" @@ -244,6 +250,14 @@ def load_state(self): # Load the state of the validator from file. state = np.load(self.config.neuron.full_path + "/state.npz") - self.step = state["step"] - self.scores = state["scores"] - self.hotkeys = state["hotkeys"] + if "step" in state: + self.step = state["step"] + self.scores = state["scores"] + self.hotkeys = state["hotkeys"] + else: + bt.logging.warning("No state found. Initializing with default values.") + self.step = 0 + self.scores = np.zeros(self.metagraph.n, dtype=np.float32) + self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) + + bt.logging.debug(f"Loaded state: step={self.step}, scores={self.scores}, hotkeys={self.hotkeys}") diff --git a/webgenie/rewards/incentive_rewards.py b/webgenie/rewards/incentive_rewards.py index f048d06b..1da56501 100644 --- a/webgenie/rewards/incentive_rewards.py +++ b/webgenie/rewards/incentive_rewards.py @@ -1,3 +1,4 @@ +import bittensor as bt import numpy as np def get_incentive_rewards(scores: np.ndarray, base_reward=100, alpha=1.5) -> np.ndarray: @@ -13,6 +14,7 @@ def get_incentive_rewards(scores: np.ndarray, base_reward=100, alpha=1.5) -> np. Returns: - rewards: NumPy array of rewards corresponding to the original order of scores. """ + bt.logging.debug(f"Scores: {scores}") threshold = scores.shape[0] // 2 # Ensure input is a NumPy array @@ -35,7 +37,8 @@ def get_incentive_rewards(scores: np.ndarray, base_reward=100, alpha=1.5) -> np. reward = base_reward * (alpha ** (rank - threshold)) # Exponential scaling rewards[idx] = reward # Assign reward to the corresponding index - + + bt.logging.debug(f"Rewards: {rewards}") return rewards diff --git a/webgenie/rewards/visual_reward.py b/webgenie/rewards/visual_reward.py index 68824984..14c94be0 100644 --- a/webgenie/rewards/visual_reward.py +++ b/webgenie/rewards/visual_reward.py @@ -33,4 +33,5 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: miner_html_paths.append(path) visual_scores = visual_eval_v3_multi([miner_html_paths, original_html_path]) + bt.logging.debug(f"Visual scores: {visual_scores}") return np.array([score[1] for score in visual_scores]) From 1569d5c4b4b164fc90c2d778e95a5f2c64be0ccf Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 19 Dec 2024 20:02:51 -0600 Subject: [PATCH 114/554] feat: updated s_bert --- requirements.txt | 1 + webgenie/base/validator.py | 4 ++-- webgenie/rewards/metrics/s_bert.py | 25 +++++++++++++++++++++++++ webgenie/rewards/rtc_reward.py | 11 ++++++++--- 4 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 webgenie/rewards/metrics/s_bert.py diff --git a/requirements.txt b/requirements.txt index 151b2c3e..396d10d2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,7 @@ pip-chill==1.0.3 playwright==1.49.1 python-dotenv==1.0.1 scikit-learn==1.6.0 +sentence-transformers shtab==1.6.5 tinycss2==1.4.0 wandb==0.19.0 diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index b7bc27be..2033134e 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -242,7 +242,7 @@ def save_state(self): hotkeys=self.hotkeys, ) - bt.logging.debug(f"Saved state: step={self.step}, scores={self.scores}, hotkeys={self.hotkeys}") + bt.logging.debug(f"Saved state: step={self.step}, scores={self.scores}") def load_state(self): """Loads the state of the validator from a file.""" @@ -260,4 +260,4 @@ def load_state(self): self.scores = np.zeros(self.metagraph.n, dtype=np.float32) self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) - bt.logging.debug(f"Loaded state: step={self.step}, scores={self.scores}, hotkeys={self.hotkeys}") + bt.logging.debug(f"Loaded state: step={self.step}, scores={self.scores}") diff --git a/webgenie/rewards/metrics/s_bert.py b/webgenie/rewards/metrics/s_bert.py new file mode 100644 index 00000000..e58152f6 --- /dev/null +++ b/webgenie/rewards/metrics/s_bert.py @@ -0,0 +1,25 @@ +from sentence_transformers import SentenceTransformer +from sklearn.metrics.pairwise import cosine_similarity + +model = SentenceTransformer('all-MiniLM-L6-v2') + +def score(sentences1, sentences2): + embeddings1 = model.encode(sentences1) + embeddings2 = model.encode(sentences2) + similarities = cosine_similarity(embeddings1, embeddings2) + # Scale similarities to be between 0 and 1 + scores = [(float(similarity[0]) + 1) / 2 for similarity in similarities] + return scores + +if __name__ == "__main__": + # Define a list of sentence pairs + sentence_pairs = [ + ("The cat is on the mat.", "A cat sits on a rug."), + ("I am going to the store.", "I will head to the shop."), + ("The weather is great today.", "It is sunny outside."), + ("She loves playing the piano.", "She enjoys playing the guitar.") + ] + sentences1 = [pair[0] for pair in sentence_pairs] + sentences2 = [pair[1] for pair in sentence_pairs] + # Call the function + print(score(sentences1, sentences2)) diff --git a/webgenie/rewards/rtc_reward.py b/webgenie/rewards/rtc_reward.py index e4b75f0b..424cef58 100644 --- a/webgenie/rewards/rtc_reward.py +++ b/webgenie/rewards/rtc_reward.py @@ -14,9 +14,11 @@ from webgenie.prompts import PROMPT_RTC from webgenie.rewards import Reward +from webgenie.rewards.metrics import s_bert from webgenie.tasks.task import Task from webgenie.tasks.solution import Solution + class PromptResponse(BaseModel): prompt: str = Field(default="", description="The prompt that generates the given html code") @@ -29,7 +31,7 @@ def __init__(self): ) self.prompt_response_parser = JsonOutputParser(pydantic_object=PromptResponse) - + async def _get_prompt(self, task: Task, solutions: List[Solution]) -> str: prompt = ChatPromptTemplate.from_messages([ SystemMessagePromptTemplate.from_template(PROMPT_RTC) @@ -48,8 +50,11 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: bt.logging.debug(f"Rewarding task in rtc reward") original_prompts = [task.prompt for _ in solutions] miner_prompts = [await self._get_prompt(task, solution) for solution in solutions] - P, R, F1 = bert_score.score(original_prompts, miner_prompts, lang='en') - return np.array(R) + + #P, R, F1 = bert_score.score(original_prompts, miner_prompts, lang='en') + scores = s_bert.score(original_prompts, miner_prompts) + return np.array(scores) + From 0987d904d7f75d8cbf32be8749c6568cc3f7f9b4 Mon Sep 17 00:00:00 2001 From: Sangar <8627971+sangar-1028@users.noreply.github.com> Date: Thu, 19 Dec 2024 19:33:20 -0700 Subject: [PATCH 115/554] Update README.md Add references --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 40d4afff..34161922 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Welcome to WebGenieAI Subnet, a pioneering Bittensor-based subnet designed to re - [Features](#features) - [Incentive Mechanism](#incentive-mechanism-v1) - [Roadmap](#roadmap) +- [References](#references) ## Overview @@ -45,7 +46,7 @@ The WebGenieAI subnet incentivizes miners and validators to ensure high-quality 1) Image to HTML Model -### Automatic evaluation of ImageToHTML task for design-wise +### Automatic evaluation of ImageToHTML task for design-wise [Ref: [[1]](#references)] We automatically evaluate generated webpages by calculating the similarity between the original input image and the rendered screenshot of generated webpage. We break down the evaluation into both high-level visual similarity and low-level element matching. @@ -82,7 +83,7 @@ blocks are, the lower this score is. 2) Text Prompt to Html Model -### Unsupervised Evaluation of Model by Round-Trip Correctness +### Unsupervised Evaluation of Model by Round-Trip Correctness (Ref: [[2]](#references)) We draw inspiration from a software testing technique known as property-based testing. It allows defining properties that must hold between inputs and outputs of a program (e.g., all items in the input list must also appear in the output list) Round-trip correctness is one such property (e.g., compressing and subsequently decompressing data must yield the original data). Consider two forms of data X and Y, such as text prompt and HTML and two (probabilistic) models whose task is to “translate” from one form of data to the other, i.e., a forward model M : X → Y and a backward model M-1: Y → X. These models could be a single LLM prompted differently. @@ -168,3 +169,12 @@ pm2 start --name auto_update auto_update.sh - Automate the downloading of fully functional projects - [ ] Market and B2B sales expansion - [ ] Grow the team + +## References +- [1] [Design2Code: Benchmarking Multimodal Code Generation for Automated Front-End Engineering](https://arxiv.org/pdf/2403.03163) +- [2] [Unsupervised Evaluation of Code LLMs with Round-Trip Correctness](https://arxiv.org/pdf/2402.08699#page=11&zoom=100,384,458) +- [3] [Unlocking the conversion of Web Screenshots into HTML Code with the WebSight Dataset](https://arxiv.org/pdf/2403.09029v1#bib.bib5) +- [4] [VLM_WebSight_finetuned](https://huggingface.co/HuggingFaceM4/VLM_WebSight_finetuned) +- [5] [falcon-7b-sharded-bf16-finetuned-html-code-generation](https://huggingface.co/PrincySinghal991/falcon-7b-sharded-bf16-finetuned-html-code-generation) +- [6] [SALT/NLP](https://huggingface.co/datasets/SALT-NLP/Design2Code) +- [7] [How you can train an AI to convert your design mockups into HTML and CSS](https://www.freecodecamp.org/news/how-you-can-train-an-ai-to-convert-your-design-mockups-into-html-and-css-cc7afd82fed4/) From 3ec010c2c9b77ce3a8e9d0095506d7b5947d60ed Mon Sep 17 00:00:00 2001 From: cardoso-topdev Date: Sun, 29 Dec 2024 21:24:11 -0600 Subject: [PATCH 116/554] feat: update the readme --- README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 34161922..53f12d35 100644 --- a/README.md +++ b/README.md @@ -139,12 +139,6 @@ pm2 start neurons/validators/validator.py --name "webgenie_validator" --interpre pm2 start --name auto_update auto_update.sh ``` -## Requirements - -- Miners can use any port. -- Miners can use OpenAI API key or can use their own model. -- Validators need to use OpenAI API key to generate a task for miners. - ## Roadmap ### Phase 1: Foundation (Q4 2024) @@ -161,9 +155,11 @@ pm2 start --name auto_update auto_update.sh - [ ] Upgrade front-end application to v2 - Enable figma design inputs - [ ] Upgrade incentive mechanism to v2 - - Generate full framework based on React, Vue, and Next.js projects from text, image, and figma prompts + - Output CMS such as Wordpress, shopify, and Magento ### Phase 3: Expand (Q2 2025) +- [ ] Upgrade incentive mechanism to v3 + - Generate full framework based projects on React, Vue, and Next.js from text and image prompts - [ ] Add features to monetize the application - Add payment gateways - Automate the downloading of fully functional projects From 664076d8ec6855b81d925d107cbe9d56e8ee9468 Mon Sep 17 00:00:00 2001 From: cardoso-topdev Date: Sun, 29 Dec 2024 21:24:59 -0600 Subject: [PATCH 117/554] feat: update the readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 53f12d35..fcd572d5 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,6 @@ pm2 start --name auto_update auto_update.sh ### Phase 2: Upgrade (Q1 2025) - [ ] Build dashboard to track miner performance and progress - [ ] Upgrade front-end application to v2 - - Enable figma design inputs - [ ] Upgrade incentive mechanism to v2 - Output CMS such as Wordpress, shopify, and Magento From 7df5a6244efeb6f28e2435dc12980b3d88a19273 Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 2 Jan 2025 02:25:44 -0600 Subject: [PATCH 118/554] feat: implemented batch scoring --- neurons/validators/genie_validator.py | 32 ++++++++++++++++++++------- neurons/validators/validator.py | 3 +-- webgenie/base/validator.py | 7 +++--- webgenie/constants.py | 3 +++ webgenie/rewards/metrics/s_bert.py | 2 +- webgenie/tasks/task_generator.py | 3 +-- 6 files changed, 33 insertions(+), 17 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 14f67f2d..ee3912f5 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -1,11 +1,20 @@ +import os import bittensor as bt +import numpy as np import random -from typing import Union +from typing import Union, List from webgenie.base.neuron import BaseNeuron -from webgenie.constants import MAX_SYNTHETIC_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE, MAX_DEBUG_IMAGE_STRING_LENGTH +from webgenie.constants import ( + MAX_SYNTHETIC_HISTORY_SIZE, + MAX_SYNTHETIC_TASK_SIZE, + MAX_DEBUG_IMAGE_STRING_LENGTH, + UPDATE_SCORES_STEP, + WORK_DIR +) from webgenie.helpers.htmls import preprocess_html from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse +from webgenie.rewards.incentive_rewards import get_incentive_rewards from webgenie.tasks.solution import Solution from webgenie.tasks.image_task_generator import ImageTaskGenerator from webgenie.tasks.text_task_generator import TextTaskGenerator @@ -26,9 +35,6 @@ def __init__(self, neuron: BaseNeuron): self.make_work_dir() def make_work_dir(self): - import os - from webgenie.constants import WORK_DIR - if not os.path.exists(WORK_DIR): os.makedirs(WORK_DIR) bt.logging.info(f"Created work directory at {WORK_DIR}") @@ -65,7 +71,18 @@ async def forward(self): except Exception as e: bt.logging.error(f"Error in forward: {e}") raise e - + + def update_raw_scores(self, rewards: np.ndarray, uids: List[int]): + rewards = np.asarray(rewards) + uids = np.asarray(uids) + + self.neuron.raw_scores[uids] += rewards + self.neuron.step += 1 + + if self.neuron.step % UPDATE_SCORES_STEP == 0: + incentive_rewards = get_incentive_rewards(self.neuron.raw_scores) + self.neuron.update_scores(incentive_rewards, [i for i in range(self.neuron.metagraph.n)]) + async def score(self): if not self.synthetic_history: return @@ -79,8 +96,7 @@ async def score(self): rewards = await task_generator.reward(task, solutions) bt.logging.debug(f"Incentive rewards: {rewards}") - self.neuron.update_scores(rewards, miner_uids) - self.neuron.sync() + self.update_raw_scores(rewards, miner_uids) async def synthensize_task(self): try: diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index a3cbe5f6..a7a61bb2 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -92,10 +92,8 @@ async def forward_loop(self): bt.logging.info(f"Validator starting at block: {self.block}") while True: try: - bt.logging.info(f"step({self.step}) block({self.block})") self.loop.run_until_complete(self.concurrent_forward()) self.sync() - self.step += 1 except Exception as e: bt.logging.error(f"Error during forward loop: {str(e)}") await asyncio.sleep(1) @@ -105,6 +103,7 @@ async def scoring_loop(self): while True: try: await self.genie_validator.score() + self.sync() except Exception as e: bt.logging.error(f"Error during scoring: {str(e)}") await asyncio.sleep(1) diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index 2033134e..137a47a1 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -61,10 +61,6 @@ def __init__(self, config=None): else: self.dendrite = bt.dendrite(wallet=self.wallet) bt.logging.info(f"Dendrite: {self.dendrite}") - - # Set up initial scoring weights for validation - bt.logging.info("Building validation weights.") - self.scores = np.zeros(self.metagraph.n, dtype=np.float32) bt.logging.info("load_state()") self.load_state() @@ -238,6 +234,7 @@ def save_state(self): np.savez( self.config.neuron.full_path + "/state.npz", step=self.step, + raw_scores=self.raw_scores, scores=self.scores, hotkeys=self.hotkeys, ) @@ -252,11 +249,13 @@ def load_state(self): state = np.load(self.config.neuron.full_path + "/state.npz") if "step" in state: self.step = state["step"] + self.raw_scores = state["raw_scores"] self.scores = state["scores"] self.hotkeys = state["hotkeys"] else: bt.logging.warning("No state found. Initializing with default values.") self.step = 0 + self.raw_scores = np.zeros(self.metagraph.n, dtype=np.float32) self.scores = np.zeros(self.metagraph.n, dtype=np.float32) self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) diff --git a/webgenie/constants.py b/webgenie/constants.py index df5f284d..d4155f92 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -10,6 +10,9 @@ # max debug image string length MAX_DEBUG_IMAGE_STRING_LENGTH = 20 +# update scores step +UPDATE_SCORES_STEP = 10 + # place holder image url PLACE_HOLDER_IMAGE_URL = "https://picsum.photos/seed/picsum/800/600" diff --git a/webgenie/rewards/metrics/s_bert.py b/webgenie/rewards/metrics/s_bert.py index e58152f6..264cb55c 100644 --- a/webgenie/rewards/metrics/s_bert.py +++ b/webgenie/rewards/metrics/s_bert.py @@ -9,7 +9,7 @@ def score(sentences1, sentences2): similarities = cosine_similarity(embeddings1, embeddings2) # Scale similarities to be between 0 and 1 scores = [(float(similarity[0]) + 1) / 2 for similarity in similarities] - return scores + return [similarity[0] for similarity in similarities] if __name__ == "__main__": # Define a list of sentence pairs diff --git a/webgenie/tasks/task_generator.py b/webgenie/tasks/task_generator.py index 6a546498..562f66dc 100644 --- a/webgenie/tasks/task_generator.py +++ b/webgenie/tasks/task_generator.py @@ -21,6 +21,5 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: for reward, weight in self.rewards: reward_scores = await reward.reward(task, solutions) scores += weight * np.array(reward_scores) - rewards = get_incentive_rewards(scores) - return rewards + return scores From 73165c16bf0da04a3a56954259d050aee5612cb7 Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 2 Jan 2025 02:31:34 -0600 Subject: [PATCH 119/554] feat: updated incentive reward --- webgenie/rewards/incentive_rewards.py | 34 ++++----------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/webgenie/rewards/incentive_rewards.py b/webgenie/rewards/incentive_rewards.py index 1da56501..e82efaf5 100644 --- a/webgenie/rewards/incentive_rewards.py +++ b/webgenie/rewards/incentive_rewards.py @@ -1,44 +1,20 @@ -import bittensor as bt import numpy as np def get_incentive_rewards(scores: np.ndarray, base_reward=100, alpha=1.5) -> np.ndarray: - """ - Calculate rewards based on the piecewise linear with exponential growth method, - preserving the original order of the scores, and returning rewards as a NumPy array. - - Parameters: - - scores: NumPy array of raw scores. - - base_reward: The minimum reward for the highest rank (rank 1). - - alpha: The exponential scaling factor for ranks above the threshold. - - Returns: - - rewards: NumPy array of rewards corresponding to the original order of scores. - """ - bt.logging.debug(f"Scores: {scores}") threshold = scores.shape[0] // 2 - - # Ensure input is a NumPy array scores = np.array(scores) - - # Rank players based on scores (highest score gets better rank) - sorted_scores = np.sort(scores) # Sort in ascending order - score_to_rank = {score: idx + 1 for idx, score in enumerate(sorted_scores)} # Map each score to its rank - # Calculate rewards for each score based on its rank - rewards = np.zeros_like(scores, dtype=float) # Initialize the rewards array + sorted_scores = np.sort(scores) + score_to_rank = {score: idx + 1 for idx, score in enumerate(sorted_scores)} + rewards = np.zeros_like(scores, dtype=float) for idx, score in enumerate(scores): rank = score_to_rank[score] - if rank <= threshold: - # Linear reward scaling for ranks <= threshold - reward = base_reward + (rank - 1) * (base_reward / 2) # Linear scaling + reward = (rank - 1) * (base_reward / 2) # Linear scaling else: - # Exponential reward scaling for ranks > threshold reward = base_reward * (alpha ** (rank - threshold)) # Exponential scaling - - rewards[idx] = reward # Assign reward to the corresponding index + rewards[idx] = reward - bt.logging.debug(f"Rewards: {rewards}") return rewards From a8aa6aaf093f550b87e6f5f818990a5d623b4ee1 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 3 Jan 2025 07:43:26 -0600 Subject: [PATCH 120/554] feat: added code quality reward --- webgenie/base/miner.py | 4 -- webgenie/prompts.py | 12 ++++++ webgenie/rewards/quality_reward.py | 58 ++++++++++++++++++++++++++ webgenie/tasks/image_task_generator.py | 4 +- webgenie/tasks/text_task_generator.py | 9 ++-- 5 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 webgenie/rewards/quality_reward.py diff --git a/webgenie/base/miner.py b/webgenie/base/miner.py index 0f567652..4f449b0d 100644 --- a/webgenie/base/miner.py +++ b/webgenie/base/miner.py @@ -180,8 +180,4 @@ def __exit__(self, exc_type, exc_value, traceback): def resync_metagraph(self): """Resyncs the metagraph and updates the hotkeys and moving averages based on the new metagraph.""" - #TODO: Implement this - #bt.logging.info("resync_metagraph()") - - # Sync the metagraph. self.metagraph.sync(subtensor=self.subtensor) diff --git a/webgenie/prompts.py b/webgenie/prompts.py index c4b89048..6caa4394 100644 --- a/webgenie/prompts.py +++ b/webgenie/prompts.py @@ -40,3 +40,15 @@ {instructions} """ + +PROMPT_QUALITY = """ +You are an HTML, CSS expert. I have an HTML code. +I want you to evaluate the html code on the following criteria and give a score from 0 to 100. +1. The html code is in the professional style. +2. The html code is not using redundant code. + +The following is the given html code: +{html} + +{instructions} +""" \ No newline at end of file diff --git a/webgenie/rewards/quality_reward.py b/webgenie/rewards/quality_reward.py new file mode 100644 index 00000000..26b7bca3 --- /dev/null +++ b/webgenie/rewards/quality_reward.py @@ -0,0 +1,58 @@ +# The paper [Unsupervised Evaluation of Code LLMs with Round-Trip Correctness] +# (https://arxiv.org/pdf/2402.08699#page=11&zoom=100,384,458) is our inspiration for this reward. + +import bittensor as bt +import bert_score +import os +import numpy as np +from typing import List + +from langchain_openai import ChatOpenAI +from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate +from langchain_core.output_parsers import JsonOutputParser +from langchain_core.pydantic_v1 import BaseModel, Field + +from webgenie.prompts import PROMPT_QUALITY +from webgenie.rewards import Reward +from webgenie.rewards.metrics import s_bert +from webgenie.tasks.task import Task +from webgenie.tasks.solution import Solution + + +class ScoreResponse(BaseModel): + score: float = Field(default=0, description="The score of the html code") + +class QualityReward(Reward): + def __init__(self): + self.model = ChatOpenAI( + api_key= os.getenv("OPENAI_API_KEY"), + model_name=os.getenv("LLM_MODEL_ID"), + base_url=os.getenv("LLM_MODEL_URL"), + ) + + self.prompt_response_parser = JsonOutputParser(pydantic_object=ScoreResponse) + + async def _get_score(self, solution: Solution) -> float: + prompt = ChatPromptTemplate.from_messages([ + SystemMessagePromptTemplate.from_template(PROMPT_QUALITY) + ]) + + chain = prompt | self.model | self.prompt_response_parser + prompt_response = await chain.ainvoke({ + "html": solution.html, + "instructions": self.prompt_response_parser.get_format_instructions() + }) + + return float(prompt_response["score"]) / 100 + + async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: + bt.logging.debug(f"Rewarding task in quality reward") + scores = [] + for solution in solutions: + score = await self._get_score(solution) + scores.append(score) + return np.array(scores) + + + + \ No newline at end of file diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 9e5507ba..cf25584d 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -8,6 +8,7 @@ from webgenie.tasks.solution import Solution from webgenie.tasks.task import Task, ImageTask from webgenie.tasks.task_generator import TaskGenerator +from webgenie.rewards.quality_reward import QualityReward from webgenie.rewards.visual_reward import VisualReward from webgenie.datasets.mockup_dataset import MockUpDataset from webgenie.datasets.synthetic_dataset import SyntheticDataset @@ -17,7 +18,8 @@ class ImageTaskGenerator(TaskGenerator): def __init__(self): super().__init__() self.rewards = [ - (VisualReward(), 1.0) + (VisualReward(), 0.9), + (QualityReward(), 0.1) ] self.datasets = [ # MockUpDataset(), diff --git a/webgenie/tasks/text_task_generator.py b/webgenie/tasks/text_task_generator.py index 9387b83a..272e8ecf 100644 --- a/webgenie/tasks/text_task_generator.py +++ b/webgenie/tasks/text_task_generator.py @@ -8,6 +8,7 @@ ) from webgenie.protocol import WebgenieTextSynapse from webgenie.rewards.bert_reward import BertReward +from webgenie.rewards.quality_reward import QualityReward from webgenie.rewards.rtc_reward import RtcReward from webgenie.tasks.task import Task, TextTask from webgenie.tasks.task_generator import TaskGenerator @@ -17,8 +18,9 @@ def __init__(self, has_ground_truth_html: bool = True): super().__init__() if has_ground_truth_html: self.rewards = [ - (BertReward(), 0.5), - (RtcReward(), 0.5) + (BertReward(), 0.4), + (RtcReward(), 0.5), + (QualityReward(), 0.1) ] self.datasets = [ @@ -27,7 +29,8 @@ def __init__(self, has_ground_truth_html: bool = True): ] else: self.rewards = [ - (RtcReward(), 1.0) + (RtcReward(), 0.9), + (QualityReward(), 0.1) ] self.datasets = [ From b862df3d8c027d95b1c07d9576af3093c91c6d6b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 3 Jan 2025 07:49:58 -0600 Subject: [PATCH 121/554] chore: added code quality criteria --- webgenie/prompts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webgenie/prompts.py b/webgenie/prompts.py index 6caa4394..a6771b2a 100644 --- a/webgenie/prompts.py +++ b/webgenie/prompts.py @@ -45,7 +45,8 @@ You are an HTML, CSS expert. I have an HTML code. I want you to evaluate the html code on the following criteria and give a score from 0 to 100. 1. The html code is in the professional style. -2. The html code is not using redundant code. +2. The html code is using SEO-friendly practices. +3. The html code is not using redundant code. The following is the given html code: {html} From 975ff6ec56df04730363e8a25e3784f0ba301d06 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sun, 5 Jan 2025 00:18:04 -0600 Subject: [PATCH 122/554] chore: implemented stressing miners --- .env.miner.example | 2 +- .env.validator.example | 2 +- neurons/miners/openai_miner.py | 2 +- neurons/validators/genie_validator.py | 36 +++++++++++++----------- neurons/validators/validator.py | 31 +++++++++++--------- webgenie/base/neuron.py | 8 ------ webgenie/base/validator.py | 7 ----- webgenie/constants.py | 7 ++--- webgenie/datasets/huggingface_dataset.py | 2 +- webgenie/datasets/synthetic_dataset.py | 2 +- webgenie/helpers/llm_calls.py | 0 webgenie/rewards/quality_reward.py | 2 +- webgenie/rewards/rtc_reward.py | 2 +- 13 files changed, 45 insertions(+), 58 deletions(-) create mode 100644 webgenie/helpers/llm_calls.py diff --git a/.env.miner.example b/.env.miner.example index 4a124b0b..a974dd68 100644 --- a/.env.miner.example +++ b/.env.miner.example @@ -1,4 +1,4 @@ -OPENAI_API_KEY = your_openai_api_key +LLM_API_KEY = your_openai_api_key WANDB_API_KEY = your_wandb_api_key WANDB_ENTITY_NAME = your_wandb_entity_name diff --git a/.env.validator.example b/.env.validator.example index 810ecffc..c47a6452 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -1,4 +1,4 @@ -OPENAI_API_KEY = your_openai_api_key +LLM_API_KEY = your_openai_api_key WANDB_API_KEY = your_wandb_api_key WANDB_ENTITY_NAME = your_wandb_entity_name diff --git a/neurons/miners/openai_miner.py b/neurons/miners/openai_miner.py index 7bcda60d..1a2bde79 100644 --- a/neurons/miners/openai_miner.py +++ b/neurons/miners/openai_miner.py @@ -18,7 +18,7 @@ def __init__(self, neuron: BaseNeuron): self.neuron = neuron self.model = ChatOpenAI( - api_key= os.getenv("OPENAI_API_KEY"), + api_key= os.getenv("LLM_API_KEY"), model_name="gpt-4o", ) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index ee3912f5..db2b8fa9 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -26,6 +26,7 @@ def __init__(self, neuron: BaseNeuron): self.config = neuron.config self.synthetic_history = [] self.synthetic_tasks = [] + self.un_responsed_count = [0] * self.neuron.metagraph.n self.task_generators = [ (TextTaskGenerator(), 0.1), @@ -39,15 +40,17 @@ def make_work_dir(self): os.makedirs(WORK_DIR) bt.logging.info(f"Created work directory at {WORK_DIR}") - async def forward(self): + async def query_miners(self): try: if len(self.synthetic_history) > MAX_SYNTHETIC_HISTORY_SIZE: return if not self.synthetic_tasks: return - bt.logging.info("Popping synthetic task and sending it to miners") + task, synapse = self.synthetic_tasks.pop(0) + bt.logging.info("Popping synthetic task and sending it to miners") + miner_uids = get_random_uids(self.neuron, k=self.config.neuron.sample_size) bt.logging.debug(f"Selected miner uids: {miner_uids}") @@ -58,45 +61,44 @@ async def forward(self): ) solutions = [] + for synapse, miner_uid in zip(all_synapse_results, miner_uids): processed_synapse = await self.process_synapse(synapse) if processed_synapse is not None: solutions.append(Solution(html = processed_synapse.html, miner_uid = miner_uid, process_time = processed_synapse.dendrite.process_time)) - + else: + self.un_responsed_count[miner_uid] += 1 + if not solutions: bt.logging.warning(f"No valid solutions received") return bt.logging.info(f"Received {len(solutions)} solutions") + self.synthetic_history.append((task, solutions)) except Exception as e: bt.logging.error(f"Error in forward: {e}") raise e - - def update_raw_scores(self, rewards: np.ndarray, uids: List[int]): - rewards = np.asarray(rewards) - uids = np.asarray(uids) - - self.neuron.raw_scores[uids] += rewards - self.neuron.step += 1 - - if self.neuron.step % UPDATE_SCORES_STEP == 0: - incentive_rewards = get_incentive_rewards(self.neuron.raw_scores) - self.neuron.update_scores(incentive_rewards, [i for i in range(self.neuron.metagraph.n)]) async def score(self): if not self.synthetic_history: return - task, solutions = self.synthetic_history.pop(0) + task, solutions = random.choice(self.synthetic_history) + self.synthetic_history = [] + task_generator = task.generator miner_uids = [solution.miner_uid for solution in solutions] bt.logging.debug(f"Miner uids: {miner_uids}") rewards = await task_generator.reward(task, solutions) - bt.logging.debug(f"Incentive rewards: {rewards}") - self.update_raw_scores(rewards, miner_uids) + for i in range(len(miner_uids)): + responsed_ratio = 1 - self.un_responsed_count[miner_uids[i]] / len(self.synthetic_history) + rewards[i] = rewards[i] * responsed_ratio * responsed_ratio + + bt.logging.debug(f"Incentive rewards: {rewards}") + self.neuron.update_scores(rewards, miner_uids) async def synthensize_task(self): try: diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index a7a61bb2..c833de85 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -77,28 +77,28 @@ def serve_axon(self): bt.logging.error(f"Failed to serve Axon with exception: {e}") pass - async def forward(self): - return await self.genie_validator.forward() + async def query_miners(self): + return await self.genie_validator.query_miners() - async def concurrent_forward(self): + async def concurrent_query(self): coroutines = [ - self.forward() + self.query_miners() for _ in range(self.config.neuron.num_concurrent_forwards) ] await asyncio.gather(*coroutines) - async def forward_loop(self): - self.sync() + async def query_miners_loop(self): bt.logging.info(f"Validator starting at block: {self.block}") + self.sync() while True: try: - self.loop.run_until_complete(self.concurrent_forward()) + self.loop.run_until_complete(self.concurrent_query()) self.sync() except Exception as e: bt.logging.error(f"Error during forward loop: {str(e)}") await asyncio.sleep(1) - async def scoring_loop(self): + async def score_loop(self): bt.logging.info(f"Scoring loop starting") while True: try: @@ -119,17 +119,20 @@ async def synthensize_task_loop(self): async def __aenter__(self): self.loop.create_task(self.synthensize_task_loop()) - self.loop.create_task(self.forward_loop()) - self.loop.create_task(self.scoring_loop()) + self.loop.create_task(self.query_miners_loop()) + self.loop.create_task(self.score_loop()) self.is_running = True + bt.logging.debug("Starting validator in background thread") return self async def __aexit__(self, exc_type, exc_value, traceback): - if self.is_running: - self.should_exit = True - self.is_running = False - bt.logging.debug("Stopping validator in background thread") + if not self.is_running: + return + + self.should_exit = True + self.is_running = False + bt.logging.debug("Stopping validator in background thread") async def main(): async with Validator() as validator: diff --git a/webgenie/base/neuron.py b/webgenie/base/neuron.py index 1eed9a77..3fffe10b 100644 --- a/webgenie/base/neuron.py +++ b/webgenie/base/neuron.py @@ -108,14 +108,6 @@ def __init__(self, config=None): ) self.step = 0 - @abstractmethod - async def forward(self, synapse: bt.Synapse) -> bt.Synapse: - ... - - @abstractmethod - def run(self): - ... - def sync(self): """ Wrapper for synchronizing the state of the network for the given miner or validator. diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index 137a47a1..bfc9d8e7 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -76,10 +76,6 @@ def __init__(self, config=None): self.is_running: bool = False self.thread: Union[threading.Thread, None] = None self.lock = asyncio.Lock() - - - def run(self): - pass def set_weights(self): """ @@ -234,7 +230,6 @@ def save_state(self): np.savez( self.config.neuron.full_path + "/state.npz", step=self.step, - raw_scores=self.raw_scores, scores=self.scores, hotkeys=self.hotkeys, ) @@ -249,13 +244,11 @@ def load_state(self): state = np.load(self.config.neuron.full_path + "/state.npz") if "step" in state: self.step = state["step"] - self.raw_scores = state["raw_scores"] self.scores = state["scores"] self.hotkeys = state["hotkeys"] else: bt.logging.warning("No state found. Initializing with default values.") self.step = 0 - self.raw_scores = np.zeros(self.metagraph.n, dtype=np.float32) self.scores = np.zeros(self.metagraph.n, dtype=np.float32) self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) diff --git a/webgenie/constants.py b/webgenie/constants.py index d4155f92..0cc3725c 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -2,17 +2,14 @@ API_HOTKEY = "5DXDCYTuPfLqQXbxfvvnarG31SdTDtaubqpQrzjrcMgoP9dp" # max synthetic history size -MAX_SYNTHETIC_HISTORY_SIZE = 3 +MAX_SYNTHETIC_HISTORY_SIZE = 30 # max synthensize task size -MAX_SYNTHETIC_TASK_SIZE = 3 +MAX_SYNTHETIC_TASK_SIZE = 30 # max debug image string length MAX_DEBUG_IMAGE_STRING_LENGTH = 20 -# update scores step -UPDATE_SCORES_STEP = 10 - # place holder image url PLACE_HOLDER_IMAGE_URL = "https://picsum.photos/seed/picsum/800/600" diff --git a/webgenie/datasets/huggingface_dataset.py b/webgenie/datasets/huggingface_dataset.py index 6ef1fb80..1ae7c7b7 100644 --- a/webgenie/datasets/huggingface_dataset.py +++ b/webgenie/datasets/huggingface_dataset.py @@ -24,7 +24,7 @@ def __init__(self , dataset_name: str, split: str, html_field: str): self.model = ChatOpenAI( base_url=os.getenv("LLM_MODEL_URL"), model=os.getenv("LLM_MODEL_ID"), - api_key=os.getenv("OPENAI_API_KEY") + api_key=os.getenv("LLM_API_KEY") ) self.output_parser = JsonOutputParser(pydantic_object=HTMLResponse) diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index 58e57124..0c8b3100 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -26,7 +26,7 @@ def __init__(self, has_ground_truth_html: bool = True): self.has_ground_truth_html = has_ground_truth_html self.model = ChatOpenAI( - api_key= os.getenv("OPENAI_API_KEY"), + api_key= os.getenv("LLM_API_KEY"), model_name=os.getenv("LLM_MODEL_ID"), base_url=os.getenv("LLM_MODEL_URL"), temperature=0.6, diff --git a/webgenie/helpers/llm_calls.py b/webgenie/helpers/llm_calls.py new file mode 100644 index 00000000..e69de29b diff --git a/webgenie/rewards/quality_reward.py b/webgenie/rewards/quality_reward.py index 26b7bca3..35fc3ea9 100644 --- a/webgenie/rewards/quality_reward.py +++ b/webgenie/rewards/quality_reward.py @@ -25,7 +25,7 @@ class ScoreResponse(BaseModel): class QualityReward(Reward): def __init__(self): self.model = ChatOpenAI( - api_key= os.getenv("OPENAI_API_KEY"), + api_key= os.getenv("LLM_API_KEY"), model_name=os.getenv("LLM_MODEL_ID"), base_url=os.getenv("LLM_MODEL_URL"), ) diff --git a/webgenie/rewards/rtc_reward.py b/webgenie/rewards/rtc_reward.py index 424cef58..36f6e8c4 100644 --- a/webgenie/rewards/rtc_reward.py +++ b/webgenie/rewards/rtc_reward.py @@ -25,7 +25,7 @@ class PromptResponse(BaseModel): class RtcReward(Reward): def __init__(self): self.model = ChatOpenAI( - api_key= os.getenv("OPENAI_API_KEY"), + api_key= os.getenv("LLM_API_KEY"), model_name=os.getenv("LLM_MODEL_ID"), base_url=os.getenv("LLM_MODEL_URL"), ) From 6fd2e2fffefb185d5792fcec529054710b29dbca Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sun, 5 Jan 2025 02:44:45 -0600 Subject: [PATCH 123/554] feat: implemented stressing miners --- neurons/miners/miner.py | 1 - neurons/miners/openai_miner.py | 33 ++- neurons/validators/genie_validator.py | 10 +- run_miners.sh | 2 + run_validator copy.sh | 3 + webgenie/base/validator.py | 3 - webgenie/constants.py | 9 + webgenie/datasets/__init__.py | 1 - webgenie/datasets/huggingface_dataset.py | 32 ++- webgenie/datasets/mockup_dataset.py | 264 ----------------------- webgenie/datasets/synthetic_dataset.py | 38 ++-- webgenie/helpers/llm_calls.py | 0 webgenie/helpers/llms.py | 26 +++ webgenie/rewards/__init__.py | 4 +- webgenie/rewards/quality_reward.py | 32 +-- webgenie/rewards/rtc_reward.py | 32 +-- webgenie/tasks/image_task_generator.py | 12 +- webgenie/tasks/text_task_generator.py | 36 +--- webgenie/utils/config.py | 2 +- 19 files changed, 131 insertions(+), 409 deletions(-) create mode 100644 run_miners.sh create mode 100644 run_validator copy.sh delete mode 100644 webgenie/datasets/mockup_dataset.py delete mode 100644 webgenie/helpers/llm_calls.py create mode 100644 webgenie/helpers/llms.py diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index 61d17ec6..acc01d50 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -168,7 +168,6 @@ async def priority(self, synapse: bt.Synapse) -> float: bt.logging.warning("Received a request without a dendrite or hotkey.") return 0.0 - # TODO(developer): Define how miners should prioritize requests. caller_uid = self.metagraph.hotkeys.index( synapse.dendrite.hotkey ) # Get the caller index. diff --git a/neurons/miners/openai_miner.py b/neurons/miners/openai_miner.py index 1a2bde79..daaff6ea 100644 --- a/neurons/miners/openai_miner.py +++ b/neurons/miners/openai_miner.py @@ -7,6 +7,7 @@ from langchain_core.pydantic_v1 import BaseModel, Field from webgenie.base.neuron import BaseNeuron +from webgenie.helpers.llms import call_llm from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from webgenie.tasks.solution import Solution @@ -17,16 +18,11 @@ class OpenaiMiner: def __init__(self, neuron: BaseNeuron): self.neuron = neuron - self.model = ChatOpenAI( - api_key= os.getenv("LLM_API_KEY"), - model_name="gpt-4o", - ) - self.html_response_parser = JsonOutputParser(pydantic_object=HTMLResponse) async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynapse: try: - prompt = ChatPromptTemplate.from_messages([ + template = [ ("system", """You are an expert web developer who specializes in HTML and CSS. A user will provide you with the webpage requirements. You need to return a single html file that uses HTML and CSS to satisfy the requirements. Include all CSS code in the HTML file itself. If it involves any images, use "rick.jpg" as the placeholder. @@ -35,13 +31,13 @@ async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynaps Respond with the content of the HTML+CSS file: {instructions}"""), ("user", "{query}"), - ]) + ] - chain = prompt | self.model | self.html_response_parser - html_response = await chain.ainvoke({ - "query": synapse.prompt, - "instructions": self.html_response_parser.get_format_instructions() - }) + html_response = await call_llm( + template=template, + params={"query": synapse.prompt, "instructions": self.html_response_parser.get_format_instructions()}, + output_parser=self.html_response_parser + ) synapse.html = html_response["html"] return synapse @@ -71,14 +67,11 @@ async def forward_image(self, synapse: WebgenieImageSynapse) -> WebgenieImageSyn ) ] - prompt = ChatPromptTemplate(messages=prompt_messages) - - chain = prompt | self.model | self.html_response_parser - - html_response = chain.invoke({ - "instructions": self.html_response_parser.get_format_instructions(), - "image_url": f"data:image/jpeg;base64,{synapse.base64_image}", - }) + html_response = await call_llm( + template=prompt_messages, + params={"instructions": self.html_response_parser.get_format_instructions(), "image_url": f"data:image/jpeg;base64,{synapse.base64_image}"}, + output_parser=self.html_response_parser + ) synapse.html = html_response["html"] return synapse diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index db2b8fa9..2e743948 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -9,12 +9,11 @@ MAX_SYNTHETIC_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE, MAX_DEBUG_IMAGE_STRING_LENGTH, - UPDATE_SCORES_STEP, + MIN_SYNTHETIC_HISTORY_SIZE_TO_SCORE, WORK_DIR ) from webgenie.helpers.htmls import preprocess_html from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse -from webgenie.rewards.incentive_rewards import get_incentive_rewards from webgenie.tasks.solution import Solution from webgenie.tasks.image_task_generator import ImageTaskGenerator from webgenie.tasks.text_task_generator import TextTaskGenerator @@ -26,7 +25,9 @@ def __init__(self, neuron: BaseNeuron): self.config = neuron.config self.synthetic_history = [] self.synthetic_tasks = [] - self.un_responsed_count = [0] * self.neuron.metagraph.n + bt.logging.info(f"GenieValidator initialized with neuron: {self.neuron.metagraph.n}") + self.un_responsed_count = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + bt.logging.info(f"Un responsed count: {self.un_responsed_count}") self.task_generators = [ (TextTaskGenerator(), 0.1), @@ -80,7 +81,7 @@ async def query_miners(self): raise e async def score(self): - if not self.synthetic_history: + if len(self.synthetic_history) < MIN_SYNTHETIC_HISTORY_SIZE_TO_SCORE: return task, solutions = random.choice(self.synthetic_history) @@ -99,6 +100,7 @@ async def score(self): bt.logging.debug(f"Incentive rewards: {rewards}") self.neuron.update_scores(rewards, miner_uids) + self.neuron.step += 1 async def synthensize_task(self): try: diff --git a/run_miners.sh b/run_miners.sh new file mode 100644 index 00000000..140c643a --- /dev/null +++ b/run_miners.sh @@ -0,0 +1,2 @@ +export PYTHONPATH=. +pm2 start neurons/miners/miner.py --name "webgenie_miner" --interpreter python -- --netuid 214 --subtensor.network test --wallet.name s-miner --wallet.hotkey miner2 --logging.debug --axon.port 8090 \ No newline at end of file diff --git a/run_validator copy.sh b/run_validator copy.sh new file mode 100644 index 00000000..1e3606ac --- /dev/null +++ b/run_validator copy.sh @@ -0,0 +1,3 @@ +export PYTHONPATH=. + +pm2 start neurons/validators/validator.py --name "webgenie_validator" --interpreter python -- --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug --neuron.axon_port 8091 \ No newline at end of file diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index bfc9d8e7..944bb3aa 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -224,7 +224,6 @@ def update_scores(self, rewards: np.ndarray, uids: List[int]): def save_state(self): """Saves the state of the validator to a file.""" - bt.logging.info("Saving validator state.") # Save the state of the validator to file. np.savez( @@ -234,8 +233,6 @@ def save_state(self): hotkeys=self.hotkeys, ) - bt.logging.debug(f"Saved state: step={self.step}, scores={self.scores}") - def load_state(self): """Loads the state of the validator from a file.""" bt.logging.info("Loading validator state.") diff --git a/webgenie/constants.py b/webgenie/constants.py index 0cc3725c..7bc7361b 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -1,9 +1,18 @@ # backend api hotkey API_HOTKEY = "5DXDCYTuPfLqQXbxfvvnarG31SdTDtaubqpQrzjrcMgoP9dp" +# image task timeout +IMAGE_TASK_TIMEOUT = 100 + +# text task timeout +TEXT_TASK_TIMEOUT = 100 + # max synthetic history size MAX_SYNTHETIC_HISTORY_SIZE = 30 +# min synthetic history size to score +MIN_SYNTHETIC_HISTORY_SIZE_TO_SCORE = 5 + # max synthensize task size MAX_SYNTHETIC_TASK_SIZE = 30 diff --git a/webgenie/datasets/__init__.py b/webgenie/datasets/__init__.py index 4e1069f2..0e4c0f25 100644 --- a/webgenie/datasets/__init__.py +++ b/webgenie/datasets/__init__.py @@ -1,3 +1,2 @@ from .dataset import Dataset, DatasetEntry from .synthetic_dataset import SyntheticDataset -from .mockup_dataset import MockUpDataset, MockUpPromptDataset diff --git a/webgenie/datasets/huggingface_dataset.py b/webgenie/datasets/huggingface_dataset.py index 1ae7c7b7..febd94f7 100644 --- a/webgenie/datasets/huggingface_dataset.py +++ b/webgenie/datasets/huggingface_dataset.py @@ -11,40 +11,38 @@ from langchain_core.pydantic_v1 import BaseModel, Field from webgenie.datasets.dataset import Dataset, DatasetEntry +from webgenie.helpers.llms import call_llm from webgenie.prompts import PROMPT_MAKE_HTML_COMPLEX - class HTMLResponse(BaseModel): complex_html: str = Field(description="the complex html code") class HuggingfaceDataset(Dataset): - def __init__(self , dataset_name: str, split: str, html_field: str): + def __init__(self , **kwargs): + dataset_name = kwargs["dataset_name"] + html_column = kwargs["html_column"] + split = kwargs["split"] + self.dataset = load_dataset(dataset_name, split=split) - self.html_field = html_field - self.model = ChatOpenAI( - base_url=os.getenv("LLM_MODEL_URL"), - model=os.getenv("LLM_MODEL_ID"), - api_key=os.getenv("LLM_API_KEY") - ) + self.html_column = html_column self.output_parser = JsonOutputParser(pydantic_object=HTMLResponse) async def _make_html_complex(self, html: str)->str: bt.logging.info("Making HTML complex") - prompt = ChatPromptTemplate.from_messages([ - ("system", PROMPT_MAKE_HTML_COMPLEX), - ]) - chain = prompt | self.model | self.output_parser - response = await chain.ainvoke({ - "html": html, - "instructions": self.output_parser.get_format_instructions() - }) + response = await call_llm( + template=[ + ("system", PROMPT_MAKE_HTML_COMPLEX), + ], + params={"html": html, "instructions": self.output_parser.get_format_instructions()}, + output_parser=self.output_parser + ) return response["complex_html"] async def generate_context(self)->DatasetEntry: try: bt.logging.info("Generating Huggingface context") random_index = random.randint(0, len(self.dataset) - 1) - html = self.dataset[random_index][self.html_field] + html = self.dataset[random_index][self.html_column] complex_html = await self._make_html_complex(html) return DatasetEntry( src="huggingface", diff --git a/webgenie/datasets/mockup_dataset.py b/webgenie/datasets/mockup_dataset.py deleted file mode 100644 index f480a504..00000000 --- a/webgenie/datasets/mockup_dataset.py +++ /dev/null @@ -1,264 +0,0 @@ -from webgenie.datasets.dataset import Dataset, DatasetEntry - -class MockUpDataset(Dataset): - async def generate_context(self)->DatasetEntry: - html = """ - - - - - -Tech Company - - - - -
-
- - -
-
-
-Hero Image -
-
-

Welcome to Tech Company

-

At Tech Company, we are dedicated to providing the best technology solutions for your needs. Our team of experts is always ready to help you with any questions or problems you may have.

-
- - - - """ - return DatasetEntry( - src="mockup", - topic="tech company", - ground_truth_html=html, - prompt="", - base64_image="" - ) - -class MockUpPromptDataset(Dataset): - async def generate_context(self)->DatasetEntry: - html = """ - - - - - - Coming Soon - - - - - -
- -
- - -
-

Coming Soon!

-

We're working hard to launch something amazing. Stay tuned!

- -
- - - - - - - -""" - return DatasetEntry( - src="mockup", - topic="tech company", - ground_truth_html=html, - prompt="CommingSoon Page with goback button, navHeader, and footer", - base64_image="" - ) \ No newline at end of file diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index 0c8b3100..5de18660 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -13,6 +13,7 @@ from langchain_core.pydantic_v1 import BaseModel, Field from webgenie.datasets.dataset import Dataset, DatasetEntry +from webgenie.helpers.llms import call_llm from webgenie.prompts import PROMPT_GEN_CONCEPT, PROMPT_GEN_HTML class ConceptResponse(BaseModel): @@ -24,39 +25,30 @@ class HTMLResponse(BaseModel): class SyntheticDataset(Dataset): def __init__(self, has_ground_truth_html: bool = True): self.has_ground_truth_html = has_ground_truth_html - - self.model = ChatOpenAI( - api_key= os.getenv("LLM_API_KEY"), - model_name=os.getenv("LLM_MODEL_ID"), - base_url=os.getenv("LLM_MODEL_URL"), - temperature=0.6, - ) - self.concept_parser = JsonOutputParser(pydantic_object=ConceptResponse) self.html_parser = JsonOutputParser(pydantic_object=HTMLResponse) self.concepts = [] async def _generate_concepts(self): bt.logging.info("Generating concepts") - prompt = ChatPromptTemplate.from_messages([ - ("system", PROMPT_GEN_CONCEPT), - ]) - chain = prompt | self.model | self.concept_parser - response = await chain.ainvoke({ - "instructions": self.concept_parser.get_format_instructions() - }) + response = await call_llm( + template=[ + ("system", PROMPT_GEN_CONCEPT), + ], + params={"instructions": self.concept_parser.get_format_instructions()}, + output_parser=self.concept_parser + ) return response["concepts"] async def _generate_html(self, concept: str): bt.logging.info("Generating HTML from concept") - prompt = ChatPromptTemplate.from_messages([ - ("system", PROMPT_GEN_HTML), - ]) - chain = prompt | self.model | self.html_parser - response = await chain.ainvoke({ - "concept": concept, - "instructions": self.html_parser.get_format_instructions() - }) + response = await call_llm( + template=[ + ("system", PROMPT_GEN_HTML), + ], + params={"concept": concept, "instructions": self.html_parser.get_format_instructions()}, + output_parser=self.html_parser + ) return response["html"] async def generate_context(self)->DatasetEntry: diff --git a/webgenie/helpers/llm_calls.py b/webgenie/helpers/llm_calls.py deleted file mode 100644 index e69de29b..00000000 diff --git a/webgenie/helpers/llms.py b/webgenie/helpers/llms.py new file mode 100644 index 00000000..2a8cefe4 --- /dev/null +++ b/webgenie/helpers/llms.py @@ -0,0 +1,26 @@ +import os +import bittensor as bt + +from langchain_core.prompts import ChatPromptTemplate +from langchain_openai import ChatOpenAI + +LLM = ChatOpenAI( + base_url=os.getenv("LLM_MODEL_URL"), + model=os.getenv("LLM_MODEL_ID"), + api_key=os.getenv("LLM_API_KEY"), + temperature=0.7 +) + +async def call_llm(template, params, output_parser, retries=3): + if not os.getenv("LLM_API_KEY"): + raise Exception("LLM_API_KEY is not set") + + for _ in range(retries): + try: + prompt = ChatPromptTemplate.from_messages(template) + chain = prompt | LLM | output_parser + return await chain.ainvoke(params) + except Exception as e: + bt.logging.error(f"Error calling LLM: {e}") + continue + raise Exception("Failed to call LLM") \ No newline at end of file diff --git a/webgenie/rewards/__init__.py b/webgenie/rewards/__init__.py index a4073de3..9624c634 100644 --- a/webgenie/rewards/__init__.py +++ b/webgenie/rewards/__init__.py @@ -1,2 +1,4 @@ from .reward import Reward -from .visual_reward import VisualReward \ No newline at end of file +from .visual_reward import VisualReward +from .quality_reward import QualityReward +from .rtc_reward import RtcReward \ No newline at end of file diff --git a/webgenie/rewards/quality_reward.py b/webgenie/rewards/quality_reward.py index 35fc3ea9..4173f73f 100644 --- a/webgenie/rewards/quality_reward.py +++ b/webgenie/rewards/quality_reward.py @@ -7,43 +7,31 @@ import numpy as np from typing import List -from langchain_openai import ChatOpenAI -from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate from langchain_core.output_parsers import JsonOutputParser from langchain_core.pydantic_v1 import BaseModel, Field +from webgenie.helpers.llms import call_llm from webgenie.prompts import PROMPT_QUALITY -from webgenie.rewards import Reward -from webgenie.rewards.metrics import s_bert +from webgenie.rewards.reward import Reward from webgenie.tasks.task import Task from webgenie.tasks.solution import Solution - class ScoreResponse(BaseModel): score: float = Field(default=0, description="The score of the html code") class QualityReward(Reward): def __init__(self): - self.model = ChatOpenAI( - api_key= os.getenv("LLM_API_KEY"), - model_name=os.getenv("LLM_MODEL_ID"), - base_url=os.getenv("LLM_MODEL_URL"), - ) - self.prompt_response_parser = JsonOutputParser(pydantic_object=ScoreResponse) async def _get_score(self, solution: Solution) -> float: - prompt = ChatPromptTemplate.from_messages([ - SystemMessagePromptTemplate.from_template(PROMPT_QUALITY) - ]) - - chain = prompt | self.model | self.prompt_response_parser - prompt_response = await chain.ainvoke({ - "html": solution.html, - "instructions": self.prompt_response_parser.get_format_instructions() - }) - - return float(prompt_response["score"]) / 100 + response = await call_llm( + template=[ + ("system", PROMPT_QUALITY), + ], + params={"html": solution.html, "instructions": self.prompt_response_parser.get_format_instructions()}, + output_parser=self.prompt_response_parser + ) + return float(response["score"]) / 100 async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: bt.logging.debug(f"Rewarding task in quality reward") diff --git a/webgenie/rewards/rtc_reward.py b/webgenie/rewards/rtc_reward.py index 36f6e8c4..49653bcf 100644 --- a/webgenie/rewards/rtc_reward.py +++ b/webgenie/rewards/rtc_reward.py @@ -7,13 +7,12 @@ import numpy as np from typing import List -from langchain_openai import ChatOpenAI -from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate from langchain_core.output_parsers import JsonOutputParser from langchain_core.pydantic_v1 import BaseModel, Field +from webgenie.helpers.llms import call_llm from webgenie.prompts import PROMPT_RTC -from webgenie.rewards import Reward +from webgenie.rewards.reward import Reward from webgenie.rewards.metrics import s_bert from webgenie.tasks.task import Task from webgenie.tasks.solution import Solution @@ -24,27 +23,18 @@ class PromptResponse(BaseModel): class RtcReward(Reward): def __init__(self): - self.model = ChatOpenAI( - api_key= os.getenv("LLM_API_KEY"), - model_name=os.getenv("LLM_MODEL_ID"), - base_url=os.getenv("LLM_MODEL_URL"), - ) - self.prompt_response_parser = JsonOutputParser(pydantic_object=PromptResponse) async def _get_prompt(self, task: Task, solutions: List[Solution]) -> str: - prompt = ChatPromptTemplate.from_messages([ - SystemMessagePromptTemplate.from_template(PROMPT_RTC) - ]) - - chain = prompt | self.model | self.prompt_response_parser - prompt_response = await chain.ainvoke({ - "html": task.ground_truth_html, - "prompt": task.prompt, - "instructions": self.prompt_response_parser.get_format_instructions() - }) - - return prompt_response["prompt"] + response = await call_llm( + template=[ + ("system", PROMPT_RTC), + ], + params={"html": task.ground_truth_html, "prompt": task.prompt, "instructions": self.prompt_response_parser.get_format_instructions()}, + output_parser=self.prompt_response_parser + ) + + return response["prompt"] async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: bt.logging.debug(f"Rewarding task in rtc reward") diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index cf25584d..ca7ac7ed 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -3,6 +3,7 @@ import random from typing import List, Tuple +from webgenie.constants import IMAGE_TASK_TIMEOUT from webgenie.helpers.htmls import html_to_screenshot, preprocess_html, is_empty_html from webgenie.protocol import WebgenieImageSynapse from webgenie.tasks.solution import Solution @@ -10,10 +11,9 @@ from webgenie.tasks.task_generator import TaskGenerator from webgenie.rewards.quality_reward import QualityReward from webgenie.rewards.visual_reward import VisualReward -from webgenie.datasets.mockup_dataset import MockUpDataset from webgenie.datasets.synthetic_dataset import SyntheticDataset -from webgenie.datasets.huggingface_dataset import HuggingfaceDataset - +from webgenie.datasets.huggingface_dataset import HuggingfaceDataset + class ImageTaskGenerator(TaskGenerator): def __init__(self): super().__init__() @@ -22,9 +22,8 @@ def __init__(self): (QualityReward(), 0.1) ] self.datasets = [ - # MockUpDataset(), SyntheticDataset(), - HuggingfaceDataset("SALT-NLP/Design2Code-hf", "train", "text"), + HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), ] async def generate_task(self) -> Tuple[Task, bt.Synapse]: @@ -41,7 +40,8 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: return ImageTask( base64_image=base64_image, ground_truth_html=ground_truth_html, - timeout=250, + timeout=IMAGE_TASK_TIMEOUT, generator=self, ), WebgenieImageSynapse(base64_image=base64_image) + diff --git a/webgenie/tasks/text_task_generator.py b/webgenie/tasks/text_task_generator.py index 272e8ecf..d9d4abd5 100644 --- a/webgenie/tasks/text_task_generator.py +++ b/webgenie/tasks/text_task_generator.py @@ -3,11 +3,10 @@ import random from typing import List, Tuple from webgenie.datasets import ( - MockUpPromptDataset, SyntheticDataset, ) +from webgenie.constants import TEXT_TASK_TIMEOUT from webgenie.protocol import WebgenieTextSynapse -from webgenie.rewards.bert_reward import BertReward from webgenie.rewards.quality_reward import QualityReward from webgenie.rewards.rtc_reward import RtcReward from webgenie.tasks.task import Task, TextTask @@ -16,34 +15,21 @@ class TextTaskGenerator(TaskGenerator): def __init__(self, has_ground_truth_html: bool = True): super().__init__() - if has_ground_truth_html: - self.rewards = [ - (BertReward(), 0.4), - (RtcReward(), 0.5), - (QualityReward(), 0.1) - ] - - self.datasets = [ - MockUpPromptDataset(), - SyntheticDataset(has_ground_truth_html = True) - ] - else: - self.rewards = [ - (RtcReward(), 0.9), - (QualityReward(), 0.1) - ] - - self.datasets = [ - MockUpPromptDataset(), - SyntheticDataset(has_ground_truth_html = False) - ] - + self.rewards = [ + (RtcReward(), 0.9), + (QualityReward(), 0.1) + ] + + self.datasets = [ + SyntheticDataset(has_ground_truth_html = True) + ] + async def generate_task(self) -> Tuple[Task, bt.Synapse]: bt.logging.info("Generating Text task") dataset_entry = await random.choice(self.datasets).generate_context() return TextTask( prompt=dataset_entry.prompt, ground_truth_html=dataset_entry.ground_truth_html, - timeout=50, + timeout=TEXT_TASK_TIMEOUT, generator=self ), WebgenieTextSynapse(prompt=dataset_entry.prompt) diff --git a/webgenie/utils/config.py b/webgenie/utils/config.py index 059fac05..ab2c2609 100644 --- a/webgenie/utils/config.py +++ b/webgenie/utils/config.py @@ -194,7 +194,7 @@ def add_validator_args(cls, parser): "--neuron.num_concurrent_forwards", type=int, help="The number of concurrent forwards running at any time.", - default=1, + default=3, ) parser.add_argument( From fa03ba0056ebc520a7821cf442f70bf7bc66a95a Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sun, 5 Jan 2025 02:46:43 -0600 Subject: [PATCH 124/554] chore: removed logs --- neurons/validators/genie_validator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 2e743948..3928499a 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -25,9 +25,7 @@ def __init__(self, neuron: BaseNeuron): self.config = neuron.config self.synthetic_history = [] self.synthetic_tasks = [] - bt.logging.info(f"GenieValidator initialized with neuron: {self.neuron.metagraph.n}") self.un_responsed_count = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - bt.logging.info(f"Un responsed count: {self.un_responsed_count}") self.task_generators = [ (TextTaskGenerator(), 0.1), From b05a0aa540411d6321fd83a5958900bdf729031f Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sun, 5 Jan 2025 02:54:16 -0600 Subject: [PATCH 125/554] chore: edited .env files --- .env.miner.example | 7 +++++-- .env.validator.example | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.env.miner.example b/.env.miner.example index a974dd68..e9fd95e7 100644 --- a/.env.miner.example +++ b/.env.miner.example @@ -1,5 +1,8 @@ -LLM_API_KEY = your_openai_api_key WANDB_API_KEY = your_wandb_api_key WANDB_ENTITY_NAME = your_wandb_entity_name -HF_TOKEN = your_hf_token \ No newline at end of file +HF_TOKEN = your_huggingface_token + +LLM_API_KEY = your_openai_api_key +LLM_MODEL_ID = your_openai_model_id +LLM_MODEL_URL = your_openai_model_url \ No newline at end of file diff --git a/.env.validator.example b/.env.validator.example index c47a6452..3e0b781f 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -1,6 +1,6 @@ -LLM_API_KEY = your_openai_api_key WANDB_API_KEY = your_wandb_api_key WANDB_ENTITY_NAME = your_wandb_entity_name -LLM_MODEL_ID = gpt-3.5-turbo -LLM_MODEL_URL = https://api.openai.com/v1/ \ No newline at end of file +LLM_API_KEY = your_openai_api_key +LLM_MODEL_ID = your_openai_model_id +LLM_MODEL_URL = your_openai_model_url \ No newline at end of file From eea7232a93f6a1b0255ffa9097bb5d20d4bb5ccc Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sun, 5 Jan 2025 03:01:43 -0600 Subject: [PATCH 126/554] hotfix: fixed division by zero --- neurons/validators/genie_validator.py | 10 +++------- webgenie/utils/config.py | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 3928499a..46b83d9f 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -81,22 +81,18 @@ async def query_miners(self): async def score(self): if len(self.synthetic_history) < MIN_SYNTHETIC_HISTORY_SIZE_TO_SCORE: return - + history_size = len(self.synthetic_history) task, solutions = random.choice(self.synthetic_history) - self.synthetic_history = [] - task_generator = task.generator - miner_uids = [solution.miner_uid for solution in solutions] - bt.logging.debug(f"Miner uids: {miner_uids}") rewards = await task_generator.reward(task, solutions) for i in range(len(miner_uids)): - responsed_ratio = 1 - self.un_responsed_count[miner_uids[i]] / len(self.synthetic_history) + responsed_ratio = 1 - self.un_responsed_count[miner_uids[i]] / history_size rewards[i] = rewards[i] * responsed_ratio * responsed_ratio - bt.logging.debug(f"Incentive rewards: {rewards}") + bt.logging.success(f"Incentive rewards for {miner_uids}: {rewards}") self.neuron.update_scores(rewards, miner_uids) self.neuron.step += 1 diff --git a/webgenie/utils/config.py b/webgenie/utils/config.py index ab2c2609..1d02aa14 100644 --- a/webgenie/utils/config.py +++ b/webgenie/utils/config.py @@ -194,7 +194,7 @@ def add_validator_args(cls, parser): "--neuron.num_concurrent_forwards", type=int, help="The number of concurrent forwards running at any time.", - default=3, + default=5, ) parser.add_argument( From 7942716f83e59b7be7e9bfbf5fd5fb8d80e078fa Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sun, 5 Jan 2025 03:05:11 -0600 Subject: [PATCH 127/554] chore: created new dendrite when query to miners --- neurons/validators/genie_validator.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 46b83d9f..1e06a6dd 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -53,11 +53,12 @@ async def query_miners(self): miner_uids = get_random_uids(self.neuron, k=self.config.neuron.sample_size) bt.logging.debug(f"Selected miner uids: {miner_uids}") - all_synapse_results = await self.neuron.dendrite( - axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], - synapse=synapse, - timeout=task.timeout - ) + async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: + all_synapse_results = await dendrite( + axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], + synapse=synapse, + timeout=task.timeout + ) solutions = [] From fa478f06465c4e50765cdbbf4c57e8313f2a37d0 Mon Sep 17 00:00:00 2001 From: Sangar <8627971+sangar-1028@users.noreply.github.com> Date: Sun, 5 Jan 2025 02:33:04 -0700 Subject: [PATCH 128/554] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fcd572d5..d22d8c3a 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ CodeBERTScore is an evaluation metric for code generation, which builds on BERTS - See [Running on Testnet](docs/running_on_testnet.md) for instructions on how to run the subnet on testnet. - See [Running on Mainnet](docs/running_on_mainnet.md) for instructions on how to run the subnet on mainnet. -#### Scripts for running miners and validators on the test network +#### Scripts for running miners and validators ```bash npm install pm2 -g git clone https://github.com/web-genie-ai/web-genie-ai.git @@ -126,13 +126,13 @@ pip install -r requirements.txt ``` - miner ```bash -pm2 start neurons/miners/miner.py --name "webgenie_miner" --interpreter python -- --netuid 214 --subtensor.network test --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port] +pm2 start neurons/miners/miner.py --name "webgenie_miner" --interpreter python -- --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port] ``` - validator ```bash playwright install-deps playwright install -pm2 start neurons/validators/validator.py --name "webgenie_validator" --interpreter python -- --netuid 214 --subtensor.network test --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port] +pm2 start neurons/validators/validator.py --name "webgenie_validator" --interpreter python -- --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port] ``` - running auto_update script for validators ```bash From 00a432a12a736b46972ea20db3f123dc6aa5f739 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sun, 5 Jan 2025 04:11:43 -0600 Subject: [PATCH 129/554] hotfix: fixed bugs when new miners register --- neurons/validators/genie_validator.py | 29 ++++++++++++++++++--------- webgenie/base/validator.py | 28 ++++++++++++-------------- webgenie/constants.py | 5 ++++- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 1e06a6dd..b450b245 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -10,6 +10,7 @@ MAX_SYNTHETIC_TASK_SIZE, MAX_DEBUG_IMAGE_STRING_LENGTH, MIN_SYNTHETIC_HISTORY_SIZE_TO_SCORE, + UPDATE_SCORE_STEPS, WORK_DIR ) from webgenie.helpers.htmls import preprocess_html @@ -78,24 +79,32 @@ async def query_miners(self): except Exception as e: bt.logging.error(f"Error in forward: {e}") raise e - + + def update_raw_scores(self, rewards: List[float], miner_uids: List[int]): + for i in range(len(miner_uids)): + self.neuron.raw_scores[miner_uids[i]] += rewards[i] + self.neuron.step += 1 + + if self.neuron.step % UPDATE_SCORE_STEPS == 0: + rewards_array = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + rewards_array[:] = self.neuron.raw_scores[:] ** 3 + + bt.logging.success(f"Blockchain rewards: {rewards_array}") + self.neuron.update_scores(rewards_array, range(self.neuron.metagraph.n)) + self.neuron.raw_scores[:] = 0 + async def score(self): if len(self.synthetic_history) < MIN_SYNTHETIC_HISTORY_SIZE_TO_SCORE: return - history_size = len(self.synthetic_history) + task, solutions = random.choice(self.synthetic_history) task_generator = task.generator miner_uids = [solution.miner_uid for solution in solutions] rewards = await task_generator.reward(task, solutions) - - for i in range(len(miner_uids)): - responsed_ratio = 1 - self.un_responsed_count[miner_uids[i]] / history_size - rewards[i] = rewards[i] * responsed_ratio * responsed_ratio - - bt.logging.success(f"Incentive rewards for {miner_uids}: {rewards}") - self.neuron.update_scores(rewards, miner_uids) - self.neuron.step += 1 + bt.logging.success(f"Rewards for {miner_uids}: {rewards}") + self.update_raw_scores(rewards, miner_uids) + self.synthetic_history = [] async def synthensize_task(self): try: diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index 944bb3aa..9a452ca6 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -51,17 +51,14 @@ def add_args(cls, parser: argparse.ArgumentParser): def __init__(self, config=None): super().__init__(config=config) init_wandb(self) - - # Save a copy of the hotkeys to local memory. - self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) - + # Dendrite lets us send messages to other nodes (axons) in the network. if self.config.mock: self.dendrite = MockDendrite(wallet=self.wallet) else: self.dendrite = bt.dendrite(wallet=self.wallet) bt.logging.info(f"Dendrite: {self.dendrite}") - + bt.logging.info("load_state()") self.load_state() @@ -143,10 +140,6 @@ def set_weights(self): def resync_metagraph(self): """Resyncs the metagraph and updates the hotkeys and moving averages based on the new metagraph.""" - #TODO: Implement this - - #bt.logging.info("resync_metagraph()") - # Copies state of metagraph before syncing. previous_metagraph = copy.deepcopy(self.metagraph) @@ -164,16 +157,20 @@ def resync_metagraph(self): for uid, hotkey in enumerate(self.hotkeys): if hotkey != self.metagraph.hotkeys[uid]: self.scores[uid] = 0 # hotkey has been replaced + self.raw_scores[uid] = 0 # Check to see if the metagraph has changed size. # If so, we need to add new hotkeys and moving averages. if len(self.hotkeys) < len(self.metagraph.hotkeys): - # Update the size of the moving average scores. new_moving_average = np.zeros((self.metagraph.n)) min_len = min(len(self.hotkeys), len(self.scores)) new_moving_average[:min_len] = self.scores[:min_len] self.scores = new_moving_average + new_raw_scores = np.zeros(self.metagraph.n, dtype=np.float32) + new_raw_scores[:min_len] = self.raw_scores[:min_len] + self.raw_scores = new_raw_scores + # Update the hotkeys. self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) @@ -183,7 +180,6 @@ def update_scores(self, rewards: np.ndarray, uids: List[int]): # Check if rewards contains NaN values. if np.isnan(rewards).any(): bt.logging.warning(f"NaN values detected in rewards: {rewards}") - # Replace any NaN values in rewards with 0. rewards = np.nan_to_num(rewards, nan=0) # Ensure rewards is a numpy array. @@ -229,6 +225,7 @@ def save_state(self): np.savez( self.config.neuron.full_path + "/state.npz", step=self.step, + raw_scores=self.raw_scores, scores=self.scores, hotkeys=self.hotkeys, ) @@ -238,14 +235,15 @@ def load_state(self): bt.logging.info("Loading validator state.") # Load the state of the validator from file. - state = np.load(self.config.neuron.full_path + "/state.npz") - if "step" in state: + try: + state = np.load(self.config.neuron.full_path + "/state.npz") self.step = state["step"] self.scores = state["scores"] + self.raw_scores = state["raw_scores"] self.hotkeys = state["hotkeys"] - else: - bt.logging.warning("No state found. Initializing with default values.") + except Exception as e: self.step = 0 + self.raw_scores = np.zeros(self.metagraph.n, dtype=np.float32) self.scores = np.zeros(self.metagraph.n, dtype=np.float32) self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) diff --git a/webgenie/constants.py b/webgenie/constants.py index 7bc7361b..5f7bb60b 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -11,7 +11,10 @@ MAX_SYNTHETIC_HISTORY_SIZE = 30 # min synthetic history size to score -MIN_SYNTHETIC_HISTORY_SIZE_TO_SCORE = 5 +MIN_SYNTHETIC_HISTORY_SIZE_TO_SCORE = 3 + +# update score interval +UPDATE_SCORE_STEPS = 10 # max synthensize task size MAX_SYNTHETIC_TASK_SIZE = 30 From 531387eb84aebba405aedca62ee1ae9e1740fbe8 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sun, 5 Jan 2025 05:32:28 -0600 Subject: [PATCH 130/554] chore: removed temp files --- run_miners.sh | 2 -- run_validator copy.sh | 3 --- 2 files changed, 5 deletions(-) delete mode 100644 run_miners.sh delete mode 100644 run_validator copy.sh diff --git a/run_miners.sh b/run_miners.sh deleted file mode 100644 index 140c643a..00000000 --- a/run_miners.sh +++ /dev/null @@ -1,2 +0,0 @@ -export PYTHONPATH=. -pm2 start neurons/miners/miner.py --name "webgenie_miner" --interpreter python -- --netuid 214 --subtensor.network test --wallet.name s-miner --wallet.hotkey miner2 --logging.debug --axon.port 8090 \ No newline at end of file diff --git a/run_validator copy.sh b/run_validator copy.sh deleted file mode 100644 index 1e3606ac..00000000 --- a/run_validator copy.sh +++ /dev/null @@ -1,3 +0,0 @@ -export PYTHONPATH=. - -pm2 start neurons/validators/validator.py --name "webgenie_validator" --interpreter python -- --netuid 214 --subtensor.network test --wallet.name sc-val1 --wallet.hotkey sh-val1 --logging.debug --neuron.axon_port 8091 \ No newline at end of file From b26bac54a79b8c1d73efa3b24cbff3e85b939658 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sun, 5 Jan 2025 06:50:14 -0600 Subject: [PATCH 131/554] chore: updated reward system --- neurons/validators/genie_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index b450b245..9e243b34 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -103,7 +103,7 @@ async def score(self): rewards = await task_generator.reward(task, solutions) bt.logging.success(f"Rewards for {miner_uids}: {rewards}") - self.update_raw_scores(rewards, miner_uids) + self.update_raw_scores(rewards * len(self.synthetic_history), miner_uids) self.synthetic_history = [] async def synthensize_task(self): From 53a8830aed992f73e9686f47ef0eaa3da066d068 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sun, 5 Jan 2025 18:35:00 -0600 Subject: [PATCH 132/554] feat: updated code quality prompt --- webgenie/prompts.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/webgenie/prompts.py b/webgenie/prompts.py index a6771b2a..8e88b35b 100644 --- a/webgenie/prompts.py +++ b/webgenie/prompts.py @@ -44,9 +44,20 @@ PROMPT_QUALITY = """ You are an HTML, CSS expert. I have an HTML code. I want you to evaluate the html code on the following criteria and give a score from 0 to 100. -1. The html code is in the professional style. -2. The html code is using SEO-friendly practices. -3. The html code is not using redundant code. + +The following criteria: +1. Semantic HTML: Use appropriate HTML tags to convey meaning. weight: 20 +2. Accessibility: Ensure content is usable for all, including those with disabilities. weight: 15 +3. Clean and Readable Code: Maintain consistent formatting and meaningful naming conventions. weight: 10 +4. Responsive Design: Implement designs that adapt to various screen sizes. weight: 12 +5. Performance Optimization: Minimize file sizes and optimize selectors for faster loading. weight: 10 +6. Cross-Browser Compatibility: Ensure consistent rendering across different browsers. weight: 8 +7. Validation: Use W3C validators to check for errors and deprecated elements. weight: 7 +8. Maintainability: Structure code for easy updates and modifications. weight: 8 +9. Use of Best Practices: Avoid anti-patterns like excessive specificity and inline styles. weight: 5 +10. Documentation: Provide clear documentation for styles and design choices. weight: 5 + +If the html/css code is not following each criteria, reduce score by its weight. The following is the given html code: {html} From 7175af84ea8d219c23c576a3e806d61c29d1bee0 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 07:43:18 -0600 Subject: [PATCH 133/554] feat: updated incentive mechanize to avoid uid pressure --- neurons/validators/genie_validator.py | 98 ++++++++++++++------------- webgenie/base/validator.py | 8 --- webgenie/constants.py | 9 +-- webgenie/utils/config.py | 2 +- 4 files changed, 55 insertions(+), 62 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 9e243b34..2e06c2fd 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -1,16 +1,16 @@ import os import bittensor as bt +import asyncio import numpy as np import random from typing import Union, List from webgenie.base.neuron import BaseNeuron from webgenie.constants import ( + NUM_CONCURRENT_QUERIES, MAX_SYNTHETIC_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE, MAX_DEBUG_IMAGE_STRING_LENGTH, - MIN_SYNTHETIC_HISTORY_SIZE_TO_SCORE, - UPDATE_SCORE_STEPS, WORK_DIR ) from webgenie.helpers.htmls import preprocess_html @@ -26,7 +26,6 @@ def __init__(self, neuron: BaseNeuron): self.config = neuron.config self.synthetic_history = [] self.synthetic_tasks = [] - self.un_responsed_count = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.task_generators = [ (TextTaskGenerator(), 0.1), @@ -40,20 +39,8 @@ def make_work_dir(self): os.makedirs(WORK_DIR) bt.logging.info(f"Created work directory at {WORK_DIR}") - async def query_miners(self): - try: - if len(self.synthetic_history) > MAX_SYNTHETIC_HISTORY_SIZE: - return - - if not self.synthetic_tasks: - return - - task, synapse = self.synthetic_tasks.pop(0) - bt.logging.info("Popping synthetic task and sending it to miners") - - miner_uids = get_random_uids(self.neuron, k=self.config.neuron.sample_size) - bt.logging.debug(f"Selected miner uids: {miner_uids}") - + async def query_one_task(self, task, synapse, miner_uids): + try: async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: all_synapse_results = await dendrite( axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], @@ -67,44 +54,61 @@ async def query_miners(self): processed_synapse = await self.process_synapse(synapse) if processed_synapse is not None: solutions.append(Solution(html = processed_synapse.html, miner_uid = miner_uid, process_time = processed_synapse.dendrite.process_time)) - else: - self.un_responsed_count[miner_uid] += 1 - - if not solutions: - bt.logging.warning(f"No valid solutions received") + + return task, solutions + except Exception as e: + bt.logging.error(f"Error in query_one_task: {e}") + raise e + + async def query_miners(self): + try: + if len(self.synthetic_history) > MAX_SYNTHETIC_HISTORY_SIZE: return - bt.logging.info(f"Received {len(solutions)} solutions") - self.synthetic_history.append((task, solutions)) + if len(self.synthetic_tasks) < NUM_CONCURRENT_QUERIES: + return + + bt.logging.info("querying miners") + + miner_uids = get_random_uids(self.neuron, k=self.config.neuron.sample_size) + bt.logging.debug(f"Selected miner uids: {miner_uids}") + + query_coroutines = [self.query_one_task(task, synapse, miner_uids) for task, synapse in self.synthetic_tasks] + + self.synthetic_tasks = [] + + results = await asyncio.gather(*query_coroutines, return_exceptions=True) + self.synthetic_history.append(results) + except Exception as e: - bt.logging.error(f"Error in forward: {e}") + bt.logging.error(f"Error in query_miners: {e}") raise e - def update_raw_scores(self, rewards: List[float], miner_uids: List[int]): - for i in range(len(miner_uids)): - self.neuron.raw_scores[miner_uids[i]] += rewards[i] - self.neuron.step += 1 + async def score(self): + if not self.synthetic_history: + return - if self.neuron.step % UPDATE_SCORE_STEPS == 0: - rewards_array = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - rewards_array[:] = self.neuron.raw_scores[:] ** 3 + results = self.synthetic_history.pop(0) + raw_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + for result in results: + if isinstance(result, Exception): + continue + + task, solutions = result + if not solutions: + continue + + task_generator = task.generator + miner_uids = [solution.miner_uid for solution in solutions] + rewards = await task_generator.reward(task, solutions) + bt.logging.success(f"Rewards for {miner_uids}: {rewards}") - bt.logging.success(f"Blockchain rewards: {rewards_array}") - self.neuron.update_scores(rewards_array, range(self.neuron.metagraph.n)) - self.neuron.raw_scores[:] = 0 + for i in range(len(miner_uids)): + raw_scores[miner_uids[i]] += rewards[i] - async def score(self): - if len(self.synthetic_history) < MIN_SYNTHETIC_HISTORY_SIZE_TO_SCORE: - return - - task, solutions = random.choice(self.synthetic_history) - task_generator = task.generator - miner_uids = [solution.miner_uid for solution in solutions] - - rewards = await task_generator.reward(task, solutions) - bt.logging.success(f"Rewards for {miner_uids}: {rewards}") - self.update_raw_scores(rewards * len(self.synthetic_history), miner_uids) - self.synthetic_history = [] + raw_scores[:] = raw_scores[:] ** 3 + self.neuron.update_scores(raw_scores, range(self.neuron.metagraph.n)) + self.neuron.step += 1 async def synthensize_task(self): try: diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index 9a452ca6..89f13319 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -157,7 +157,6 @@ def resync_metagraph(self): for uid, hotkey in enumerate(self.hotkeys): if hotkey != self.metagraph.hotkeys[uid]: self.scores[uid] = 0 # hotkey has been replaced - self.raw_scores[uid] = 0 # Check to see if the metagraph has changed size. # If so, we need to add new hotkeys and moving averages. @@ -167,10 +166,6 @@ def resync_metagraph(self): new_moving_average[:min_len] = self.scores[:min_len] self.scores = new_moving_average - new_raw_scores = np.zeros(self.metagraph.n, dtype=np.float32) - new_raw_scores[:min_len] = self.raw_scores[:min_len] - self.raw_scores = new_raw_scores - # Update the hotkeys. self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) @@ -225,7 +220,6 @@ def save_state(self): np.savez( self.config.neuron.full_path + "/state.npz", step=self.step, - raw_scores=self.raw_scores, scores=self.scores, hotkeys=self.hotkeys, ) @@ -239,11 +233,9 @@ def load_state(self): state = np.load(self.config.neuron.full_path + "/state.npz") self.step = state["step"] self.scores = state["scores"] - self.raw_scores = state["raw_scores"] self.hotkeys = state["hotkeys"] except Exception as e: self.step = 0 - self.raw_scores = np.zeros(self.metagraph.n, dtype=np.float32) self.scores = np.zeros(self.metagraph.n, dtype=np.float32) self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) diff --git a/webgenie/constants.py b/webgenie/constants.py index 5f7bb60b..0eb9312a 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -10,15 +10,12 @@ # max synthetic history size MAX_SYNTHETIC_HISTORY_SIZE = 30 -# min synthetic history size to score -MIN_SYNTHETIC_HISTORY_SIZE_TO_SCORE = 3 - -# update score interval -UPDATE_SCORE_STEPS = 10 - # max synthensize task size MAX_SYNTHETIC_TASK_SIZE = 30 +# the number of concurrent queries +NUM_CONCURRENT_QUERIES = 10 + # max debug image string length MAX_DEBUG_IMAGE_STRING_LENGTH = 20 diff --git a/webgenie/utils/config.py b/webgenie/utils/config.py index 1d02aa14..059fac05 100644 --- a/webgenie/utils/config.py +++ b/webgenie/utils/config.py @@ -194,7 +194,7 @@ def add_validator_args(cls, parser): "--neuron.num_concurrent_forwards", type=int, help="The number of concurrent forwards running at any time.", - default=5, + default=1, ) parser.add_argument( From b0b40936eb7cd1f8072e3cfab47ee099faabee02 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 07:50:57 -0600 Subject: [PATCH 134/554] chore: renamed the var name --- neurons/validators/genie_validator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 2e06c2fd..d8724956 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -89,7 +89,7 @@ async def score(self): return results = self.synthetic_history.pop(0) - raw_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + tatal_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) for result in results: if isinstance(result, Exception): continue @@ -104,10 +104,10 @@ async def score(self): bt.logging.success(f"Rewards for {miner_uids}: {rewards}") for i in range(len(miner_uids)): - raw_scores[miner_uids[i]] += rewards[i] + tatal_scores[miner_uids[i]] += rewards[i] - raw_scores[:] = raw_scores[:] ** 3 - self.neuron.update_scores(raw_scores, range(self.neuron.metagraph.n)) + tatal_scores[:] = tatal_scores[:] ** 3 + self.neuron.update_scores(tatal_scores, range(self.neuron.metagraph.n)) self.neuron.step += 1 async def synthensize_task(self): From d3ff553b38384d6484202f69e239e66c049e8ab5 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 12:25:14 -0600 Subject: [PATCH 135/554] style: rename var name --- neurons/validators/genie_validator.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index d8724956..aaa779a5 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -24,7 +24,7 @@ class GenieValidator: def __init__(self, neuron: BaseNeuron): self.neuron = neuron self.config = neuron.config - self.synthetic_history = [] + self.competetions = [] self.synthetic_tasks = [] self.task_generators = [ @@ -62,7 +62,7 @@ async def query_one_task(self, task, synapse, miner_uids): async def query_miners(self): try: - if len(self.synthetic_history) > MAX_SYNTHETIC_HISTORY_SIZE: + if len(self.competetions) > MAX_SYNTHETIC_HISTORY_SIZE: return if len(self.synthetic_tasks) < NUM_CONCURRENT_QUERIES: @@ -78,17 +78,17 @@ async def query_miners(self): self.synthetic_tasks = [] results = await asyncio.gather(*query_coroutines, return_exceptions=True) - self.synthetic_history.append(results) + self.competetions.append(results) except Exception as e: bt.logging.error(f"Error in query_miners: {e}") raise e async def score(self): - if not self.synthetic_history: + if not self.competetions: return - results = self.synthetic_history.pop(0) + results = self.competetions.pop(0) tatal_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) for result in results: if isinstance(result, Exception): From 1c1500139dfa6fb7f2a98b717627a479892372e3 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 12:30:08 -0600 Subject: [PATCH 136/554] style: rename var name --- neurons/validators/genie_validator.py | 8 ++++---- webgenie/constants.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index aaa779a5..2b09abe6 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -8,7 +8,7 @@ from webgenie.base.neuron import BaseNeuron from webgenie.constants import ( NUM_CONCURRENT_QUERIES, - MAX_SYNTHETIC_HISTORY_SIZE, + MAX_COMPETETION_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE, MAX_DEBUG_IMAGE_STRING_LENGTH, WORK_DIR @@ -28,8 +28,8 @@ def __init__(self, neuron: BaseNeuron): self.synthetic_tasks = [] self.task_generators = [ - (TextTaskGenerator(), 0.1), - (ImageTaskGenerator(), 0.9), + (TextTaskGenerator(), 0.5), + (ImageTaskGenerator(), 0.5), ] self.make_work_dir() @@ -62,7 +62,7 @@ async def query_one_task(self, task, synapse, miner_uids): async def query_miners(self): try: - if len(self.competetions) > MAX_SYNTHETIC_HISTORY_SIZE: + if len(self.competetions) > MAX_COMPETETION_HISTORY_SIZE: return if len(self.synthetic_tasks) < NUM_CONCURRENT_QUERIES: diff --git a/webgenie/constants.py b/webgenie/constants.py index 0eb9312a..b9a3719c 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -7,8 +7,8 @@ # text task timeout TEXT_TASK_TIMEOUT = 100 -# max synthetic history size -MAX_SYNTHETIC_HISTORY_SIZE = 30 +# max competition history size +MAX_COMPETETION_HISTORY_SIZE = 30 # max synthensize task size MAX_SYNTHETIC_TASK_SIZE = 30 From 668c0ae77450a919fe5ee59fcb07151fc6972dff Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 12:58:03 -0600 Subject: [PATCH 137/554] feat: implement score-best-one mechanizm --- neurons/validators/genie_validator.py | 84 +++++++++++---------------- webgenie/constants.py | 14 +++-- webgenie/tasks/task.py | 4 ++ webgenie/utils/uids.py | 4 ++ 4 files changed, 52 insertions(+), 54 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 2b09abe6..cd1ac037 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -7,18 +7,18 @@ from webgenie.base.neuron import BaseNeuron from webgenie.constants import ( - NUM_CONCURRENT_QUERIES, MAX_COMPETETION_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE, - MAX_DEBUG_IMAGE_STRING_LENGTH, - WORK_DIR + MAX_DEBUG_IMAGE_STRING_LENGTH, + MIN_REWARD_THRESHOLD, + WORK_DIR, ) from webgenie.helpers.htmls import preprocess_html from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.tasks.solution import Solution from webgenie.tasks.image_task_generator import ImageTaskGenerator from webgenie.tasks.text_task_generator import TextTaskGenerator -from webgenie.utils.uids import get_random_uids +from webgenie.utils.uids import get_all_available_uids, get_most_available_uid class GenieValidator: def __init__(self, neuron: BaseNeuron): @@ -39,8 +39,16 @@ def make_work_dir(self): os.makedirs(WORK_DIR) bt.logging.info(f"Created work directory at {WORK_DIR}") - async def query_one_task(self, task, synapse, miner_uids): - try: + async def query_miners(self): + try: + if len(self.competetions) > MAX_COMPETETION_HISTORY_SIZE: + return + + bt.logging.info("querying miners") + + task, synapse = self.synthetic_tasks.pop(0) + miner_uids = get_all_available_uids(self.neuron) + async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: all_synapse_results = await dendrite( axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], @@ -49,37 +57,12 @@ async def query_one_task(self, task, synapse, miner_uids): ) solutions = [] - for synapse, miner_uid in zip(all_synapse_results, miner_uids): processed_synapse = await self.process_synapse(synapse) if processed_synapse is not None: solutions.append(Solution(html = processed_synapse.html, miner_uid = miner_uid, process_time = processed_synapse.dendrite.process_time)) - return task, solutions - except Exception as e: - bt.logging.error(f"Error in query_one_task: {e}") - raise e - - async def query_miners(self): - try: - if len(self.competetions) > MAX_COMPETETION_HISTORY_SIZE: - return - - if len(self.synthetic_tasks) < NUM_CONCURRENT_QUERIES: - return - - bt.logging.info("querying miners") - - miner_uids = get_random_uids(self.neuron, k=self.config.neuron.sample_size) - bt.logging.debug(f"Selected miner uids: {miner_uids}") - - query_coroutines = [self.query_one_task(task, synapse, miner_uids) for task, synapse in self.synthetic_tasks] - - self.synthetic_tasks = [] - - results = await asyncio.gather(*query_coroutines, return_exceptions=True) - self.competetions.append(results) - + self.competetions.append((task, solutions)) except Exception as e: bt.logging.error(f"Error in query_miners: {e}") raise e @@ -88,26 +71,27 @@ async def score(self): if not self.competetions: return - results = self.competetions.pop(0) - tatal_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - for result in results: - if isinstance(result, Exception): - continue + task, solutions = self.competetions.pop(0) + if not solutions: + return - task, solutions = result - if not solutions: - continue + best_miner = -1 + best_reward = MIN_REWARD_THRESHOLD - task_generator = task.generator - miner_uids = [solution.miner_uid for solution in solutions] - rewards = await task_generator.reward(task, solutions) - bt.logging.success(f"Rewards for {miner_uids}: {rewards}") - - for i in range(len(miner_uids)): - tatal_scores[miner_uids[i]] += rewards[i] + task_generator = task.generator + miner_uids = [solution.miner_uid for solution in solutions] + rewards = await task_generator.reward(task, solutions) + bt.logging.success(f"Rewards for {miner_uids}: {rewards}") + + for i in range(len(miner_uids)): + if rewards[i] > best_reward: + best_reward = rewards[i] + best_miner = miner_uids[i] - tatal_scores[:] = tatal_scores[:] ** 3 - self.neuron.update_scores(tatal_scores, range(self.neuron.metagraph.n)) + if best_miner == -1: + return + + self.neuron.update_scores([task.reserved_reward], [best_miner]) self.neuron.step += 1 async def synthensize_task(self): @@ -133,7 +117,7 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag else: bt.logging.debug(f"Organic image forward: {synapse.base64_image[:MAX_DEBUG_IMAGE_STRING_LENGTH]}...") - best_miner_uid = 3 + best_miner_uid = get_most_available_uid(self.neuron) try: axon = self.neuron.metagraph.axons[best_miner_uid] async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: diff --git a/webgenie/constants.py b/webgenie/constants.py index b9a3719c..2222a51b 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -4,18 +4,24 @@ # image task timeout IMAGE_TASK_TIMEOUT = 100 +# image task reward +IMAGE_TASK_REWARD = 80 + # text task timeout TEXT_TASK_TIMEOUT = 100 +# text task reward +TEXT_TASK_REWARD = 20 + +# minimum reward threshold +MIN_REWARD_THRESHOLD = 0.5 + # max competition history size MAX_COMPETETION_HISTORY_SIZE = 30 -# max synthensize task size +# max synthetic task size MAX_SYNTHETIC_TASK_SIZE = 30 -# the number of concurrent queries -NUM_CONCURRENT_QUERIES = 10 - # max debug image string length MAX_DEBUG_IMAGE_STRING_LENGTH = 20 diff --git a/webgenie/tasks/task.py b/webgenie/tasks/task.py index 3a85d86d..38bc5b74 100644 --- a/webgenie/tasks/task.py +++ b/webgenie/tasks/task.py @@ -1,14 +1,18 @@ from typing import Any from pydantic import BaseModel, Field +from webgenie.constants import IMAGE_TASK_REWARD, TEXT_TASK_REWARD class Task(BaseModel): timeout: float = Field(default=50) generator: Any = Field(default=None) + reserved_reward: float = Field(default=0.0) class ImageTask(Task): base64_image: str = Field(default="", description="The base64 encoded image") ground_truth_html: str = Field(default="", description="The ground truth html") + reserved_reward: float = Field(default=IMAGE_TASK_REWARD) class TextTask(Task): prompt: str = Field(default="", description="The prompt for the text task") ground_truth_html: str = Field(default="", description="The ground truth html") + reserved_reward: float = Field(default=TEXT_TASK_REWARD) \ No newline at end of file diff --git a/webgenie/utils/uids.py b/webgenie/utils/uids.py index 53c3d13f..80278a64 100644 --- a/webgenie/utils/uids.py +++ b/webgenie/utils/uids.py @@ -47,6 +47,10 @@ def get_most_available_uid(self, exclude: List[int] = None) -> int: return candidate_uids[np.argmax(self.metagraph.S[candidate_uids])] +def get_all_available_uids( + self, exclude: List[int] = None +) -> np.ndarray: + return [uid for uid in range(self.metagraph.n.item()) if check_uid_availability(self.metagraph, uid, self.config.neuron.vpermit_tao_limit) and (exclude is None or uid not in exclude)] def get_random_uids( self, k: int, exclude: List[int] = None From 072d31bb1fc30cbad707084a40c685c2b194ccee Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 13:04:44 -0600 Subject: [PATCH 138/554] fix: fix bugs(poping from empty list) --- neurons/validators/genie_validator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index cd1ac037..9b72e5f2 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -43,6 +43,9 @@ async def query_miners(self): try: if len(self.competetions) > MAX_COMPETETION_HISTORY_SIZE: return + + if not self.synthetic_tasks: + return bt.logging.info("querying miners") From 9f43009ab0b9ed0a5da93f70fe1df98300e34767 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 13:19:28 -0600 Subject: [PATCH 139/554] style: style long line --- webgenie/utils/uids.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/webgenie/utils/uids.py b/webgenie/utils/uids.py index 80278a64..4e8edd48 100644 --- a/webgenie/utils/uids.py +++ b/webgenie/utils/uids.py @@ -50,7 +50,13 @@ def get_most_available_uid(self, exclude: List[int] = None) -> int: def get_all_available_uids( self, exclude: List[int] = None ) -> np.ndarray: - return [uid for uid in range(self.metagraph.n.item()) if check_uid_availability(self.metagraph, uid, self.config.neuron.vpermit_tao_limit) and (exclude is None or uid not in exclude)] + avail_uids = [] + for uid in range(self.metagraph.n.item()): + uid_is_available = check_uid_availability(self.metagraph, uid, self.config.neuron.vpermit_tao_limit) + uid_is_not_excluded = exclude is None or uid not in exclude + if uid_is_available and uid_is_not_excluded: + avail_uids.append(uid) + return np.array(avail_uids) def get_random_uids( self, k: int, exclude: List[int] = None From e58f8e83938c02599ad76ba61a2f7329c8b0ab0b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 13:20:54 -0600 Subject: [PATCH 140/554] chore: fix typo --- neurons/validators/genie_validator.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 9b72e5f2..a5ae37c9 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -24,7 +24,7 @@ class GenieValidator: def __init__(self, neuron: BaseNeuron): self.neuron = neuron self.config = neuron.config - self.competetions = [] + self.competitions = [] self.synthetic_tasks = [] self.task_generators = [ @@ -41,7 +41,7 @@ def make_work_dir(self): async def query_miners(self): try: - if len(self.competetions) > MAX_COMPETETION_HISTORY_SIZE: + if len(self.competitions) > MAX_COMPETETION_HISTORY_SIZE: return if not self.synthetic_tasks: @@ -65,16 +65,16 @@ async def query_miners(self): if processed_synapse is not None: solutions.append(Solution(html = processed_synapse.html, miner_uid = miner_uid, process_time = processed_synapse.dendrite.process_time)) - self.competetions.append((task, solutions)) + self.competitions.append((task, solutions)) except Exception as e: bt.logging.error(f"Error in query_miners: {e}") raise e async def score(self): - if not self.competetions: + if not self.competitions: return - task, solutions = self.competetions.pop(0) + task, solutions = self.competitions.pop(0) if not solutions: return From 8301a27461ad86244ac9da496aaa322f59338ab6 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 14:12:13 -0600 Subject: [PATCH 141/554] feat: create competitions --- neurons/validators/genie_validator.py | 30 +++++++++++-------- webgenie/competitions/__init__.py | 12 ++++++++ .../competition.py} | 7 ++--- .../image_task_competition.py} | 27 ++++++++++++----- .../text_task_competition.py} | 28 ++++++++++++----- webgenie/tasks/__init__.py | 5 +--- webgenie/tasks/task.py | 6 +--- 7 files changed, 74 insertions(+), 41 deletions(-) create mode 100644 webgenie/competitions/__init__.py rename webgenie/{tasks/task_generator.py => competitions/competition.py} (87%) rename webgenie/{tasks/image_task_generator.py => competitions/image_task_competition.py} (78%) rename webgenie/{tasks/text_task_generator.py => competitions/text_task_competition.py} (69%) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index a5ae37c9..84983654 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -13,11 +13,15 @@ MIN_REWARD_THRESHOLD, WORK_DIR, ) +from webgenie.competitions import ( + ImageTaskAccuracyCompetition, + TextTaskAccuracyCompetition, + ImageTaskQualityCompetition, + TextTaskQualityCompetition +) from webgenie.helpers.htmls import preprocess_html from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.tasks.solution import Solution -from webgenie.tasks.image_task_generator import ImageTaskGenerator -from webgenie.tasks.text_task_generator import TextTaskGenerator from webgenie.utils.uids import get_all_available_uids, get_most_available_uid class GenieValidator: @@ -27,9 +31,11 @@ def __init__(self, neuron: BaseNeuron): self.competitions = [] self.synthetic_tasks = [] - self.task_generators = [ - (TextTaskGenerator(), 0.5), - (ImageTaskGenerator(), 0.5), + self.avail_competitions = [ + (TextTaskAccuracyCompetition(), 0.5), + (TextTaskQualityCompetition(), 0.5), + (ImageTaskAccuracyCompetition(), 0.5), + (ImageTaskQualityCompetition(), 0.5), ] self.make_work_dir() @@ -79,11 +85,11 @@ async def score(self): return best_miner = -1 - best_reward = MIN_REWARD_THRESHOLD + best_reward = 0.0 - task_generator = task.generator + competition = task.competition miner_uids = [solution.miner_uid for solution in solutions] - rewards = await task_generator.reward(task, solutions) + rewards = await competition.reward(task, solutions) bt.logging.success(f"Rewards for {miner_uids}: {rewards}") for i in range(len(miner_uids)): @@ -104,12 +110,12 @@ async def synthensize_task(self): bt.logging.debug(f"Synthensize task") - task_generator, _ = random.choices( - self.task_generators, - weights=[weight for _, weight in self.task_generators] + competition, _ = random.choices( + self.avail_competitions, + weights=[weight for _, weight in self.avail_competitions] )[0] - task, synapse = await task_generator.generate_task() + task, synapse = await competition.generate_task() self.synthetic_tasks.append((task, synapse)) except Exception as e: bt.logging.error(f"Error in synthensize_task: {e}") diff --git a/webgenie/competitions/__init__.py b/webgenie/competitions/__init__.py new file mode 100644 index 00000000..8808ad9f --- /dev/null +++ b/webgenie/competitions/__init__.py @@ -0,0 +1,12 @@ +from webgenie.competitions.text_task_competition import TextTaskCompetition, TextTaskAccuracyCompetition, TextTaskQualityCompetition +from webgenie.competitions.image_task_competition import ImageTaskCompetition, ImageTaskAccuracyCompetition, ImageTaskQualityCompetition + +RESERVED_WEIGHTS = { + TextTaskAccuracyCompetition: 50, + TextTaskQualityCompetition: 20, + ImageTaskAccuracyCompetition: 90, + ImageTaskQualityCompetition: 10 +} + + + diff --git a/webgenie/tasks/task_generator.py b/webgenie/competitions/competition.py similarity index 87% rename from webgenie/tasks/task_generator.py rename to webgenie/competitions/competition.py index 562f66dc..b6001597 100644 --- a/webgenie/tasks/task_generator.py +++ b/webgenie/competitions/competition.py @@ -6,12 +6,9 @@ from webgenie.tasks import Task from webgenie.tasks.solution import Solution -class TaskGenerator: - """ - A singleton generator for tasks. - """ +class Competition: def __init__(self): - pass + self.rewards = [] async def generate_task(self) -> Tuple[Task, bt.Synapse]: pass diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/competitions/image_task_competition.py similarity index 78% rename from webgenie/tasks/image_task_generator.py rename to webgenie/competitions/image_task_competition.py index ca7ac7ed..15b6b6e2 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/competitions/image_task_competition.py @@ -8,19 +8,16 @@ from webgenie.protocol import WebgenieImageSynapse from webgenie.tasks.solution import Solution from webgenie.tasks.task import Task, ImageTask -from webgenie.tasks.task_generator import TaskGenerator +from webgenie.competitions.competition import Competition from webgenie.rewards.quality_reward import QualityReward from webgenie.rewards.visual_reward import VisualReward from webgenie.datasets.synthetic_dataset import SyntheticDataset from webgenie.datasets.huggingface_dataset import HuggingfaceDataset -class ImageTaskGenerator(TaskGenerator): +class ImageTaskCompetition(Competition): def __init__(self): super().__init__() - self.rewards = [ - (VisualReward(), 0.9), - (QualityReward(), 0.1) - ] + self.datasets = [ SyntheticDataset(), HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), @@ -41,7 +38,23 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: base64_image=base64_image, ground_truth_html=ground_truth_html, timeout=IMAGE_TASK_TIMEOUT, - generator=self, + competition=self, ), WebgenieImageSynapse(base64_image=base64_image) +class ImageTaskAccuracyCompetition(ImageTaskCompetition): + def __init__(self): + super().__init__() + self.rewards = [ + (VisualReward(), 0.9), + (QualityReward(), 0.1) + ] + +class ImageTaskQualityCompetition(ImageTaskCompetition): + def __init__(self): + super().__init__() + + self.rewards = [ + (VisualReward(), 0.5), + (QualityReward(), 0.5) + ] diff --git a/webgenie/tasks/text_task_generator.py b/webgenie/competitions/text_task_competition.py similarity index 69% rename from webgenie/tasks/text_task_generator.py rename to webgenie/competitions/text_task_competition.py index d9d4abd5..7a845e73 100644 --- a/webgenie/tasks/text_task_generator.py +++ b/webgenie/competitions/text_task_competition.py @@ -10,15 +10,11 @@ from webgenie.rewards.quality_reward import QualityReward from webgenie.rewards.rtc_reward import RtcReward from webgenie.tasks.task import Task, TextTask -from webgenie.tasks.task_generator import TaskGenerator +from webgenie.competitions.competition import Competition -class TextTaskGenerator(TaskGenerator): - def __init__(self, has_ground_truth_html: bool = True): +class TextTaskCompetition(Competition): + def __init__(self): super().__init__() - self.rewards = [ - (RtcReward(), 0.9), - (QualityReward(), 0.1) - ] self.datasets = [ SyntheticDataset(has_ground_truth_html = True) @@ -31,5 +27,21 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: prompt=dataset_entry.prompt, ground_truth_html=dataset_entry.ground_truth_html, timeout=TEXT_TASK_TIMEOUT, - generator=self + competition=self ), WebgenieTextSynapse(prompt=dataset_entry.prompt) + +class TextTaskAccuracyCompetition(TextTaskCompetition): + def __init__(self): + super().__init__() + self.rewards = [ + (RtcReward(), 0.9), + (QualityReward(), 0.1) + ] + +class TextTaskQualityCompetition(TextTaskCompetition): + def __init__(self): + super().__init__() + self.rewards = [ + (RtcReward(), 0.5), + (QualityReward(), 0.5) + ] diff --git a/webgenie/tasks/__init__.py b/webgenie/tasks/__init__.py index 0670b304..8bfd415d 100644 --- a/webgenie/tasks/__init__.py +++ b/webgenie/tasks/__init__.py @@ -1,5 +1,2 @@ from .solution import Solution -from .task import Task, ImageTask, TextTask -from .task_generator import TaskGenerator -from .image_task_generator import ImageTaskGenerator -from .text_task_generator import TextTaskGenerator \ No newline at end of file +from .task import Task, ImageTask, TextTask \ No newline at end of file diff --git a/webgenie/tasks/task.py b/webgenie/tasks/task.py index 38bc5b74..9ecf989f 100644 --- a/webgenie/tasks/task.py +++ b/webgenie/tasks/task.py @@ -1,18 +1,14 @@ from typing import Any from pydantic import BaseModel, Field -from webgenie.constants import IMAGE_TASK_REWARD, TEXT_TASK_REWARD class Task(BaseModel): timeout: float = Field(default=50) - generator: Any = Field(default=None) - reserved_reward: float = Field(default=0.0) + competition: Any = Field(default=None) class ImageTask(Task): base64_image: str = Field(default="", description="The base64 encoded image") ground_truth_html: str = Field(default="", description="The ground truth html") - reserved_reward: float = Field(default=IMAGE_TASK_REWARD) class TextTask(Task): prompt: str = Field(default="", description="The prompt for the text task") ground_truth_html: str = Field(default="", description="The ground truth html") - reserved_reward: float = Field(default=TEXT_TASK_REWARD) \ No newline at end of file From d8d77f7b9e7812bf251e0aff4d5a07942691cede Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 14:12:54 -0600 Subject: [PATCH 142/554] chore: delete unneccessary file --- webgenie/rewards/incentive_rewards.py | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 webgenie/rewards/incentive_rewards.py diff --git a/webgenie/rewards/incentive_rewards.py b/webgenie/rewards/incentive_rewards.py deleted file mode 100644 index e82efaf5..00000000 --- a/webgenie/rewards/incentive_rewards.py +++ /dev/null @@ -1,26 +0,0 @@ -import numpy as np - -def get_incentive_rewards(scores: np.ndarray, base_reward=100, alpha=1.5) -> np.ndarray: - threshold = scores.shape[0] // 2 - scores = np.array(scores) - sorted_scores = np.sort(scores) - score_to_rank = {score: idx + 1 for idx, score in enumerate(sorted_scores)} - rewards = np.zeros_like(scores, dtype=float) - - for idx, score in enumerate(scores): - rank = score_to_rank[score] - if rank <= threshold: - reward = (rank - 1) * (base_reward / 2) # Linear scaling - else: - reward = base_reward * (alpha ** (rank - threshold)) # Exponential scaling - rewards[idx] = reward - - return rewards - - -if __name__ == "__main__": - scores = np.array([500, 450, 400, 750, 300, 250, 200]) # Raw scores as a NumPy array - rewards = get_incentive_rewards(scores, base_reward=100, alpha=1.5) - - for score, reward in zip(scores, rewards): - print(f"Score: {score}, Reward = {reward:.2f}") \ No newline at end of file From bee2de102763e9ffd1de3e038c5884dcd8aebd72 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 14:27:37 -0600 Subject: [PATCH 143/554] feat: implement reserved_weights --- neurons/validators/genie_validator.py | 6 +++--- webgenie/competitions/__init__.py | 12 ++++++------ webgenie/competitions/competition.py | 2 +- webgenie/competitions/image_task_competition.py | 3 +++ webgenie/competitions/text_task_competition.py | 3 +++ webgenie/constants.py | 9 --------- webgenie/datasets/__init__.py | 1 + webgenie/rewards/__init__.py | 3 ++- 8 files changed, 19 insertions(+), 20 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 84983654..64f39280 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -10,14 +10,14 @@ MAX_COMPETETION_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE, MAX_DEBUG_IMAGE_STRING_LENGTH, - MIN_REWARD_THRESHOLD, WORK_DIR, ) from webgenie.competitions import ( ImageTaskAccuracyCompetition, TextTaskAccuracyCompetition, ImageTaskQualityCompetition, - TextTaskQualityCompetition + TextTaskQualityCompetition, + RESERVED_WEIGHTS ) from webgenie.helpers.htmls import preprocess_html from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse @@ -100,7 +100,7 @@ async def score(self): if best_miner == -1: return - self.neuron.update_scores([task.reserved_reward], [best_miner]) + self.neuron.update_scores([RESERVED_WEIGHTS[competition.name]], [best_miner]) self.neuron.step += 1 async def synthensize_task(self): diff --git a/webgenie/competitions/__init__.py b/webgenie/competitions/__init__.py index 8808ad9f..47b3d03c 100644 --- a/webgenie/competitions/__init__.py +++ b/webgenie/competitions/__init__.py @@ -1,11 +1,11 @@ -from webgenie.competitions.text_task_competition import TextTaskCompetition, TextTaskAccuracyCompetition, TextTaskQualityCompetition -from webgenie.competitions.image_task_competition import ImageTaskCompetition, ImageTaskAccuracyCompetition, ImageTaskQualityCompetition +from webgenie.competitions.text_task_competition import TextTaskAccuracyCompetition, TextTaskQualityCompetition +from webgenie.competitions.image_task_competition import ImageTaskAccuracyCompetition, ImageTaskQualityCompetition RESERVED_WEIGHTS = { - TextTaskAccuracyCompetition: 50, - TextTaskQualityCompetition: 20, - ImageTaskAccuracyCompetition: 90, - ImageTaskQualityCompetition: 10 + TextTaskAccuracyCompetition.name: 50, + TextTaskQualityCompetition.name: 20, + ImageTaskAccuracyCompetition.name: 90, + ImageTaskQualityCompetition.name: 10 } diff --git a/webgenie/competitions/competition.py b/webgenie/competitions/competition.py index b6001597..50f598b9 100644 --- a/webgenie/competitions/competition.py +++ b/webgenie/competitions/competition.py @@ -2,11 +2,11 @@ import numpy as np from typing import List, Tuple -from webgenie.rewards.incentive_rewards import get_incentive_rewards from webgenie.tasks import Task from webgenie.tasks.solution import Solution class Competition: + name = "Competition" def __init__(self): self.rewards = [] diff --git a/webgenie/competitions/image_task_competition.py b/webgenie/competitions/image_task_competition.py index 15b6b6e2..15ddda69 100644 --- a/webgenie/competitions/image_task_competition.py +++ b/webgenie/competitions/image_task_competition.py @@ -15,6 +15,7 @@ from webgenie.datasets.huggingface_dataset import HuggingfaceDataset class ImageTaskCompetition(Competition): + name = "ImageTaskCompetition" def __init__(self): super().__init__() @@ -42,6 +43,7 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: ), WebgenieImageSynapse(base64_image=base64_image) class ImageTaskAccuracyCompetition(ImageTaskCompetition): + name = "ImageTaskAccuracyCompetition" def __init__(self): super().__init__() @@ -51,6 +53,7 @@ def __init__(self): ] class ImageTaskQualityCompetition(ImageTaskCompetition): + name = "ImageTaskQualityCompetition" def __init__(self): super().__init__() diff --git a/webgenie/competitions/text_task_competition.py b/webgenie/competitions/text_task_competition.py index 7a845e73..27b0fa40 100644 --- a/webgenie/competitions/text_task_competition.py +++ b/webgenie/competitions/text_task_competition.py @@ -13,6 +13,7 @@ from webgenie.competitions.competition import Competition class TextTaskCompetition(Competition): + name = "TextTaskCompetition" def __init__(self): super().__init__() @@ -31,6 +32,7 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: ), WebgenieTextSynapse(prompt=dataset_entry.prompt) class TextTaskAccuracyCompetition(TextTaskCompetition): + name = "TextTaskAccuracyCompetition" def __init__(self): super().__init__() self.rewards = [ @@ -39,6 +41,7 @@ def __init__(self): ] class TextTaskQualityCompetition(TextTaskCompetition): + name = "TextTaskQualityCompetition" def __init__(self): super().__init__() self.rewards = [ diff --git a/webgenie/constants.py b/webgenie/constants.py index 2222a51b..1c68ae3a 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -4,18 +4,9 @@ # image task timeout IMAGE_TASK_TIMEOUT = 100 -# image task reward -IMAGE_TASK_REWARD = 80 - # text task timeout TEXT_TASK_TIMEOUT = 100 -# text task reward -TEXT_TASK_REWARD = 20 - -# minimum reward threshold -MIN_REWARD_THRESHOLD = 0.5 - # max competition history size MAX_COMPETETION_HISTORY_SIZE = 30 diff --git a/webgenie/datasets/__init__.py b/webgenie/datasets/__init__.py index 0e4c0f25..1aac0cad 100644 --- a/webgenie/datasets/__init__.py +++ b/webgenie/datasets/__init__.py @@ -1,2 +1,3 @@ from .dataset import Dataset, DatasetEntry from .synthetic_dataset import SyntheticDataset +from .huggingface_dataset import HuggingfaceDataset diff --git a/webgenie/rewards/__init__.py b/webgenie/rewards/__init__.py index 9624c634..dcf724d7 100644 --- a/webgenie/rewards/__init__.py +++ b/webgenie/rewards/__init__.py @@ -1,4 +1,5 @@ from .reward import Reward from .visual_reward import VisualReward from .quality_reward import QualityReward -from .rtc_reward import RtcReward \ No newline at end of file +from .rtc_reward import RtcReward +from .bert_reward import BertReward \ No newline at end of file From d7c2c492a6d8df99d7ef1f6275b1d5112b288e80 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 14:36:59 -0600 Subject: [PATCH 144/554] feat: validate html resources --- neurons/validators/genie_validator.py | 9 +++++--- webgenie/helpers/htmls.py | 32 ++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 64f39280..f3dacf56 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -19,7 +19,7 @@ TextTaskQualityCompetition, RESERVED_WEIGHTS ) -from webgenie.helpers.htmls import preprocess_html +from webgenie.helpers.htmls import preprocess_html, validate_resources from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.tasks.solution import Solution from webgenie.utils.uids import get_all_available_uids, get_most_available_uid @@ -148,8 +148,11 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag async def process_synapse(self, synapse: bt.Synapse) -> bt.Synapse: if synapse.dendrite.status_code == 200: - synapse.html = preprocess_html(synapse.html) - if not synapse.html: + html = preprocess_html(synapse.html) + if not html: return None + if not validate_resources(html): + return None + synapse.html = html return synapse return None diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 616d721f..884e38d9 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -5,6 +5,7 @@ import time import re import uuid + from webgenie.constants import ( SCREENSHOT_SCRIPT_PATH, WORK_DIR, @@ -12,6 +13,32 @@ PYTHON_CMD ) from webgenie.helpers.images import image_to_base64 +from bs4 import BeautifulSoup +import re + +def validate_resources(html: str) -> bool: + # List of allowed patterns for CSS and JavaScript resources + allowed_patterns = [ + r"https?://cdn.jsdelivr.net/npm/tailwindcss@[^/]+/dist/tailwind.min.css", + r"https?://stackpath.bootstrapcdn.com/bootstrap/[^/]+/css/bootstrap.min.css", + r"https?://code.jquery.com/jquery-[^/]+.min.js", + r"https?://stackpath.bootstrapcdn.com/bootstrap/[^/]+/js/bootstrap.bundle.min.js" + ] + + soup = BeautifulSoup(html, 'html.parser') + resources = soup.find_all(['link', 'script']) + + for resource in resources: + if resource.name == 'link' and resource.get('rel') == ['stylesheet']: + href = resource.get('href') + if href and not any(re.match(pattern, href) for pattern in allowed_patterns): + return False + elif resource.name == 'script': + src = resource.get('src') + if src and not any(re.match(pattern, src) for pattern in allowed_patterns): + return False + + return True def is_valid_html(html: str): try: @@ -59,9 +86,6 @@ def beautify_html(html: str) -> str: soup = BeautifulSoup(html, 'html.parser') return soup.prettify() -import re -from bs4 import BeautifulSoup - def replace_image_sources(html_content, new_url=PLACE_HOLDER_IMAGE_URL): soup = BeautifulSoup(html_content, 'html.parser') @@ -112,11 +136,9 @@ def is_empty_html(html: str) -> bool: soup = BeautifulSoup(html, 'html.parser') body = soup.find('body') - # Return True if no body tag exists if not body: return True - # Return True if body has no content (whitespace is stripped) if not body.get_text(strip=True): return True From ebd53c312fa860b431bdf3e2f31c61dc46fbc483 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 14:47:03 -0600 Subject: [PATCH 145/554] fix: wait for 10s to ensure page fully loaded --- webgenie/rewards/metrics/screenshot_single.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webgenie/rewards/metrics/screenshot_single.py b/webgenie/rewards/metrics/screenshot_single.py index 86b0b6b6..8f2dd54e 100644 --- a/webgenie/rewards/metrics/screenshot_single.py +++ b/webgenie/rewards/metrics/screenshot_single.py @@ -22,6 +22,9 @@ def take_screenshot(url, output_file="screenshot.png", do_it_again=False): # Navigate to the URL page.goto(url, timeout=60000) + # Wait for 10 seconds to ensure page is fully loaded + page.wait_for_timeout(10000) + # Take the screenshot page.screenshot(path=output_file, full_page=True, animations="disabled", timeout=60000) From 381949529bf966ff714d2e9110dd31af5d13ffd6 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 14:48:01 -0600 Subject: [PATCH 146/554] chore: remove unused file --- .../rewards/metrics/multi_processing_eval.py | 123 ------------------ 1 file changed, 123 deletions(-) delete mode 100644 webgenie/rewards/metrics/multi_processing_eval.py diff --git a/webgenie/rewards/metrics/multi_processing_eval.py b/webgenie/rewards/metrics/multi_processing_eval.py deleted file mode 100644 index a1f6b377..00000000 --- a/webgenie/rewards/metrics/multi_processing_eval.py +++ /dev/null @@ -1,123 +0,0 @@ -from metrics.visual_score import visual_eval_v3_multi -from multiprocessing import Pool -import contextlib, joblib -from joblib import Parallel, delayed -from tqdm import tqdm -import numpy as np -import json -import os -import shutil - -@contextlib.contextmanager -def tqdm_joblib(tqdm_object): - """Context manager to patch joblib to report into tqdm progress bar given as argument""" - class TqdmBatchCompletionCallback(joblib.parallel.BatchCompletionCallBack): - def __call__(self, *args, **kwargs): - tqdm_object.update(n=self.batch_size) - return super().__call__(*args, **kwargs) - - old_batch_callback = joblib.parallel.BatchCompletionCallBack - joblib.parallel.BatchCompletionCallBack = TqdmBatchCompletionCallback - try: - yield tqdm_object - finally: - joblib.parallel.BatchCompletionCallBack = old_batch_callback - tqdm_object.close() - - -def print_multi_score(multi_score): - _, final_size_score, final_matched_text_score, final_position_score, final_text_color_score, final_clip_score = multi_score - print() - print("Block-Match: ", final_size_score) - print("Text: ", final_matched_text_score) - print("Position: ", final_position_score) - print("Color: ", final_text_color_score) - print("CLIP: ", final_clip_score) - print("--------------------------------\n") - -if __name__ == "__main__": - debug = False - multiprocessing = True - - orig_reference_dir = "../testset_final" - eval_name = "testset_final" - - ## copy the original reference directory to a new directory - ## because we will be creating new screenshots - reference_dir = "../testset_final_" + eval_name - os.makedirs(reference_dir, exist_ok=True) - for filename in os.listdir(orig_reference_dir): - if filename.endswith(".html") or filename == "rick.jpg": - shutil.copy(os.path.join(orig_reference_dir, filename), os.path.join(reference_dir, filename)) - print ("copied original reference directory to ", reference_dir) - - test_dirs = { - "gpt4v_direct_prompting": "../predictions_final/gpt4v_direct_prompting", - "gemini_direct_prompting": "../predictions_final/gemini_direct_prompting" - } - - file_name_list = [] - - ## check if the file is in all prediction directories - for filename in os.listdir(reference_dir): - if filename.endswith(".html"): - if all([os.path.exists(os.path.join(test_dirs[key], filename)) for key in test_dirs]): - file_name_list.append(filename) - - print ("total #egs: ", len(file_name_list)) - - input_lists = [] - for filename in file_name_list: - - input_pred_list = [os.path.join(test_dirs[key], filename) for key in test_dirs] - original = os.path.join(reference_dir, filename) - - input_list = [input_pred_list, original] - input_lists.append(input_list) - - # print ("input_list: ", input_lists) - if multiprocessing: - with tqdm_joblib(tqdm(total=len(input_lists))) as progress_bar: - return_score_lists = list(tqdm(Parallel(n_jobs=8)(delayed(visual_eval_v3_multi)(input_list, debug=debug) for input_list in input_lists), total=len(input_lists))) - else: - return_score_lists = [] - for input_list in tqdm(input_lists): - return_score_list = visual_eval_v3_multi(input_list, debug=debug) - return_score_lists.append(return_score_list) - # print ("return lists: ", return_score_lists) - - res_dict = {} - for key in test_dirs: - res_dict[key] = {} - - for i, filename in enumerate(file_name_list): - idx = 0 - return_score_list = return_score_lists[i] - # print ("return score list: ", return_score_list) - if return_score_list: - for key in test_dirs: - if multiprocessing: - matched, final_score, multi_score = return_score_list[idx] - else: - matched = return_score_list[idx][0] - final_score = return_score_list[idx][1] - multi_score = return_score_list[idx][2] - idx += 1 - current_score = [final_score] + [item for item in multi_score] - res_dict[key][filename] = current_score - else: - print (filename + " didn't get a score") - for key in test_dirs: - res_dict[key][filename] = [0, 0, 0, 0, 0, 0] - - ## cache all scores - with open("metrics/res_dict_{}.json".format(eval_name), "w") as f: - json.dump(res_dict, f, indent=4) - - for key in test_dirs: - print(key) - values = list(res_dict[key].values()) - # print (values) - current_res = np.mean(np.array(values), axis=0) - # print(current_res) - print_multi_score(current_res) \ No newline at end of file From 212d8e675232ed5e4cdba33bf1ae3be376b62ffe Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 22:24:59 -0600 Subject: [PATCH 147/554] feat: implement random website dataset --- requirements.txt | 2 + .../competitions/image_task_competition.py | 14 ++- webgenie/datasets/__init__.py | 1 + webgenie/datasets/random_website_dataset.py | 103 ++++++++++++++++++ 4 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 webgenie/datasets/random_website_dataset.py diff --git a/requirements.txt b/requirements.txt index 396d10d2..331276bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,12 +5,14 @@ bittensor colormath==3.0.0 datasets==3.2.0 ddt==1.6.0 +duckduckgo_search datasets einops langchain==0.3.11 langchain-openai==0.2.12 lxml==5.3.0 matplotlib-inline==0.1.7 +nltk opencv-python==4.10.0.84 peft pip-chill==1.0.3 diff --git a/webgenie/competitions/image_task_competition.py b/webgenie/competitions/image_task_competition.py index 15ddda69..0422dc30 100644 --- a/webgenie/competitions/image_task_competition.py +++ b/webgenie/competitions/image_task_competition.py @@ -11,17 +11,21 @@ from webgenie.competitions.competition import Competition from webgenie.rewards.quality_reward import QualityReward from webgenie.rewards.visual_reward import VisualReward -from webgenie.datasets.synthetic_dataset import SyntheticDataset -from webgenie.datasets.huggingface_dataset import HuggingfaceDataset - +from webgenie.datasets import ( + RandomWebsiteDataset, + SyntheticDataset, + HuggingfaceDataset, +) + class ImageTaskCompetition(Competition): name = "ImageTaskCompetition" def __init__(self): super().__init__() self.datasets = [ - SyntheticDataset(), - HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), + RandomWebsiteDataset(), + #SyntheticDataset(), + #HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), ] async def generate_task(self) -> Tuple[Task, bt.Synapse]: diff --git a/webgenie/datasets/__init__.py b/webgenie/datasets/__init__.py index 1aac0cad..73e0323f 100644 --- a/webgenie/datasets/__init__.py +++ b/webgenie/datasets/__init__.py @@ -1,3 +1,4 @@ from .dataset import Dataset, DatasetEntry from .synthetic_dataset import SyntheticDataset from .huggingface_dataset import HuggingfaceDataset +from .random_website_dataset import RandomWebsiteDataset \ No newline at end of file diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py new file mode 100644 index 00000000..122ae19a --- /dev/null +++ b/webgenie/datasets/random_website_dataset.py @@ -0,0 +1,103 @@ +import bittensor as bt + +from bs4 import BeautifulSoup +from collections import Counter +from duckduckgo_search import DDGS +import nltk +from urllib.parse import urljoin +from nltk.corpus import brown +from playwright.sync_api import sync_playwright +import random +from typing import Optional + +from webgenie.datasets.dataset import Dataset, DatasetEntry + +class RandomWebsiteDataset(Dataset): + def __init__(self , **kwargs): + nltk.download("brown", quiet=True) + words = brown.words() + word_freq = Counter(word.lower() for word in words) + most_common = word_freq.most_common(25000) + common_words = [word for word, _ in most_common] + self.english_words = common_words + + async def get_random_website_url(self, retries: int = 3) -> Optional[str]: + try: + ddg = DDGS() + for _ in range(retries): + random_words = " ".join(random.sample(self.english_words, 5)) + results = list(ddg.text(random_words)) + if results: + website_url = random.choice(results)["href"] + return website_url + + except Exception as ex: + print(f"Failed to get search results from DuckDuckGo: {ex}") + return None + + + async def get_rendered_html(self, url): + with sync_playwright() as p: + browser = p.chromium.launch() + page = browser.new_page() + page.goto(url) + # Wait for 10 seconds to ensure content loads + page.wait_for_timeout(10000) + rendered_html = page.content() # Get the rendered HTML + browser.close() + + # Parse the HTML with BeautifulSoup + soup = BeautifulSoup(rendered_html, 'html.parser') + + # Attributes that need to be absolute + attributes = ['href', 'src', 'srcset'] + + # Find all elements with 'href', 'src', or 'srcset' attributes + for attr in attributes: + for element in soup.find_all(attrs={attr: True}): + original_attr = element[attr] + # Handle 'srcset' differently because it can contain multiple URLs + if attr == 'srcset': + new_urls = [] + parts = original_attr.split(',') + for part in parts: + # Split on whitespace and check if there is a descriptor + pieces = part.strip().split(maxsplit=1) + if len(pieces) == 2: + url_part, descriptor = pieces + else: + url_part = pieces[0] + descriptor = '' + + new_url = urljoin(url, url_part.strip()) + if descriptor: + new_urls.append(f"{new_url} {descriptor}") + else: + new_urls.append(new_url) + + element[attr] = ', '.join(new_urls) + else: + element[attr] = urljoin(url, original_attr) + + # Return the modified HTML as a string + return str(soup) + + async def generate_context(self)->DatasetEntry: + try: + bt.logging.info("Generating Random Website context") + website_url = await self.get_random_website_url() + if website_url is None: + raise Exception("Failed to get a valid website URL") + + html = self.get_rendered_html(website_url) + return DatasetEntry( + src="random_website", + topic="random_website", + ground_truth_html=html, + prompt="", + base64_image="" + ) + except Exception as e: + bt.logging.error(f"Error in generate_context: {e}") + raise e + From 9a2c3095a94c7730959c15779343b036c2b3cb24 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 6 Jan 2025 22:28:06 -0600 Subject: [PATCH 148/554] fix: fix bug --- webgenie/datasets/random_website_dataset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index 122ae19a..6e3c963e 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -88,8 +88,8 @@ async def generate_context(self)->DatasetEntry: website_url = await self.get_random_website_url() if website_url is None: raise Exception("Failed to get a valid website URL") - - html = self.get_rendered_html(website_url) + bt.logging.info(f"Generated website URL: {website_url}") + html = await self.get_rendered_html(website_url) return DatasetEntry( src="random_website", topic="random_website", From 496cd96f636291d8614e95fae7ad197b85cd6815 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 7 Jan 2025 07:01:09 -0600 Subject: [PATCH 149/554] style: style code --- .gitignore | 4 ++- neurons/validators/genie_validator.py | 19 +++++++----- neurons/validators/validator.py | 8 +++-- .../competitions/image_task_competition.py | 30 +++++++++++++++---- .../competitions/text_task_competition.py | 11 ++++--- webgenie/datasets/dataset.py | 2 ++ webgenie/datasets/huggingface_dataset.py | 5 ++-- webgenie/datasets/random_website_dataset.py | 22 +++++++------- webgenie/datasets/synthetic_dataset.py | 15 ++++++---- webgenie/helpers/htmls.py | 15 +++++++--- webgenie/helpers/images.py | 3 ++ webgenie/helpers/llms.py | 3 +- webgenie/helpers/weights.py | 5 +++- webgenie/prompts.py | 5 +++- webgenie/protocol.py | 10 ++++--- webgenie/rewards/bert_reward.py | 1 + webgenie/rewards/quality_reward.py | 11 ++++--- webgenie/rewards/reward.py | 1 + webgenie/rewards/rtc_reward.py | 9 ++++-- webgenie/rewards/visual_reward.py | 1 + webgenie/tasks/solution.py | 1 + webgenie/tasks/task.py | 3 ++ 22 files changed, 127 insertions(+), 57 deletions(-) diff --git a/.gitignore b/.gitignore index b064338d..4ee9a092 100644 --- a/.gitignore +++ b/.gitignore @@ -188,4 +188,6 @@ run_miner_2.sh run_validator.sh # developer doc -developer_doc.md \ No newline at end of file +developer_doc.md + +debug_images/ \ No newline at end of file diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index f3dacf56..e49e56fe 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -1,9 +1,7 @@ import os import bittensor as bt -import asyncio -import numpy as np import random -from typing import Union, List +from typing import Union from webgenie.base.neuron import BaseNeuron from webgenie.constants import ( @@ -17,13 +15,14 @@ TextTaskAccuracyCompetition, ImageTaskQualityCompetition, TextTaskQualityCompetition, - RESERVED_WEIGHTS + RESERVED_WEIGHTS, ) from webgenie.helpers.htmls import preprocess_html, validate_resources from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.tasks.solution import Solution from webgenie.utils.uids import get_all_available_uids, get_most_available_uid + class GenieValidator: def __init__(self, neuron: BaseNeuron): self.neuron = neuron @@ -62,14 +61,20 @@ async def query_miners(self): all_synapse_results = await dendrite( axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], synapse=synapse, - timeout=task.timeout + timeout=task.timeout, ) solutions = [] for synapse, miner_uid in zip(all_synapse_results, miner_uids): processed_synapse = await self.process_synapse(synapse) if processed_synapse is not None: - solutions.append(Solution(html = processed_synapse.html, miner_uid = miner_uid, process_time = processed_synapse.dendrite.process_time)) + solutions.append( + Solution( + html = processed_synapse.html, + miner_uid = miner_uid, + process_time = processed_synapse.dendrite.process_time, + ) + ) self.competitions.append((task, solutions)) except Exception as e: @@ -112,7 +117,7 @@ async def synthensize_task(self): competition, _ = random.choices( self.avail_competitions, - weights=[weight for _, weight in self.avail_competitions] + weights=[weight for _, weight in self.avail_competitions], )[0] task, synapse = await competition.generate_task() diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index c833de85..47832868 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -8,13 +8,15 @@ from dotenv import load_dotenv, find_dotenv load_dotenv(find_dotenv(filename=".env.validator")) -from typing import Tuple, Union +from typing import Tuple from webgenie.base.validator import BaseValidatorNeuron from webgenie.constants import API_HOTKEY from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse + from neurons.validators.genie_validator import GenieValidator + class Validator(BaseValidatorNeuron): """ Your validator neuron class. You should use this class to define your validator's behavior. In particular, you should replace the forward function with your own logic. @@ -61,10 +63,10 @@ def serve_axon(self): self.axon.attach( forward_fn = self.organic_forward_text, - blacklist_fn = self.blacklist_text + blacklist_fn = self.blacklist_text, ).attach( forward_fn = self.organic_forward_image, - blacklist_fn = self.blacklist_image + blacklist_fn = self.blacklist_image, ) self.axon.serve( diff --git a/webgenie/competitions/image_task_competition.py b/webgenie/competitions/image_task_competition.py index 0422dc30..003d1aa0 100644 --- a/webgenie/competitions/image_task_competition.py +++ b/webgenie/competitions/image_task_competition.py @@ -1,12 +1,11 @@ import bittensor as bt -import numpy as np import random -from typing import List, Tuple +from typing import Tuple from webgenie.constants import IMAGE_TASK_TIMEOUT from webgenie.helpers.htmls import html_to_screenshot, preprocess_html, is_empty_html +from webgenie.helpers.images import base64_to_image from webgenie.protocol import WebgenieImageSynapse -from webgenie.tasks.solution import Solution from webgenie.tasks.task import Task, ImageTask from webgenie.competitions.competition import Competition from webgenie.rewards.quality_reward import QualityReward @@ -17,6 +16,7 @@ HuggingfaceDataset, ) + class ImageTaskCompetition(Competition): name = "ImageTaskCompetition" def __init__(self): @@ -39,6 +39,24 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: raise ValueError("Empty ground truth html") base64_image = html_to_screenshot(ground_truth_html) + + # Save base64_image for debugging purposes + import os + import base64 + from datetime import datetime + + debug_dir = "debug_images" + os.makedirs(debug_dir, exist_ok=True) + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = os.path.join(debug_dir, f"image_{timestamp}.png") + + try: + image = base64_to_image(base64_image) + image.save(filename) + except Exception as e: + bt.logging.error(f"Failed to save debug image: {e}") + return ImageTask( base64_image=base64_image, ground_truth_html=ground_truth_html, @@ -46,6 +64,7 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: competition=self, ), WebgenieImageSynapse(base64_image=base64_image) + class ImageTaskAccuracyCompetition(ImageTaskCompetition): name = "ImageTaskAccuracyCompetition" def __init__(self): @@ -53,9 +72,10 @@ def __init__(self): self.rewards = [ (VisualReward(), 0.9), - (QualityReward(), 0.1) + (QualityReward(), 0.1), ] + class ImageTaskQualityCompetition(ImageTaskCompetition): name = "ImageTaskQualityCompetition" def __init__(self): @@ -63,5 +83,5 @@ def __init__(self): self.rewards = [ (VisualReward(), 0.5), - (QualityReward(), 0.5) + (QualityReward(), 0.5), ] diff --git a/webgenie/competitions/text_task_competition.py b/webgenie/competitions/text_task_competition.py index 27b0fa40..c0880214 100644 --- a/webgenie/competitions/text_task_competition.py +++ b/webgenie/competitions/text_task_competition.py @@ -12,13 +12,14 @@ from webgenie.tasks.task import Task, TextTask from webgenie.competitions.competition import Competition + class TextTaskCompetition(Competition): name = "TextTaskCompetition" def __init__(self): super().__init__() self.datasets = [ - SyntheticDataset(has_ground_truth_html = True) + SyntheticDataset(has_ground_truth_html = True), ] async def generate_task(self) -> Tuple[Task, bt.Synapse]: @@ -28,23 +29,25 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: prompt=dataset_entry.prompt, ground_truth_html=dataset_entry.ground_truth_html, timeout=TEXT_TASK_TIMEOUT, - competition=self + competition=self, ), WebgenieTextSynapse(prompt=dataset_entry.prompt) + class TextTaskAccuracyCompetition(TextTaskCompetition): name = "TextTaskAccuracyCompetition" def __init__(self): super().__init__() self.rewards = [ (RtcReward(), 0.9), - (QualityReward(), 0.1) + (QualityReward(), 0.1), ] + class TextTaskQualityCompetition(TextTaskCompetition): name = "TextTaskQualityCompetition" def __init__(self): super().__init__() self.rewards = [ (RtcReward(), 0.5), - (QualityReward(), 0.5) + (QualityReward(), 0.5), ] diff --git a/webgenie/datasets/dataset.py b/webgenie/datasets/dataset.py index 8b8cf31b..8bf11edc 100644 --- a/webgenie/datasets/dataset.py +++ b/webgenie/datasets/dataset.py @@ -1,5 +1,6 @@ from pydantic import Field, BaseModel + class DatasetEntry(BaseModel): src: str = Field(default="", description="The source of the dataset entry") topic: str = Field(default="", description="The topic of the dataset entry") @@ -7,6 +8,7 @@ class DatasetEntry(BaseModel): prompt: str = Field(default="", description="The prompt for the text task") base64_image: str = Field(default="", description="The base64 encoded image") + class Dataset: async def generate_context(self)->DatasetEntry: pass diff --git a/webgenie/datasets/huggingface_dataset.py b/webgenie/datasets/huggingface_dataset.py index febd94f7..390f4474 100644 --- a/webgenie/datasets/huggingface_dataset.py +++ b/webgenie/datasets/huggingface_dataset.py @@ -1,12 +1,9 @@ # https://huggingface.co/datasets/SALT-NLP/Design2Code_human_eval_pairwise import bittensor as bt -import os import random from datasets import load_dataset -from langchain_openai import ChatOpenAI -from langchain.prompts import ChatPromptTemplate from langchain_core.output_parsers import JsonOutputParser from langchain_core.pydantic_v1 import BaseModel, Field @@ -14,9 +11,11 @@ from webgenie.helpers.llms import call_llm from webgenie.prompts import PROMPT_MAKE_HTML_COMPLEX + class HTMLResponse(BaseModel): complex_html: str = Field(description="the complex html code") + class HuggingfaceDataset(Dataset): def __init__(self , **kwargs): dataset_name = kwargs["dataset_name"] diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index 6e3c963e..90e1bac7 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -4,14 +4,15 @@ from collections import Counter from duckduckgo_search import DDGS import nltk -from urllib.parse import urljoin from nltk.corpus import brown -from playwright.sync_api import sync_playwright +from playwright.async_api import async_playwright +from urllib.parse import urljoin import random from typing import Optional from webgenie.datasets.dataset import Dataset, DatasetEntry + class RandomWebsiteDataset(Dataset): def __init__(self , **kwargs): nltk.download("brown", quiet=True) @@ -35,16 +36,15 @@ async def get_random_website_url(self, retries: int = 3) -> Optional[str]: print(f"Failed to get search results from DuckDuckGo: {ex}") return None - async def get_rendered_html(self, url): - with sync_playwright() as p: - browser = p.chromium.launch() - page = browser.new_page() - page.goto(url) + async with async_playwright() as p: + browser = await p.chromium.launch() + page = await browser.new_page() + await page.goto(url) # Wait for 10 seconds to ensure content loads - page.wait_for_timeout(10000) - rendered_html = page.content() # Get the rendered HTML - browser.close() + await page.wait_for_timeout(10000) + rendered_html = await page.content() # Get the rendered HTML + await browser.close() # Parse the HTML with BeautifulSoup soup = BeautifulSoup(rendered_html, 'html.parser') @@ -95,7 +95,7 @@ async def generate_context(self)->DatasetEntry: topic="random_website", ground_truth_html=html, prompt="", - base64_image="" + base64_image="", ) except Exception as e: bt.logging.error(f"Error in generate_context: {e}") diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index 5de18660..5e436161 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -4,11 +4,8 @@ # to generate html, but now we are using openai models here. We are going to use that models on the mainnet import bittensor as bt -import os from typing import List -from langchain_openai import ChatOpenAI -from langchain.prompts import ChatPromptTemplate from langchain_core.output_parsers import JsonOutputParser from langchain_core.pydantic_v1 import BaseModel, Field @@ -16,12 +13,15 @@ from webgenie.helpers.llms import call_llm from webgenie.prompts import PROMPT_GEN_CONCEPT, PROMPT_GEN_HTML + class ConceptResponse(BaseModel): concepts: List[str] = Field(description="The concept of the website") + class HTMLResponse(BaseModel): html: str = Field(description="The html code of the website") + class SyntheticDataset(Dataset): def __init__(self, has_ground_truth_html: bool = True): self.has_ground_truth_html = has_ground_truth_html @@ -36,7 +36,7 @@ async def _generate_concepts(self): ("system", PROMPT_GEN_CONCEPT), ], params={"instructions": self.concept_parser.get_format_instructions()}, - output_parser=self.concept_parser + output_parser=self.concept_parser, ) return response["concepts"] @@ -46,8 +46,11 @@ async def _generate_html(self, concept: str): template=[ ("system", PROMPT_GEN_HTML), ], - params={"concept": concept, "instructions": self.html_parser.get_format_instructions()}, - output_parser=self.html_parser + params={ + "concept": concept, + "instructions": self.html_parser.get_format_instructions(), + }, + output_parser=self.html_parser, ) return response["html"] diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 884e38d9..a9625bfb 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -10,11 +10,10 @@ SCREENSHOT_SCRIPT_PATH, WORK_DIR, PLACE_HOLDER_IMAGE_URL, - PYTHON_CMD + PYTHON_CMD, ) from webgenie.helpers.images import image_to_base64 -from bs4 import BeautifulSoup -import re + def validate_resources(html: str) -> bool: # List of allowed patterns for CSS and JavaScript resources @@ -22,7 +21,7 @@ def validate_resources(html: str) -> bool: r"https?://cdn.jsdelivr.net/npm/tailwindcss@[^/]+/dist/tailwind.min.css", r"https?://stackpath.bootstrapcdn.com/bootstrap/[^/]+/css/bootstrap.min.css", r"https?://code.jquery.com/jquery-[^/]+.min.js", - r"https?://stackpath.bootstrapcdn.com/bootstrap/[^/]+/js/bootstrap.bundle.min.js" + r"https?://stackpath.bootstrapcdn.com/bootstrap/[^/]+/js/bootstrap.bundle.min.js", ] soup = BeautifulSoup(html, 'html.parser') @@ -40,6 +39,7 @@ def validate_resources(html: str) -> bool: return True + def is_valid_html(html: str): try: soup = BeautifulSoup(html, 'html.parser') @@ -48,6 +48,7 @@ def is_valid_html(html: str): bt.logging.debug(f"Error during HTML parsing: {e}") return False + def seperate_html_css(html_content: str): soup = BeautifulSoup(html_content, 'html.parser') @@ -67,6 +68,7 @@ def seperate_html_css(html_content: str): cleaned_html = str(soup) return cleaned_html, css + def html_to_screenshot(html: str) -> str: html_path = f"{WORK_DIR}/screenshot_{uuid.uuid4()}.html" with open(html_path, "w") as f: @@ -82,10 +84,12 @@ def html_to_screenshot(html: str) -> str: os.remove(png_path) return base64_image + def beautify_html(html: str) -> str: soup = BeautifulSoup(html, 'html.parser') return soup.prettify() + def replace_image_sources(html_content, new_url=PLACE_HOLDER_IMAGE_URL): soup = BeautifulSoup(html_content, 'html.parser') @@ -117,6 +121,7 @@ def replace_image_sources(html_content, new_url=PLACE_HOLDER_IMAGE_URL): return str(soup) + def preprocess_html(html: str) -> str: if not is_valid_html(html): return "" @@ -124,6 +129,7 @@ def preprocess_html(html: str) -> str: html = replace_image_sources(html) return html + def is_empty_html(html: str) -> bool: """Check if HTML body is empty or missing. @@ -144,6 +150,7 @@ def is_empty_html(html: str) -> bool: return False + if __name__ == "__main__": html = """ diff --git a/webgenie/helpers/images.py b/webgenie/helpers/images.py index c2e7f57a..447cf626 100644 --- a/webgenie/helpers/images.py +++ b/webgenie/helpers/images.py @@ -2,6 +2,7 @@ import io import base64 + def pil_image_to_base64(img: Image.Image) -> str: buffered = io.BytesIO() img.save(buffered, format="jpeg") @@ -10,10 +11,12 @@ def pil_image_to_base64(img: Image.Image) -> str: return base64_str + def image_to_base64(image_path: str) -> str: img = Image.open(image_path) return pil_image_to_base64(img) + def base64_to_image(base64_str: str) -> Image.Image: img_bytes = base64.b64decode(base64_str) img = Image.open(io.BytesIO(img_bytes)) diff --git a/webgenie/helpers/llms.py b/webgenie/helpers/llms.py index 2a8cefe4..3a63eaa3 100644 --- a/webgenie/helpers/llms.py +++ b/webgenie/helpers/llms.py @@ -8,9 +8,10 @@ base_url=os.getenv("LLM_MODEL_URL"), model=os.getenv("LLM_MODEL_ID"), api_key=os.getenv("LLM_API_KEY"), - temperature=0.7 + temperature=0.7, ) + async def call_llm(template, params, output_parser, retries=3): if not os.getenv("LLM_API_KEY"): raise Exception("LLM_API_KEY is not set") diff --git a/webgenie/helpers/weights.py b/webgenie/helpers/weights.py index 0ea254b6..88a3e2ed 100644 --- a/webgenie/helpers/weights.py +++ b/webgenie/helpers/weights.py @@ -6,6 +6,7 @@ wandb_on = False + def init_wandb(self): try: global wandb_on @@ -22,7 +23,7 @@ def init_wandb(self): entity=os.getenv("WANDB_ENTITY_NAME"), name=run_name, config=self.config, - reinit=True + reinit=True, ) signature = self.wallet.hotkey.sign(run.id.encode()).hex() @@ -34,6 +35,8 @@ def init_wandb(self): bt.logging.error(f"Error initializing wandb: {e}") raise e + + def log_wandb(data: dict): try: if not wandb_on: diff --git a/webgenie/prompts.py b/webgenie/prompts.py index 8e88b35b..d830a875 100644 --- a/webgenie/prompts.py +++ b/webgenie/prompts.py @@ -1,4 +1,3 @@ -# prompt to make rounded trip correctness PROMPT_RTC = """ You are an HTML, CSS expert. And you are well versed in the AI, ML. I have a model that converts the prompt to html. @@ -11,6 +10,7 @@ {instructions} """ + PROMPT_GEN_CONCEPT = """ Generate diverse website layout ideas for different companies, each with a unique design element. Examples include: a car company site with a left column, a webpage footer with a centered logo. @@ -19,6 +19,7 @@ {instructions} """ + PROMPT_GEN_HTML = """ Code a complete website with a good design in HTML and Tailwind CSS about this: {concept} Write real and long sentences about the business. NEVER USE sentences starting with Lorem @@ -32,6 +33,7 @@ {instructions} """ + PROMPT_MAKE_HTML_COMPLEX = """ You are an HTML, CSS expert. I have an HTML code. I want you to make the html code more complex. @@ -41,6 +43,7 @@ {instructions} """ + PROMPT_QUALITY = """ You are an HTML, CSS expert. I have an HTML code. I want you to evaluate the html code on the following criteria and give a score from 0 to 100. diff --git a/webgenie/protocol.py b/webgenie/protocol.py index 56ba95c7..83b080f5 100644 --- a/webgenie/protocol.py +++ b/webgenie/protocol.py @@ -4,6 +4,7 @@ import bittensor as bt import pydantic + class WebgenieTextSynapse(bt.Synapse): """ A protocol for the webgenie text task. @@ -11,15 +12,16 @@ class WebgenieTextSynapse(bt.Synapse): prompt: str = pydantic.Field( "", title="Prompt", - description="The prompt to be sent to miners." + description="The prompt to be sent to miners.", ) html: str = pydantic.Field( "", title="HTML", - description="The HTML received from miners." + description="The HTML received from miners.", ) + class WebgenieImageSynapse(bt.Synapse): """ A protocol for the webgenie image task. @@ -27,11 +29,11 @@ class WebgenieImageSynapse(bt.Synapse): base64_image: str = pydantic.Field( "", title="Base64 Image", - description="The base64 image to be sent to miners." + description="The base64 image to be sent to miners.", ) html: str = pydantic.Field( "", title="HTML", - description="The HTML received from miners." + description="The HTML received from miners.", ) diff --git a/webgenie/rewards/bert_reward.py b/webgenie/rewards/bert_reward.py index eda471a4..50447a12 100644 --- a/webgenie/rewards/bert_reward.py +++ b/webgenie/rewards/bert_reward.py @@ -7,6 +7,7 @@ from webgenie.tasks.task import Task from webgenie.tasks.solution import Solution + class BertReward(Reward): def __init__(self): pass diff --git a/webgenie/rewards/quality_reward.py b/webgenie/rewards/quality_reward.py index 4173f73f..956726b6 100644 --- a/webgenie/rewards/quality_reward.py +++ b/webgenie/rewards/quality_reward.py @@ -2,8 +2,6 @@ # (https://arxiv.org/pdf/2402.08699#page=11&zoom=100,384,458) is our inspiration for this reward. import bittensor as bt -import bert_score -import os import numpy as np from typing import List @@ -16,9 +14,11 @@ from webgenie.tasks.task import Task from webgenie.tasks.solution import Solution + class ScoreResponse(BaseModel): score: float = Field(default=0, description="The score of the html code") + class QualityReward(Reward): def __init__(self): self.prompt_response_parser = JsonOutputParser(pydantic_object=ScoreResponse) @@ -28,8 +28,11 @@ async def _get_score(self, solution: Solution) -> float: template=[ ("system", PROMPT_QUALITY), ], - params={"html": solution.html, "instructions": self.prompt_response_parser.get_format_instructions()}, - output_parser=self.prompt_response_parser + params={ + "html": solution.html, + "instructions": self.prompt_response_parser.get_format_instructions(), + }, + output_parser=self.prompt_response_parser, ) return float(response["score"]) / 100 diff --git a/webgenie/rewards/reward.py b/webgenie/rewards/reward.py index b46e2b79..ef970a29 100644 --- a/webgenie/rewards/reward.py +++ b/webgenie/rewards/reward.py @@ -5,6 +5,7 @@ from webgenie.tasks import Task from webgenie.tasks.solution import Solution + class Reward(ABC): @abstractmethod async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: diff --git a/webgenie/rewards/rtc_reward.py b/webgenie/rewards/rtc_reward.py index 49653bcf..e3d2ccad 100644 --- a/webgenie/rewards/rtc_reward.py +++ b/webgenie/rewards/rtc_reward.py @@ -21,6 +21,7 @@ class PromptResponse(BaseModel): prompt: str = Field(default="", description="The prompt that generates the given html code") + class RtcReward(Reward): def __init__(self): self.prompt_response_parser = JsonOutputParser(pydantic_object=PromptResponse) @@ -30,8 +31,12 @@ async def _get_prompt(self, task: Task, solutions: List[Solution]) -> str: template=[ ("system", PROMPT_RTC), ], - params={"html": task.ground_truth_html, "prompt": task.prompt, "instructions": self.prompt_response_parser.get_format_instructions()}, - output_parser=self.prompt_response_parser + params={ + "html": task.ground_truth_html, + "prompt": task.prompt, + "instructions": self.prompt_response_parser.get_format_instructions(), + }, + output_parser=self.prompt_response_parser, ) return response["prompt"] diff --git a/webgenie/rewards/visual_reward.py b/webgenie/rewards/visual_reward.py index 14c94be0..e6c5682e 100644 --- a/webgenie/rewards/visual_reward.py +++ b/webgenie/rewards/visual_reward.py @@ -11,6 +11,7 @@ from webgenie.tasks.task import Task, ImageTask from webgenie.tasks.solution import Solution + class VisualReward(Reward): def __init__(self): pass diff --git a/webgenie/tasks/solution.py b/webgenie/tasks/solution.py index 7a898cc6..1653e90d 100644 --- a/webgenie/tasks/solution.py +++ b/webgenie/tasks/solution.py @@ -1,5 +1,6 @@ from pydantic import BaseModel, Field + class Solution(BaseModel): html: str = Field("", description="The html solution") process_time: float = Field(0, description="The time it took to process the solution") diff --git a/webgenie/tasks/task.py b/webgenie/tasks/task.py index 9ecf989f..bcbe8958 100644 --- a/webgenie/tasks/task.py +++ b/webgenie/tasks/task.py @@ -1,14 +1,17 @@ from typing import Any from pydantic import BaseModel, Field + class Task(BaseModel): timeout: float = Field(default=50) competition: Any = Field(default=None) + class ImageTask(Task): base64_image: str = Field(default="", description="The base64 encoded image") ground_truth_html: str = Field(default="", description="The ground truth html") + class TextTask(Task): prompt: str = Field(default="", description="The prompt for the text task") ground_truth_html: str = Field(default="", description="The ground truth html") From 9e59453ff4ef63d73053c5119d3cf4ad419e17e0 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 7 Jan 2025 07:09:29 -0600 Subject: [PATCH 150/554] style: style code --- neurons/miners/hf_miner.py | 2 +- neurons/miners/miner.py | 1 + neurons/miners/openai_miner.py | 23 ++++++++++++++--------- neurons/validators/validator.py | 2 ++ 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/neurons/miners/hf_miner.py b/neurons/miners/hf_miner.py index 93ee3ab7..adc62b1f 100644 --- a/neurons/miners/hf_miner.py +++ b/neurons/miners/hf_miner.py @@ -1,5 +1,4 @@ import bittensor as bt -import os from webgenie.base.neuron import BaseNeuron from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse @@ -21,6 +20,7 @@ if total_memory_mb > 1024 * 50: from neurons.miners.hf_models.falcon7b import generate_html_from_text + class HfMiner: def __init__(self, neuron: BaseNeuron): self.neuron = neuron diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index acc01d50..20a39065 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -30,6 +30,7 @@ from neurons.miners.openai_miner import OpenaiMiner + class Miner(BaseMinerNeuron): """ Your miner neuron class. You should use this class to define your miner's behavior. In particular, you should replace the forward function with your own logic. You may also want to override the blacklist and priority functions according to your needs. diff --git a/neurons/miners/openai_miner.py b/neurons/miners/openai_miner.py index daaff6ea..165a748b 100644 --- a/neurons/miners/openai_miner.py +++ b/neurons/miners/openai_miner.py @@ -1,19 +1,18 @@ import bittensor as bt -import os -from langchain_openai import ChatOpenAI -from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate +from langchain.prompts import HumanMessagePromptTemplate, SystemMessagePromptTemplate from langchain_core.output_parsers import JsonOutputParser from langchain_core.pydantic_v1 import BaseModel, Field from webgenie.base.neuron import BaseNeuron from webgenie.helpers.llms import call_llm from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse -from webgenie.tasks.solution import Solution + class HTMLResponse(BaseModel): html: str = Field(default="", description="The HTML code for the webpage") + class OpenaiMiner: def __init__(self, neuron: BaseNeuron): self.neuron = neuron @@ -35,8 +34,11 @@ async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynaps html_response = await call_llm( template=template, - params={"query": synapse.prompt, "instructions": self.html_response_parser.get_format_instructions()}, - output_parser=self.html_response_parser + params={ + "query": synapse.prompt, + "instructions": self.html_response_parser.get_format_instructions(), + }, + output_parser=self.html_response_parser, ) synapse.html = html_response["html"] @@ -64,13 +66,16 @@ async def forward_image(self, synapse: WebgenieImageSynapse) -> WebgenieImageSyn template=[ {"type": "image_url", "image_url": {"url": "{image_url}"}}, ] - ) + ), ] html_response = await call_llm( template=prompt_messages, - params={"instructions": self.html_response_parser.get_format_instructions(), "image_url": f"data:image/jpeg;base64,{synapse.base64_image}"}, - output_parser=self.html_response_parser + params={ + "instructions": self.html_response_parser.get_format_instructions(), + "image_url": f"data:image/jpeg;base64,{synapse.base64_image}", + }, + output_parser=self.html_response_parser, ) synapse.html = html_response["html"] diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 47832868..8fbca4fa 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -136,10 +136,12 @@ async def __aexit__(self, exc_type, exc_value, traceback): self.is_running = False bt.logging.debug("Stopping validator in background thread") + async def main(): async with Validator() as validator: while validator.is_running and not validator.should_exit: await asyncio.sleep(15) + # The main function parses the configuration and runs the validator. if __name__ == "__main__": From 3714cd09b159ae15c5ab8cde74d594605b0e4cd9 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 7 Jan 2025 07:14:29 -0600 Subject: [PATCH 151/554] chore: create image_debug_str func --- neurons/miners/miner.py | 4 ++-- neurons/validators/genie_validator.py | 4 ++-- webgenie/helpers/images.py | 5 +++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index 20a39065..a46435fc 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -24,7 +24,7 @@ import bittensor as bt from webgenie.base.miner import BaseMinerNeuron -from webgenie.constants import MAX_DEBUG_IMAGE_STRING_LENGTH +from webgenie.helpers.images import image_debug_str from webgenie.helpers.weights import init_wandb from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse @@ -68,7 +68,7 @@ async def forward_text( async def forward_image( self, synapse: WebgenieImageSynapse ) -> WebgenieImageSynapse: - bt.logging.debug(f"Miner image forward called with image: {synapse.base64_image[:MAX_DEBUG_IMAGE_STRING_LENGTH]}...") + bt.logging.debug(f"Miner image forward called with image: {image_debug_str(synapse.base64_image)}...") return await self.genie_miner.forward_image(synapse) async def blacklist_text(self, synapse: WebgenieTextSynapse) -> typing.Tuple[bool, str]: diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index e49e56fe..45644a19 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -7,7 +7,6 @@ from webgenie.constants import ( MAX_COMPETETION_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE, - MAX_DEBUG_IMAGE_STRING_LENGTH, WORK_DIR, ) from webgenie.competitions import ( @@ -18,6 +17,7 @@ RESERVED_WEIGHTS, ) from webgenie.helpers.htmls import preprocess_html, validate_resources +from webgenie.helpers.images import image_debug_str from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.tasks.solution import Solution from webgenie.utils.uids import get_all_available_uids, get_most_available_uid @@ -129,7 +129,7 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag if isinstance(synapse, WebgenieTextSynapse): bt.logging.debug(f"Organic text forward: {synapse.prompt}") else: - bt.logging.debug(f"Organic image forward: {synapse.base64_image[:MAX_DEBUG_IMAGE_STRING_LENGTH]}...") + bt.logging.debug(f"Organic image forward: {image_debug_str(synapse.base64_image)}...") best_miner_uid = get_most_available_uid(self.neuron) try: diff --git a/webgenie/helpers/images.py b/webgenie/helpers/images.py index 447cf626..45801378 100644 --- a/webgenie/helpers/images.py +++ b/webgenie/helpers/images.py @@ -2,6 +2,7 @@ import io import base64 +from webgenie.constants import MAX_DEBUG_IMAGE_STRING_LENGTH def pil_image_to_base64(img: Image.Image) -> str: buffered = io.BytesIO() @@ -21,3 +22,7 @@ def base64_to_image(base64_str: str) -> Image.Image: img_bytes = base64.b64decode(base64_str) img = Image.open(io.BytesIO(img_bytes)) return img + + +def image_debug_str(base64_image: str) -> str: + return base64_image[:MAX_DEBUG_IMAGE_STRING_LENGTH] From 31a66405a653b9cd25a8953a1c25ba86ffc42934 Mon Sep 17 00:00:00 2001 From: AI Fullstack engineer <75736745+Cardoso-topdev@users.noreply.github.com> Date: Tue, 7 Jan 2025 07:15:19 -0600 Subject: [PATCH 152/554] Update README.md Update some features and roadmap --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d22d8c3a..e368b8ea 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # WebGenieAI Subnet -Welcome to WebGenieAI Subnet, a pioneering Bittensor-based subnet designed to revolutionize project generation through advanced AI models. WebGenieAI aims to transform diverse prompts—ranging from text and voice to images and Figma designs—into fully functional, ready-to-deploy projects. This subnet is tailored for developers, designers, and innovators who seek to accelerate their project development process with high-quality, AI-generated outputs. +Welcome to WebGenieAI Subnet, a pioneering Bittensor-based subnet designed to revolutionize project generation through advanced AI models. WebGenieAI aims to transform diverse prompts—ranging from texts to concept sketches and images into fully functional, ready-to-deploy projects. This subnet is tailored for developers, designers, and innovators who seek to accelerate their project development process with high-quality, AI-generated outputs. ## Table of Contents @@ -12,7 +12,7 @@ Welcome to WebGenieAI Subnet, a pioneering Bittensor-based subnet designed to re ## Overview -WebGenieAI Subnet leverages state-of-the-art AI models to interpret and convert various types of prompts into complete, deployable projects. Whether you're starting with a simple HTML/CSS framework or aiming to develop a complex React application, WebGenieAI can generate the entire codebase, ensuring it meets your specified requirements and is ready for immediate deployment. +WebGenieAI Subnet leverages state-of-the-art AI models to interpret and convert various prompts into complete, deployable projects. Whether starting with a simple HTML/CSS framework or aiming to develop a complex CMS or React application, WebGenieAI can generate the entire codebase, ensuring it meets your specified requirements and is ready for immediate deployment. ### Vision @@ -22,24 +22,23 @@ WebGenieAI envisions a future where project creation is seamless, automated, and The primary purpose of WebGenieAI is to: -- Automate Project Generation: Provide a platform that can autonomously generate high-quality projects from diverse input prompts. -- Enhance Productivity: Reduce the time and effort required for project development, enabling developers to quickly bring their ideas to life. +- Automate Project Generation: Provide a platform that autonomously generates high-quality projects from diverse input prompts. +- Enhance Productivity: Reduce the time and effort required for project development, enabling developers to bring their ideas to life quickly. - Promote Innovation: Encourage innovative solutions and optimizations in project generation through competitive incentivization. ## Features - **Text Prompt**: Generate projects by describing them in text. -- **Voice Prompt**: Create projects by giving voice commands. - **Image Prompt**: Upload an image of a website or app, and WebGenieAI will generate a pixel-perfect project. -- **Figma Prompt**: Convert Figma designs into functional projects. -- **Automated Downloads**: Directly download the generated projects as complete folders. +- **Sketch Prompt**: Convert concept sketches into functional projects. +- **Automated Deployment**: Deploy the generated projects into your own domain with click. ## Incentive Mechanism v1 The WebGenieAI subnet incentivizes miners and validators to ensure high-quality outputs. Here’s how it works specifically for this subnet: - Task Assignment: Subnet miners are assigned tasks related to generating and improving machine learning models based on various prompts (text and image). -- Performance Evaluation: Validators evaluate the outputs produced by miners. The evaluation criteria include accuracy, efficiency, and innovation. +- Evaluation: Validators evaluate the outputs produced by miners. The evaluation criteria include accuracy, code quality, and performance metrics. - Ranking and Rewarding: Validators rank the miners according to their performance. The Bittensor blockchain’s Yuma Consensus mechanism determines the TAO rewards distribution based on these rankings. ## Evaluation Process @@ -151,14 +150,16 @@ pm2 start --name auto_update auto_update.sh - [ ] Launch on mainnet ### Phase 2: Upgrade (Q1 2025) -- [ ] Build dashboard to track miner performance and progress +- [ ] Build a leaderboard to track miner performance and progress - [ ] Upgrade front-end application to v2 + - Online IDE like code sandbox and auto-deployment with one click - [ ] Upgrade incentive mechanism to v2 - - Output CMS such as Wordpress, shopify, and Magento + - Output CMS such as WordPress, Shopify, and Magento ### Phase 3: Expand (Q2 2025) - [ ] Upgrade incentive mechanism to v3 - - Generate full framework based projects on React, Vue, and Next.js from text and image prompts + - Add sketch prompt + - Generate full framework-based projects on React, Vue, and Next.js from text/image/sketch prompts - [ ] Add features to monetize the application - Add payment gateways - Automate the downloading of fully functional projects From 20869187b4bbf52cbbbfdc9db7d0c6c9d6505fa7 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 7 Jan 2025 07:49:41 -0600 Subject: [PATCH 153/554] style: style code --- webgenie/helpers/llms.py | 1 + webgenie/helpers/weights.py | 1 + 2 files changed, 2 insertions(+) diff --git a/webgenie/helpers/llms.py b/webgenie/helpers/llms.py index 3a63eaa3..262ccf0d 100644 --- a/webgenie/helpers/llms.py +++ b/webgenie/helpers/llms.py @@ -4,6 +4,7 @@ from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI + LLM = ChatOpenAI( base_url=os.getenv("LLM_MODEL_URL"), model=os.getenv("LLM_MODEL_ID"), diff --git a/webgenie/helpers/weights.py b/webgenie/helpers/weights.py index 88a3e2ed..9c39a40b 100644 --- a/webgenie/helpers/weights.py +++ b/webgenie/helpers/weights.py @@ -4,6 +4,7 @@ import webgenie + wandb_on = False From 1ab90eea5a7c1c1f30d423d65daf464d930bb36f Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 7 Jan 2025 11:53:22 -0600 Subject: [PATCH 154/554] style: style code --- neurons/validators/genie_validator.py | 2 +- webgenie/competitions/__init__.py | 1 + webgenie/competitions/competition.py | 3 +-- webgenie/competitions/image_task_competition.py | 16 +++++++++++----- webgenie/competitions/text_task_competition.py | 16 +++++++++------- webgenie/rewards/bert_reward.py | 8 +++++--- webgenie/rewards/quality_reward.py | 3 +-- webgenie/rewards/reward.py | 3 +-- webgenie/rewards/rtc_reward.py | 3 +-- webgenie/rewards/visual_reward.py | 3 +-- 10 files changed, 32 insertions(+), 26 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 45644a19..ab662201 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -19,7 +19,7 @@ from webgenie.helpers.htmls import preprocess_html, validate_resources from webgenie.helpers.images import image_debug_str from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse -from webgenie.tasks.solution import Solution +from webgenie.tasks import Solution from webgenie.utils.uids import get_all_available_uids, get_most_available_uid diff --git a/webgenie/competitions/__init__.py b/webgenie/competitions/__init__.py index 47b3d03c..4f8b05c5 100644 --- a/webgenie/competitions/__init__.py +++ b/webgenie/competitions/__init__.py @@ -1,3 +1,4 @@ +from webgenie.competitions.competition import Competition from webgenie.competitions.text_task_competition import TextTaskAccuracyCompetition, TextTaskQualityCompetition from webgenie.competitions.image_task_competition import ImageTaskAccuracyCompetition, ImageTaskQualityCompetition diff --git a/webgenie/competitions/competition.py b/webgenie/competitions/competition.py index 50f598b9..7fade953 100644 --- a/webgenie/competitions/competition.py +++ b/webgenie/competitions/competition.py @@ -2,8 +2,7 @@ import numpy as np from typing import List, Tuple -from webgenie.tasks import Task -from webgenie.tasks.solution import Solution +from webgenie.tasks import Task, Solution class Competition: name = "Competition" diff --git a/webgenie/competitions/image_task_competition.py b/webgenie/competitions/image_task_competition.py index 003d1aa0..c936dae0 100644 --- a/webgenie/competitions/image_task_competition.py +++ b/webgenie/competitions/image_task_competition.py @@ -2,14 +2,20 @@ import random from typing import Tuple +from webgenie.competitions.competition import Competition from webgenie.constants import IMAGE_TASK_TIMEOUT -from webgenie.helpers.htmls import html_to_screenshot, preprocess_html, is_empty_html +from webgenie.helpers.htmls import ( + html_to_screenshot, + preprocess_html, + is_empty_html, +) from webgenie.helpers.images import base64_to_image from webgenie.protocol import WebgenieImageSynapse -from webgenie.tasks.task import Task, ImageTask -from webgenie.competitions.competition import Competition -from webgenie.rewards.quality_reward import QualityReward -from webgenie.rewards.visual_reward import VisualReward +from webgenie.tasks import Task, ImageTask +from webgenie.rewards import ( + QualityReward, + VisualReward, +) from webgenie.datasets import ( RandomWebsiteDataset, SyntheticDataset, diff --git a/webgenie/competitions/text_task_competition.py b/webgenie/competitions/text_task_competition.py index c0880214..9aaafdd5 100644 --- a/webgenie/competitions/text_task_competition.py +++ b/webgenie/competitions/text_task_competition.py @@ -1,16 +1,18 @@ import bittensor as bt -import numpy as np import random -from typing import List, Tuple +from typing import Tuple + +from webgenie.competitions.competition import Competition +from webgenie.constants import TEXT_TASK_TIMEOUT from webgenie.datasets import ( SyntheticDataset, ) -from webgenie.constants import TEXT_TASK_TIMEOUT from webgenie.protocol import WebgenieTextSynapse -from webgenie.rewards.quality_reward import QualityReward -from webgenie.rewards.rtc_reward import RtcReward -from webgenie.tasks.task import Task, TextTask -from webgenie.competitions.competition import Competition +from webgenie.rewards import ( + QualityReward, + RtcReward, +) +from webgenie.tasks import Task, TextTask class TextTaskCompetition(Competition): diff --git a/webgenie/rewards/bert_reward.py b/webgenie/rewards/bert_reward.py index 50447a12..e920854c 100644 --- a/webgenie/rewards/bert_reward.py +++ b/webgenie/rewards/bert_reward.py @@ -3,9 +3,11 @@ import numpy as np from typing import List -from webgenie.rewards import Reward -from webgenie.tasks.task import Task -from webgenie.tasks.solution import Solution +from webgenie.rewards.reward import Reward +from webgenie.tasks import ( + Task, + Solution, +) class BertReward(Reward): diff --git a/webgenie/rewards/quality_reward.py b/webgenie/rewards/quality_reward.py index 956726b6..8101518a 100644 --- a/webgenie/rewards/quality_reward.py +++ b/webgenie/rewards/quality_reward.py @@ -11,8 +11,7 @@ from webgenie.helpers.llms import call_llm from webgenie.prompts import PROMPT_QUALITY from webgenie.rewards.reward import Reward -from webgenie.tasks.task import Task -from webgenie.tasks.solution import Solution +from webgenie.tasks import Task, Solution class ScoreResponse(BaseModel): diff --git a/webgenie/rewards/reward.py b/webgenie/rewards/reward.py index ef970a29..cc88ae9e 100644 --- a/webgenie/rewards/reward.py +++ b/webgenie/rewards/reward.py @@ -2,8 +2,7 @@ import numpy as np from typing import List -from webgenie.tasks import Task -from webgenie.tasks.solution import Solution +from webgenie.tasks import Task, Solution class Reward(ABC): diff --git a/webgenie/rewards/rtc_reward.py b/webgenie/rewards/rtc_reward.py index e3d2ccad..1eae9211 100644 --- a/webgenie/rewards/rtc_reward.py +++ b/webgenie/rewards/rtc_reward.py @@ -14,8 +14,7 @@ from webgenie.prompts import PROMPT_RTC from webgenie.rewards.reward import Reward from webgenie.rewards.metrics import s_bert -from webgenie.tasks.task import Task -from webgenie.tasks.solution import Solution +from webgenie.tasks import Task, Solution class PromptResponse(BaseModel): diff --git a/webgenie/rewards/visual_reward.py b/webgenie/rewards/visual_reward.py index e6c5682e..b5835079 100644 --- a/webgenie/rewards/visual_reward.py +++ b/webgenie/rewards/visual_reward.py @@ -8,8 +8,7 @@ from webgenie.constants import WORK_DIR from webgenie.rewards.reward import Reward from webgenie.rewards.metrics.visual_score import visual_eval_v3_multi -from webgenie.tasks.task import Task, ImageTask -from webgenie.tasks.solution import Solution +from webgenie.tasks import Task, ImageTask, Solution class VisualReward(Reward): From ccc137384df4fe23a678ec8a35d4b128d1e5d1d3 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 7 Jan 2025 12:06:28 -0600 Subject: [PATCH 155/554] chore: remove ground_truth_html in prompt task --- webgenie/competitions/text_task_competition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/competitions/text_task_competition.py b/webgenie/competitions/text_task_competition.py index 9aaafdd5..52a93c6b 100644 --- a/webgenie/competitions/text_task_competition.py +++ b/webgenie/competitions/text_task_competition.py @@ -21,7 +21,7 @@ def __init__(self): super().__init__() self.datasets = [ - SyntheticDataset(has_ground_truth_html = True), + SyntheticDataset(has_ground_truth_html = False), ] async def generate_task(self) -> Tuple[Task, bt.Synapse]: From 753f9503e7fcb0fd64772b073042c668d575d7d9 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 7 Jan 2025 19:41:34 -0600 Subject: [PATCH 156/554] hotfix: fix bug --- webgenie/rewards/rtc_reward.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/rewards/rtc_reward.py b/webgenie/rewards/rtc_reward.py index 1eae9211..331a48a0 100644 --- a/webgenie/rewards/rtc_reward.py +++ b/webgenie/rewards/rtc_reward.py @@ -25,13 +25,13 @@ class RtcReward(Reward): def __init__(self): self.prompt_response_parser = JsonOutputParser(pydantic_object=PromptResponse) - async def _get_prompt(self, task: Task, solutions: List[Solution]) -> str: + async def _get_prompt(self, task: Task, solution: Solution) -> str: response = await call_llm( template=[ ("system", PROMPT_RTC), ], params={ - "html": task.ground_truth_html, + "html": solution.html, "prompt": task.prompt, "instructions": self.prompt_response_parser.get_format_instructions(), }, From 7740b4239dff769cf964248a2e546552adfb044b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 8 Jan 2025 03:03:24 -0600 Subject: [PATCH 157/554] fix: fix bugs in concurrent visual score --- webgenie/rewards/visual_reward.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/webgenie/rewards/visual_reward.py b/webgenie/rewards/visual_reward.py index b5835079..34ddc123 100644 --- a/webgenie/rewards/visual_reward.py +++ b/webgenie/rewards/visual_reward.py @@ -4,6 +4,7 @@ import bittensor as bt import numpy as np from typing import List +import uuid from webgenie.constants import WORK_DIR from webgenie.rewards.reward import Reward @@ -21,17 +22,18 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: bt.logging.debug(f"Rewarding image task in visual reward") - original_html_path = f"{WORK_DIR}/original.html" + original_html_path = f"{WORK_DIR}/original_{uuid.uuid4()}.html" with open(original_html_path, "w") as f: f.write(task.ground_truth_html) miner_html_paths = [] for solution in solutions: - path = f"{WORK_DIR}/miner{solution.miner_uid}.html" + path = f"{WORK_DIR}/miner{solution.miner_uid}_{uuid.uuid4()}.html" with open(path, "w") as f: f.write(solution.html) miner_html_paths.append(path) visual_scores = visual_eval_v3_multi([miner_html_paths, original_html_path]) bt.logging.debug(f"Visual scores: {visual_scores}") + return np.array([score[1] for score in visual_scores]) From c3aae566b57d48d4a174d8b7184b0d143c8850a3 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 8 Jan 2025 08:12:45 -0600 Subject: [PATCH 158/554] chore: get fast and best response --- neurons/validators/genie_validator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index ab662201..bb4bad54 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -91,7 +91,8 @@ async def score(self): best_miner = -1 best_reward = 0.0 - + + solutions.sort(key=lambda solution: solution.process_time) competition = task.competition miner_uids = [solution.miner_uid for solution in solutions] rewards = await competition.reward(task, solutions) From e03ef3d0ba5e2d360daec451c055f7d3eb7f9418 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 8 Jan 2025 12:12:32 -0600 Subject: [PATCH 159/554] chore: pseudo upload_competition_results --- neurons/validators/genie_validator.py | 2 ++ webgenie/helpers/dashboard.py | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 webgenie/helpers/dashboard.py diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index bb4bad54..992f84b9 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -16,6 +16,7 @@ TextTaskQualityCompetition, RESERVED_WEIGHTS, ) +from webgenie.helpers.dashboard import upload_competition_result from webgenie.helpers.htmls import preprocess_html, validate_resources from webgenie.helpers.images import image_debug_str from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse @@ -99,6 +100,7 @@ async def score(self): bt.logging.success(f"Rewards for {miner_uids}: {rewards}") for i in range(len(miner_uids)): + upload_competition_result(miner_uids[i], rewards[i]) if rewards[i] > best_reward: best_reward = rewards[i] best_miner = miner_uids[i] diff --git a/webgenie/helpers/dashboard.py b/webgenie/helpers/dashboard.py new file mode 100644 index 00000000..6166a0b1 --- /dev/null +++ b/webgenie/helpers/dashboard.py @@ -0,0 +1,2 @@ +def upload_competition_result(): + pass \ No newline at end of file From 25ff209f94984d5faa9748aa5969112a4959905f Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 8 Jan 2025 13:24:07 -0600 Subject: [PATCH 160/554] style: rename var name --- neurons/validators/genie_validator.py | 23 ++++-- webgenie/competitions/__init__.py | 29 +++++--- webgenie/competitions/competition.py | 24 +++++-- .../competitions/image_task_competition.py | 71 +++++++++++-------- .../competitions/text_task_competition.py | 69 +++++++++++------- 5 files changed, 138 insertions(+), 78 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 992f84b9..d7d5e707 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -15,6 +15,8 @@ ImageTaskQualityCompetition, TextTaskQualityCompetition, RESERVED_WEIGHTS, + ACCURACY_METRIC_NAME, + QUALITY_METRIC_NAME, ) from webgenie.helpers.dashboard import upload_competition_result from webgenie.helpers.htmls import preprocess_html, validate_resources @@ -91,24 +93,31 @@ async def score(self): return best_miner = -1 - best_reward = 0.0 + best_final_score = 0.0 solutions.sort(key=lambda solution: solution.process_time) competition = task.competition miner_uids = [solution.miner_uid for solution in solutions] - rewards = await competition.reward(task, solutions) - bt.logging.success(f"Rewards for {miner_uids}: {rewards}") + final_scores, scores = await competition.calculate_final_scores(task, solutions) + bt.logging.success(f"Final scores for {miner_uids}: {final_scores}") for i in range(len(miner_uids)): - upload_competition_result(miner_uids[i], rewards[i]) - if rewards[i] > best_reward: - best_reward = rewards[i] + upload_competition_result({ + "miner_uid": miner_uids[i], + "final_score": final_scores[i], + "accuracy": scores[ACCURACY_METRIC_NAME][i], + "quality": scores[QUALITY_METRIC_NAME][i], + "html": solutions[i].html, + }) + + if final_scores[i] > best_final_score: + best_final_score = final_scores[i] best_miner = miner_uids[i] if best_miner == -1: return - self.neuron.update_scores([RESERVED_WEIGHTS[competition.name]], [best_miner]) + self.neuron.update_scores([RESERVED_WEIGHTS[competition.COMPETITION_TYPE]], [best_miner]) self.neuron.step += 1 async def synthensize_task(self): diff --git a/webgenie/competitions/__init__.py b/webgenie/competitions/__init__.py index 4f8b05c5..9fd8f066 100644 --- a/webgenie/competitions/__init__.py +++ b/webgenie/competitions/__init__.py @@ -1,13 +1,24 @@ -from webgenie.competitions.competition import Competition -from webgenie.competitions.text_task_competition import TextTaskAccuracyCompetition, TextTaskQualityCompetition -from webgenie.competitions.image_task_competition import ImageTaskAccuracyCompetition, ImageTaskQualityCompetition +from webgenie.competitions.competition import ( + Competition, + ACCURACY_METRIC_NAME, + QUALITY_METRIC_NAME, +) +from webgenie.competitions.text_task_competition import ( + TextTaskCompetition, + TextTaskAccuracyCompetition, + TextTaskQualityCompetition, +) +from webgenie.competitions.image_task_competition import ( + ImageTaskCompetition, + ImageTaskAccuracyCompetition, + ImageTaskQualityCompetition, +) + RESERVED_WEIGHTS = { - TextTaskAccuracyCompetition.name: 50, - TextTaskQualityCompetition.name: 20, - ImageTaskAccuracyCompetition.name: 90, - ImageTaskQualityCompetition.name: 10 + TextTaskAccuracyCompetition.COMPETITION_TYPE: 50, + TextTaskQualityCompetition.COMPETITION_TYPE: 20, + ImageTaskAccuracyCompetition.COMPETITION_TYPE: 90, + ImageTaskQualityCompetition.COMPETITION_TYPE: 10 } - - diff --git a/webgenie/competitions/competition.py b/webgenie/competitions/competition.py index 7fade953..3e7979f8 100644 --- a/webgenie/competitions/competition.py +++ b/webgenie/competitions/competition.py @@ -2,20 +2,30 @@ import numpy as np from typing import List, Tuple +from webgenie.rewards import Reward from webgenie.tasks import Task, Solution + +ACCURACY_METRIC_NAME = "Accuracy" +QUALITY_METRIC_NAME = "Quality" + + class Competition: - name = "Competition" + COMPETITION_TYPE = "UnknownCompetition" + def __init__(self): - self.rewards = [] + self.metrics: dict[str, Reward] = {} async def generate_task(self) -> Tuple[Task, bt.Synapse]: pass - async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: - scores = np.zeros(len(solutions)) - for reward, weight in self.rewards: - reward_scores = await reward.reward(task, solutions) - scores += weight * np.array(reward_scores) + async def calculate_scores(self, task: Task, solutions: List[Solution]) -> dict[str, np.ndarray]: + scores: dict[str, np.ndarray] = {} + for metric_name, reward_model in self.metrics.items(): + reward_scores = await reward_model.reward(task, solutions) + scores[metric_name] = reward_scores return scores + async def calculate_final_scores(self, task: Task, solutions: List[Solution]) -> Tuple[np.ndarray, dict[str, np.ndarray]]: + pass + diff --git a/webgenie/competitions/image_task_competition.py b/webgenie/competitions/image_task_competition.py index c936dae0..489124ff 100644 --- a/webgenie/competitions/image_task_competition.py +++ b/webgenie/competitions/image_task_competition.py @@ -1,8 +1,13 @@ import bittensor as bt +import numpy as np import random -from typing import Tuple +from typing import Tuple, List -from webgenie.competitions.competition import Competition +from webgenie.competitions.competition import ( + Competition, + ACCURACY_METRIC_NAME, + QUALITY_METRIC_NAME, +) from webgenie.constants import IMAGE_TASK_TIMEOUT from webgenie.helpers.htmls import ( html_to_screenshot, @@ -11,7 +16,7 @@ ) from webgenie.helpers.images import base64_to_image from webgenie.protocol import WebgenieImageSynapse -from webgenie.tasks import Task, ImageTask +from webgenie.tasks import Task, ImageTask, Solution from webgenie.rewards import ( QualityReward, VisualReward, @@ -24,19 +29,28 @@ class ImageTaskCompetition(Competition): - name = "ImageTaskCompetition" + COMPETITION_TYPE = "ImageTaskCompetition" + def __init__(self): super().__init__() self.datasets = [ - RandomWebsiteDataset(), - #SyntheticDataset(), - #HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), + (RandomWebsiteDataset(), 0.8), + (SyntheticDataset(), 0.1), + (HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), 0.1), ] + self.metrics = { + ACCURACY_METRIC_NAME: VisualReward(), + QUALITY_METRIC_NAME: QualityReward(), + } + async def generate_task(self) -> Tuple[Task, bt.Synapse]: bt.logging.info("Generating Image task") - dataset_entry = await random.choice(self.datasets).generate_context() + + dataset, _ = random.choices(self.datasets, weights=[weight for _, weight in self.datasets])[0] + dataset_entry = await dataset.generate_context() + ground_truth_html = preprocess_html(dataset_entry.ground_truth_html) if not ground_truth_html : raise ValueError("Invalid ground truth html") @@ -63,31 +77,32 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: except Exception as e: bt.logging.error(f"Failed to save debug image: {e}") - return ImageTask( - base64_image=base64_image, - ground_truth_html=ground_truth_html, - timeout=IMAGE_TASK_TIMEOUT, - competition=self, - ), WebgenieImageSynapse(base64_image=base64_image) + return ( + ImageTask( + base64_image=base64_image, + ground_truth_html=ground_truth_html, + timeout=IMAGE_TASK_TIMEOUT, + competition=self, + ), + WebgenieImageSynapse(base64_image=base64_image), + ) class ImageTaskAccuracyCompetition(ImageTaskCompetition): - name = "ImageTaskAccuracyCompetition" - def __init__(self): - super().__init__() + COMPETITION_TYPE = "ImageTaskAccuracyCompetition" - self.rewards = [ - (VisualReward(), 0.9), - (QualityReward(), 0.1), - ] + async def calculate_final_scores(self, task: Task, solutions: List[Solution]) -> np.ndarray: + scores = await self.calculate_scores(task, solutions) + return scores[ACCURACY_METRIC_NAME] * 0.9 + scores[QUALITY_METRIC_NAME] * 0.1, scores class ImageTaskQualityCompetition(ImageTaskCompetition): - name = "ImageTaskQualityCompetition" - def __init__(self): - super().__init__() + COMPETITION_TYPE = "ImageTaskQualityCompetition" + + async def calculate_final_scores(self, task: Task, solutions: List[Solution]) -> np.ndarray: + scores = await self.calculate_scores(task, solutions) + accuracy_scores = scores[ACCURACY_METRIC_NAME] + quality_scores = scores[QUALITY_METRIC_NAME] + final_scores = np.where(accuracy_scores > 0.7, quality_scores, 0) + return final_scores, scores - self.rewards = [ - (VisualReward(), 0.5), - (QualityReward(), 0.5), - ] diff --git a/webgenie/competitions/text_task_competition.py b/webgenie/competitions/text_task_competition.py index 52a93c6b..4dac769e 100644 --- a/webgenie/competitions/text_task_competition.py +++ b/webgenie/competitions/text_task_competition.py @@ -1,8 +1,13 @@ import bittensor as bt +import numpy as np import random -from typing import Tuple +from typing import Tuple, List -from webgenie.competitions.competition import Competition +from webgenie.competitions.competition import ( + Competition, + ACCURACY_METRIC_NAME, + QUALITY_METRIC_NAME, +) from webgenie.constants import TEXT_TASK_TIMEOUT from webgenie.datasets import ( SyntheticDataset, @@ -12,44 +17,54 @@ QualityReward, RtcReward, ) -from webgenie.tasks import Task, TextTask +from webgenie.tasks import Task, TextTask, Solution class TextTaskCompetition(Competition): - name = "TextTaskCompetition" + COMPETITION_TYPE = "TextTaskCompetition" + def __init__(self): super().__init__() self.datasets = [ - SyntheticDataset(has_ground_truth_html = False), + (SyntheticDataset(has_ground_truth_html = False), 0.8), ] - + + self.metrics = { + ACCURACY_METRIC_NAME: RtcReward(), + QUALITY_METRIC_NAME: QualityReward(), + } + async def generate_task(self) -> Tuple[Task, bt.Synapse]: bt.logging.info("Generating Text task") - dataset_entry = await random.choice(self.datasets).generate_context() - return TextTask( - prompt=dataset_entry.prompt, - ground_truth_html=dataset_entry.ground_truth_html, - timeout=TEXT_TASK_TIMEOUT, - competition=self, - ), WebgenieTextSynapse(prompt=dataset_entry.prompt) + dataset, _ = random.choices(self.datasets, weights=[weight for _, weight in self.datasets])[0] + dataset_entry = await dataset.generate_context() + + return ( + TextTask( + prompt=dataset_entry.prompt, + ground_truth_html=dataset_entry.ground_truth_html, + timeout=TEXT_TASK_TIMEOUT, + competition=self, + ), + WebgenieTextSynapse(prompt=dataset_entry.prompt), + ) class TextTaskAccuracyCompetition(TextTaskCompetition): - name = "TextTaskAccuracyCompetition" - def __init__(self): - super().__init__() - self.rewards = [ - (RtcReward(), 0.9), - (QualityReward(), 0.1), - ] + COMPETITION_TYPE = "TextTaskAccuracyCompetition" + + async def calculate_final_scores(self, task: Task, solutions: List[Solution]) -> np.ndarray: + scores = await self.calculate_scores(task, solutions) + return scores[ACCURACY_METRIC_NAME] * 0.9 + scores[QUALITY_METRIC_NAME] * 0.1, scores class TextTaskQualityCompetition(TextTaskCompetition): - name = "TextTaskQualityCompetition" - def __init__(self): - super().__init__() - self.rewards = [ - (RtcReward(), 0.5), - (QualityReward(), 0.5), - ] + COMPETITION_TYPE = "TextTaskQualityCompetition" + + async def calculate_final_scores(self, task: Task, solutions: List[Solution]) -> np.ndarray: + scores = await self.calculate_scores(task, solutions) + accuracy_scores = scores[ACCURACY_METRIC_NAME] + quality_scores = scores[QUALITY_METRIC_NAME] + final_scores = np.where(accuracy_scores > 0.7, quality_scores, 0) + return final_scores, scores From 0188b44ee40493cd691a6144935e7c24d9063c36 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 8 Jan 2025 13:26:00 -0600 Subject: [PATCH 161/554] style: add empty line --- neurons/validators/genie_validator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index d7d5e707..5bb5acdc 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -98,6 +98,7 @@ async def score(self): solutions.sort(key=lambda solution: solution.process_time) competition = task.competition miner_uids = [solution.miner_uid for solution in solutions] + final_scores, scores = await competition.calculate_final_scores(task, solutions) bt.logging.success(f"Final scores for {miner_uids}: {final_scores}") From 673e11c3318017355d6b30e448e34aa31e2405d4 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 9 Jan 2025 04:16:26 -0600 Subject: [PATCH 162/554] feat: update incentive mechanizm --- requirements.txt | 1 + webgenie/base/validator.py | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 331276bc..02a6c391 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,6 +21,7 @@ python-dotenv==1.0.1 scikit-learn==1.6.0 sentence-transformers shtab==1.6.5 +sqlalchemy tinycss2==1.4.0 wandb==0.19.0 git+https://github.com/openai/CLIP.git \ No newline at end of file diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index 89f13319..24015535 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -205,12 +205,9 @@ def update_scores(self, rewards: np.ndarray, uids: List[int]): # shape: [ metagraph.n ] scattered_rewards: np.ndarray = np.zeros_like(self.scores) scattered_rewards[uids_array] = rewards - bt.logging.debug(f"Scattered rewards: {rewards}") + bt.logging.debug(f"Scattered rewards: {scattered_rewards}") - # Update scores with rewards produced by this step. - # shape: [ metagraph.n ] - alpha: float = self.config.neuron.moving_average_alpha - self.scores: np.ndarray = alpha * scattered_rewards + (1 - alpha) * self.scores + self.scores: np.ndarray = scattered_rewards + self.scores bt.logging.debug(f"Updated moving avg scores: {self.scores}") def save_state(self): From 3e7e4448a7b8a8375c54df02033ebc892b4e177c Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 9 Jan 2025 04:17:55 -0600 Subject: [PATCH 163/554] chore: remove func doc --- webgenie/base/validator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index 24015535..ab6ed9ef 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -170,8 +170,6 @@ def resync_metagraph(self): self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) def update_scores(self, rewards: np.ndarray, uids: List[int]): - """Performs exponential moving average on the scores based on the rewards received from the miners.""" - # Check if rewards contains NaN values. if np.isnan(rewards).any(): bt.logging.warning(f"NaN values detected in rewards: {rewards}") From f61e505848fc7d916b533ebfdbd65168f2f70f9f Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 9 Jan 2025 04:19:15 -0600 Subject: [PATCH 164/554] chore: remove log --- webgenie/base/validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index ab6ed9ef..2b838d31 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -206,7 +206,7 @@ def update_scores(self, rewards: np.ndarray, uids: List[int]): bt.logging.debug(f"Scattered rewards: {scattered_rewards}") self.scores: np.ndarray = scattered_rewards + self.scores - bt.logging.debug(f"Updated moving avg scores: {self.scores}") + bt.logging.debug(f"Updated scores: {self.scores}") def save_state(self): """Saves the state of the validator to a file.""" From 78dde6ea576e7c9eaf4ad9cade5f7c4eed2d4467 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 9 Jan 2025 07:08:05 -0600 Subject: [PATCH 165/554] chore: convert uv project --- .python-version | 1 + pyproject.toml | 41 +++++++++++++++++++++ requirements.txt | 27 -------------- setup.py | 95 ------------------------------------------------ uv.lock | 7 ++++ 5 files changed, 49 insertions(+), 122 deletions(-) create mode 100644 .python-version create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 setup.py create mode 100644 uv.lock diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..c8cfe395 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..413c2ab1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +[project] +name = "web-genie-ai" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "ansible-vault==2.1.0", + "beautifulsoup4==4.12.3", + "bert-score==0.3.13", + "bittensor==8.5.1", + "clip", + "colormath==3.0.0", + "datasets==3.2.0", + "ddt==1.6.0", + "duckduckgo_search", + "datasets", + "einops", + "langchain==0.3.11", + "langchain-openai==0.2.12", + "lxml==5.3.0", + "matplotlib-inline==0.1.7", + "nltk", + "opencv-python==4.10.0.84", + "peft", + "pip-chill==1.0.3", + "playwright==1.49.1", + "python-dotenv==1.0.1", + "scikit-learn==1.6.0", + "sentence-transformers", + "shtab==1.6.5", + "sqlalchemy", + "tinycss2==1.4.0", + "wandb==0.19.0" +] + +[project.urls] +repository = "https://github.com/web-genie-ai/web-genie-ai" + +[tool.uv.sources] +clip = {git = "https://github.com/openai/CLIP.git"} diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 02a6c391..00000000 --- a/requirements.txt +++ /dev/null @@ -1,27 +0,0 @@ -ansible-vault==2.1.0 -beautifulsoup4==4.12.3 -bert-score==0.3.13 -bittensor -colormath==3.0.0 -datasets==3.2.0 -ddt==1.6.0 -duckduckgo_search -datasets -einops -langchain==0.3.11 -langchain-openai==0.2.12 -lxml==5.3.0 -matplotlib-inline==0.1.7 -nltk -opencv-python==4.10.0.84 -peft -pip-chill==1.0.3 -playwright==1.49.1 -python-dotenv==1.0.1 -scikit-learn==1.6.0 -sentence-transformers -shtab==1.6.5 -sqlalchemy -tinycss2==1.4.0 -wandb==0.19.0 -git+https://github.com/openai/CLIP.git \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index a71f5056..00000000 --- a/setup.py +++ /dev/null @@ -1,95 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao -# Copyright © 2023 Sangar, pycorn0729 - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. - -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - -import re -import os -import codecs -import pathlib -from os import path -from io import open -from setuptools import setup, find_packages -from pkg_resources import parse_requirements - - -def read_requirements(path): - with open(path, "r") as f: - requirements = f.read().splitlines() - processed_requirements = [] - - for req in requirements: - # For git or other VCS links - if req.startswith("git+") or "@" in req: - pkg_name = re.search(r"(#egg=)([\w\-_]+)", req) - if pkg_name: - processed_requirements.append(pkg_name.group(2)) - else: - # You may decide to raise an exception here, - # if you want to ensure every VCS link has an #egg= at the end - continue - else: - processed_requirements.append(req) - return processed_requirements - - -requirements = read_requirements("requirements.txt") -here = path.abspath(path.dirname(__file__)) - -with open(path.join(here, "README.md"), encoding="utf-8") as f: - long_description = f.read() - -# loading version from setup.py -with codecs.open( - os.path.join(here, "template/__init__.py"), encoding="utf-8" -) as init_file: - version_match = re.search( - r"^__version__ = ['\"]([^'\"]*)['\"]", init_file.read(), re.M - ) - version_string = version_match.group(1) - -setup( - name="webgenie", - version=version_string, - description="webgenie aims to transform diverse prompts—ranging from text and voice to images and Figma designs—into fully functional, ready-to-deploy projects.", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/web-genie-ai/web-genie-ai", - author="Sangar, Dominique Hayes", - packages=find_packages(), - include_package_data=True, - author_email="sangar.work1028@gmail.com, hayesdominique0729@gmail.com", - license="MIT", - python_requires=">=3.12", - install_requires=requirements, - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Topic :: Software Development :: Build Tools", - # Pick your license as you wish - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Topic :: Scientific/Engineering", - "Topic :: Scientific/Engineering :: Mathematics", - "Topic :: Scientific/Engineering :: Artificial Intelligence", - "Topic :: Software Development", - "Topic :: Software Development :: Libraries", - "Topic :: Software Development :: Libraries :: Python Modules", - ], -) diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..d6cc9006 --- /dev/null +++ b/uv.lock @@ -0,0 +1,7 @@ +version = 1 +requires-python = ">=3.10" + +[[package]] +name = "web-genie-ai" +version = "0.1.0" +source = { virtual = "." } From 4fd285f9c6f84efe590de49b111f5ed41d81fe7c Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 9 Jan 2025 07:35:03 -0600 Subject: [PATCH 166/554] build: update dependency --- pyproject.toml | 4 +- uv.lock | 4536 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 4538 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 413c2ab1..e8924e95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,8 +16,8 @@ dependencies = [ "duckduckgo_search", "datasets", "einops", - "langchain==0.3.11", - "langchain-openai==0.2.12", + "langchain", + "langchain-openai", "lxml==5.3.0", "matplotlib-inline==0.1.7", "nltk", diff --git a/uv.lock b/uv.lock index d6cc9006..386ee570 100644 --- a/uv.lock +++ b/uv.lock @@ -1,7 +1,4543 @@ version = 1 requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", +] + +[[package]] +name = "accelerate" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/09/7947691b7d44bfc739da4a44cc47d6a6d75e6fe9adf047c5234d7cb6be64/accelerate-1.2.1.tar.gz", hash = "sha256:03e161fc69d495daf2b9b5c8d5b43d06e2145520c04727b5bda56d49f1a43ab5", size = 341652 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/60/a585c806d6c0ec5f8149d44eb202714792802f484e6e2b1bf96b23bd2b00/accelerate-1.2.1-py3-none-any.whl", hash = "sha256:be1cbb958cf837e7cdfbde46b812964b1b8ae94c9c7d94d921540beafcee8ddf", size = 336355 }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/55/e4373e888fdacb15563ef6fa9fa8c8252476ea071e96fb46defac9f18bf2/aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745", size = 21977 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/74/fbb6559de3607b3300b9be3cc64e97548d55678e44623db17820dbd20002/aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8", size = 14756 }, +] + +[[package]] +name = "aiohttp" +version = "3.10.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/a8/8e2ba36c6e3278d62e0c88aa42bb92ddbef092ac363b390dab4421da5cf5/aiohttp-3.10.11.tar.gz", hash = "sha256:9dc2b8f3dcab2e39e0fa309c8da50c3b55e6f34ab25f1a71d3288f24924d33a7", size = 7551886 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/c7/575f9e82d7ef13cb1b45b9db8a5b8fadb35107fb12e33809356ae0155223/aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e", size = 588218 }, + { url = "https://files.pythonhosted.org/packages/12/7b/a800dadbd9a47b7f921bfddcd531371371f39b9cd05786c3638bfe2e1175/aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298", size = 400815 }, + { url = "https://files.pythonhosted.org/packages/cb/28/7dbd53ab10b0ded397feed914880f39ce075bd39393b8dfc322909754a0a/aiohttp-3.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffbfde2443696345e23a3c597049b1dd43049bb65337837574205e7368472177", size = 392099 }, + { url = "https://files.pythonhosted.org/packages/6a/2e/c6390f49e67911711c2229740e261c501685fe7201f7f918d6ff2fd1cfb0/aiohttp-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20b3d9e416774d41813bc02fdc0663379c01817b0874b932b81c7f777f67b217", size = 1224854 }, + { url = "https://files.pythonhosted.org/packages/69/68/c96afae129201bff4edbece52b3e1abf3a8af57529a42700669458b00b9f/aiohttp-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b943011b45ee6bf74b22245c6faab736363678e910504dd7531a58c76c9015a", size = 1259641 }, + { url = "https://files.pythonhosted.org/packages/63/89/bedd01456442747946114a8c2f30ff1b23d3b2ea0c03709f854c4f354a5a/aiohttp-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48bc1d924490f0d0b3658fe5c4b081a4d56ebb58af80a6729d4bd13ea569797a", size = 1295412 }, + { url = "https://files.pythonhosted.org/packages/9b/4d/942198e2939efe7bfa484781590f082135e9931b8bcafb4bba62cf2d8f2f/aiohttp-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e12eb3f4b1f72aaaf6acd27d045753b18101524f72ae071ae1c91c1cd44ef115", size = 1218311 }, + { url = "https://files.pythonhosted.org/packages/a3/5b/8127022912f1fa72dfc39cf37c36f83e0b56afc3b93594b1cf377b6e4ffc/aiohttp-3.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f14ebc419a568c2eff3c1ed35f634435c24ead2fe19c07426af41e7adb68713a", size = 1189448 }, + { url = "https://files.pythonhosted.org/packages/af/12/752878033c8feab3362c0890a4d24e9895921729a53491f6f6fad64d3287/aiohttp-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:72b191cdf35a518bfc7ca87d770d30941decc5aaf897ec8b484eb5cc8c7706f3", size = 1186484 }, + { url = "https://files.pythonhosted.org/packages/61/24/1d91c304fca47d5e5002ca23abab9b2196ac79d5c531258e048195b435b2/aiohttp-3.10.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ab2328a61fdc86424ee540d0aeb8b73bbcad7351fb7cf7a6546fc0bcffa0038", size = 1183864 }, + { url = "https://files.pythonhosted.org/packages/c1/70/022d28b898314dac4cb5dd52ead2a372563c8590b1eaab9c5ed017eefb1e/aiohttp-3.10.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa93063d4af05c49276cf14e419550a3f45258b6b9d1f16403e777f1addf4519", size = 1241460 }, + { url = "https://files.pythonhosted.org/packages/c3/15/2b43853330f82acf180602de0f68be62a2838d25d03d2ed40fecbe82479e/aiohttp-3.10.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30283f9d0ce420363c24c5c2421e71a738a2155f10adbb1a11a4d4d6d2715cfc", size = 1258521 }, + { url = "https://files.pythonhosted.org/packages/28/38/9ef2076cb06dcc155e7f02275f5da403a3e7c9327b6b075e999f0eb73613/aiohttp-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e5358addc8044ee49143c546d2182c15b4ac3a60be01c3209374ace05af5733d", size = 1207329 }, + { url = "https://files.pythonhosted.org/packages/c2/5f/c5329d67a2c83d8ae17a84e11dec14da5773520913bfc191caaf4cd57e50/aiohttp-3.10.11-cp310-cp310-win32.whl", hash = "sha256:e1ffa713d3ea7cdcd4aea9cddccab41edf6882fa9552940344c44e59652e1120", size = 363835 }, + { url = "https://files.pythonhosted.org/packages/0f/c6/ca5d70eea2fdbe283dbc1e7d30649a1a5371b2a2a9150db192446f645789/aiohttp-3.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:778cbd01f18ff78b5dd23c77eb82987ee4ba23408cbed233009fd570dda7e674", size = 382169 }, + { url = "https://files.pythonhosted.org/packages/73/96/221ec59bc38395a6c205cbe8bf72c114ce92694b58abc8c3c6b7250efa7f/aiohttp-3.10.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:80ff08556c7f59a7972b1e8919f62e9c069c33566a6d28586771711e0eea4f07", size = 587742 }, + { url = "https://files.pythonhosted.org/packages/24/17/4e606c969b19de5c31a09b946bd4c37e30c5288ca91d4790aa915518846e/aiohttp-3.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c8f96e9ee19f04c4914e4e7a42a60861066d3e1abf05c726f38d9d0a466e695", size = 400357 }, + { url = "https://files.pythonhosted.org/packages/a2/e5/433f59b87ba69736e446824710dd7f26fcd05b24c6647cb1e76554ea5d02/aiohttp-3.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fb8601394d537da9221947b5d6e62b064c9a43e88a1ecd7414d21a1a6fba9c24", size = 392099 }, + { url = "https://files.pythonhosted.org/packages/d2/a3/3be340f5063970bb9e47f065ee8151edab639d9c2dce0d9605a325ab035d/aiohttp-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea224cf7bc2d8856d6971cea73b1d50c9c51d36971faf1abc169a0d5f85a382", size = 1300367 }, + { url = "https://files.pythonhosted.org/packages/ba/7d/a3043918466cbee9429792ebe795f92f70eeb40aee4ccbca14c38ee8fa4d/aiohttp-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db9503f79e12d5d80b3efd4d01312853565c05367493379df76d2674af881caa", size = 1339448 }, + { url = "https://files.pythonhosted.org/packages/2c/60/192b378bd9d1ae67716b71ae63c3e97c48b134aad7675915a10853a0b7de/aiohttp-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0f449a50cc33f0384f633894d8d3cd020e3ccef81879c6e6245c3c375c448625", size = 1374875 }, + { url = "https://files.pythonhosted.org/packages/e0/d7/cd58bd17f5277d9cc32ecdbb0481ca02c52fc066412de413aa01268dc9b4/aiohttp-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82052be3e6d9e0c123499127782a01a2b224b8af8c62ab46b3f6197035ad94e9", size = 1285626 }, + { url = "https://files.pythonhosted.org/packages/bb/b2/da4953643b7dcdcd29cc99f98209f3653bf02023d95ce8a8fd57ffba0f15/aiohttp-3.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20063c7acf1eec550c8eb098deb5ed9e1bb0521613b03bb93644b810986027ac", size = 1246120 }, + { url = "https://files.pythonhosted.org/packages/6c/22/1217b3c773055f0cb172e3b7108274a74c0fe9900c716362727303931cbb/aiohttp-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:489cced07a4c11488f47aab1f00d0c572506883f877af100a38f1fedaa884c3a", size = 1265177 }, + { url = "https://files.pythonhosted.org/packages/63/5e/3827ad7e61544ed1e73e4fdea7bb87ea35ac59a362d7eb301feb5e859780/aiohttp-3.10.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea9b3bab329aeaa603ed3bf605f1e2a6f36496ad7e0e1aa42025f368ee2dc07b", size = 1257238 }, + { url = "https://files.pythonhosted.org/packages/53/31/951f78751d403da6086b662760e6e8b08201b0dcf5357969f48261b4d0e1/aiohttp-3.10.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ca117819d8ad113413016cb29774b3f6d99ad23c220069789fc050267b786c16", size = 1315944 }, + { url = "https://files.pythonhosted.org/packages/0d/79/06ef7a2a69880649261818b135b245de5a4e89fed5a6987c8645428563fc/aiohttp-3.10.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2dfb612dcbe70fb7cdcf3499e8d483079b89749c857a8f6e80263b021745c730", size = 1332065 }, + { url = "https://files.pythonhosted.org/packages/10/39/a273857c2d0bbf2152a4201fbf776931c2dac74aa399c6683ed4c286d1d1/aiohttp-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9b615d3da0d60e7d53c62e22b4fd1c70f4ae5993a44687b011ea3a2e49051b8", size = 1291882 }, + { url = "https://files.pythonhosted.org/packages/49/39/7aa387f88403febc96e0494101763afaa14d342109329a01b413b2bac075/aiohttp-3.10.11-cp311-cp311-win32.whl", hash = "sha256:29103f9099b6068bbdf44d6a3d090e0a0b2be6d3c9f16a070dd9d0d910ec08f9", size = 363409 }, + { url = "https://files.pythonhosted.org/packages/6f/e9/8eb3dc095ce48499d867ad461d02f1491686b79ad92e4fad4df582f6be7b/aiohttp-3.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:236b28ceb79532da85d59aa9b9bf873b364e27a0acb2ceaba475dc61cffb6f3f", size = 382644 }, + { url = "https://files.pythonhosted.org/packages/01/16/077057ef3bd684dbf9a8273a5299e182a8d07b4b252503712ff8b5364fd1/aiohttp-3.10.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7480519f70e32bfb101d71fb9a1f330fbd291655a4c1c922232a48c458c52710", size = 584830 }, + { url = "https://files.pythonhosted.org/packages/2c/cf/348b93deb9597c61a51b6682e81f7c7d79290249e886022ef0705d858d90/aiohttp-3.10.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f65267266c9aeb2287a6622ee2bb39490292552f9fbf851baabc04c9f84e048d", size = 397090 }, + { url = "https://files.pythonhosted.org/packages/70/bf/903df5cd739dfaf5b827b3d8c9d68ff4fcea16a0ca1aeb948c9da30f56c8/aiohttp-3.10.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7400a93d629a0608dc1d6c55f1e3d6e07f7375745aaa8bd7f085571e4d1cee97", size = 392361 }, + { url = "https://files.pythonhosted.org/packages/fb/97/e4792675448a2ac5bd56f377a095233b805dd1315235c940c8ba5624e3cb/aiohttp-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f34b97e4b11b8d4eb2c3a4f975be626cc8af99ff479da7de49ac2c6d02d35725", size = 1309839 }, + { url = "https://files.pythonhosted.org/packages/96/d0/ba19b1260da6fbbda4d5b1550d8a53ba3518868f2c143d672aedfdbc6172/aiohttp-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e7b825da878464a252ccff2958838f9caa82f32a8dbc334eb9b34a026e2c636", size = 1348116 }, + { url = "https://files.pythonhosted.org/packages/b3/b9/15100ee7113a2638bfdc91aecc54641609a92a7ce4fe533ebeaa8d43ff93/aiohttp-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f92a344c50b9667827da308473005f34767b6a2a60d9acff56ae94f895f385", size = 1391402 }, + { url = "https://files.pythonhosted.org/packages/c5/36/831522618ac0dcd0b28f327afd18df7fb6bbf3eaf302f912a40e87714846/aiohttp-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f1ab987a27b83c5268a17218463c2ec08dbb754195113867a27b166cd6087", size = 1304239 }, + { url = "https://files.pythonhosted.org/packages/60/9f/b7230d0c48b076500ae57adb717aa0656432acd3d8febb1183dedfaa4e75/aiohttp-3.10.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1dc0f4ca54842173d03322793ebcf2c8cc2d34ae91cc762478e295d8e361e03f", size = 1256565 }, + { url = "https://files.pythonhosted.org/packages/63/c2/35c7b4699f4830b3b0a5c3d5619df16dca8052ae8b488e66065902d559f6/aiohttp-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7ce6a51469bfaacff146e59e7fb61c9c23006495d11cc24c514a455032bcfa03", size = 1269285 }, + { url = "https://files.pythonhosted.org/packages/51/48/bc20ea753909bdeb09f9065260aefa7453e3a57f6a51f56f5216adc1a5e7/aiohttp-3.10.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aad3cd91d484d065ede16f3cf15408254e2469e3f613b241a1db552c5eb7ab7d", size = 1276716 }, + { url = "https://files.pythonhosted.org/packages/0c/7b/a8708616b3810f55ead66f8e189afa9474795760473aea734bbea536cd64/aiohttp-3.10.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f4df4b8ca97f658c880fb4b90b1d1ec528315d4030af1ec763247ebfd33d8b9a", size = 1315023 }, + { url = "https://files.pythonhosted.org/packages/2a/d6/dfe9134a921e05b01661a127a37b7d157db93428905450e32f9898eef27d/aiohttp-3.10.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e4e18a0a2d03531edbc06c366954e40a3f8d2a88d2b936bbe78a0c75a3aab3e", size = 1342735 }, + { url = "https://files.pythonhosted.org/packages/ca/1a/3bd7f18e3909eabd57e5d17ecdbf5ea4c5828d91341e3676a07de7c76312/aiohttp-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ce66780fa1a20e45bc753cda2a149daa6dbf1561fc1289fa0c308391c7bc0a4", size = 1302618 }, + { url = "https://files.pythonhosted.org/packages/cf/51/d063133781cda48cfdd1e11fc8ef45ab3912b446feba41556385b3ae5087/aiohttp-3.10.11-cp312-cp312-win32.whl", hash = "sha256:a919c8957695ea4c0e7a3e8d16494e3477b86f33067478f43106921c2fef15bb", size = 360497 }, + { url = "https://files.pythonhosted.org/packages/55/4e/f29def9ed39826fe8f85955f2e42fe5cc0cbe3ebb53c97087f225368702e/aiohttp-3.10.11-cp312-cp312-win_amd64.whl", hash = "sha256:b5e29706e6389a2283a91611c91bf24f218962717c8f3b4e528ef529d112ee27", size = 380577 }, + { url = "https://files.pythonhosted.org/packages/1f/63/654c185dfe3cf5d4a0d35b6ee49ee6ca91922c694eaa90732e1ba4b40ef1/aiohttp-3.10.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:703938e22434d7d14ec22f9f310559331f455018389222eed132808cd8f44127", size = 577381 }, + { url = "https://files.pythonhosted.org/packages/4e/c4/ee9c350acb202ba2eb0c44b0f84376b05477e870444192a9f70e06844c28/aiohttp-3.10.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9bc50b63648840854e00084c2b43035a62e033cb9b06d8c22b409d56eb098413", size = 393289 }, + { url = "https://files.pythonhosted.org/packages/3d/7c/30d161a7e3b208cef1b922eacf2bbb8578b7e5a62266a6a2245a1dd044dc/aiohttp-3.10.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f0463bf8b0754bc744e1feb61590706823795041e63edf30118a6f0bf577461", size = 388859 }, + { url = "https://files.pythonhosted.org/packages/79/10/8d050e04be447d3d39e5a4a910fa289d930120cebe1b893096bd3ee29063/aiohttp-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6c6dec398ac5a87cb3a407b068e1106b20ef001c344e34154616183fe684288", size = 1280983 }, + { url = "https://files.pythonhosted.org/packages/31/b3/977eca40afe643dcfa6b8d8bb9a93f4cba1d8ed1ead22c92056b08855c7a/aiohttp-3.10.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcaf2d79104d53d4dcf934f7ce76d3d155302d07dae24dff6c9fffd217568067", size = 1317132 }, + { url = "https://files.pythonhosted.org/packages/1a/43/b5ee8e697ed0f96a2b3d80b3058fa7590cda508e9cd256274246ba1cf37a/aiohttp-3.10.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25fd5470922091b5a9aeeb7e75be609e16b4fba81cdeaf12981393fb240dd10e", size = 1362630 }, + { url = "https://files.pythonhosted.org/packages/28/20/3ae8e993b2990fa722987222dea74d6bac9331e2f530d086f309b4aa8847/aiohttp-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbde2ca67230923a42161b1f408c3992ae6e0be782dca0c44cb3206bf330dee1", size = 1276865 }, + { url = "https://files.pythonhosted.org/packages/02/08/1afb0ab7dcff63333b683e998e751aa2547d1ff897b577d2244b00e6fe38/aiohttp-3.10.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:249c8ff8d26a8b41a0f12f9df804e7c685ca35a207e2410adbd3e924217b9006", size = 1230448 }, + { url = "https://files.pythonhosted.org/packages/c6/fd/ccd0ff842c62128d164ec09e3dd810208a84d79cd402358a3038ae91f3e9/aiohttp-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:878ca6a931ee8c486a8f7b432b65431d095c522cbeb34892bee5be97b3481d0f", size = 1244626 }, + { url = "https://files.pythonhosted.org/packages/9f/75/30e9537ab41ed7cb062338d8df7c4afb0a715b3551cd69fc4ea61cfa5a95/aiohttp-3.10.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8663f7777ce775f0413324be0d96d9730959b2ca73d9b7e2c2c90539139cbdd6", size = 1243608 }, + { url = "https://files.pythonhosted.org/packages/c2/e0/3e7a62d99b9080793affddc12a82b11c9bc1312916ad849700d2bddf9786/aiohttp-3.10.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6cd3f10b01f0c31481fba8d302b61603a2acb37b9d30e1d14e0f5a58b7b18a31", size = 1286158 }, + { url = "https://files.pythonhosted.org/packages/71/b8/df67886802e71e976996ed9324eb7dc379e53a7d972314e9c7fe3f6ac6bc/aiohttp-3.10.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e8d8aad9402d3aa02fdc5ca2fe68bcb9fdfe1f77b40b10410a94c7f408b664d", size = 1313636 }, + { url = "https://files.pythonhosted.org/packages/3c/3b/aea9c3e70ff4e030f46902df28b4cdf486695f4d78fd9c6698827e2bafab/aiohttp-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38e3c4f80196b4f6c3a85d134a534a56f52da9cb8d8e7af1b79a32eefee73a00", size = 1273772 }, + { url = "https://files.pythonhosted.org/packages/e9/9e/4b4c5705270d1c4ee146516ad288af720798d957ba46504aaf99b86e85d9/aiohttp-3.10.11-cp313-cp313-win32.whl", hash = "sha256:fc31820cfc3b2863c6e95e14fcf815dc7afe52480b4dc03393c4873bb5599f71", size = 358679 }, + { url = "https://files.pythonhosted.org/packages/28/1d/18ef37549901db94717d4389eb7be807acbfbdeab48a73ff2993fc909118/aiohttp-3.10.11-cp313-cp313-win_amd64.whl", hash = "sha256:4996ff1345704ffdd6d75fb06ed175938c133425af616142e7187f28dc75f14e", size = 378073 }, +] + +[[package]] +name = "aiosignal" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597 }, +] + +[[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 = "ansible" +version = "10.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", +] +dependencies = [ + { name = "ansible-core", version = "2.17.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/64/29fdff6fe7682342adb54802c1cd90b2272d382e1743089af88f90a1d986/ansible-10.7.0.tar.gz", hash = "sha256:59d29e3de1080e740dfa974517d455217601b16d16880314d9be26145c68dc22", size = 41256974 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/95/cb8944902a2cdd94b1e19ff73695548679a388b9c473dc63c8dc64ffea3a/ansible-10.7.0-py3-none-any.whl", hash = "sha256:0089f08e047ceb70edd011be009f5c6273add613fbe491e9697c0556c989d8ea", size = 51576038 }, +] + +[[package]] +name = "ansible" +version = "11.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", +] +dependencies = [ + { name = "ansible-core", version = "2.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/54/dc559b305948e9c234f79ef00f7aed52d7c127c8616c0c2f3f336103ccdd/ansible-11.1.0.tar.gz", hash = "sha256:d01b425990d960d2a33fc378e1b73dbca1c0e28bc22f4056ab6b3c8e9ae74fba", size = 41299850 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/75/4c0583e76a3c2eac0c8d878568fd18c8a66f3da8888dca712cae14b195ac/ansible-11.1.0-py3-none-any.whl", hash = "sha256:bbaf7073993f019fc0293fc8b76c7b215081831957c28eb020f12c270a16e8f0", size = 51414573 }, +] + +[[package]] +name = "ansible-core" +version = "2.17.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", +] +dependencies = [ + { name = "cryptography", marker = "python_full_version < '3.11'" }, + { name = "jinja2", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pyyaml", marker = "python_full_version < '3.11'" }, + { name = "resolvelib", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/f3/3621360fee8c4929aa82c62acedab1baaf3f649e435c5dd56a6c5465ceb0/ansible_core-2.17.7.tar.gz", hash = "sha256:3aaab735d6c4e2d6239bc326800dc0ecda2a1490caa8455b41084ec0bc54dacf", size = 3104429 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/4b/e176f8ae78fdff0e2e2d593dabcd5e8fb31cf94b1f655778666c91f91241/ansible_core-2.17.7-py3-none-any.whl", hash = "sha256:64d4f0a006687a5621aa80dca54fd0c5ae75145b7aac8c1b8d7f07a1399c4705", size = 2196119 }, +] + +[[package]] +name = "ansible-core" +version = "2.18.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", +] +dependencies = [ + { name = "cryptography", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "python_full_version >= '3.11'" }, + { name = "resolvelib", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/46/15836f1f48e2682bd5d04f7b3e2cb27e17626bec3cd7a4f2a7b0ccefbbd2/ansible_core-2.18.1.tar.gz", hash = "sha256:14cac1f92bbdae881cb0616eddeb17925e8cb507e486087975e724533d9de74f", size = 3069965 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/24/dd43ac6d09c8adab1368171853989bf0c84a653a35f57eaeee0411e91bb7/ansible_core-2.18.1-py3-none-any.whl", hash = "sha256:4a312e416e09c7271188d6b8e2b1062fc6834fefd6a1814d0e02fb8aadb3e1ba", size = 2214624 }, +] + +[[package]] +name = "ansible-vault" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ansible", version = "10.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "ansible", version = "11.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/68/968aea6bc9894fb0e19aeaa03f6b284b573b5815f4f3d3a9daa9e519c3df/ansible-vault-2.1.0.tar.gz", hash = "sha256:5ce8fdb5470f1449b76bf07ae2abc56480dad48356ae405c85b686efb64dbd5e", size = 3519 } + +[[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 = "async-property" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/12/900eb34b3af75c11b69d6b78b74ec0fd1ba489376eceb3785f787d1a0a1d/async_property-0.2.2.tar.gz", hash = "sha256:17d9bd6ca67e27915a75d92549df64b5c7174e9dc806b30a3934dc4ff0506380", size = 16523 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/80/9f608d13b4b3afcebd1dd13baf9551c95fc424d6390e4b1cfd7b1810cd06/async_property-0.2.2-py2.py3-none-any.whl", hash = "sha256:8924d792b5843994537f8ed411165700b27b2bd966cefc4daeefc1253442a9d7", size = 9546 }, +] + +[[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 = "attrs" +version = "24.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/48/c8/6260f8ccc11f0917360fc0da435c5c9c7504e3db174d5a12a1494887b045/attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", size = 805984 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 }, +] + +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148 }, +] + +[[package]] +name = "base58" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/45/8ae61209bb9015f516102fa559a2914178da1d5868428bd86a1b4421141d/base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c", size = 6528 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/45/ec96b29162a402fc4c1c5512d114d7b3787b9d1c2ec241d9568b4816ee23/base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2", size = 5621 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 }, +] + +[[package]] +name = "bert-score" +version = "0.3.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "requests" }, + { name = "torch" }, + { name = "tqdm" }, + { name = "transformers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/93/2c97a85cbb66a8a256a13176e11c9c4508074e2341299fe75ee955c81eff/bert_score-0.3.13.tar.gz", hash = "sha256:8ffe5838eac8cdd988b8b1a896af7f49071188c8c011a1ed160d71a9899a2ba4", size = 48621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/8c/bc5457de4c004b1a623b31f7bc8d0375fb699b7d67df11879098b4b7b7c8/bert_score-0.3.13-py3-none-any.whl", hash = "sha256:bbbb4c7fcdaa46d7681aff49f37f96faa09ed74e1b150e659bdc6b58a66989b9", size = 61135 }, +] + +[[package]] +name = "bittensor" +version = "8.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "async-property" }, + { name = "bittensor-cli" }, + { name = "bittensor-commit-reveal" }, + { name = "bittensor-wallet" }, + { name = "bt-decode" }, + { name = "colorama" }, + { name = "fastapi" }, + { name = "msgpack-numpy-opentensor" }, + { name = "munch" }, + { name = "nest-asyncio" }, + { name = "netaddr" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pycryptodome" }, + { name = "pydantic" }, + { name = "python-levenshtein" }, + { name = "python-statemachine" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "retry" }, + { name = "rich" }, + { name = "scalecodec" }, + { name = "setuptools" }, + { name = "substrate-interface" }, + { name = "uvicorn" }, + { name = "websockets" }, + { name = "wheel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/b2/09ec0664822d32fcf2f708742c69249f8a70a09fb941cef3a1f4ab8eca02/bittensor-8.5.1.tar.gz", hash = "sha256:f1bb033ba1e2641881d37f9d8cfebdcb7145ae20975861863710bdd17941cce4", size = 210235 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/34/29f5b570d734b8474b21071756db41b4717c8cbebc3fa5716f0c499a12e8/bittensor-8.5.1-py3-none-any.whl", hash = "sha256:8dbf9c389d10fd043dab5da163377a43ec2ae1b1715e819a3602e07d36304f94", size = 257860 }, +] + +[[package]] +name = "bittensor-cli" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "async-property" }, + { name = "backoff" }, + { name = "bittensor-wallet" }, + { name = "bt-decode" }, + { name = "fuzzywuzzy" }, + { name = "gitpython" }, + { name = "jinja2" }, + { name = "netaddr" }, + { name = "numpy" }, + { name = "pycryptodome" }, + { name = "pytest" }, + { name = "python-levenshtein" }, + { name = "pyyaml" }, + { name = "rich" }, + { name = "scalecodec" }, + { name = "substrate-interface" }, + { name = "typer" }, + { name = "websockets" }, + { name = "wheel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/77/a4250b215d7a5dabef2090c8b300843aa962a135a85b8af0ca499b15a23e/bittensor-cli-8.4.2.tar.gz", hash = "sha256:43efc081ed2ecf4357bf5c5322ccd6f7d1a5110eb842cf138c75adb3f21686fd", size = 161405 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/7b/3f43e1e453697aba95734f94a44aeb96d72139a2ca83837ecaf170a58de2/bittensor_cli-8.4.2-py3-none-any.whl", hash = "sha256:e7fc5ff510f039fa0cb9c0c701a56c4eb2b644befb019b1cd0fac29546bfb764", size = 171700 }, +] + +[[package]] +name = "bittensor-commit-reveal" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/93/f6361d6d617f1620f1b642308384d7f22c7917c169b821ddb3a90856a0c9/bittensor_commit_reveal-0.1.0.tar.gz", hash = "sha256:1c8bb8d77f6279988902c5c28361cc460167829c63ffa8d788209f8810933211", size = 23249 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/53/77d8490726b253e06a9c29a89e3a117494c73a02e05230bbb8f9fa157a58/bittensor_commit_reveal-0.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e628b052ebdbd6893eb996a0d03b4c5e0823e42ee410ff5066018700a35539a", size = 711939 }, + { url = "https://files.pythonhosted.org/packages/56/7a/6cc644423a9e0ac13327b3ff38b168042f85406151e436f8f67485a5be04/bittensor_commit_reveal-0.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:245a7e0defb79f6a45c243f72739ee5c58840cba51342d28322c6d7a73f475b9", size = 552388 }, + { url = "https://files.pythonhosted.org/packages/2d/cf/fcc202fb07594933f759287ceea9e891cbb8ce779f24cc84311af2b50802/bittensor_commit_reveal-0.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2bb23935ac60a981bfb3d83397b83e858c0b69a11806969cf56486f5ebc90943", size = 493021 }, + { url = "https://files.pythonhosted.org/packages/c6/bd/0e438e505036fda9370f352dc9a8f1ff7fa777a8b07479b9874f5742e7b4/bittensor_commit_reveal-0.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4917b215c24b10bd80c84db921113b9cd1346ca7dcaca75e286905ede81a3b18", size = 493236 }, + { url = "https://files.pythonhosted.org/packages/2b/7a/cded935634bf0a077e8f7454b47164e1b3e45064234eaf9722e6a35c1cbf/bittensor_commit_reveal-0.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c46cee3e5fa5fc9e6f6a444793062855f40495c1a00b52df6508e4449ac5e89f", size = 711674 }, + { url = "https://files.pythonhosted.org/packages/06/ab/ea0f20581a786ec4b497bdaab8fb4a046c81d125820fc1ec4bfe79854f96/bittensor_commit_reveal-0.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56407b879dcf82bdde5eaefede43c8891e122fefc03a32c77a063dfc52e0c8", size = 552162 }, + { url = "https://files.pythonhosted.org/packages/a8/3a/7705ea18c3d61c8affc4696b8ab483bdb7e3d0bfdfb61ca1583a787ef1e0/bittensor_commit_reveal-0.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8509250549b6f5c475a9150e941b28fc66e82f30b27fe078fd80fa840943bb7b", size = 491259 }, + { url = "https://files.pythonhosted.org/packages/80/21/02b400750c7d1d5ed081dc22c740e21e22fd72fbb18b72517d5687eca8bd/bittensor_commit_reveal-0.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bed04f82f162121747cfd7f51bb5d625dda0bf763a0699054565f255d219a9c2", size = 492612 }, + { url = "https://files.pythonhosted.org/packages/9a/82/bf02fda4c7bfbe6830709476cf1893ad4e7b591c4e1f62eab2abbfcd0106/bittensor_commit_reveal-0.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25af2d9c82cacc4278095460493430d36070cb2843c0aa54b1c563788d0742eb", size = 712159 }, + { url = "https://files.pythonhosted.org/packages/31/d1/7e41e52251c277bf0bebe0fcb3f700e6faf6a488c9cefa8b8fb2bae42cee/bittensor_commit_reveal-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8f530793274698aaf4ac7cc8f24e915749d8156df8302c9e1e16446177b429d", size = 551180 }, + { url = "https://files.pythonhosted.org/packages/fa/20/272b35206c52db8b385ff7f2a6579ca700fa996c147e4533cd4d323446a7/bittensor_commit_reveal-0.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e52955d652b61f091310e2b9b232d26b9e586a928e3b414a09a1b6615e9cc7a0", size = 491231 }, + { url = "https://files.pythonhosted.org/packages/ee/05/02329c66db0970569a31779c0effcee67a1f6bb20a12ccbd667123d89f3f/bittensor_commit_reveal-0.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7be8c8f79dea2e137f5add6ee4711447c4f5d43668be26616ab7c1cacf317e07", size = 492469 }, + { url = "https://files.pythonhosted.org/packages/f4/47/ca9a347273e6993b8775a2a04e9d3df5569aaab46dc95247bf0c1f1b5ea1/bittensor_commit_reveal-0.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b88ecb6a0989c2200486e29419a8e7d3b3f7918bdbde4ec04dbb4464abdee08f", size = 711920 }, + { url = "https://files.pythonhosted.org/packages/fe/87/cbef0fa4b4d3159030d61d09da5a09181c0ca8f25bbb451437cb50627ac7/bittensor_commit_reveal-0.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac015f9eefa9dbddd2875cd7214e3a0bc2e394a2915772e655bdcc5c0af67de", size = 551137 }, + { url = "https://files.pythonhosted.org/packages/11/a4/98ad5a74461960ec4c33334fd65c9445b38a57f2a07a2b3805355cf7039e/bittensor_commit_reveal-0.1.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df0ef502f07804bff843a7e6318f95b9a9ca5bcc9c75e501040202b38d09d8b5", size = 712315 }, + { url = "https://files.pythonhosted.org/packages/be/7e/a4381b45757a67fc61cf7cf16b17c235f64962013d692a78c777140330a9/bittensor_commit_reveal-0.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8706ae43e3455913d210c7ab5fc0110595e45b8fa3964441e2842fc7975aec3", size = 553449 }, +] + +[[package]] +name = "bittensor-wallet" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "eth-utils" }, + { name = "munch" }, + { name = "password-strength" }, + { name = "py-bip39-bindings" }, + { name = "rich" }, + { name = "substrate-interface" }, + { name = "termcolor" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/17/38a9ec85be2167dd2c1aa2e75f0ac7c25ccf7c31859fe9b0d325b474fbbb/bittensor_wallet-2.1.3.tar.gz", hash = "sha256:41927d7e5d68fff1494cef5abd861ede0afc684dff366824b0806cfa3ce13af0", size = 70285 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/40/52e054e1d15901a9eae659a72bd6aaf8d34e9b0a682115b83d937fb8d74c/bittensor_wallet-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07462933ace69992079013ff7497c5f67e94b5d1adbada4ff08c5b18ebc18afe", size = 3146662 }, + { url = "https://files.pythonhosted.org/packages/18/3c/5fd63fd5fcb72629cf705921bd00ff9e8b0382129232f08ceb0bd0a596f5/bittensor_wallet-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:23110aca2d8f3e58c0b7c7bb58a74a66227aea85b30e4fa3eb616f5a13a0f659", size = 2953456 }, + { url = "https://files.pythonhosted.org/packages/62/5d/3b4a4ed5e4d4bbc3575001455dfd5631620147e65ab07f3f3a31891ea56a/bittensor_wallet-2.1.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a5199c84e9d33ccec451294f89d9354b61568a0b623ceee995f588ccdc14ea5c", size = 800061 }, + { url = "https://files.pythonhosted.org/packages/97/7c/8f55e5dfda6c28a74a63ca60cd4d9e860bb798da5e58ea4b88eead124f38/bittensor_wallet-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a34e524f21e8c7bd8edfd54db530480b81f48d2334a0a11b86ea22d9e349137c", size = 752208 }, + { url = "https://files.pythonhosted.org/packages/07/5b/bf271ddda747244ff044d8f7e21e30ff684f24d0a5447662cc020c3c301c/bittensor_wallet-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45a1556e02304e1e8e91059cc11bb8346fa2334ac039f79bb1e6f630fa26657f", size = 3146730 }, + { url = "https://files.pythonhosted.org/packages/c2/97/a74c138b92db1d455d2be371cea3777616fc6cb94ac401cecddd27e4d9d4/bittensor_wallet-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9399c753c37dbe63430c5aff4fba0a038e0349dde0061d623506a24e3b4d2cec", size = 2953376 }, + { url = "https://files.pythonhosted.org/packages/a7/b0/a803fb7abe4b004464d67f6812f5067ee0346e7ba0bfb1e3012f569261cd/bittensor_wallet-2.1.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e2f0d03a21a0c54b1f8cd59f34941d7a60df490e9aab7d7776b03f290de6074", size = 797657 }, + { url = "https://files.pythonhosted.org/packages/24/35/506d88aed623872fe4ecbcc2d6484ac864dc2c639ef8810141628fd28763/bittensor_wallet-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24c446b0af4c9ffc3ac122f97a1de25b283c877aa49892748ad06a8a61a74e13", size = 752425 }, + { url = "https://files.pythonhosted.org/packages/eb/37/c6feb7d6ac75c24bfe170ffabbd42f2d91bc34cc75b99575f2417ec486b1/bittensor_wallet-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eafd9c82720644b3eeac2f68deaa9cec4cf175836b16c89206d98ce22590e8e", size = 3146851 }, + { url = "https://files.pythonhosted.org/packages/8e/63/0dfe52c8c4c7d943d3ca2f52530039e1ee0dbdbffb3d16a90d770725b9bd/bittensor_wallet-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f5122b05d8eede2bfc2eb242214b75ecab08f0da5d4f7547ed01ad253349e019", size = 2954118 }, + { url = "https://files.pythonhosted.org/packages/ad/81/670424362f512f96760694839cd44a1d4aa6401d5e1c93ff1bf37f3a3653/bittensor_wallet-2.1.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:88020b18aa2f91b336a6f04354b7acb124701f9678d74e41f5ffb64a7e1e5731", size = 797707 }, + { url = "https://files.pythonhosted.org/packages/e8/de/81744fd99af5339aa196c4c5e559ae3d2dd773d8fc1e39059fd651982b4b/bittensor_wallet-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb7dd2ed4c12e617574b7302a6c20fb8e915477ce2942627f624293b5de9a003", size = 752028 }, + { url = "https://files.pythonhosted.org/packages/41/3c/309505722c2390337d417c17cc50040ddcbdaee03cc8fc664a34320f777a/bittensor_wallet-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de47dea7d283e83449465f9780d4dde608fe09da45d6ef8c795806e49ccf4fd2", size = 3145919 }, + { url = "https://files.pythonhosted.org/packages/bc/3f/e973420941b0d0b23d944fd60cd95c3bbbca38f5c582d83409f6243880fa/bittensor_wallet-2.1.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e35adc5303b2186df889e07c79bf0bc074df382df49e6c216a8feb27f00453a4", size = 2953541 }, + { url = "https://files.pythonhosted.org/packages/74/f8/91130b3bc6f4def2f08b5bda47b6db8fa65d687ca01f5481f2c67806dbdd/bittensor_wallet-2.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e1ee5be2b8b4c8fa36bc1750da723152dd7c96c4e606121146913adf83cf667", size = 3147414 }, + { url = "https://files.pythonhosted.org/packages/04/f9/dde7ca4ae4ca864345d12447e6f6aae479a238d74762f2b6e9a8edfc90cb/bittensor_wallet-2.1.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b879438249fc70dc2f7b8b579566c526dde68c2baa31d12ee3c4fcd4087f7b9", size = 2955324 }, +] + +[[package]] +name = "bt-decode" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "toml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/a9/7449c1073af4ef57520fc01e587a664591ff0331b694a3ec9c1aff3c3133/bt_decode-0.4.0.tar.gz", hash = "sha256:5c7e6286a4f8b9b704f6a0c263ce0e8854fb95d94da5dff6e8835be6de04d508", size = 3496621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/34/f151606685d868dffec3b02b1086a3ea6bf7f30e7193d0462be351e4c1d6/bt_decode-0.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c176595c23f3d9a632b8a4fe71f8ed74e05be0ff4d447719eab3de686699c6b", size = 603411 }, + { url = "https://files.pythonhosted.org/packages/04/2b/b9cc3583a3a48b0d3fd1bf58247a607838366868d6e526243e8e9c9fd98d/bt_decode-0.4.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a5232cc226d7c537303691dbb27c5c734cabcf51e6c74d641d1721a2d3a119c", size = 600803 }, + { url = "https://files.pythonhosted.org/packages/1c/88/4734639827b1cecb66d698828f4c4d43127f3087fa343b1a03b807897d9b/bt_decode-0.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93dfa1c342a6fb3cbd199b46f511951174503c8405854de484390776ff94228a", size = 669779 }, + { url = "https://files.pythonhosted.org/packages/b7/d2/1924c3de6bada823c7640cc134a762b859280f2fd911a83dc4f86161f7e3/bt_decode-0.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17f6f94d3dee3d9c9909e936b57bc87acef29de9b1b8d4157efd806bc7ff3eee", size = 708492 }, + { url = "https://files.pythonhosted.org/packages/de/f8/07a88e0373a70bc15dd54514ce1ef4b2b8a51723fd1c32371eebb8241931/bt_decode-0.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba2d5f8ef69dde9880db38e45beb4ed965868d660f8de68d8cc7838d6b244295", size = 613639 }, + { url = "https://files.pythonhosted.org/packages/e1/d6/0636e4605376f6145470612a14b6d8a9183705daf6ff3071c266a10658d3/bt_decode-0.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f76a6949edbb7bc9a095f1a732974db04ec39c671e188ee001998901b6cd460", size = 663996 }, + { url = "https://files.pythonhosted.org/packages/b3/97/83ec9bf604fb9086c05a9fa10f5de2ffb470b22322afec421110ade29cd8/bt_decode-0.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6df00582855bc84c1cbb4f7f63900097b456a43fd92fd397466c85943c5ba9f2", size = 781163 }, + { url = "https://files.pythonhosted.org/packages/c5/35/011d29e2e6bbb03d802306535b758d7ac1fff35aea187f7b7f38c995c135/bt_decode-0.4.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:67547de47eb41026f3ec106f2681c45e34fc5d610dd462cbcca9885bf7581af5", size = 861838 }, + { url = "https://files.pythonhosted.org/packages/91/a1/ade95fe5db1cc1a5edc21f61780e41df075e55f2248c2c4fd7bf17e88cf8/bt_decode-0.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fb100ff9d8688c1e5dd98f7aa721279f267408cf7079d8f2ca9ea1abd6c0edfc", size = 819408 }, + { url = "https://files.pythonhosted.org/packages/cd/66/14bae1a496796761121bfaeeb1b934505b531101e5505f11c25279b7e9bd/bt_decode-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:66b599c2af3a7a3f40af22fa3e6304bde56237242120cb37253e4a465dfd419c", size = 784031 }, + { url = "https://files.pythonhosted.org/packages/ac/9b/0e1488e5f8dc7c744525732a40ede44dbb2df5d6449040773e305b3c9b96/bt_decode-0.4.0-cp310-cp310-win32.whl", hash = "sha256:0635af47f0abd4a1c1d9566fb101c4b851c2499a8f8b53e37a496efcd69409da", size = 389613 }, + { url = "https://files.pythonhosted.org/packages/75/c4/cf51cf92b5aa6f9272dafb43e639a2d1f2311475a4838f57e48be550cddc/bt_decode-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:01421093b5e97751624de0113fb3da7fb50a1d70c883887555e73abff081ffcc", size = 416377 }, + { url = "https://files.pythonhosted.org/packages/ad/53/43502e90c428e0ff4946112349a6072a52b3c0e73f770284f1c530f5ad53/bt_decode-0.4.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e2dd446b5956c3c772cdcbfe08fe0d483e68dc07b1606cde5d39c689dffd736c", size = 561621 }, + { url = "https://files.pythonhosted.org/packages/64/f2/a869f4d3bf750a2247a10028b7523e12ba9c62fad072fc88741e64d42236/bt_decode-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcbb0fb758460c5fe7e5276b4406dd15d22ff544d309dd4ebb8fc998ce30d51f", size = 547050 }, + { url = "https://files.pythonhosted.org/packages/b8/c0/d6295ccf4c83dc4b10a19c54a116939a0935350b182d55abf86a36cae7aa/bt_decode-0.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:816f45a75dc78d6beafaf7cc02ab51d73a3dd1c91d4ba0e6b43aae3c637d793d", size = 603391 }, + { url = "https://files.pythonhosted.org/packages/e9/c0/457f63f087b0072e877582e61fac115218b28902df5d9c62d60a42c899d5/bt_decode-0.4.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:39d44102ea27a23644c262d98378ac0ac650e481508f5d6989b8b4e3fd638faf", size = 600597 }, + { url = "https://files.pythonhosted.org/packages/3b/2d/e90271fa86038fcace7eb544923422d91ae36ebf8627291c84ec05d9d22c/bt_decode-0.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:82e959521c60bc48276a91a01bd97726820128a4f4670ae043da35ca11823ca3", size = 669588 }, + { url = "https://files.pythonhosted.org/packages/c0/3e/5d8be99d4d1b3193f526ba12e64fb8c0132511c19859def040f19cdcd2d5/bt_decode-0.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bdea70a4b83e46432999f7743d130dbd49ccf1974c87c87153f7ad3733f5ccea", size = 707978 }, + { url = "https://files.pythonhosted.org/packages/0e/de/2757cab0397594e8547c897696c0983d067c758b1d3ad9cfb944e401bde2/bt_decode-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99b6cc694fe05037c1dca02111d25b2357fd460bea8d8ce9b2432e3ed1d049c", size = 613663 }, + { url = "https://files.pythonhosted.org/packages/7c/15/c0d12ac696b7472f63bb32c61b4b94d75298311840ba315a76b9e2c9a5aa/bt_decode-0.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:645e82838b2e8d7b03686f5cee44e880c56bed3a9dbf2a530c818d1a63544967", size = 664223 }, + { url = "https://files.pythonhosted.org/packages/28/99/c6199f74f1f36279ced846c32d03f245b0d4d8fd2ae1b22842f6cfc4623d/bt_decode-0.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cb32f5c5fda6cada107e3d82b5d760c87cd49075f28105de0900e495ee211659", size = 781056 }, + { url = "https://files.pythonhosted.org/packages/a1/77/896b5f76f4b10d637ffdfd5645f739f5037ff7e7c871cb874528f2c02c40/bt_decode-0.4.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d2ecb71c8b40f3a4abd9c8fda54febffaa298eceafc12a47e9c0cf93e4ccbb8b", size = 861550 }, + { url = "https://files.pythonhosted.org/packages/5a/ea/d2f0b5c8bc2ac59676aa904b4af040f38730caec73fefd8547aabc4222ae/bt_decode-0.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9b7691207021f023485d5adff6758bc0f938f80cf7e1ca05d291189e869217b5", size = 819734 }, + { url = "https://files.pythonhosted.org/packages/dd/82/f7bd11e8b351d5c560daefe87b8884c6e735e1d3eabcd2919684395fb361/bt_decode-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:912957e7373014acf4203f3a701f4b820d9d7f5bee1f710298d7346f12bcff59", size = 783927 }, + { url = "https://files.pythonhosted.org/packages/36/0c/0818b22b21ac168cfa07a9f7a46ca7676b175b1e65956dc5700d12c7f744/bt_decode-0.4.0-cp311-cp311-win32.whl", hash = "sha256:fb47926e13f39663e62b4105b436abc84b913cb27edd621308f441cb405956ac", size = 389847 }, + { url = "https://files.pythonhosted.org/packages/96/60/94e86a68062d69c42f3409a48143407a67c6c4cfbcd428ab46d10993fd0a/bt_decode-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:001995ff6a20438c5542b13ae0af6458845381ccfd0ef484ae5f7e012c6fb383", size = 416482 }, + { url = "https://files.pythonhosted.org/packages/29/08/090efa626ad7bb545febf8e47a96dd976effcf6c027ff06cf6e053d83104/bt_decode-0.4.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ee9731ecf76ba4f60e10378b16d15bea826b41183ab208e32a9a7fd86d3b7c21", size = 557364 }, + { url = "https://files.pythonhosted.org/packages/6c/53/7e32ff14583db56a9f1ecc2a506a4af9ca6106e2240928d937b0516e0934/bt_decode-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e0ebd9e6f6e710fce9432d448a6add5b266f19af5ec518a2faf19ddd19ce3dc", size = 542812 }, + { url = "https://files.pythonhosted.org/packages/30/39/835655b931dd4b7734743bf66caf28bd94cd5067a8141f6ce22bb8e2de91/bt_decode-0.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd898558c915dd9374a1860c1aee944cd6acb25f8e0f33f58d18eb989c49fab", size = 604124 }, + { url = "https://files.pythonhosted.org/packages/15/8d/0920fcfa46296fb23093d80554cc305d66a0e66d82b392aea8cd70004dc8/bt_decode-0.4.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f87500550b030c3d265ab6847ef25f1e4f756b455605f1977329a665e41b330", size = 600859 }, + { url = "https://files.pythonhosted.org/packages/6a/86/0a709fb430d157d0be29733a66e56ee78f8354b2dfba42a64feeb54d6e42/bt_decode-0.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59fa64d5eff9fcc00f536e3ef74932f40aeff1335bd75a469bce90c1762451ae", size = 669825 }, + { url = "https://files.pythonhosted.org/packages/d4/83/58495d791a8be3ee5064af3d6e4039f11a0b13dd3b30e8c91dc247405f23/bt_decode-0.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2be0732720588d047b00eb87e234dd83ebbdb717da8d704b8930b9ab580a6c3", size = 708326 }, + { url = "https://files.pythonhosted.org/packages/56/be/ac3f35a7c23929c428a705e872f596a86afc0eae76d3276b79872abb2817/bt_decode-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4107e8b75966c5be0822a5f0525b568c94dbc1faa8d928090fa48daa329b45", size = 614048 }, + { url = "https://files.pythonhosted.org/packages/7e/ee/6b16c47b5ac00cd511da91ab762c3d2353ba9983f205e8d47a77419221f5/bt_decode-0.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:46e09e7c557fe753c20226ec4db887a4a1b520d36dc4d01eb5d2bd2e2846970e", size = 664008 }, + { url = "https://files.pythonhosted.org/packages/04/09/97f411183dd7497edcf5f0d6cbbd1ef56655395b18e614e272698a9d6802/bt_decode-0.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e817fe5e805bc393b266909709660dc14bd34a671712da0087e164a760b928b4", size = 781116 }, + { url = "https://files.pythonhosted.org/packages/71/f8/ec920e1713e24462142f55aa85c1ad6969d826e2cb32d583ccc37fa8ddb4/bt_decode-0.4.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:59f9a61789003c345b423f1728ee0d774f89cc41be0ab2af0f2ad6e2653084b5", size = 862290 }, + { url = "https://files.pythonhosted.org/packages/8b/c7/5b0504f14f1b8c9b60c69a080832f53774f30db181e472944260e0cfbf1c/bt_decode-0.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:905715452ecf4ce204aa937ee8266ea539fc085377f92bd9506ec76dcd874347", size = 819695 }, + { url = "https://files.pythonhosted.org/packages/13/9e/5d2953e4416db004d21f6c480657c8f9b84ee27b48fe5478d2cdba2ec49a/bt_decode-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e85f5f12e6bb00253e194372d90e60f129d613f0ddedae659d3b9a3049a69cf", size = 784116 }, + { url = "https://files.pythonhosted.org/packages/7e/b2/26f374ee94c88a90310569bd5d2f282c105a7ee1ae298e0282d3ee560f50/bt_decode-0.4.0-cp312-cp312-win32.whl", hash = "sha256:ed4c3c4383c9903f371502c0d62ce88ecd2c531044e04deaeb60c827ae45ad8e", size = 390937 }, + { url = "https://files.pythonhosted.org/packages/7e/35/0610ddaf739013a3fff13961edadeefff4be83fff7735bc0592214f0246b/bt_decode-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:68beccbb00f129b75d189d2ffc48fd430bf4eab8a456aab79615b17eec82437d", size = 417431 }, + { url = "https://files.pythonhosted.org/packages/6b/2f/4cdfdf8bd52a38e27b50f36e9b9288085a9bab1d703310cc426e4b4243be/bt_decode-0.4.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:88de7129c3323c36cd6cce28844fb475556a865ec6fc87934ec5deeb95ff2d86", size = 557018 }, + { url = "https://files.pythonhosted.org/packages/43/16/7d29d9f719bab8f3890d6d6dfaaade16aa7616e57bdde8f0114781430134/bt_decode-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:056e6245a2119b391306542134651df54df29569136be892411073fc10840c8e", size = 542668 }, + { url = "https://files.pythonhosted.org/packages/c5/d3/a15421174b9943fd86f2470bfe109b6b6a800a2e9cca414b5bb1b2367752/bt_decode-0.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:faa76d0b8fcb0f9ae2107e8c6ae84ea670de81c0adda4967a52d4b7d1de8c605", size = 603689 }, + { url = "https://files.pythonhosted.org/packages/9e/e7/ef333c2c6c2b2319fef3e28ef9d5a2e82c30b8c7f7f3875b182dae7fc957/bt_decode-0.4.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7a3ff15bfe86d482e642dfaa6e5581b65815e7663f337af7502b422fea2fdcc2", size = 600436 }, + { url = "https://files.pythonhosted.org/packages/7c/4c/3bd5c96dcf2ef09d73f0d35cbdc0d32c1b8f9f0c0d9e10af087405f38e7d/bt_decode-0.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa7687c01c516f84274a2e71ba717898eef095e08ec7125823f7a4e230bd46fe", size = 669460 }, + { url = "https://files.pythonhosted.org/packages/81/d7/df22e559dfe7941edfb33357fbc2dc9f6025ae4fb58740213dc09b1dd53b/bt_decode-0.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d3cf8cfff714600db01c6cd144906fe0a8be85293711e279b8089f6ccaffd71", size = 707396 }, + { url = "https://files.pythonhosted.org/packages/2c/19/0d1eeb47ac8844021e6f7f69c92069c0c80ccee1de1614a9e5dac96da50e/bt_decode-0.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:983972ecc83bd0507e72ae316281960b7e26e31386525c7905f7cdb8fa3e7de1", size = 613845 }, + { url = "https://files.pythonhosted.org/packages/a2/06/308512e5f17e3b3a9472d2271114da0caa394c38523b7d0aa5fc75ee3b89/bt_decode-0.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:32e3950b120b8b59ae5ab70005ba9b5c7560a0e222e805f47878cb259a32ed39", size = 663927 }, + { url = "https://files.pythonhosted.org/packages/a4/53/ec4fc237ffe8b8f7e8e4bd78b54b0c82abad5407f3faed7df0828ba2f0f2/bt_decode-0.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66d906ac225e3cd169dde1e0af21e8d73e8ea7dea3f7e9afcdec501bced3d83a", size = 781071 }, + { url = "https://files.pythonhosted.org/packages/d2/d7/700ddb1280e5aafd0404f445847ec6c4c27f7df949a7d148e8dc3c0f5a3f/bt_decode-0.4.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:58bf09b004dc182748e285b5bc15ac6305af4ab9c318f995c443ba33bb61fbb6", size = 862093 }, + { url = "https://files.pythonhosted.org/packages/53/32/c9d9a5787f793da0ac8a9b5c950f45ad8b2449a751cf5b84ab430c2bc9f7/bt_decode-0.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c202f22152b3186cbc1c319250d6b0ecfe87cf9a4e8e90b19cc9f83786acdf1a", size = 819486 }, + { url = "https://files.pythonhosted.org/packages/3f/94/c182bd002357d68d663a118dc41b95d5f400aac6e9e5074c53693b6de41a/bt_decode-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6dd31b0947b7b15a36f7f9bfdb8ae30ffe3f3f97e0dc4d60bf79b9baf57f4e5", size = 784067 }, + { url = "https://files.pythonhosted.org/packages/29/9c/a17e71aa0e4f674c7a59b5e65b042d2bdf91bebc316e969a1c31c6b51ef1/bt_decode-0.4.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ebb3b72146e7feb08e235d78457b597697708149d7410f184098b73c5ab38aa", size = 600955 }, + { url = "https://files.pythonhosted.org/packages/f4/c6/429323a3c72251c6bc22926995ea3e490db07bb96e608ac4ca9eaa282e62/bt_decode-0.4.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9571680e6b74fab00cbd10dc255594692a9cdf615e33170d5a32112c1da8e3e4", size = 599227 }, + { url = "https://files.pythonhosted.org/packages/15/f4/3495a7d242668d347e851424e95acbbd2916ae70f7827e0533bd3c59e653/bt_decode-0.4.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dec8af1719ced86da6f7b1dcf70e1d480cfb86e2cf7530692d3e66ad1e16067d", size = 666872 }, + { url = "https://files.pythonhosted.org/packages/f1/3a/f0875014848888259f8646f915c1a8046d420799a155ce80d5af10e77044/bt_decode-0.4.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46d2308e13615951f89ff7ba05364a2e3747626b29fd4ee39c085ea56cb5fe", size = 709410 }, + { url = "https://files.pythonhosted.org/packages/be/e5/bc31c0f2a29945c548cda2538c8b5368da722217da7ca0a64eedd4df56a2/bt_decode-0.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0df0436d736544587002e0fa4fe3887b28cec8de4a9036c1ea776c560e966b8d", size = 778135 }, + { url = "https://files.pythonhosted.org/packages/25/48/387fd8cef96a86c39e6716455b493a759fbe9a67bcaa2dfe39c3d3b6b11b/bt_decode-0.4.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:579aba5010a078831af2025cd03df9d429fa35008ec46bc1561e6147e2c9769e", size = 860601 }, + { url = "https://files.pythonhosted.org/packages/12/85/1458d9eaf9a74390ac5e0a1a3be5eaf53550aa4f4c28362fb4f80a94c8a6/bt_decode-0.4.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:039e880688d4c5f2ee090980649811b700593e21eccee520b294c07b85008bce", size = 817941 }, + { url = "https://files.pythonhosted.org/packages/70/72/723265284f71fb95556c5b27c83a370b2e38e02666fd17dbb129856fb1f2/bt_decode-0.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a45173a6f0e48b28b190bfb250b6683984d115d70a6d2ff5102a2421d581de6", size = 783857 }, + { url = "https://files.pythonhosted.org/packages/3e/0e/99a9eb10977368489f114b66b7d70fa1573b2e6eef90fd83e7d5553ff533/bt_decode-0.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49cbf7ef7174d57b89c8e72d54749176da7f01926d963846042af7c141fc7c88", size = 604720 }, + { url = "https://files.pythonhosted.org/packages/8a/4a/45fd8f7f058fdf6b46cd8aa333312fafe0b8c7d1c1a92d0c2645f5094bdd/bt_decode-0.4.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f7e85d5dfb4aaefa9dba9ed86b9dfc2efff35322053da2f774942a9da6d50486", size = 601660 }, + { url = "https://files.pythonhosted.org/packages/5b/fc/67fa266990f2be203573cc3184e678bd9a7e31e530badd0e9a75107dc164/bt_decode-0.4.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a061a29489eb9680a01085f87e575e7e69fbfdc2c533d361ab84486d65470986", size = 669550 }, + { url = "https://files.pythonhosted.org/packages/62/06/44bc4f2112573d3e7e8e826c3dae3e591d0c9a16422c61d33a54d23983bf/bt_decode-0.4.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6def48997eac2b9aafde742c4c2a7d159623824e7f9d36bbfa95f12ba6354d5", size = 708913 }, + { url = "https://files.pythonhosted.org/packages/a4/e0/ae0a2e1cd5bd818ae3ddfb954fe7a7fce48071cf2be7d4ecef8a857781b0/bt_decode-0.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a5eee81c7a20bd2739f5867354afc38372b0307211a4c9a580bb99369f84835", size = 614747 }, + { url = "https://files.pythonhosted.org/packages/f5/31/0277f6a1d01730f5826aec952637150d038dce594c842b24e4e60af67a6d/bt_decode-0.4.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8914f5bd5bfe16e79fe6f8f94766d22635f1f4bef1567c545c22ecdf4f150313", size = 663773 }, + { url = "https://files.pythonhosted.org/packages/90/53/1b1c1a0e39c7b02725eae20b072df5044730b5a84d2844d151a1624e3aba/bt_decode-0.4.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3b268f170bcf85e229078f3af589b977c56ed9b696fe9e1198c5d4c9607406f1", size = 782839 }, + { url = "https://files.pythonhosted.org/packages/7a/75/53e83c6834c6c8f1fd2dfd9f97f3badafdd8f73132930eb80c1753c791ff/bt_decode-0.4.0-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:f3c54b14d914bf20669bbeedb97da18b3379c6d7f801404227519416cceda614", size = 862685 }, + { url = "https://files.pythonhosted.org/packages/8d/cc/36fdb495df7b5a48ae72ea4554916e1ac3e1b5b3cbc916cbfeefe9b5a902/bt_decode-0.4.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:a7733ff7bcded3211e3b64fb38a1c917543045a092153999ede98333af766d3c", size = 819340 }, + { url = "https://files.pythonhosted.org/packages/2d/a2/2bcf4bc636933b6c8078ca08b2af8995715a133c327623e0e8d7e111d3a3/bt_decode-0.4.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e1036e0db9f75fb2c2c690bddd2a02d0e94347c13d906eb5dbbf22202f3fa46f", size = 785373 }, +] + +[[package]] +name = "certifi" +version = "2024.12.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[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 = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[package]] +name = "clip" +version = "1.0" +source = { git = "https://github.com/openai/CLIP.git#dcba3cb2e2827b402d2701e7e1c7d9fed8a20ef1" } +dependencies = [ + { name = "ftfy" }, + { name = "packaging" }, + { name = "regex" }, + { name = "torch" }, + { name = "torchvision" }, + { name = "tqdm" }, +] + +[[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 = "colormath" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "networkx" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/cf/70ea34103a76cc6fb1892289bda321cd0cc73b1a5500ee7fe9ef9f64acef/colormath-3.0.0.tar.gz", hash = "sha256:3d4605af344527da0e4f9f504fad7ddbebda35322c566a6c72e28edb1ff31217", size = 39761 } + +[[package]] +name = "contourpy" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/c2/fc7193cc5383637ff390a712e88e4ded0452c9fbcf84abe3de5ea3df1866/contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699", size = 13465753 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/80937fe3efe0edacf67c9a20b955139a1a622730042c1ea991956f2704ad/contourpy-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab", size = 268466 }, + { url = "https://files.pythonhosted.org/packages/82/1d/e3eaebb4aa2d7311528c048350ca8e99cdacfafd99da87bc0a5f8d81f2c2/contourpy-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124", size = 253314 }, + { url = "https://files.pythonhosted.org/packages/de/f3/d796b22d1a2b587acc8100ba8c07fb7b5e17fde265a7bb05ab967f4c935a/contourpy-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2f926efda994cdf3c8d3fdb40b9962f86edbc4457e739277b961eced3d0b4c1", size = 312003 }, + { url = "https://files.pythonhosted.org/packages/bf/f5/0e67902bc4394daee8daa39c81d4f00b50e063ee1a46cb3938cc65585d36/contourpy-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adce39d67c0edf383647a3a007de0a45fd1b08dedaa5318404f1a73059c2512b", size = 351896 }, + { url = "https://files.pythonhosted.org/packages/1f/d6/e766395723f6256d45d6e67c13bb638dd1fa9dc10ef912dc7dd3dcfc19de/contourpy-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abbb49fb7dac584e5abc6636b7b2a7227111c4f771005853e7d25176daaf8453", size = 320814 }, + { url = "https://files.pythonhosted.org/packages/a9/57/86c500d63b3e26e5b73a28b8291a67c5608d4aa87ebd17bd15bb33c178bc/contourpy-1.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0cffcbede75c059f535725c1680dfb17b6ba8753f0c74b14e6a9c68c29d7ea3", size = 324969 }, + { url = "https://files.pythonhosted.org/packages/b8/62/bb146d1289d6b3450bccc4642e7f4413b92ebffd9bf2e91b0404323704a7/contourpy-1.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab29962927945d89d9b293eabd0d59aea28d887d4f3be6c22deaefbb938a7277", size = 1265162 }, + { url = "https://files.pythonhosted.org/packages/18/04/9f7d132ce49a212c8e767042cc80ae390f728060d2eea47058f55b9eff1c/contourpy-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974d8145f8ca354498005b5b981165b74a195abfae9a8129df3e56771961d595", size = 1324328 }, + { url = "https://files.pythonhosted.org/packages/46/23/196813901be3f97c83ababdab1382e13e0edc0bb4e7b49a7bff15fcf754e/contourpy-1.3.1-cp310-cp310-win32.whl", hash = "sha256:ac4578ac281983f63b400f7fe6c101bedc10651650eef012be1ccffcbacf3697", size = 173861 }, + { url = "https://files.pythonhosted.org/packages/e0/82/c372be3fc000a3b2005061ca623a0d1ecd2eaafb10d9e883a2fc8566e951/contourpy-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:174e758c66bbc1c8576992cec9599ce8b6672b741b5d336b5c74e35ac382b18e", size = 218566 }, + { url = "https://files.pythonhosted.org/packages/12/bb/11250d2906ee2e8b466b5f93e6b19d525f3e0254ac8b445b56e618527718/contourpy-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8b974d8db2c5610fb4e76307e265de0edb655ae8169e8b21f41807ccbeec4b", size = 269555 }, + { url = "https://files.pythonhosted.org/packages/67/71/1e6e95aee21a500415f5d2dbf037bf4567529b6a4e986594d7026ec5ae90/contourpy-1.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20914c8c973f41456337652a6eeca26d2148aa96dd7ac323b74516988bea89fc", size = 254549 }, + { url = "https://files.pythonhosted.org/packages/31/2c/b88986e8d79ac45efe9d8801ae341525f38e087449b6c2f2e6050468a42c/contourpy-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d40d37c1c3a4961b4619dd9d77b12124a453cc3d02bb31a07d58ef684d3d86", size = 313000 }, + { url = "https://files.pythonhosted.org/packages/c4/18/65280989b151fcf33a8352f992eff71e61b968bef7432fbfde3a364f0730/contourpy-1.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:113231fe3825ebf6f15eaa8bc1f5b0ddc19d42b733345eae0934cb291beb88b6", size = 352925 }, + { url = "https://files.pythonhosted.org/packages/f5/c7/5fd0146c93220dbfe1a2e0f98969293b86ca9bc041d6c90c0e065f4619ad/contourpy-1.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dbbc03a40f916a8420e420d63e96a1258d3d1b58cbdfd8d1f07b49fcbd38e85", size = 323693 }, + { url = "https://files.pythonhosted.org/packages/85/fc/7fa5d17daf77306840a4e84668a48ddff09e6bc09ba4e37e85ffc8e4faa3/contourpy-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a04ecd68acbd77fa2d39723ceca4c3197cb2969633836ced1bea14e219d077c", size = 326184 }, + { url = "https://files.pythonhosted.org/packages/ef/e7/104065c8270c7397c9571620d3ab880558957216f2b5ebb7e040f85eeb22/contourpy-1.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c414fc1ed8ee1dbd5da626cf3710c6013d3d27456651d156711fa24f24bd1291", size = 1268031 }, + { url = "https://files.pythonhosted.org/packages/e2/4a/c788d0bdbf32c8113c2354493ed291f924d4793c4a2e85b69e737a21a658/contourpy-1.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:31c1b55c1f34f80557d3830d3dd93ba722ce7e33a0b472cba0ec3b6535684d8f", size = 1325995 }, + { url = "https://files.pythonhosted.org/packages/a6/e6/a2f351a90d955f8b0564caf1ebe4b1451a3f01f83e5e3a414055a5b8bccb/contourpy-1.3.1-cp311-cp311-win32.whl", hash = "sha256:f611e628ef06670df83fce17805c344710ca5cde01edfdc72751311da8585375", size = 174396 }, + { url = "https://files.pythonhosted.org/packages/a8/7e/cd93cab453720a5d6cb75588cc17dcdc08fc3484b9de98b885924ff61900/contourpy-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b2bdca22a27e35f16794cf585832e542123296b4687f9fd96822db6bae17bfc9", size = 219787 }, + { url = "https://files.pythonhosted.org/packages/37/6b/175f60227d3e7f5f1549fcb374592be311293132207e451c3d7c654c25fb/contourpy-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509", size = 271494 }, + { url = "https://files.pythonhosted.org/packages/6b/6a/7833cfae2c1e63d1d8875a50fd23371394f540ce809d7383550681a1fa64/contourpy-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc", size = 255444 }, + { url = "https://files.pythonhosted.org/packages/7f/b3/7859efce66eaca5c14ba7619791b084ed02d868d76b928ff56890d2d059d/contourpy-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454", size = 307628 }, + { url = "https://files.pythonhosted.org/packages/48/b2/011415f5e3f0a50b1e285a0bf78eb5d92a4df000553570f0851b6e309076/contourpy-1.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47734d7073fb4590b4a40122b35917cd77be5722d80683b249dac1de266aac80", size = 347271 }, + { url = "https://files.pythonhosted.org/packages/84/7d/ef19b1db0f45b151ac78c65127235239a8cf21a59d1ce8507ce03e89a30b/contourpy-1.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ba94a401342fc0f8b948e57d977557fbf4d515f03c67682dd5c6191cb2d16ec", size = 318906 }, + { url = "https://files.pythonhosted.org/packages/ba/99/6794142b90b853a9155316c8f470d2e4821fe6f086b03e372aca848227dd/contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa874e87e4a647fd2e4f514d5e91c7d493697127beb95e77d2f7561f6905bd9", size = 323622 }, + { url = "https://files.pythonhosted.org/packages/3c/0f/37d2c84a900cd8eb54e105f4fa9aebd275e14e266736778bb5dccbf3bbbb/contourpy-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf98051f1045b15c87868dbaea84f92408337d4f81d0e449ee41920ea121d3b", size = 1266699 }, + { url = "https://files.pythonhosted.org/packages/3a/8a/deb5e11dc7d9cc8f0f9c8b29d4f062203f3af230ba83c30a6b161a6effc9/contourpy-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61332c87493b00091423e747ea78200659dc09bdf7fd69edd5e98cef5d3e9a8d", size = 1326395 }, + { url = "https://files.pythonhosted.org/packages/1a/35/7e267ae7c13aaf12322ccc493531f1e7f2eb8fba2927b9d7a05ff615df7a/contourpy-1.3.1-cp312-cp312-win32.whl", hash = "sha256:e914a8cb05ce5c809dd0fe350cfbb4e881bde5e2a38dc04e3afe1b3e58bd158e", size = 175354 }, + { url = "https://files.pythonhosted.org/packages/a1/35/c2de8823211d07e8a79ab018ef03960716c5dff6f4d5bff5af87fd682992/contourpy-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:08d9d449a61cf53033612cb368f3a1b26cd7835d9b8cd326647efe43bca7568d", size = 220971 }, + { url = "https://files.pythonhosted.org/packages/9a/e7/de62050dce687c5e96f946a93546910bc67e483fe05324439e329ff36105/contourpy-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2", size = 271548 }, + { url = "https://files.pythonhosted.org/packages/78/4d/c2a09ae014ae984c6bdd29c11e74d3121b25eaa117eca0bb76340efd7e1c/contourpy-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5", size = 255576 }, + { url = "https://files.pythonhosted.org/packages/ab/8a/915380ee96a5638bda80cd061ccb8e666bfdccea38d5741cb69e6dbd61fc/contourpy-1.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81", size = 306635 }, + { url = "https://files.pythonhosted.org/packages/29/5c/c83ce09375428298acd4e6582aeb68b1e0d1447f877fa993d9bf6cd3b0a0/contourpy-1.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2", size = 345925 }, + { url = "https://files.pythonhosted.org/packages/29/63/5b52f4a15e80c66c8078a641a3bfacd6e07106835682454647aca1afc852/contourpy-1.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7", size = 318000 }, + { url = "https://files.pythonhosted.org/packages/9a/e2/30ca086c692691129849198659bf0556d72a757fe2769eb9620a27169296/contourpy-1.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c", size = 322689 }, + { url = "https://files.pythonhosted.org/packages/6b/77/f37812ef700f1f185d348394debf33f22d531e714cf6a35d13d68a7003c7/contourpy-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3", size = 1268413 }, + { url = "https://files.pythonhosted.org/packages/3f/6d/ce84e79cdd128542ebeb268f84abb4b093af78e7f8ec504676673d2675bc/contourpy-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1", size = 1326530 }, + { url = "https://files.pythonhosted.org/packages/72/22/8282f4eae20c73c89bee7a82a19c4e27af9b57bb602ecaa00713d5bdb54d/contourpy-1.3.1-cp313-cp313-win32.whl", hash = "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82", size = 175315 }, + { url = "https://files.pythonhosted.org/packages/e3/d5/28bca491f65312b438fbf076589dcde7f6f966b196d900777f5811b9c4e2/contourpy-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd", size = 220987 }, + { url = "https://files.pythonhosted.org/packages/2f/24/a4b285d6adaaf9746e4700932f579f1a7b6f9681109f694cfa233ae75c4e/contourpy-1.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30", size = 285001 }, + { url = "https://files.pythonhosted.org/packages/48/1d/fb49a401b5ca4f06ccf467cd6c4f1fd65767e63c21322b29b04ec40b40b9/contourpy-1.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751", size = 268553 }, + { url = "https://files.pythonhosted.org/packages/79/1e/4aef9470d13fd029087388fae750dccb49a50c012a6c8d1d634295caa644/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342", size = 310386 }, + { url = "https://files.pythonhosted.org/packages/b0/34/910dc706ed70153b60392b5305c708c9810d425bde12499c9184a1100888/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c", size = 349806 }, + { url = "https://files.pythonhosted.org/packages/31/3c/faee6a40d66d7f2a87f7102236bf4780c57990dd7f98e5ff29881b1b1344/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f", size = 321108 }, + { url = "https://files.pythonhosted.org/packages/17/69/390dc9b20dd4bb20585651d7316cc3054b7d4a7b4f8b710b2b698e08968d/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda", size = 327291 }, + { url = "https://files.pythonhosted.org/packages/ef/74/7030b67c4e941fe1e5424a3d988080e83568030ce0355f7c9fc556455b01/contourpy-1.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242", size = 1263752 }, + { url = "https://files.pythonhosted.org/packages/f0/ed/92d86f183a8615f13f6b9cbfc5d4298a509d6ce433432e21da838b4b63f4/contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1", size = 1318403 }, + { url = "https://files.pythonhosted.org/packages/b3/0e/c8e4950c77dcfc897c71d61e56690a0a9df39543d2164040301b5df8e67b/contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1", size = 185117 }, + { url = "https://files.pythonhosted.org/packages/c1/31/1ae946f11dfbd229222e6d6ad8e7bd1891d3d48bde5fbf7a0beb9491f8e3/contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546", size = 236668 }, + { url = "https://files.pythonhosted.org/packages/3e/4f/e56862e64b52b55b5ddcff4090085521fc228ceb09a88390a2b103dccd1b/contourpy-1.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b457d6430833cee8e4b8e9b6f07aa1c161e5e0d52e118dc102c8f9bd7dd060d6", size = 265605 }, + { url = "https://files.pythonhosted.org/packages/b0/2e/52bfeeaa4541889f23d8eadc6386b442ee2470bd3cff9baa67deb2dd5c57/contourpy-1.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb76c1a154b83991a3cbbf0dfeb26ec2833ad56f95540b442c73950af2013750", size = 315040 }, + { url = "https://files.pythonhosted.org/packages/52/94/86bfae441707205634d80392e873295652fc313dfd93c233c52c4dc07874/contourpy-1.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:44a29502ca9c7b5ba389e620d44f2fbe792b1fb5734e8b931ad307071ec58c53", size = 218221 }, +] + +[[package]] +name = "cryptography" +version = "43.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", size = 686989 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/f3/01fdf26701a26f4b4dbc337a26883ad5bccaa6f1bbbdd29cd89e22f18a1c/cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", size = 6225303 }, + { url = "https://files.pythonhosted.org/packages/a3/01/4896f3d1b392025d4fcbecf40fdea92d3df8662123f6835d0af828d148fd/cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", size = 3760905 }, + { url = "https://files.pythonhosted.org/packages/0a/be/f9a1f673f0ed4b7f6c643164e513dbad28dd4f2dcdf5715004f172ef24b6/cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", size = 3977271 }, + { url = "https://files.pythonhosted.org/packages/4e/49/80c3a7b5514d1b416d7350830e8c422a4d667b6d9b16a9392ebfd4a5388a/cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", size = 3746606 }, + { url = "https://files.pythonhosted.org/packages/0e/16/a28ddf78ac6e7e3f25ebcef69ab15c2c6be5ff9743dd0709a69a4f968472/cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", size = 3986484 }, + { url = "https://files.pythonhosted.org/packages/01/f5/69ae8da70c19864a32b0315049866c4d411cce423ec169993d0434218762/cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", size = 3852131 }, + { url = "https://files.pythonhosted.org/packages/fd/db/e74911d95c040f9afd3612b1f732e52b3e517cb80de8bf183be0b7d413c6/cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", size = 4075647 }, + { url = "https://files.pythonhosted.org/packages/56/48/7b6b190f1462818b324e674fa20d1d5ef3e24f2328675b9b16189cbf0b3c/cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", size = 2623873 }, + { url = "https://files.pythonhosted.org/packages/eb/b1/0ebff61a004f7f89e7b65ca95f2f2375679d43d0290672f7713ee3162aff/cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", size = 3068039 }, + { url = "https://files.pythonhosted.org/packages/30/d5/c8b32c047e2e81dd172138f772e81d852c51f0f2ad2ae8a24f1122e9e9a7/cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", size = 6222984 }, + { url = "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", size = 3762968 }, + { url = "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", size = 3977754 }, + { url = "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", size = 3749458 }, + { url = "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", size = 3988220 }, + { url = "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", size = 3853898 }, + { url = "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", size = 4076592 }, + { url = "https://files.pythonhosted.org/packages/81/1e/ffcc41b3cebd64ca90b28fd58141c5f68c83d48563c88333ab660e002cd3/cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", size = 2623145 }, + { url = "https://files.pythonhosted.org/packages/87/5c/3dab83cc4aba1f4b0e733e3f0c3e7d4386440d660ba5b1e3ff995feb734d/cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", size = 3068026 }, + { url = "https://files.pythonhosted.org/packages/6f/db/d8b8a039483f25fc3b70c90bc8f3e1d4497a99358d610c5067bf3bd4f0af/cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c", size = 3144545 }, + { url = "https://files.pythonhosted.org/packages/93/90/116edd5f8ec23b2dc879f7a42443e073cdad22950d3c8ee834e3b8124543/cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3", size = 3679828 }, + { url = "https://files.pythonhosted.org/packages/d8/32/1e1d78b316aa22c0ba6493cc271c1c309969e5aa5c22c830a1d7ce3471e6/cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83", size = 3908132 }, + { url = "https://files.pythonhosted.org/packages/91/bb/cd2c13be3332e7af3cdf16154147952d39075b9f61ea5e6b5241bf4bf436/cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7", size = 2988811 }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, +] + +[[package]] +name = "cytoolz" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "toolz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/f9/3243eed3a6545c2a33a21f74f655e3fcb5d2192613cd3db81a93369eb339/cytoolz-1.0.1.tar.gz", hash = "sha256:89cc3161b89e1bb3ed7636f74ed2e55984fd35516904fc878cae216e42b2c7d6", size = 626652 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d9/f13d66c16cff1fa1cb6c234698029877c456f35f577ef274aba3b86e7c51/cytoolz-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cec9af61f71fc3853eb5dca3d42eb07d1f48a4599fa502cbe92adde85f74b042", size = 403515 }, + { url = "https://files.pythonhosted.org/packages/4b/2d/4cdf848a69300c7d44984f2ebbebb3b8576e5449c8dea157298f3bdc4da3/cytoolz-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:140bbd649dbda01e91add7642149a5987a7c3ccc251f2263de894b89f50b6608", size = 383936 }, + { url = "https://files.pythonhosted.org/packages/72/a4/ccfdd3f0ed9cc818f734b424261f6018fc61e3ec833bf85225a9aca0d994/cytoolz-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e90124bdc42ff58b88cdea1d24a6bc5f776414a314cc4d94f25c88badb3a16d1", size = 1934569 }, + { url = "https://files.pythonhosted.org/packages/50/fc/38d5344fa595683ad10dc819cfc1d8b9d2b3391ccf3e8cb7bab4899a01f5/cytoolz-1.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e74801b751e28f7c5cc3ad264c123954a051f546f2fdfe089f5aa7a12ccfa6da", size = 2015129 }, + { url = "https://files.pythonhosted.org/packages/28/29/75261748dc54a20a927f33641f4e9aac674cfc6d3fbd4f332e10d0b37639/cytoolz-1.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:582dad4545ddfb5127494ef23f3fa4855f1673a35d50c66f7638e9fb49805089", size = 2000506 }, + { url = "https://files.pythonhosted.org/packages/00/ae/e4ead004cc2698281d153c4a5388638d67cdb5544d6d6cc1e5b3db2bd2a3/cytoolz-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd7bd0618e16efe03bd12f19c2a26a27e6e6b75d7105adb7be1cd2a53fa755d8", size = 1957537 }, + { url = "https://files.pythonhosted.org/packages/4a/ff/4f3aa07f4f47701f7f63df60ce0a5669fa09c256c3d4a33503a9414ea5cc/cytoolz-1.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d74cca6acf1c4af58b2e4a89cc565ed61c5e201de2e434748c93e5a0f5c541a5", size = 1863331 }, + { url = "https://files.pythonhosted.org/packages/a2/29/654f57f2a9b8e9765a4ab876765f64f94530b61fc6471a07feea42ece6d4/cytoolz-1.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:823a3763828d8d457f542b2a45d75d6b4ced5e470b5c7cf2ed66a02f508ed442", size = 1849938 }, + { url = "https://files.pythonhosted.org/packages/bc/7b/11f457db6b291060a98315ab2c7198077d8bddeeebe5f7126d9dad98cc54/cytoolz-1.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:51633a14e6844c61db1d68c1ffd077cf949f5c99c60ed5f1e265b9e2966f1b52", size = 1852345 }, + { url = "https://files.pythonhosted.org/packages/6b/92/0dccc96ce0323be236d404f5084479b79b747fa0e74e43a270e95868b5f9/cytoolz-1.0.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3ec9b01c45348f1d0d712507d54c2bfd69c62fbd7c9ef555c9d8298693c2432", size = 1989877 }, + { url = "https://files.pythonhosted.org/packages/a3/c8/1c5203a81200bae51aa8f7b5fad613f695bf1afa03f16251ca23ecb2ef9f/cytoolz-1.0.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1855022b712a9c7a5bce354517ab4727a38095f81e2d23d3eabaf1daeb6a3b3c", size = 1994492 }, + { url = "https://files.pythonhosted.org/packages/e2/8a/04bc193c4d7ced8ef6bb62cdcd0bf40b5e5eb26586ed2cfb4433ec7dfd0a/cytoolz-1.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9930f7288c4866a1dc1cc87174f0c6ff4cad1671eb1f6306808aa6c445857d78", size = 1896077 }, + { url = "https://files.pythonhosted.org/packages/21/a5/bee63a58f51d2c74856db66e6119a014464ff8cb1c9387fa4bd2d94e49b0/cytoolz-1.0.1-cp310-cp310-win32.whl", hash = "sha256:a9baad795d72fadc3445ccd0f122abfdbdf94269157e6d6d4835636dad318804", size = 322135 }, + { url = "https://files.pythonhosted.org/packages/e8/16/7abfb1685e8b7f2838264551ee33651748994813f566ac4c3d737dfe90e5/cytoolz-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:ad95b386a84e18e1f6136f6d343d2509d4c3aae9f5a536f3dc96808fcc56a8cf", size = 363599 }, + { url = "https://files.pythonhosted.org/packages/dc/ea/8131ae39119820b8867cddc23716fa9f681f2b3bbce6f693e68dfb36b55b/cytoolz-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2d958d4f04d9d7018e5c1850790d9d8e68b31c9a2deebca74b903706fdddd2b6", size = 406162 }, + { url = "https://files.pythonhosted.org/packages/26/18/3d9bd4c146f6ea6e51300c242b20cb416966b21d481dac230e1304f1e54b/cytoolz-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0f445b8b731fc0ecb1865b8e68a070084eb95d735d04f5b6c851db2daf3048ab", size = 384961 }, + { url = "https://files.pythonhosted.org/packages/e4/73/9034827907c7f85c7c484c9494e905d022fb8174526004e9ef332570349e/cytoolz-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f546a96460a7e28eb2ec439f4664fa646c9b3e51c6ebad9a59d3922bbe65e30", size = 2091698 }, + { url = "https://files.pythonhosted.org/packages/74/af/d5c2733b0fde1a08254ff1a8a8d567874040c9eb1606363cfebc0713c73f/cytoolz-1.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0317681dd065532d21836f860b0563b199ee716f55d0c1f10de3ce7100c78a3b", size = 2188452 }, + { url = "https://files.pythonhosted.org/packages/6a/bb/77c71fa9c217260b4056a732d754748903423c2cdd82a673d6064741e375/cytoolz-1.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c0ef52febd5a7821a3fd8d10f21d460d1a3d2992f724ba9c91fbd7a96745d41", size = 2174203 }, + { url = "https://files.pythonhosted.org/packages/fc/a9/a5b4a3ff5d22faa1b60293bfe97362e2caf4a830c26d37ab5557f60d04b2/cytoolz-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebaf419acf2de73b643cf96108702b8aef8e825cf4f63209ceb078d5fbbbfd", size = 2099831 }, + { url = "https://files.pythonhosted.org/packages/35/08/7f6869ea1ff31ce5289a7d58d0e7090acfe7058baa2764473048ff61ea3c/cytoolz-1.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f7f04eeb4088947585c92d6185a618b25ad4a0f8f66ea30c8db83cf94a425e3", size = 1996744 }, + { url = "https://files.pythonhosted.org/packages/46/b4/9ac424c994b51763fd1bbed62d95f8fba8fa0e45c8c3c583904fdaf8f51d/cytoolz-1.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f61928803bb501c17914b82d457c6f50fe838b173fb40d39c38d5961185bd6c7", size = 2013733 }, + { url = "https://files.pythonhosted.org/packages/3e/99/03009765c4b87d742d5b5a8670abb56a8c7ede033c2cdaa4be8662d3b001/cytoolz-1.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d2960cb4fa01ccb985ad1280db41f90dc97a80b397af970a15d5a5de403c8c61", size = 1994850 }, + { url = "https://files.pythonhosted.org/packages/40/9a/8458af9a5557e177ea42f8cf7e477bede518b0bbef564e28c4151feaa52c/cytoolz-1.0.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2b407cc3e9defa8df5eb46644f6f136586f70ba49eba96f43de67b9a0984fd3", size = 2155352 }, + { url = "https://files.pythonhosted.org/packages/5e/5c/2a701423e001fcbec288b4f3fc2bf67557d114c2388237fc1ae67e1e2686/cytoolz-1.0.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8245f929144d4d3bd7b972c9593300195c6cea246b81b4c46053c48b3f044580", size = 2163515 }, + { url = "https://files.pythonhosted.org/packages/36/16/ee2e06e65d9d533bc05cd52a0b355ba9072fc8f60d77289e529c6d2e3750/cytoolz-1.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e37385db03af65763933befe89fa70faf25301effc3b0485fec1c15d4ce4f052", size = 2054431 }, + { url = "https://files.pythonhosted.org/packages/d8/d5/2fac8315f210fa1bc7106e27c19e1211580aa25bb7fa17dfd79505e5baf2/cytoolz-1.0.1-cp311-cp311-win32.whl", hash = "sha256:50f9c530f83e3e574fc95c264c3350adde8145f4f8fc8099f65f00cc595e5ead", size = 322004 }, + { url = "https://files.pythonhosted.org/packages/a9/9e/0b70b641850a95f9ff90adde9d094a4b1d81ec54dadfd97fec0a2aaf440e/cytoolz-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:b7f6b617454b4326af7bd3c7c49b0fc80767f134eb9fd6449917a058d17a0e3c", size = 365358 }, + { url = "https://files.pythonhosted.org/packages/d8/e8/218098344ed2cb5f8441fade9b2428e435e7073962374a9c71e59ac141a7/cytoolz-1.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fcb8f7d0d65db1269022e7e0428471edee8c937bc288ebdcb72f13eaa67c2fe4", size = 414121 }, + { url = "https://files.pythonhosted.org/packages/de/27/4d729a5653718109262b758fec1a959aa9facb74c15460d9074dc76d6635/cytoolz-1.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:207d4e4b445e087e65556196ff472ff134370d9a275d591724142e255f384662", size = 390904 }, + { url = "https://files.pythonhosted.org/packages/72/c0/cbabfa788bab9c6038953bf9478adaec06e88903a726946ea7c88092f5c4/cytoolz-1.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21cdf6bac6fd843f3b20280a66fd8df20dea4c58eb7214a2cd8957ec176f0bb3", size = 2090734 }, + { url = "https://files.pythonhosted.org/packages/c3/66/369262c60f9423c2da82a60864a259c852f1aa122aced4acd2c679af58c0/cytoolz-1.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a55ec098036c0dea9f3bdc021f8acd9d105a945227d0811589f0573f21c9ce1", size = 2155933 }, + { url = "https://files.pythonhosted.org/packages/aa/4e/ee55186802f8d24b5fbf9a11405ccd1203b30eded07cc17750618219b94e/cytoolz-1.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a13ab79ff4ce202e03ab646a2134696988b554b6dc4b71451e948403db1331d8", size = 2171903 }, + { url = "https://files.pythonhosted.org/packages/a1/96/bd1a9f3396e9b7f618db8cd08d15630769ce3c8b7d0534f92cd639c977ae/cytoolz-1.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2d944799026e1ff08a83241f1027a2d9276c41f7a74224cd98b7df6e03957d", size = 2125270 }, + { url = "https://files.pythonhosted.org/packages/28/48/2a3762873091c88a69e161111cfbc6c222ff145d57ff011a642b169f04f1/cytoolz-1.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88ba85834cd523b91fdf10325e1e6d71c798de36ea9bdc187ca7bd146420de6f", size = 1973967 }, + { url = "https://files.pythonhosted.org/packages/e4/50/500bd69774bdc49a4d78ec8779eb6ac7c1a9d706bfd91cf2a1dba604373a/cytoolz-1.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a750b1af7e8bf6727f588940b690d69e25dc47cce5ce467925a76561317eaf7", size = 2021695 }, + { url = "https://files.pythonhosted.org/packages/e4/4e/ba5a0ce34869495eb50653de8d676847490cf13a2cac1760fc4d313e78de/cytoolz-1.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44a71870f7eae31d263d08b87da7c2bf1176f78892ed8bdade2c2850478cb126", size = 2010177 }, + { url = "https://files.pythonhosted.org/packages/87/57/615c630b3089a13adb15351d958d227430cf624f03b1dd39eb52c34c1f59/cytoolz-1.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8231b9abbd8e368e036f4cc2e16902c9482d4cf9e02a6147ed0e9a3cd4a9ab0", size = 2154321 }, + { url = "https://files.pythonhosted.org/packages/7f/0f/fe1aa2d931e3b35ecc05215bd75da945ea7346095b3b6f6027164e602d5a/cytoolz-1.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa87599ccc755de5a096a4d6c34984de6cd9dc928a0c5eaa7607457317aeaf9b", size = 2188374 }, + { url = "https://files.pythonhosted.org/packages/de/fa/fd363d97a641b6d0e2fd1d5c35b8fd41d9ccaeb4df56302f53bf23a58e3a/cytoolz-1.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67cd16537df51baabde3baa770ab7b8d16839c4d21219d5b96ac59fb012ebd2d", size = 2077911 }, + { url = "https://files.pythonhosted.org/packages/d9/68/0a22946b98ae5201b54ccb4e651295285c0fb79406022b6ee8b2f791940c/cytoolz-1.0.1-cp312-cp312-win32.whl", hash = "sha256:fb988c333f05ee30ad4693fe4da55d95ec0bb05775d2b60191236493ea2e01f9", size = 321903 }, + { url = "https://files.pythonhosted.org/packages/62/1a/f3903197956055032f8cb297342e2dff07e50f83991aebfe5b4c4fcb55e4/cytoolz-1.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:8f89c48d8e5aec55ffd566a8ec858706d70ed0c6a50228eca30986bfa5b4da8b", size = 364490 }, + { url = "https://files.pythonhosted.org/packages/aa/2e/a9f069db0107749e9e72baf6c21abe3f006841a3bcfdc9b8420e22ef31eb/cytoolz-1.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6944bb93b287032a4c5ca6879b69bcd07df46f3079cf8393958cf0b0454f50c0", size = 407365 }, + { url = "https://files.pythonhosted.org/packages/a9/9b/5e87dd0e31f54c778b4f9f34cc14c1162d3096c8d746b0f8be97d70dd73c/cytoolz-1.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e027260fd2fc5cb041277158ac294fc13dca640714527219f702fb459a59823a", size = 385233 }, + { url = "https://files.pythonhosted.org/packages/63/00/2fd32b16284cdb97cfe092822179bc0c3bcdd5e927dd39f986169a517642/cytoolz-1.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88662c0e07250d26f5af9bc95911e6137e124a5c1ec2ce4a5d74de96718ab242", size = 2062903 }, + { url = "https://files.pythonhosted.org/packages/85/39/b3cbb5a9847ba59584a263772ad4f8ca2dbfd2a0e11efd09211d1219804c/cytoolz-1.0.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309dffa78b0961b4c0cf55674b828fbbc793cf2d816277a5c8293c0c16155296", size = 2139517 }, + { url = "https://files.pythonhosted.org/packages/ea/39/bfcab4a46d50c467e36fe704f19d8904efead417787806ee210327f68390/cytoolz-1.0.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:edb34246e6eb40343c5860fc51b24937698e4fa1ee415917a73ad772a9a1746b", size = 2154849 }, + { url = "https://files.pythonhosted.org/packages/fd/42/3bc6ee61b0aa47e1cb40819adc1a456d7efa809f0dea9faddacb43fdde8f/cytoolz-1.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a54da7a8e4348a18d45d4d5bc84af6c716d7f131113a4f1cc45569d37edff1b", size = 2102302 }, + { url = "https://files.pythonhosted.org/packages/00/66/3f636c6ddea7b18026b90a8c238af472e423b86e427b11df02213689b012/cytoolz-1.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:241c679c3b1913c0f7259cf1d9639bed5084c86d0051641d537a0980548aa266", size = 1960872 }, + { url = "https://files.pythonhosted.org/packages/40/36/cb3b7cdd651007b69f9c48e9d104cec7cb8dc53afa1d6a720e5ad08022fa/cytoolz-1.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5bfc860251a8f280ac79696fc3343cfc3a7c30b94199e0240b6c9e5b6b01a2a5", size = 2014430 }, + { url = "https://files.pythonhosted.org/packages/88/3f/2e9bd2a16cfd269808922147551dcb2d8b68ba54a2c4deca2fa6a6cd0d5f/cytoolz-1.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c8edd1547014050c1bdad3ff85d25c82bd1c2a3c96830c6181521eb78b9a42b3", size = 2003127 }, + { url = "https://files.pythonhosted.org/packages/c4/7d/08604ff940aa784df8343c387fdf2489b948b714a6afb587775ae94da912/cytoolz-1.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b349bf6162e8de215403d7f35f8a9b4b1853dc2a48e6e1a609a5b1a16868b296", size = 2142369 }, + { url = "https://files.pythonhosted.org/packages/d2/c6/39919a0645bdbdf720e97cae107f959ea9d1267fbc3b0d94fc6e1d12ac8f/cytoolz-1.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1b18b35256219b6c3dd0fa037741b85d0bea39c552eab0775816e85a52834140", size = 2180427 }, + { url = "https://files.pythonhosted.org/packages/d8/03/dbb9d47556ee54337e7e0ac209d17ceff2d2a197c34de08005abc7a7449b/cytoolz-1.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:738b2350f340ff8af883eb301054eb724997f795d20d90daec7911c389d61581", size = 2069785 }, + { url = "https://files.pythonhosted.org/packages/ea/f8/11bb7b8947002231faae3ec2342df5896afbc19eb783a332cce6d219ff79/cytoolz-1.0.1-cp313-cp313-win32.whl", hash = "sha256:9cbd9c103df54fcca42be55ef40e7baea624ac30ee0b8bf1149f21146d1078d9", size = 320685 }, + { url = "https://files.pythonhosted.org/packages/40/eb/dde173cf2357084ca9423950be1f2f11ab11d65d8bd30165bfb8fd4213e9/cytoolz-1.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:90e577e08d3a4308186d9e1ec06876d4756b1e8164b92971c69739ea17e15297", size = 362898 }, + { url = "https://files.pythonhosted.org/packages/d9/f7/ef2a10daaec5c0f7d781d50758c6187eee484256e356ae8ef178d6c48497/cytoolz-1.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:83d19d55738ad9c60763b94f3f6d3c6e4de979aeb8d76841c1401081e0e58d96", size = 345702 }, + { url = "https://files.pythonhosted.org/packages/c8/14/53c84adddedb67ff1546abb86fea04d26e24298c3ceab8436d20122ed0b9/cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f112a71fad6ea824578e6393765ce5c054603afe1471a5c753ff6c67fd872d10", size = 385695 }, + { url = "https://files.pythonhosted.org/packages/bd/80/3ae356c5e7b8d7dc7d1adb52f6932fee85cd748ed4e1217c269d2dfd610f/cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a515df8f8aa6e1eaaf397761a6e4aff2eef73b5f920aedf271416d5471ae5ee", size = 406261 }, + { url = "https://files.pythonhosted.org/packages/0c/31/8e43761ffc82d90bf9cab7e0959712eedcd1e33c211397e143dd42d7af57/cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c398e7b7023460bea2edffe5fcd0a76029580f06c3f6938ac3d198b47156f3", size = 397207 }, + { url = "https://files.pythonhosted.org/packages/d1/b9/fe9da37090b6444c65f848a83e390f87d8cb43d6a4df46de1556ad7e5ceb/cytoolz-1.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3237e56211e03b13df47435b2369f5df281e02b04ad80a948ebd199b7bc10a47", size = 343358 }, +] + +[[package]] +name = "datasets" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/48/744286c044e2b942d4fa67f92816126522ad1f0675def0ea3264e6242005/datasets-3.2.0.tar.gz", hash = "sha256:9a6e1a356052866b5dbdd9c9eedb000bf3fc43d986e3584d9b028f4976937229", size = 558366 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/84/0df6c5981f5fc722381662ff8cfbdf8aad64bec875f75d80b55bfef394ce/datasets-3.2.0-py3-none-any.whl", hash = "sha256:f3d2ba2698b7284a4518019658596a6a8bc79f31e51516524249d6c59cf0fe2a", size = 480647 }, +] + +[[package]] +name = "ddt" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/b2/20cfe16303e0bef0b2fb54024118ff97fa752414763ab626486794dab072/ddt-1.6.0.tar.gz", hash = "sha256:f71b348731b8c78c3100bffbd951a769fbd439088d1fdbb3841eee019af80acd", size = 13357 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/05/310e94d212fa6654261e40887de1d155afb72e3dadf7b625550dd5c71678/ddt-1.6.0-py2.py3-none-any.whl", hash = "sha256:e3c93b961a108b4f4d5a6c7f2263513d928baf3bb5b32af8e1c804bfb041141d", size = 7095 }, +] + +[[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 = "dill" +version = "0.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/17/4d/ac7ffa80c69ea1df30a8aa11b3578692a5118e7cd1aa157e3ef73b092d15/dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", size = 184847 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252 }, +] + +[[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 = "docker-pycreds" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/e6/d1f6c00b7221e2d7c4b470132c931325c8b22c51ca62417e300f5ce16009/docker-pycreds-0.4.0.tar.gz", hash = "sha256:6ce3270bcaf404cc4c3e27e4b6c70d3521deae82fb508767870fdbf772d584d4", size = 8754 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/e8/f6bd1eee09314e7e6dee49cbe2c5e22314ccdb38db16c9fc72d2fa80d054/docker_pycreds-0.4.0-py2.py3-none-any.whl", hash = "sha256:7266112468627868005106ec19cd0d722702d2b7d5912a28e19b826c3d37af49", size = 8982 }, +] + +[[package]] +name = "duckduckgo-search" +version = "7.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "lxml" }, + { name = "primp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/e5/8ac183cadbefa444183f4aca22140b44ed399e80a93caf0b338a043a3c7f/duckduckgo_search-7.2.1.tar.gz", hash = "sha256:cb214b6cd9505a41c228445a9c254620b93519c59292662d62ef19d0220618a0", size = 23897 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/8f/ee72af555cd58feb928ff0fd3977913f4ecd0ce8ad92cf4031c36de91776/duckduckgo_search-7.2.1-py3-none-any.whl", hash = "sha256:72ebbf6ad8759e3c3c79521cd66256e7a4ac741c522fd9342db94de91745ef87", size = 19720 }, +] + +[[package]] +name = "ecdsa" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/d0/ec8ac1de7accdcf18cfe468653ef00afd2f609faf67c423efbd02491051b/ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8", size = 197791 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/e7/ed3243b30d1bec41675b6394a1daae46349dc2b855cb83be846a5a918238/ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a", size = 149266 }, +] + +[[package]] +name = "einops" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/ca/9f5dcb8bead39959454c3912266bedc4c315839cee0e0ca9f4328f4588c1/einops-0.8.0.tar.gz", hash = "sha256:63486517fed345712a8385c100cb279108d9d47e6ae59099b07657e983deae85", size = 58861 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/5a/f0b9ad6c0a9017e62d4735daaeb11ba3b6c009d69a26141b258cd37b5588/einops-0.8.0-py3-none-any.whl", hash = "sha256:9572fb63046264a862693b0a87088af3bdc8c068fde03de63453cbbde245465f", size = 43223 }, +] + +[[package]] +name = "eth-hash" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/b6/57c89b91cf2dbb02b3019337f97bf346167d06cd23d3bde43c9fe52cae7e/eth-hash-0.7.0.tar.gz", hash = "sha256:bacdc705bfd85dadd055ecd35fd1b4f846b671add101427e089a4ca2e8db310a", size = 12463 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f0/a35e791bd73fa425838d8d0157754150ded141a94cf30d567dfeb9d57316/eth_hash-0.7.0-py3-none-any.whl", hash = "sha256:b8d5a230a2b251f4a291e3164a23a14057c4a6de4b0aa4a16fa4dc9161b57e2f", size = 8650 }, +] + +[[package]] +name = "eth-keys" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eth-typing" }, + { name = "eth-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/4a/aabe0bff4e299858845fba5598c435f2bee0646366b9635750133904e2d8/eth_keys-0.6.0.tar.gz", hash = "sha256:ba33230f851d02c894e83989185b21d76152c49b37e35b61b1d8a6d9f1d20430", size = 28944 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/ee/583612eed5d49f10bd1749d7dda9e93691ab02724b7af84830046e31c64c/eth_keys-0.6.0-py3-none-any.whl", hash = "sha256:b396fdfe048a5bba3ef3990739aec64901eb99901c03921caa774be668b1db6e", size = 21210 }, +] + +[[package]] +name = "eth-typing" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/6f/ecd98de0b67eefc68e17f6979433534a63e11aac88adaae7dede0b694567/eth_typing-5.1.0.tar.gz", hash = "sha256:8581f212ee6252aaa285377a77620f6e5f6e16ac3f144c61f098fafd47967b1a", size = 21727 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/2e/40e7577866f4378fb9737e0cb08e3e96e5a25b53821b0139dbfbd77dd66e/eth_typing-5.1.0-py3-none-any.whl", hash = "sha256:c0d6b93f5385aa84efc4b47ae2bd478da069bc0ffda8b67e0ccb573f43defd29", size = 19116 }, +] + +[[package]] +name = "eth-utils" +version = "2.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cytoolz", marker = "implementation_name == 'cpython'" }, + { name = "eth-hash" }, + { name = "eth-typing" }, + { name = "toolz", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/1f/b112416a32cae14cf986f22f85abcccec54054d3d4c699ce831faaf7bf37/eth-utils-2.2.2.tar.gz", hash = "sha256:5ca6265177ce544d9d43cdf2272ae2227e5d6d9529c270bbb707d17339087101", size = 21129 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/3d/f01836312cd8b4a8768546e78b48feb52375123e2f4343119b27e78db9b9/eth_utils-2.2.2-py3-none-any.whl", hash = "sha256:2580a8065273f62ca1ec4c175228c52e626a5f1007e965d2117e5eca1a93cae8", size = 23893 }, +] + +[[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 = "fastapi" +version = "0.110.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/22/7b9ee50b0a8c48f076a111d6e4071a9d4c25623dc67689c5f3aa375f779b/fastapi-0.110.3.tar.gz", hash = "sha256:555700b0159379e94fdbfc6bb66a0f1c43f4cf7060f25239af3d84b63a656626", size = 287508 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/d1/5958526c3bdbed74f88bf69b86506db5b25a600207f0f688473667690de6/fastapi-0.110.3-py3-none-any.whl", hash = "sha256:fd7600612f755e4050beb74001310b5a7e1796d149c2ee363124abdfa0289d32", size = 91834 }, +] + +[[package]] +name = "filelock" +version = "3.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, +] + +[[package]] +name = "fonttools" +version = "4.55.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/61/a300d1574dc381393424047c0396a0e213db212e28361123af9830d71a8d/fonttools-4.55.3.tar.gz", hash = "sha256:3983313c2a04d6cc1fe9251f8fc647754cf49a61dac6cb1e7249ae67afaafc45", size = 3498155 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/f3/9ac8c6705e4a0ff3c29e524df1caeee6f2987b02fb630129f21cc99a8212/fonttools-4.55.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1dcc07934a2165ccdc3a5a608db56fb3c24b609658a5b340aee4ecf3ba679dc0", size = 2769857 }, + { url = "https://files.pythonhosted.org/packages/d8/24/e8b8edd280bdb7d0ecc88a5d952b1dec2ee2335be71cc5a33c64871cdfe8/fonttools-4.55.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7d66c15ba875432a2d2fb419523f5d3d347f91f48f57b8b08a2dfc3c39b8a3f", size = 2299705 }, + { url = "https://files.pythonhosted.org/packages/f8/9e/e1ba20bd3b71870207fd45ca3b90208a7edd8ae3b001081dc31c45adb017/fonttools-4.55.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e4ae3592e62eba83cd2c4ccd9462dcfa603ff78e09110680a5444c6925d841", size = 4576104 }, + { url = "https://files.pythonhosted.org/packages/34/db/d423bc646e6703fe3e6aea0edd22a2df47b9d188c5f7f1b49070be4d2205/fonttools-4.55.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62d65a3022c35e404d19ca14f291c89cc5890032ff04f6c17af0bd1927299674", size = 4618282 }, + { url = "https://files.pythonhosted.org/packages/75/a0/e5062ac960a385b984ba74e7b55132e7f2c65e449e8330ab0f595407a3de/fonttools-4.55.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d342e88764fb201286d185093781bf6628bbe380a913c24adf772d901baa8276", size = 4570539 }, + { url = "https://files.pythonhosted.org/packages/1f/33/0d744ff518ebe50020b63e5018b8b278efd6a930c1d2eedda7defc42153b/fonttools-4.55.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd68c87a2bfe37c5b33bcda0fba39b65a353876d3b9006fde3adae31f97b3ef5", size = 4742411 }, + { url = "https://files.pythonhosted.org/packages/7e/6c/2f768652dba6b801f1567fc5d1829cda369bcd6e95e315a91e628f91c702/fonttools-4.55.3-cp310-cp310-win32.whl", hash = "sha256:1bc7ad24ff98846282eef1cbeac05d013c2154f977a79886bb943015d2b1b261", size = 2175132 }, + { url = "https://files.pythonhosted.org/packages/19/d1/4dcd865360fb2c499749a913fe80e41c26e8ae18629d87dfffa3de27e831/fonttools-4.55.3-cp310-cp310-win_amd64.whl", hash = "sha256:b54baf65c52952db65df39fcd4820668d0ef4766c0ccdf32879b77f7c804d5c5", size = 2219430 }, + { url = "https://files.pythonhosted.org/packages/4b/18/14be25545600bd100e5b74a3ac39089b7c1cb403dc513b7ca348be3381bf/fonttools-4.55.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c4491699bad88efe95772543cd49870cf756b019ad56294f6498982408ab03e", size = 2771005 }, + { url = "https://files.pythonhosted.org/packages/b2/51/2e1a5d3871cd7c2ae2054b54e92604e7d6abc3fd3656e9583c399648fe1c/fonttools-4.55.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5323a22eabddf4b24f66d26894f1229261021dacd9d29e89f7872dd8c63f0b8b", size = 2300654 }, + { url = "https://files.pythonhosted.org/packages/73/1a/50109bb2703bc6f774b52ea081db21edf2a9fa4b6d7485faadf9d1b997e9/fonttools-4.55.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5480673f599ad410695ca2ddef2dfefe9df779a9a5cda89503881e503c9c7d90", size = 4877541 }, + { url = "https://files.pythonhosted.org/packages/5d/52/c0b9857fa075da1b8806c5dc2d8342918a8cc2065fd14fbddb3303282693/fonttools-4.55.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da9da6d65cd7aa6b0f806556f4985bcbf603bf0c5c590e61b43aa3e5a0f822d0", size = 4906304 }, + { url = "https://files.pythonhosted.org/packages/0b/1b/55f85c7e962d295e456d5209581c919620ee3e877b95cd86245187a5050f/fonttools-4.55.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e894b5bd60d9f473bed7a8f506515549cc194de08064d829464088d23097331b", size = 4888087 }, + { url = "https://files.pythonhosted.org/packages/83/13/6f2809c612ea2ac51391f92468ff861c63473601530fca96458b453212bf/fonttools-4.55.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aee3b57643827e237ff6ec6d28d9ff9766bd8b21e08cd13bff479e13d4b14765", size = 5056958 }, + { url = "https://files.pythonhosted.org/packages/c1/28/d0ea9e872fa4208b9dfca686e1dd9ca22f6c9ef33ecff2f0ebc2dbe7c29b/fonttools-4.55.3-cp311-cp311-win32.whl", hash = "sha256:eb6ca911c4c17eb51853143624d8dc87cdcdf12a711fc38bf5bd21521e79715f", size = 2173939 }, + { url = "https://files.pythonhosted.org/packages/be/36/d74ae1020bc41a1dff3e6f5a99f646563beecb97e386d27abdac3ba07650/fonttools-4.55.3-cp311-cp311-win_amd64.whl", hash = "sha256:6314bf82c54c53c71805318fcf6786d986461622dd926d92a465199ff54b1b72", size = 2220363 }, + { url = "https://files.pythonhosted.org/packages/89/58/fbcf5dff7e3ea844bb00c4d806ca1e339e1f2dce5529633bf4842c0c9a1f/fonttools-4.55.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f9e736f60f4911061235603a6119e72053073a12c6d7904011df2d8fad2c0e35", size = 2765380 }, + { url = "https://files.pythonhosted.org/packages/81/dd/da6e329e51919b4f421c8738f3497e2ab08c168e76aaef7b6d5351862bdf/fonttools-4.55.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a8aa2c5e5b8b3bcb2e4538d929f6589a5c6bdb84fd16e2ed92649fb5454f11c", size = 2297940 }, + { url = "https://files.pythonhosted.org/packages/00/44/f5ee560858425c99ef07e04919e736db09d6416408e5a8d3bbfb4a6623fd/fonttools-4.55.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07f8288aacf0a38d174445fc78377a97fb0b83cfe352a90c9d9c1400571963c7", size = 4793327 }, + { url = "https://files.pythonhosted.org/packages/24/da/0a001926d791c55e29ac3c52964957a20dbc1963615446b568b7432891c3/fonttools-4.55.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8d5e8916c0970fbc0f6f1bece0063363bb5857a7f170121a4493e31c3db3314", size = 4865624 }, + { url = "https://files.pythonhosted.org/packages/3d/d8/1edd8b13a427a9fb6418373437caa586c0caa57f260af8e0548f4d11e340/fonttools-4.55.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae3b6600565b2d80b7c05acb8e24d2b26ac407b27a3f2e078229721ba5698427", size = 4774166 }, + { url = "https://files.pythonhosted.org/packages/9c/ec/ade054097976c3d6debc9032e09a351505a0196aa5493edf021be376f75e/fonttools-4.55.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:54153c49913f45065c8d9e6d0c101396725c5621c8aee744719300f79771d75a", size = 5001832 }, + { url = "https://files.pythonhosted.org/packages/e2/cd/233f0e31ad799bb91fc78099c8b4e5ec43b85a131688519640d6bae46f6a/fonttools-4.55.3-cp312-cp312-win32.whl", hash = "sha256:827e95fdbbd3e51f8b459af5ea10ecb4e30af50221ca103bea68218e9615de07", size = 2162228 }, + { url = "https://files.pythonhosted.org/packages/46/45/a498b5291f6c0d91b2394b1ed7447442a57d1c9b9cf8f439aee3c316a56e/fonttools-4.55.3-cp312-cp312-win_amd64.whl", hash = "sha256:e6e8766eeeb2de759e862004aa11a9ea3d6f6d5ec710551a88b476192b64fd54", size = 2209118 }, + { url = "https://files.pythonhosted.org/packages/9c/9f/00142a19bad96eeeb1aed93f567adc19b7f2c1af6f5bc0a1c3de90b4b1ac/fonttools-4.55.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a430178ad3e650e695167cb53242dae3477b35c95bef6525b074d87493c4bf29", size = 2752812 }, + { url = "https://files.pythonhosted.org/packages/b0/20/14b8250d63ba65e162091fb0dda07730f90c303bbf5257e9ddacec7230d9/fonttools-4.55.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:529cef2ce91dc44f8e407cc567fae6e49a1786f2fefefa73a294704c415322a4", size = 2291521 }, + { url = "https://files.pythonhosted.org/packages/34/47/a681cfd10245eb74f65e491a934053ec75c4af639655446558f29818e45e/fonttools-4.55.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e75f12c82127486fac2d8bfbf5bf058202f54bf4f158d367e41647b972342ca", size = 4770980 }, + { url = "https://files.pythonhosted.org/packages/d2/6c/a7066afc19db0705a12efd812e19c32cde2b9514eb714659522f2ebd60b6/fonttools-4.55.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:859c358ebf41db18fb72342d3080bce67c02b39e86b9fbcf1610cca14984841b", size = 4845534 }, + { url = "https://files.pythonhosted.org/packages/0c/a2/3c204fbabbfd845d9bdcab9ae35279d41e9a4bf5c80a0a2708f9c5a195d6/fonttools-4.55.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:546565028e244a701f73df6d8dd6be489d01617863ec0c6a42fa25bf45d43048", size = 4753910 }, + { url = "https://files.pythonhosted.org/packages/6e/8c/b4cb3592880340b89e4ef6601b531780bba73862332a6451d78fe135d6cb/fonttools-4.55.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aca318b77f23523309eec4475d1fbbb00a6b133eb766a8bdc401faba91261abe", size = 4976411 }, + { url = "https://files.pythonhosted.org/packages/fc/a8/4bf98840ff89fcc188470b59daec57322178bf36d2f4f756cd19a42a826b/fonttools-4.55.3-cp313-cp313-win32.whl", hash = "sha256:8c5ec45428edaa7022f1c949a632a6f298edc7b481312fc7dc258921e9399628", size = 2160178 }, + { url = "https://files.pythonhosted.org/packages/e6/57/4cc35004605416df3225ff362f3455cf09765db00df578ae9e46d0fefd23/fonttools-4.55.3-cp313-cp313-win_amd64.whl", hash = "sha256:11e5de1ee0d95af4ae23c1a138b184b7f06e0b6abacabf1d0db41c90b03d834b", size = 2206102 }, + { url = "https://files.pythonhosted.org/packages/99/3b/406d17b1f63e04a82aa621936e6e1c53a8c05458abd66300ac85ea7f9ae9/fonttools-4.55.3-py3-none-any.whl", hash = "sha256:f412604ccbeee81b091b420272841e5ec5ef68967a9790e80bffd0e30b8e2977", size = 1111638 }, +] + +[[package]] +name = "frozenlist" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/79/29d44c4af36b2b240725dce566b20f63f9b36ef267aaaa64ee7466f4f2f8/frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a", size = 94451 }, + { url = "https://files.pythonhosted.org/packages/47/47/0c999aeace6ead8a44441b4f4173e2261b18219e4ad1fe9a479871ca02fc/frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb", size = 54301 }, + { url = "https://files.pythonhosted.org/packages/8d/60/107a38c1e54176d12e06e9d4b5d755b677d71d1219217cee063911b1384f/frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec", size = 52213 }, + { url = "https://files.pythonhosted.org/packages/17/62/594a6829ac5679c25755362a9dc93486a8a45241394564309641425d3ff6/frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5", size = 240946 }, + { url = "https://files.pythonhosted.org/packages/7e/75/6c8419d8f92c80dd0ee3f63bdde2702ce6398b0ac8410ff459f9b6f2f9cb/frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76", size = 264608 }, + { url = "https://files.pythonhosted.org/packages/88/3e/82a6f0b84bc6fb7e0be240e52863c6d4ab6098cd62e4f5b972cd31e002e8/frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17", size = 261361 }, + { url = "https://files.pythonhosted.org/packages/fd/85/14e5f9ccac1b64ff2f10c927b3ffdf88772aea875882406f9ba0cec8ad84/frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba", size = 231649 }, + { url = "https://files.pythonhosted.org/packages/ee/59/928322800306f6529d1852323014ee9008551e9bb027cc38d276cbc0b0e7/frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d", size = 241853 }, + { url = "https://files.pythonhosted.org/packages/7d/bd/e01fa4f146a6f6c18c5d34cab8abdc4013774a26c4ff851128cd1bd3008e/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2", size = 243652 }, + { url = "https://files.pythonhosted.org/packages/a5/bd/e4771fd18a8ec6757033f0fa903e447aecc3fbba54e3630397b61596acf0/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f", size = 241734 }, + { url = "https://files.pythonhosted.org/packages/21/13/c83821fa5544af4f60c5d3a65d054af3213c26b14d3f5f48e43e5fb48556/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c", size = 260959 }, + { url = "https://files.pythonhosted.org/packages/71/f3/1f91c9a9bf7ed0e8edcf52698d23f3c211d8d00291a53c9f115ceb977ab1/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab", size = 262706 }, + { url = "https://files.pythonhosted.org/packages/4c/22/4a256fdf5d9bcb3ae32622c796ee5ff9451b3a13a68cfe3f68e2c95588ce/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5", size = 250401 }, + { url = "https://files.pythonhosted.org/packages/af/89/c48ebe1f7991bd2be6d5f4ed202d94960c01b3017a03d6954dd5fa9ea1e8/frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb", size = 45498 }, + { url = "https://files.pythonhosted.org/packages/28/2f/cc27d5f43e023d21fe5c19538e08894db3d7e081cbf582ad5ed366c24446/frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4", size = 51622 }, + { url = "https://files.pythonhosted.org/packages/79/43/0bed28bf5eb1c9e4301003b74453b8e7aa85fb293b31dde352aac528dafc/frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30", size = 94987 }, + { url = "https://files.pythonhosted.org/packages/bb/bf/b74e38f09a246e8abbe1e90eb65787ed745ccab6eaa58b9c9308e052323d/frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5", size = 54584 }, + { url = "https://files.pythonhosted.org/packages/2c/31/ab01375682f14f7613a1ade30149f684c84f9b8823a4391ed950c8285656/frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778", size = 52499 }, + { url = "https://files.pythonhosted.org/packages/98/a8/d0ac0b9276e1404f58fec3ab6e90a4f76b778a49373ccaf6a563f100dfbc/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a", size = 276357 }, + { url = "https://files.pythonhosted.org/packages/ad/c9/c7761084fa822f07dac38ac29f841d4587570dd211e2262544aa0b791d21/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869", size = 287516 }, + { url = "https://files.pythonhosted.org/packages/a1/ff/cd7479e703c39df7bdab431798cef89dc75010d8aa0ca2514c5b9321db27/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d", size = 283131 }, + { url = "https://files.pythonhosted.org/packages/59/a0/370941beb47d237eca4fbf27e4e91389fd68699e6f4b0ebcc95da463835b/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45", size = 261320 }, + { url = "https://files.pythonhosted.org/packages/b8/5f/c10123e8d64867bc9b4f2f510a32042a306ff5fcd7e2e09e5ae5100ee333/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d", size = 274877 }, + { url = "https://files.pythonhosted.org/packages/fa/79/38c505601ae29d4348f21706c5d89755ceded02a745016ba2f58bd5f1ea6/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3", size = 269592 }, + { url = "https://files.pythonhosted.org/packages/19/e2/39f3a53191b8204ba9f0bb574b926b73dd2efba2a2b9d2d730517e8f7622/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a", size = 265934 }, + { url = "https://files.pythonhosted.org/packages/d5/c9/3075eb7f7f3a91f1a6b00284af4de0a65a9ae47084930916f5528144c9dd/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9", size = 283859 }, + { url = "https://files.pythonhosted.org/packages/05/f5/549f44d314c29408b962fa2b0e69a1a67c59379fb143b92a0a065ffd1f0f/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2", size = 287560 }, + { url = "https://files.pythonhosted.org/packages/9d/f8/cb09b3c24a3eac02c4c07a9558e11e9e244fb02bf62c85ac2106d1eb0c0b/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf", size = 277150 }, + { url = "https://files.pythonhosted.org/packages/37/48/38c2db3f54d1501e692d6fe058f45b6ad1b358d82cd19436efab80cfc965/frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942", size = 45244 }, + { url = "https://files.pythonhosted.org/packages/ca/8c/2ddffeb8b60a4bce3b196c32fcc30d8830d4615e7b492ec2071da801b8ad/frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d", size = 51634 }, + { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, + { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, + { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, + { url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 }, + { url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 }, + { url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 }, + { url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 }, + { url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 }, + { url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 }, + { url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 }, + { url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 }, + { url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 }, + { url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 }, + { url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 }, + { url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 }, + { url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 }, + { url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 }, + { url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 }, + { url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 }, + { url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 }, + { url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 }, + { url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 }, + { url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 }, + { url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 }, + { url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 }, + { url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 }, + { url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 }, + { url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 }, + { url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 }, + { url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 }, + { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, +] + +[[package]] +name = "fsspec" +version = "2024.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/62/7c/12b0943011daaaa9c35c2a2e22e5eb929ac90002f08f1259d69aedad84de/fsspec-2024.9.0.tar.gz", hash = "sha256:4b0afb90c2f21832df142f292649035d80b421f60a9e1c027802e5a0da2b04e8", size = 286206 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/a0/6aaea0c2fbea2f89bfd5db25fb1e3481896a423002ebe4e55288907a97a3/fsspec-2024.9.0-py3-none-any.whl", hash = "sha256:a0947d552d8a6efa72cc2c730b12c41d043509156966cca4fb157b0f2a0c574b", size = 179253 }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "ftfy" +version = "6.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/d3/8650919bc3c7c6e90ee3fa7fd618bf373cbbe55dff043bd67353dbb20cd8/ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec", size = 308927 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821 }, +] + +[[package]] +name = "fuzzywuzzy" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/4b/0a002eea91be6048a2b5d53c5f1b4dafd57ba2e36eea961d05086d7c28ce/fuzzywuzzy-0.18.0.tar.gz", hash = "sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8", size = 28888 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ff/74f23998ad2f93b945c0309f825be92e04e0348e062026998b5eefef4c33/fuzzywuzzy-0.18.0-py2.py3-none-any.whl", hash = "sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993", size = 18272 }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794 }, +] + +[[package]] +name = "gitpython" +version = "3.1.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/89/37df0b71473153574a5cdef8f242de422a0f5d26d7a9e231e6f169b4ad14/gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269", size = 214196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599 }, +] + +[[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 = "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 = "huggingface-hub" +version = "0.27.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/e1/d2/d6976de7542792fc077b498d64af64882b6d8bb40679284ec0bff77d5929/huggingface_hub-0.27.1.tar.gz", hash = "sha256:c004463ca870283909d715d20f066ebd6968c2207dae9393fdffb3c1d4d8f98b", size = 379407 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/3f/50f6b25fafdcfb1c089187a328c95081abf882309afd86f4053951507cd1/huggingface_hub-0.27.1-py3-none-any.whl", hash = "sha256:1c5155ca7d60b60c2e2fc38cbb3ffb7f7c3adf48f824015b219af9061771daec", size = 450658 }, +] + +[[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 = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "jinja2" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, +] + +[[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 = "jsonpatch" +version = "1.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpointer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898 }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/5f/4d8e9e852d98ecd26cdf8eaf7ed8bc33174033bba5e07001b289f07308fd/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db", size = 124623 }, + { url = "https://files.pythonhosted.org/packages/1d/70/7f5af2a18a76fe92ea14675f8bd88ce53ee79e37900fa5f1a1d8e0b42998/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b", size = 66720 }, + { url = "https://files.pythonhosted.org/packages/c6/13/e15f804a142353aefd089fadc8f1d985561a15358c97aca27b0979cb0785/kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d", size = 65413 }, + { url = "https://files.pythonhosted.org/packages/ce/6d/67d36c4d2054e83fb875c6b59d0809d5c530de8148846b1370475eeeece9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d", size = 1650826 }, + { url = "https://files.pythonhosted.org/packages/de/c6/7b9bb8044e150d4d1558423a1568e4f227193662a02231064e3824f37e0a/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c", size = 1628231 }, + { url = "https://files.pythonhosted.org/packages/b6/38/ad10d437563063eaaedbe2c3540a71101fc7fb07a7e71f855e93ea4de605/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3", size = 1408938 }, + { url = "https://files.pythonhosted.org/packages/52/ce/c0106b3bd7f9e665c5f5bc1e07cc95b5dabd4e08e3dad42dbe2faad467e7/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed", size = 1422799 }, + { url = "https://files.pythonhosted.org/packages/d0/87/efb704b1d75dc9758087ba374c0f23d3254505edaedd09cf9d247f7878b9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f", size = 1354362 }, + { url = "https://files.pythonhosted.org/packages/eb/b3/fd760dc214ec9a8f208b99e42e8f0130ff4b384eca8b29dd0efc62052176/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff", size = 2222695 }, + { url = "https://files.pythonhosted.org/packages/a2/09/a27fb36cca3fc01700687cc45dae7a6a5f8eeb5f657b9f710f788748e10d/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d", size = 2370802 }, + { url = "https://files.pythonhosted.org/packages/3d/c3/ba0a0346db35fe4dc1f2f2cf8b99362fbb922d7562e5f911f7ce7a7b60fa/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c", size = 2334646 }, + { url = "https://files.pythonhosted.org/packages/41/52/942cf69e562f5ed253ac67d5c92a693745f0bed3c81f49fc0cbebe4d6b00/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605", size = 2467260 }, + { url = "https://files.pythonhosted.org/packages/32/26/2d9668f30d8a494b0411d4d7d4ea1345ba12deb6a75274d58dd6ea01e951/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e", size = 2288633 }, + { url = "https://files.pythonhosted.org/packages/98/99/0dd05071654aa44fe5d5e350729961e7bb535372935a45ac89a8924316e6/kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751", size = 71885 }, + { url = "https://files.pythonhosted.org/packages/6c/fc/822e532262a97442989335394d441cd1d0448c2e46d26d3e04efca84df22/kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271", size = 65175 }, + { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635 }, + { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717 }, + { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413 }, + { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994 }, + { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804 }, + { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690 }, + { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839 }, + { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109 }, + { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269 }, + { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468 }, + { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394 }, + { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901 }, + { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306 }, + { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966 }, + { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311 }, + { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152 }, + { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555 }, + { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067 }, + { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443 }, + { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728 }, + { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388 }, + { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849 }, + { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533 }, + { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898 }, + { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605 }, + { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801 }, + { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077 }, + { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410 }, + { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853 }, + { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424 }, + { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156 }, + { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555 }, + { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071 }, + { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053 }, + { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278 }, + { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139 }, + { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517 }, + { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952 }, + { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132 }, + { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997 }, + { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060 }, + { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471 }, + { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793 }, + { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855 }, + { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430 }, + { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294 }, + { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736 }, + { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194 }, + { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942 }, + { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341 }, + { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455 }, + { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138 }, + { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857 }, + { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129 }, + { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538 }, + { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661 }, + { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710 }, + { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213 }, + { url = "https://files.pythonhosted.org/packages/1f/f9/ae81c47a43e33b93b0a9819cac6723257f5da2a5a60daf46aa5c7226ea85/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a", size = 60403 }, + { url = "https://files.pythonhosted.org/packages/58/ca/f92b5cb6f4ce0c1ebfcfe3e2e42b96917e16f7090e45b21102941924f18f/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8", size = 58657 }, + { url = "https://files.pythonhosted.org/packages/80/28/ae0240f732f0484d3a4dc885d055653c47144bdf59b670aae0ec3c65a7c8/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0", size = 84948 }, + { url = "https://files.pythonhosted.org/packages/5d/eb/78d50346c51db22c7203c1611f9b513075f35c4e0e4877c5dde378d66043/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c", size = 81186 }, + { url = "https://files.pythonhosted.org/packages/43/f8/7259f18c77adca88d5f64f9a522792e178b2691f3748817a8750c2d216ef/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b", size = 80279 }, + { url = "https://files.pythonhosted.org/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762 }, +] + +[[package]] +name = "langchain" +version = "0.0.27" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", +] +dependencies = [ + { name = "numpy", marker = "python_full_version < '3.12'" }, + { name = "pydantic", marker = "python_full_version < '3.12'" }, + { name = "pyyaml", marker = "python_full_version < '3.12'" }, + { name = "requests", marker = "python_full_version < '3.12'" }, + { name = "sqlalchemy", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/53/a975d63f52873daeb2563621240a79ea1368a85a3d94b4231cf750a85048/langchain-0.0.27.tar.gz", hash = "sha256:cdaca3a61eb57a76bef990ce3c4615ac06873e83f100bf9c485f6c36e29ed95c", size = 72304 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/fd/f2aa39f8e63a6fbacf2e7be820b846c27b1e5830af9c2e2e208801b6c07f/langchain-0.0.27-py3-none-any.whl", hash = "sha256:a39260732b877b223869151db677c08b4b78fb4611c59fc5918f314d0a9d4227", size = 124889 }, +] + +[[package]] +name = "langchain" +version = "0.3.14" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", +] +dependencies = [ + { name = "aiohttp", marker = "python_full_version >= '3.12'" }, + { name = "langchain-core", marker = "python_full_version >= '3.12'" }, + { name = "langchain-text-splitters", marker = "python_full_version >= '3.12'" }, + { name = "langsmith", marker = "python_full_version >= '3.12'" }, + { name = "numpy", marker = "python_full_version >= '3.12'" }, + { name = "pydantic", marker = "python_full_version >= '3.12'" }, + { name = "pyyaml", marker = "python_full_version >= '3.12'" }, + { name = "requests", marker = "python_full_version >= '3.12'" }, + { name = "sqlalchemy", marker = "python_full_version >= '3.12'" }, + { name = "tenacity", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/35/2adb0693acc149e462bc0e7856ecd58096c285f66a78bc44fc2b8ae91ce0/langchain-0.3.14.tar.gz", hash = "sha256:4a5ae817b5832fa0e1fcadc5353fbf74bebd2f8e550294d4dc039f651ddcd3d1", size = 420409 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/a8/0a8f868615b7a30636b1d15b718e3ea9875bf0dccced03583477c2372495/langchain-0.3.14-py3-none-any.whl", hash = "sha256:5df9031702f7fe6c956e84256b4639a46d5d03a75be1ca4c1bc9479b358061a2", size = 1009213 }, +] + +[[package]] +name = "langchain-core" +version = "0.3.29" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpatch" }, + { name = "langsmith" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "tenacity" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/05/8fc844c2f79a2c30845de2db948de1cf7c17b94f419f7ae7b616d628a54f/langchain_core-0.3.29.tar.gz", hash = "sha256:773d6aeeb612e7ce3d996c0be403433d8c6a91e77bbb7a7461c13e15cfbe5b06", size = 330785 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/4f/fe1de63f6fc1ac7af3ba4ae12d420af1a19f7893b5fcb72856b9fc67f650/langchain_core-0.3.29-py3-none-any.whl", hash = "sha256:817db1474871611a81105594a3e4d11704949661008e455a10e38ca9ff601a1a", size = 411593 }, +] + +[[package]] +name = "langchain-openai" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "openai" }, + { name = "tiktoken" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/fd/8256eba9a159f95a13c5bf7f1f49683de93b3876585b768e6be5dc3a5765/langchain_openai-0.2.14.tar.gz", hash = "sha256:7a514f309e356b182a337c0ed36ab3fbe34d9834a235a3b85cb7f91ae775d978", size = 43647 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/54/63c8264d7dbc3bf31ba61bf97740fdd76386b2d4f9a58f58afd3961ce7d7/langchain_openai-0.2.14-py3-none-any.whl", hash = "sha256:d232496662f79ece9a11caf7d798ba863e559c771bc366814f7688e0fe664fe8", size = 50876 }, +] + +[[package]] +name = "langchain-text-splitters" +version = "0.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/35/a6f8d6b1bb0e6e8c00b49bce4d1a115f8b68368b1899f65bb34dbbb44160/langchain_text_splitters-0.3.5.tar.gz", hash = "sha256:11cb7ca3694e5bdd342bc16d3875b7f7381651d4a53cbb91d34f22412ae16443", size = 26318 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/83/f8081c3bea416bd9d9f0c26af795c74f42c24f9ad3c4fbf361b7d69de134/langchain_text_splitters-0.3.5-py3-none-any.whl", hash = "sha256:8c9b059827438c5fa8f327b4df857e307828a5ec815163c9b5c9569a3e82c8ee", size = 31620 }, +] + +[[package]] +name = "langsmith" +version = "0.2.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "requests-toolbelt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/f8/d642c101267101ddeb44f898a98a5700f90c8959a4119c341a9678fe18cc/langsmith-0.2.10.tar.gz", hash = "sha256:153c7b3ccbd823528ff5bec84801e7e50a164e388919fc583252df5b27dd7830", size = 314154 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/91/e72d13f6b57a0ea9d884ab1d3388f544d7fe3354dbe1d4dd67678693a9fd/langsmith-0.2.10-py3-none-any.whl", hash = "sha256:b02f2f174189ff72e54c88b1aa63343defd6f0f676c396a690c63a4b6495dcc2", size = 326432 }, +] + +[[package]] +name = "levenshtein" +version = "0.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rapidfuzz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/e6/79807d3b59a67dd78bb77072ca6a28d8db0935161fecf935e6c38c5f6825/levenshtein-0.26.1.tar.gz", hash = "sha256:0d19ba22330d50609b2349021ec3cf7d905c6fe21195a2d0d876a146e7ed2575", size = 374307 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/ae/af5f9e9f06052719df6af46d7a7fee3675fd2dea0e2845cc0f4968cf853f/levenshtein-0.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8dc4a4aecad538d944a1264c12769c99e3c0bf8e741fc5e454cc954913befb2e", size = 177032 }, + { url = "https://files.pythonhosted.org/packages/bb/a6/be36c1d43cccd032b359ba2fa66dd299bac0cd226f263672332738535553/levenshtein-0.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec108f368c12b25787c8b1a4537a1452bc53861c3ee4abc810cc74098278edcd", size = 157539 }, + { url = "https://files.pythonhosted.org/packages/d1/76/13df26b47c53db1cf01c40bae1483b13919d6eab12cede3b93b018927229/levenshtein-0.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69229d651c97ed5b55b7ce92481ed00635cdbb80fbfb282a22636e6945dc52d5", size = 153298 }, + { url = "https://files.pythonhosted.org/packages/f2/d9/c02fd7ec98d55df51c643d0475b859fab19a974eb44e5ca72f642dbfeffd/levenshtein-0.26.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79dcd157046d62482a7719b08ba9e3ce9ed3fc5b015af8ea989c734c702aedd4", size = 186766 }, + { url = "https://files.pythonhosted.org/packages/7a/71/44adaafadc5c93845048b88426ab5e2a8414efce7026478cad115fd08f92/levenshtein-0.26.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f53f9173ae21b650b4ed8aef1d0ad0c37821f367c221a982f4d2922b3044e0d", size = 187546 }, + { url = "https://files.pythonhosted.org/packages/2d/7e/24593d50e9e0911c96631a123760b96d1dabbcf1fc55a300648d4f0240dd/levenshtein-0.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3956f3c5c229257dbeabe0b6aacd2c083ebcc1e335842a6ff2217fe6cc03b6b", size = 162601 }, + { url = "https://files.pythonhosted.org/packages/54/98/2285860f07c519af3bb1af29cc4a51c3fd8c028836887615c776f6bb28d4/levenshtein-0.26.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1e83af732726987d2c4cd736f415dae8b966ba17b7a2239c8b7ffe70bfb5543", size = 249164 }, + { url = "https://files.pythonhosted.org/packages/28/f7/87008ca57377f2f296a3b9b87b46fa80a4a471c1d3de3ea4ff37acc65b5a/levenshtein-0.26.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4f052c55046c2a9c9b5f742f39e02fa6e8db8039048b8c1c9e9fdd27c8a240a1", size = 1077613 }, + { url = "https://files.pythonhosted.org/packages/7d/ca/5f2b3c4b181f4e97805ee839c47cb99c8048bf7934358af8c3d6a07fb6c2/levenshtein-0.26.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9895b3a98f6709e293615fde0dcd1bb0982364278fa2072361a1a31b3e388b7a", size = 1331030 }, + { url = "https://files.pythonhosted.org/packages/b3/f4/de5a779d178e489906fd39d7b2bdb782f80a98affc57e9d40a723b9ee89c/levenshtein-0.26.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a3777de1d8bfca054465229beed23994f926311ce666f5a392c8859bb2722f16", size = 1207001 }, + { url = "https://files.pythonhosted.org/packages/f8/61/78b25ef514a23735ae0baf230af668f16d6f5e1466c4db72a4de0e233768/levenshtein-0.26.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:81c57e1135c38c5e6e3675b5e2077d8a8d3be32bf0a46c57276c092b1dffc697", size = 1355999 }, + { url = "https://files.pythonhosted.org/packages/b9/e8/a488dbb99726e08ac05ad3359e7db79e35c2c4e4bafbaaf081ae140c7de3/levenshtein-0.26.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:91d5e7d984891df3eff7ea9fec8cf06fdfacc03cd074fd1a410435706f73b079", size = 1135174 }, + { url = "https://files.pythonhosted.org/packages/52/c1/79693b33ab4c5ba04df8b4d116c2ae4cfaa71e08b2cf2b8cd93d5fa37b07/levenshtein-0.26.1-cp310-cp310-win32.whl", hash = "sha256:f48abff54054b4142ad03b323e80aa89b1d15cabc48ff49eb7a6ff7621829a56", size = 87111 }, + { url = "https://files.pythonhosted.org/packages/e6/ed/5250c0891f6a99e41e715ce379b77863d66356eae7519e3626514f2729b6/levenshtein-0.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:79dd6ad799784ea7b23edd56e3bf94b3ca866c4c6dee845658ee75bb4aefdabf", size = 98062 }, + { url = "https://files.pythonhosted.org/packages/4f/b3/58f69cbd9f21fe7ec54a71059b3e8fdb37c43781b31a36f49c973bd387c5/levenshtein-0.26.1-cp310-cp310-win_arm64.whl", hash = "sha256:3351ddb105ef010cc2ce474894c5d213c83dddb7abb96400beaa4926b0b745bd", size = 87976 }, + { url = "https://files.pythonhosted.org/packages/af/b4/86e447173ca8d936b7ef270d21952a0053e799040e73b843a4a5ac9a15a1/levenshtein-0.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:44c51f5d33b3cfb9db518b36f1288437a509edd82da94c4400f6a681758e0cb6", size = 177037 }, + { url = "https://files.pythonhosted.org/packages/27/b3/e15e14e5836dfc23ed014c21b307cbf77b3c6fd75e11d0675ce9a0d43b31/levenshtein-0.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56b93203e725f9df660e2afe3d26ba07d71871b6d6e05b8b767e688e23dfb076", size = 157478 }, + { url = "https://files.pythonhosted.org/packages/32/f1/f4d0904c5074e4e9d33dcaf304144e02eae9eec9d61b63bf17b1108ce228/levenshtein-0.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:270d36c5da04a0d89990660aea8542227cbd8f5bc34e9fdfadd34916ff904520", size = 153873 }, + { url = "https://files.pythonhosted.org/packages/f9/0d/cd5abe809421ce0d4a2cae60fd2fdf62cb43890068515a8a0069e2b17894/levenshtein-0.26.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:480674c05077eeb0b0f748546d4fcbb386d7c737f9fff0010400da3e8b552942", size = 186850 }, + { url = "https://files.pythonhosted.org/packages/a8/69/03f4266ad83781f2602b1976a2e5a98785c148f9bfc77c343e5aa1840f64/levenshtein-0.26.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13946e37323728695ba7a22f3345c2e907d23f4600bc700bf9b4352fb0c72a48", size = 187527 }, + { url = "https://files.pythonhosted.org/packages/36/fa/ec3be1162b1a757f80e713220470fe5b4db22e23f886f50ac59a48f0a84d/levenshtein-0.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceb673f572d1d0dc9b1cd75792bb8bad2ae8eb78a7c6721e23a3867d318cb6f2", size = 162673 }, + { url = "https://files.pythonhosted.org/packages/9e/d6/dc8358b6a4174f413532aa27463dc4d167ac25742826f58916bb6e6417b1/levenshtein-0.26.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42d6fa242e3b310ce6bfd5af0c83e65ef10b608b885b3bb69863c01fb2fcff98", size = 250413 }, + { url = "https://files.pythonhosted.org/packages/57/5e/a87bf39686482a1df000fdc265fdd812f0cd316d5fb0a25f52654504a82b/levenshtein-0.26.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b8b68295808893a81e0a1dbc2274c30dd90880f14d23078e8eb4325ee615fc68", size = 1078713 }, + { url = "https://files.pythonhosted.org/packages/c5/04/30ab2f27c4ff7d6d98b3bb6bf8541521535ad2d05e50ac8fd00ab701c080/levenshtein-0.26.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b01061d377d1944eb67bc40bef5d4d2f762c6ab01598efd9297ce5d0047eb1b5", size = 1331174 }, + { url = "https://files.pythonhosted.org/packages/e4/68/9c7f60ccb097a86420d058dcc3f575e6b3d663b3a5cde3651443f7087e14/levenshtein-0.26.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9d12c8390f156745e533d01b30773b9753e41d8bbf8bf9dac4b97628cdf16314", size = 1207733 }, + { url = "https://files.pythonhosted.org/packages/64/21/222f54a1a654eca1c1cd015d32d972d70529eb218d469d516f13eac2149d/levenshtein-0.26.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:48825c9f967f922061329d1481b70e9fee937fc68322d6979bc623f69f75bc91", size = 1356116 }, + { url = "https://files.pythonhosted.org/packages/6f/65/681dced2fa798ea7882bff5682ab566689a4920006ed9aca4fd8d1edb2d2/levenshtein-0.26.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d8ec137170b95736842f99c0e7a9fd8f5641d0c1b63b08ce027198545d983e2b", size = 1135459 }, + { url = "https://files.pythonhosted.org/packages/a1/e8/1ff8a634c428ed908d20482f77491cca08fa16c96738ad82d9219da138a1/levenshtein-0.26.1-cp311-cp311-win32.whl", hash = "sha256:798f2b525a2e90562f1ba9da21010dde0d73730e277acaa5c52d2a6364fd3e2a", size = 87265 }, + { url = "https://files.pythonhosted.org/packages/8f/fb/44e9747558a7381ea6736e10ac2f871414007915afb94efac423e68cf441/levenshtein-0.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:55b1024516c59df55f1cf1a8651659a568f2c5929d863d3da1ce8893753153bd", size = 98518 }, + { url = "https://files.pythonhosted.org/packages/04/90/c476a74d8ec25d680b9cbf51966d638623a82a2fd4e99b988a383f22a681/levenshtein-0.26.1-cp311-cp311-win_arm64.whl", hash = "sha256:e52575cbc6b9764ea138a6f82d73d3b1bc685fe62e207ff46a963d4c773799f6", size = 88086 }, + { url = "https://files.pythonhosted.org/packages/4c/53/3685ee7fbe9b8eb4b82d8045255e59dd6943f94e8091697ef3808e7ecf63/levenshtein-0.26.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cc741ca406d3704dc331a69c04b061fc952509a069b79cab8287413f434684bd", size = 176447 }, + { url = "https://files.pythonhosted.org/packages/82/7f/7d6fe9b76bd030200f8f9b162f3de862d597804d292af292ec3ce9ae8bee/levenshtein-0.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:821ace3b4e1c2e02b43cf5dc61aac2ea43bdb39837ac890919c225a2c3f2fea4", size = 157589 }, + { url = "https://files.pythonhosted.org/packages/bc/d3/44539e952df93c5d88a95a0edff34af38e4f87330a76e8335bfe2c0f31bf/levenshtein-0.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92694c9396f55d4c91087efacf81297bef152893806fc54c289fc0254b45384", size = 153306 }, + { url = "https://files.pythonhosted.org/packages/ba/fe/21443c0c50824314e2d2ce7e1e9cd11d21b3643f3c14da156b15b4d399c7/levenshtein-0.26.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51ba374de7a1797d04a14a4f0ad3602d2d71fef4206bb20a6baaa6b6a502da58", size = 184409 }, + { url = "https://files.pythonhosted.org/packages/f0/7b/c95066c64bb18628cf7488e0dd6aec2b7cbda307d93ba9ede68a21af2a7b/levenshtein-0.26.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7aa5c3327dda4ef952769bacec09c09ff5bf426e07fdc94478c37955681885b", size = 193134 }, + { url = "https://files.pythonhosted.org/packages/36/22/5f9760b135bdefb8cf8d663890756136754db03214f929b73185dfa33f05/levenshtein-0.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e2517e8d3c221de2d1183f400aed64211fcfc77077b291ed9f3bb64f141cdc", size = 162266 }, + { url = "https://files.pythonhosted.org/packages/11/50/6b1a5f3600caae40db0928f6775d7efc62c13dec2407d3d540bc4afdb72c/levenshtein-0.26.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9092b622765c7649dd1d8af0f43354723dd6f4e570ac079ffd90b41033957438", size = 246339 }, + { url = "https://files.pythonhosted.org/packages/26/eb/ede282fcb495570898b39a0d2f21bbc9be5587d604c93a518ece80f3e7dc/levenshtein-0.26.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fc16796c85d7d8b259881d59cc8b5e22e940901928c2ff6924b2c967924e8a0b", size = 1077937 }, + { url = "https://files.pythonhosted.org/packages/35/41/eebe1c4a75f592d9bdc3c2595418f083bcad747e0aec52a1a9ffaae93f5c/levenshtein-0.26.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4370733967f5994ceeed8dc211089bedd45832ee688cecea17bfd35a9eb22b9", size = 1330607 }, + { url = "https://files.pythonhosted.org/packages/12/8e/4d34b1857adfd69c2a72d84bca1b8538d4cfaaf6fddd8599573f4281a9d1/levenshtein-0.26.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3535ecfd88c9b283976b5bc61265855f59bba361881e92ed2b5367b6990c93fe", size = 1197505 }, + { url = "https://files.pythonhosted.org/packages/c0/7b/6afcda1b0a0622cedaa4f7a5b3507c2384a7358fc051ccf619e5d2453bf2/levenshtein-0.26.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:90236e93d98bdfd708883a6767826fafd976dac8af8fc4a0fb423d4fa08e1bf0", size = 1352832 }, + { url = "https://files.pythonhosted.org/packages/21/5e/0ed4e7b5c820b6bc40e2c391633292c3666400339042a3d306f0dc8fdcb4/levenshtein-0.26.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:04b7cabb82edf566b1579b3ed60aac0eec116655af75a3c551fee8754ffce2ea", size = 1135970 }, + { url = "https://files.pythonhosted.org/packages/c9/91/3ff1abacb58642749dfd130ad855370e01b9c7aeaa73801964361f6e355f/levenshtein-0.26.1-cp312-cp312-win32.whl", hash = "sha256:ae382af8c76f6d2a040c0d9ca978baf461702ceb3f79a0a3f6da8d596a484c5b", size = 87599 }, + { url = "https://files.pythonhosted.org/packages/7d/f9/727f3ba7843a3fb2a0f3db825358beea2a52bc96258874ee80cb2e5ecabb/levenshtein-0.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:fd091209798cfdce53746f5769987b4108fe941c54fb2e058c016ffc47872918", size = 98809 }, + { url = "https://files.pythonhosted.org/packages/d4/f4/f87f19222d279dbac429b9bc7ccae271d900fd9c48a581b8bc180ba6cd09/levenshtein-0.26.1-cp312-cp312-win_arm64.whl", hash = "sha256:7e82f2ea44a81ad6b30d92a110e04cd3c8c7c6034b629aca30a3067fa174ae89", size = 88227 }, + { url = "https://files.pythonhosted.org/packages/7e/d6/b4b522b94d7b387c023d22944590befc0ac6b766ac6d197afd879ddd77fc/levenshtein-0.26.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:790374a9f5d2cbdb30ee780403a62e59bef51453ac020668c1564d1e43438f0e", size = 175836 }, + { url = "https://files.pythonhosted.org/packages/25/76/06d1e26a8e6d0de68ef4a157dd57f6b342413c03550309e4aa095a453b28/levenshtein-0.26.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7b05c0415c386d00efda83d48db9db68edd02878d6dbc6df01194f12062be1bb", size = 157036 }, + { url = "https://files.pythonhosted.org/packages/7e/23/21209a9e96b878aede3bea104533866762ba621e36fc344aa080db5feb02/levenshtein-0.26.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3114586032361722ddededf28401ce5baf1cf617f9f49fb86b8766a45a423ff", size = 153326 }, + { url = "https://files.pythonhosted.org/packages/06/38/9fc68685fffd8863b13864552eba8f3eb6a82a4dc558bf2c6553c2347d6c/levenshtein-0.26.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2532f8a13b68bf09f152d906f118a88da2063da22f44c90e904b142b0a53d534", size = 183693 }, + { url = "https://files.pythonhosted.org/packages/f6/82/ccd7bdd7d431329da025e649c63b731df44f8cf31b957e269ae1c1dc9a8e/levenshtein-0.26.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:219c30be6aa734bf927188d1208b7d78d202a3eb017b1c5f01ab2034d2d4ccca", size = 190581 }, + { url = "https://files.pythonhosted.org/packages/6e/c5/57f90b4aea1f89f853872b27a5a5dbce37b89ffeae42c02060b3e82038b2/levenshtein-0.26.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397e245e77f87836308bd56305bba630010cd8298c34c4c44bd94990cdb3b7b1", size = 162446 }, + { url = "https://files.pythonhosted.org/packages/fc/da/df6acca738921f896ce2d178821be866b43a583f85e2d1de63a4f8f78080/levenshtein-0.26.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeff6ea3576f72e26901544c6c55c72a7b79b9983b6f913cba0e9edbf2f87a97", size = 247123 }, + { url = "https://files.pythonhosted.org/packages/22/fb/f44a4c0d7784ccd32e4166714fea61e50f62b232162ae16332f45cb55ab2/levenshtein-0.26.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a19862e3539a697df722a08793994e334cd12791e8144851e8a1dee95a17ff63", size = 1077437 }, + { url = "https://files.pythonhosted.org/packages/f0/5e/d9b9e7daa13cc7e2184a3c2422bb847f05d354ce15ba113b20d83e9ab366/levenshtein-0.26.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:dc3b5a64f57c3c078d58b1e447f7d68cad7ae1b23abe689215d03fc434f8f176", size = 1330362 }, + { url = "https://files.pythonhosted.org/packages/bf/67/480d85bb516798014a6849be0225b246f35df4b54499c348c9c9e311f936/levenshtein-0.26.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bb6c7347424a91317c5e1b68041677e4c8ed3e7823b5bbaedb95bffb3c3497ea", size = 1198721 }, + { url = "https://files.pythonhosted.org/packages/9a/7d/889ff7d86903b6545665655627113d263c88c6d596c68fb09a640ee4f0a7/levenshtein-0.26.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b817376de4195a207cc0e4ca37754c0e1e1078c2a2d35a6ae502afde87212f9e", size = 1351820 }, + { url = "https://files.pythonhosted.org/packages/b9/29/cd42273150f08c200ed2d1879486d73502ee35265f162a77952f101d93a0/levenshtein-0.26.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7b50c3620ff47c9887debbb4c154aaaac3e46be7fc2e5789ee8dbe128bce6a17", size = 1135747 }, + { url = "https://files.pythonhosted.org/packages/1d/90/cbcfa3dd86023e82036662a19fec2fcb48782d3f9fa322d44dc898d95a5d/levenshtein-0.26.1-cp313-cp313-win32.whl", hash = "sha256:9fb859da90262eb474c190b3ca1e61dee83add022c676520f5c05fdd60df902a", size = 87318 }, + { url = "https://files.pythonhosted.org/packages/83/73/372edebc79fd09a8b2382cf1244d279ada5b795124f1e1c4fc73d9fbb00f/levenshtein-0.26.1-cp313-cp313-win_amd64.whl", hash = "sha256:8adcc90e3a5bfb0a463581d85e599d950fe3c2938ac6247b29388b64997f6e2d", size = 98418 }, + { url = "https://files.pythonhosted.org/packages/b2/6d/f0160ea5a7bb7a62b3b3d56e9fc5024b440cb59555a90be2347abf2e7888/levenshtein-0.26.1-cp313-cp313-win_arm64.whl", hash = "sha256:c2599407e029865dc66d210b8804c7768cbdbf60f061d993bb488d5242b0b73e", size = 87792 }, + { url = "https://files.pythonhosted.org/packages/c9/40/11a601baf1731d6b6927890bb7107f6cf77357dec8a22f269cd8f4ab8631/levenshtein-0.26.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6cf8f1efaf90ca585640c5d418c30b7d66d9ac215cee114593957161f63acde0", size = 172550 }, + { url = "https://files.pythonhosted.org/packages/74/1c/070757904b9fb4dfddaf9f43da8e8d9fb6feabd660631cc9e4cb49364d2b/levenshtein-0.26.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d5b2953978b8c158dd5cd93af8216a5cfddbf9de66cf5481c2955f44bb20767a", size = 154546 }, + { url = "https://files.pythonhosted.org/packages/31/7e/ef5538895aa96d6f59b5a6ed3c40c3db3b1b0df45807bd23eae250f380b8/levenshtein-0.26.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b952b3732c4631c49917d4b15d78cb4a2aa006c1d5c12e2a23ba8e18a307a055", size = 152897 }, + { url = "https://files.pythonhosted.org/packages/94/65/28fb5c59871a673f93e72c00c33c43bcc27eff6f9be5e515252e6da28a7f/levenshtein-0.26.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07227281e12071168e6ae59238918a56d2a0682e529f747b5431664f302c0b42", size = 160411 }, + { url = "https://files.pythonhosted.org/packages/4c/c7/b8fe968f92ed672cd346d38f4077586eb7ff63bade2e8d7c93a9259573c4/levenshtein-0.26.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8191241cd8934feaf4d05d0cc0e5e72877cbb17c53bbf8c92af9f1aedaa247e9", size = 247483 }, + { url = "https://files.pythonhosted.org/packages/f3/98/c119974fdce4808afdf3622230759c871bc4c73287cf34b338db2be936b8/levenshtein-0.26.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9e70d7ee157a9b698c73014f6e2b160830e7d2d64d2e342fefc3079af3c356fc", size = 95854 }, +] + +[[package]] +name = "lxml" +version = "5.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/6b/20c3a4b24751377aaa6307eb230b66701024012c29dd374999cc92983269/lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f", size = 3679318 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ce/2789e39eddf2b13fac29878bfa465f0910eb6b0096e29090e5176bc8cf43/lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656", size = 8124570 }, + { url = "https://files.pythonhosted.org/packages/24/a8/f4010166a25d41715527129af2675981a50d3bbf7df09c5d9ab8ca24fbf9/lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d", size = 4413042 }, + { url = "https://files.pythonhosted.org/packages/41/a4/7e45756cecdd7577ddf67a68b69c1db0f5ddbf0c9f65021ee769165ffc5a/lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a", size = 5139213 }, + { url = "https://files.pythonhosted.org/packages/02/e2/ecf845b12323c92748077e1818b64e8b4dba509a4cb12920b3762ebe7552/lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8", size = 4838814 }, + { url = "https://files.pythonhosted.org/packages/12/91/619f9fb72cf75e9ceb8700706f7276f23995f6ad757e6d400fbe35ca4990/lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330", size = 5425084 }, + { url = "https://files.pythonhosted.org/packages/25/3b/162a85a8f0fd2a3032ec3f936636911c6e9523a8e263fffcfd581ce98b54/lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965", size = 4875993 }, + { url = "https://files.pythonhosted.org/packages/43/af/dd3f58cc7d946da6ae42909629a2b1d5dd2d1b583334d4af9396697d6863/lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22", size = 5012462 }, + { url = "https://files.pythonhosted.org/packages/69/c1/5ea46b2d4c98f5bf5c83fffab8a0ad293c9bc74df9ecfbafef10f77f7201/lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b", size = 4815288 }, + { url = "https://files.pythonhosted.org/packages/1d/51/a0acca077ad35da458f4d3f729ef98effd2b90f003440d35fc36323f8ae6/lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7", size = 5472435 }, + { url = "https://files.pythonhosted.org/packages/4d/6b/0989c9368986961a6b0f55b46c80404c4b758417acdb6d87bfc3bd5f4967/lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8", size = 4976354 }, + { url = "https://files.pythonhosted.org/packages/05/9e/87492d03ff604fbf656ed2bf3e2e8d28f5d58ea1f00ff27ac27b06509079/lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32", size = 5029973 }, + { url = "https://files.pythonhosted.org/packages/f9/cc/9ae1baf5472af88e19e2c454b3710c1be9ecafb20eb474eeabcd88a055d2/lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86", size = 4888837 }, + { url = "https://files.pythonhosted.org/packages/d2/10/5594ffaec8c120d75b17e3ad23439b740a51549a9b5fd7484b2179adfe8f/lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5", size = 5530555 }, + { url = "https://files.pythonhosted.org/packages/ea/9b/de17f05377c8833343b629905571fb06cff2028f15a6f58ae2267662e341/lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03", size = 5405314 }, + { url = "https://files.pythonhosted.org/packages/8a/b4/227be0f1f3cca8255925985164c3838b8b36e441ff0cc10c1d3c6bdba031/lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7", size = 5079303 }, + { url = "https://files.pythonhosted.org/packages/5c/ee/19abcebb7fc40319bb71cd6adefa1ad94d09b5660228715854d6cc420713/lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80", size = 3475126 }, + { url = "https://files.pythonhosted.org/packages/a1/35/183d32551447e280032b2331738cd850da435a42f850b71ebeaab42c1313/lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3", size = 3805065 }, + { url = "https://files.pythonhosted.org/packages/5c/a8/449faa2a3cbe6a99f8d38dcd51a3ee8844c17862841a6f769ea7c2a9cd0f/lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b", size = 8141056 }, + { url = "https://files.pythonhosted.org/packages/ac/8a/ae6325e994e2052de92f894363b038351c50ee38749d30cc6b6d96aaf90f/lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18", size = 4425238 }, + { url = "https://files.pythonhosted.org/packages/f8/fb/128dddb7f9086236bce0eeae2bfb316d138b49b159f50bc681d56c1bdd19/lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442", size = 5095197 }, + { url = "https://files.pythonhosted.org/packages/b4/f9/a181a8ef106e41e3086629c8bdb2d21a942f14c84a0e77452c22d6b22091/lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4", size = 4809809 }, + { url = "https://files.pythonhosted.org/packages/25/2f/b20565e808f7f6868aacea48ddcdd7e9e9fb4c799287f21f1a6c7c2e8b71/lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f", size = 5407593 }, + { url = "https://files.pythonhosted.org/packages/23/0e/caac672ec246d3189a16c4d364ed4f7d6bf856c080215382c06764058c08/lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e", size = 4866657 }, + { url = "https://files.pythonhosted.org/packages/67/a4/1f5fbd3f58d4069000522196b0b776a014f3feec1796da03e495cf23532d/lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c", size = 4967017 }, + { url = "https://files.pythonhosted.org/packages/ee/73/623ecea6ca3c530dd0a4ed0d00d9702e0e85cd5624e2d5b93b005fe00abd/lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16", size = 4810730 }, + { url = "https://files.pythonhosted.org/packages/1d/ce/fb84fb8e3c298f3a245ae3ea6221c2426f1bbaa82d10a88787412a498145/lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79", size = 5455154 }, + { url = "https://files.pythonhosted.org/packages/b1/72/4d1ad363748a72c7c0411c28be2b0dc7150d91e823eadad3b91a4514cbea/lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080", size = 4969416 }, + { url = "https://files.pythonhosted.org/packages/42/07/b29571a58a3a80681722ea8ed0ba569211d9bb8531ad49b5cacf6d409185/lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654", size = 5013672 }, + { url = "https://files.pythonhosted.org/packages/b9/93/bde740d5a58cf04cbd38e3dd93ad1e36c2f95553bbf7d57807bc6815d926/lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d", size = 4878644 }, + { url = "https://files.pythonhosted.org/packages/56/b5/645c8c02721d49927c93181de4017164ec0e141413577687c3df8ff0800f/lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763", size = 5511531 }, + { url = "https://files.pythonhosted.org/packages/85/3f/6a99a12d9438316f4fc86ef88c5d4c8fb674247b17f3173ecadd8346b671/lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec", size = 5402065 }, + { url = "https://files.pythonhosted.org/packages/80/8a/df47bff6ad5ac57335bf552babfb2408f9eb680c074ec1ba412a1a6af2c5/lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be", size = 5069775 }, + { url = "https://files.pythonhosted.org/packages/08/ae/e7ad0f0fbe4b6368c5ee1e3ef0c3365098d806d42379c46c1ba2802a52f7/lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9", size = 3474226 }, + { url = "https://files.pythonhosted.org/packages/c3/b5/91c2249bfac02ee514ab135e9304b89d55967be7e53e94a879b74eec7a5c/lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1", size = 3814971 }, + { url = "https://files.pythonhosted.org/packages/eb/6d/d1f1c5e40c64bf62afd7a3f9b34ce18a586a1cccbf71e783cd0a6d8e8971/lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859", size = 8171753 }, + { url = "https://files.pythonhosted.org/packages/bd/83/26b1864921869784355459f374896dcf8b44d4af3b15d7697e9156cb2de9/lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e", size = 4441955 }, + { url = "https://files.pythonhosted.org/packages/e0/d2/e9bff9fb359226c25cda3538f664f54f2804f4b37b0d7c944639e1a51f69/lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f", size = 5050778 }, + { url = "https://files.pythonhosted.org/packages/88/69/6972bfafa8cd3ddc8562b126dd607011e218e17be313a8b1b9cc5a0ee876/lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e", size = 4748628 }, + { url = "https://files.pythonhosted.org/packages/5d/ea/a6523c7c7f6dc755a6eed3d2f6d6646617cad4d3d6d8ce4ed71bfd2362c8/lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179", size = 5322215 }, + { url = "https://files.pythonhosted.org/packages/99/37/396fbd24a70f62b31d988e4500f2068c7f3fd399d2fd45257d13eab51a6f/lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a", size = 4813963 }, + { url = "https://files.pythonhosted.org/packages/09/91/e6136f17459a11ce1757df864b213efbeab7adcb2efa63efb1b846ab6723/lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3", size = 4923353 }, + { url = "https://files.pythonhosted.org/packages/1d/7c/2eeecf87c9a1fca4f84f991067c693e67340f2b7127fc3eca8fa29d75ee3/lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1", size = 4740541 }, + { url = "https://files.pythonhosted.org/packages/3b/ed/4c38ba58defca84f5f0d0ac2480fdcd99fc7ae4b28fc417c93640a6949ae/lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d", size = 5346504 }, + { url = "https://files.pythonhosted.org/packages/a5/22/bbd3995437e5745cb4c2b5d89088d70ab19d4feabf8a27a24cecb9745464/lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c", size = 4898077 }, + { url = "https://files.pythonhosted.org/packages/0a/6e/94537acfb5b8f18235d13186d247bca478fea5e87d224644e0fe907df976/lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99", size = 4946543 }, + { url = "https://files.pythonhosted.org/packages/8d/e8/4b15df533fe8e8d53363b23a41df9be907330e1fa28c7ca36893fad338ee/lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff", size = 4816841 }, + { url = "https://files.pythonhosted.org/packages/1a/e7/03f390ea37d1acda50bc538feb5b2bda6745b25731e4e76ab48fae7106bf/lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a", size = 5417341 }, + { url = "https://files.pythonhosted.org/packages/ea/99/d1133ab4c250da85a883c3b60249d3d3e7c64f24faff494cf0fd23f91e80/lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8", size = 5327539 }, + { url = "https://files.pythonhosted.org/packages/7d/ed/e6276c8d9668028213df01f598f385b05b55a4e1b4662ee12ef05dab35aa/lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d", size = 5012542 }, + { url = "https://files.pythonhosted.org/packages/36/88/684d4e800f5aa28df2a991a6a622783fb73cf0e46235cfa690f9776f032e/lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30", size = 3486454 }, + { url = "https://files.pythonhosted.org/packages/fc/82/ace5a5676051e60355bd8fb945df7b1ba4f4fb8447f2010fb816bfd57724/lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f", size = 3816857 }, + { url = "https://files.pythonhosted.org/packages/94/6a/42141e4d373903bfea6f8e94b2f554d05506dfda522ada5343c651410dc8/lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a", size = 8156284 }, + { url = "https://files.pythonhosted.org/packages/91/5e/fa097f0f7d8b3d113fb7312c6308af702f2667f22644441715be961f2c7e/lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd", size = 4432407 }, + { url = "https://files.pythonhosted.org/packages/2d/a1/b901988aa6d4ff937f2e5cfc114e4ec561901ff00660c3e56713642728da/lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51", size = 5048331 }, + { url = "https://files.pythonhosted.org/packages/30/0f/b2a54f48e52de578b71bbe2a2f8160672a8a5e103df3a78da53907e8c7ed/lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b", size = 4744835 }, + { url = "https://files.pythonhosted.org/packages/82/9d/b000c15538b60934589e83826ecbc437a1586488d7c13f8ee5ff1f79a9b8/lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002", size = 5316649 }, + { url = "https://files.pythonhosted.org/packages/e3/ee/ffbb9eaff5e541922611d2c56b175c45893d1c0b8b11e5a497708a6a3b3b/lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4", size = 4812046 }, + { url = "https://files.pythonhosted.org/packages/15/ff/7ff89d567485c7b943cdac316087f16b2399a8b997007ed352a1248397e5/lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492", size = 4918597 }, + { url = "https://files.pythonhosted.org/packages/c6/a3/535b6ed8c048412ff51268bdf4bf1cf052a37aa7e31d2e6518038a883b29/lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3", size = 4738071 }, + { url = "https://files.pythonhosted.org/packages/7a/8f/cbbfa59cb4d4fd677fe183725a76d8c956495d7a3c7f111ab8f5e13d2e83/lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4", size = 5342213 }, + { url = "https://files.pythonhosted.org/packages/5c/fb/db4c10dd9958d4b52e34d1d1f7c1f434422aeaf6ae2bbaaff2264351d944/lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367", size = 4893749 }, + { url = "https://files.pythonhosted.org/packages/f2/38/bb4581c143957c47740de18a3281a0cab7722390a77cc6e610e8ebf2d736/lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832", size = 4945901 }, + { url = "https://files.pythonhosted.org/packages/fc/d5/18b7de4960c731e98037bd48fa9f8e6e8f2558e6fbca4303d9b14d21ef3b/lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff", size = 4815447 }, + { url = "https://files.pythonhosted.org/packages/97/a8/cd51ceaad6eb849246559a8ef60ae55065a3df550fc5fcd27014361c1bab/lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd", size = 5411186 }, + { url = "https://files.pythonhosted.org/packages/89/c3/1e3dabab519481ed7b1fdcba21dcfb8832f57000733ef0e71cf6d09a5e03/lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb", size = 5324481 }, + { url = "https://files.pythonhosted.org/packages/b6/17/71e9984cf0570cd202ac0a1c9ed5c1b8889b0fc8dc736f5ef0ffb181c284/lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b", size = 5011053 }, + { url = "https://files.pythonhosted.org/packages/69/68/9f7e6d3312a91e30829368c2b3217e750adef12a6f8eb10498249f4e8d72/lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957", size = 3485634 }, + { url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 }, + { url = "https://files.pythonhosted.org/packages/99/f7/b73a431c8500565aa500e99e60b448d305eaf7c0b4c893c7c5a8a69cc595/lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c", size = 3925431 }, + { url = "https://files.pythonhosted.org/packages/db/48/4a206623c0d093d0e3b15f415ffb4345b0bdf661a3d0b15a112948c033c7/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a", size = 4216683 }, + { url = "https://files.pythonhosted.org/packages/54/47/577820c45dd954523ae8453b632d91e76da94ca6d9ee40d8c98dd86f916b/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005", size = 4326732 }, + { url = "https://files.pythonhosted.org/packages/68/de/96cb6d3269bc994b4f5ede8ca7bf0840f5de0a278bc6e50cb317ff71cafa/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce", size = 4218377 }, + { url = "https://files.pythonhosted.org/packages/a5/43/19b1ef6cbffa4244a217f95cc5f41a6cb4720fed33510a49670b03c5f1a0/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83", size = 4351237 }, + { url = "https://files.pythonhosted.org/packages/ba/b2/6a22fb5c0885da3b00e116aee81f0b829ec9ac8f736cd414b4a09413fc7d/lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba", size = 3487557 }, +] + +[[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 = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "matplotlib" +version = "3.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/dd/fa2e1a45fce2d09f4aea3cee169760e672c8262325aa5796c49d543dc7e6/matplotlib-3.10.0.tar.gz", hash = "sha256:b886d02a581b96704c9d1ffe55709e49b4d2d52709ccebc4be42db856e511278", size = 36686418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/ec/3cdff7b5239adaaacefcc4f77c316dfbbdf853c4ed2beec467e0fec31b9f/matplotlib-3.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2c5829a5a1dd5a71f0e31e6e8bb449bc0ee9dbfb05ad28fc0c6b55101b3a4be6", size = 8160551 }, + { url = "https://files.pythonhosted.org/packages/41/f2/b518f2c7f29895c9b167bf79f8529c63383ae94eaf49a247a4528e9a148d/matplotlib-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2a43cbefe22d653ab34bb55d42384ed30f611bcbdea1f8d7f431011a2e1c62e", size = 8034853 }, + { url = "https://files.pythonhosted.org/packages/ed/8d/45754b4affdb8f0d1a44e4e2bcd932cdf35b256b60d5eda9f455bb293ed0/matplotlib-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:607b16c8a73943df110f99ee2e940b8a1cbf9714b65307c040d422558397dac5", size = 8446724 }, + { url = "https://files.pythonhosted.org/packages/09/5a/a113495110ae3e3395c72d82d7bc4802902e46dc797f6b041e572f195c56/matplotlib-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01d2b19f13aeec2e759414d3bfe19ddfb16b13a1250add08d46d5ff6f9be83c6", size = 8583905 }, + { url = "https://files.pythonhosted.org/packages/12/b1/8b1655b4c9ed4600c817c419f7eaaf70082630efd7556a5b2e77a8a3cdaf/matplotlib-3.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e6c6461e1fc63df30bf6f80f0b93f5b6784299f721bc28530477acd51bfc3d1", size = 9395223 }, + { url = "https://files.pythonhosted.org/packages/5a/85/b9a54d64585a6b8737a78a61897450403c30f39e0bd3214270bb0b96f002/matplotlib-3.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:994c07b9d9fe8d25951e3202a68c17900679274dadfc1248738dcfa1bd40d7f3", size = 8025355 }, + { url = "https://files.pythonhosted.org/packages/0c/f1/e37f6c84d252867d7ddc418fff70fc661cfd363179263b08e52e8b748e30/matplotlib-3.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:fd44fc75522f58612ec4a33958a7e5552562b7705b42ef1b4f8c0818e304a363", size = 8171677 }, + { url = "https://files.pythonhosted.org/packages/c7/8b/92e9da1f28310a1f6572b5c55097b0c0ceb5e27486d85fb73b54f5a9b939/matplotlib-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c58a9622d5dbeb668f407f35f4e6bfac34bb9ecdcc81680c04d0258169747997", size = 8044945 }, + { url = "https://files.pythonhosted.org/packages/c5/cb/49e83f0fd066937a5bd3bc5c5d63093703f3637b2824df8d856e0558beef/matplotlib-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:845d96568ec873be63f25fa80e9e7fae4be854a66a7e2f0c8ccc99e94a8bd4ef", size = 8458269 }, + { url = "https://files.pythonhosted.org/packages/b2/7d/2d873209536b9ee17340754118a2a17988bc18981b5b56e6715ee07373ac/matplotlib-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5439f4c5a3e2e8eab18e2f8c3ef929772fd5641876db71f08127eed95ab64683", size = 8599369 }, + { url = "https://files.pythonhosted.org/packages/b8/03/57d6cbbe85c61fe4cbb7c94b54dce443d68c21961830833a1f34d056e5ea/matplotlib-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4673ff67a36152c48ddeaf1135e74ce0d4bce1bbf836ae40ed39c29edf7e2765", size = 9405992 }, + { url = "https://files.pythonhosted.org/packages/14/cf/e382598f98be11bf51dd0bc60eca44a517f6793e3dc8b9d53634a144620c/matplotlib-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e8632baebb058555ac0cde75db885c61f1212e47723d63921879806b40bec6a", size = 8034580 }, + { url = "https://files.pythonhosted.org/packages/44/c7/6b2d8cb7cc251d53c976799cacd3200add56351c175ba89ab9cbd7c1e68a/matplotlib-3.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4659665bc7c9b58f8c00317c3c2a299f7f258eeae5a5d56b4c64226fca2f7c59", size = 8172465 }, + { url = "https://files.pythonhosted.org/packages/42/2a/6d66d0fba41e13e9ca6512a0a51170f43e7e7ed3a8dfa036324100775612/matplotlib-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d44cb942af1693cced2604c33a9abcef6205601c445f6d0dc531d813af8a2f5a", size = 8043300 }, + { url = "https://files.pythonhosted.org/packages/90/60/2a60342b27b90a16bada939a85e29589902b41073f59668b904b15ea666c/matplotlib-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a994f29e968ca002b50982b27168addfd65f0105610b6be7fa515ca4b5307c95", size = 8448936 }, + { url = "https://files.pythonhosted.org/packages/a7/b2/d872fc3d753516870d520595ddd8ce4dd44fa797a240999f125f58521ad7/matplotlib-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b0558bae37f154fffda54d779a592bc97ca8b4701f1c710055b609a3bac44c8", size = 8594151 }, + { url = "https://files.pythonhosted.org/packages/f4/bd/b2f60cf7f57d014ab33e4f74602a2b5bdc657976db8196bbc022185f6f9c/matplotlib-3.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:503feb23bd8c8acc75541548a1d709c059b7184cde26314896e10a9f14df5f12", size = 9400347 }, + { url = "https://files.pythonhosted.org/packages/9f/6e/264673e64001b99d747aff5a288eca82826c024437a3694e19aed1decf46/matplotlib-3.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:c40ba2eb08b3f5de88152c2333c58cee7edcead0a2a0d60fcafa116b17117adc", size = 8039144 }, + { url = "https://files.pythonhosted.org/packages/72/11/1b2a094d95dcb6e6edd4a0b238177c439006c6b7a9fe8d31801237bf512f/matplotlib-3.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96f2886f5c1e466f21cc41b70c5a0cd47bfa0015eb2d5793c88ebce658600e25", size = 8173073 }, + { url = "https://files.pythonhosted.org/packages/0d/c4/87b6ad2723070511a411ea719f9c70fde64605423b184face4e94986de9d/matplotlib-3.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:12eaf48463b472c3c0f8dbacdbf906e573013df81a0ab82f0616ea4b11281908", size = 8043892 }, + { url = "https://files.pythonhosted.org/packages/57/69/cb0812a136550b21361335e9ffb7d459bf6d13e03cb7b015555d5143d2d6/matplotlib-3.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fbbabc82fde51391c4da5006f965e36d86d95f6ee83fb594b279564a4c5d0d2", size = 8450532 }, + { url = "https://files.pythonhosted.org/packages/ea/3a/bab9deb4fb199c05e9100f94d7f1c702f78d3241e6a71b784d2b88d7bebd/matplotlib-3.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad2e15300530c1a94c63cfa546e3b7864bd18ea2901317bae8bbf06a5ade6dcf", size = 8593905 }, + { url = "https://files.pythonhosted.org/packages/8b/66/742fd242f989adc1847ddf5f445815f73ad7c46aa3440690cc889cfa423c/matplotlib-3.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3547d153d70233a8496859097ef0312212e2689cdf8d7ed764441c77604095ae", size = 9399609 }, + { url = "https://files.pythonhosted.org/packages/fa/d6/54cee7142cef7d910a324a7aedf335c0c147b03658b54d49ec48166f10a6/matplotlib-3.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c55b20591ced744aa04e8c3e4b7543ea4d650b6c3c4b208c08a05b4010e8b442", size = 8039076 }, + { url = "https://files.pythonhosted.org/packages/43/14/815d072dc36e88753433bfd0385113405efb947e6895ff7b4d2e8614a33b/matplotlib-3.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ade1003376731a971e398cc4ef38bb83ee8caf0aee46ac6daa4b0506db1fd06", size = 8211000 }, + { url = "https://files.pythonhosted.org/packages/9a/76/34e75f364194ec352678adcb540964be6f35ec7d3d8c75ebcb17e6839359/matplotlib-3.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95b710fea129c76d30be72c3b38f330269363fbc6e570a5dd43580487380b5ff", size = 8087707 }, + { url = "https://files.pythonhosted.org/packages/c3/2b/b6bc0dff6a72d333bc7df94a66e6ce662d224e43daa8ad8ae4eaa9a77f55/matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdbaf909887373c3e094b0318d7ff230b2ad9dcb64da7ade654182872ab2593", size = 8477384 }, + { url = "https://files.pythonhosted.org/packages/c2/2d/b5949fb2b76e9b47ab05e25a5f5f887c70de20d8b0cbc704a4e2ee71c786/matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d907fddb39f923d011875452ff1eca29a9e7f21722b873e90db32e5d8ddff12e", size = 8610334 }, + { url = "https://files.pythonhosted.org/packages/d6/9a/6e3c799d5134d9af44b01c787e1360bee38cf51850506ea2e743a787700b/matplotlib-3.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3b427392354d10975c1d0f4ee18aa5844640b512d5311ef32efd4dd7db106ede", size = 9406777 }, + { url = "https://files.pythonhosted.org/packages/0e/dd/e6ae97151e5ed648ab2ea48885bc33d39202b640eec7a2910e2c843f7ac0/matplotlib-3.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5fd41b0ec7ee45cd960a8e71aea7c946a28a0b8a4dcee47d2856b2af051f334c", size = 8109742 }, + { url = "https://files.pythonhosted.org/packages/32/5f/29def7ce4e815ab939b56280976ee35afffb3bbdb43f332caee74cb8c951/matplotlib-3.10.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81713dd0d103b379de4516b861d964b1d789a144103277769238c732229d7f03", size = 8155500 }, + { url = "https://files.pythonhosted.org/packages/de/6d/d570383c9f7ca799d0a54161446f9ce7b17d6c50f2994b653514bcaa108f/matplotlib-3.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:359f87baedb1f836ce307f0e850d12bb5f1936f70d035561f90d41d305fdacea", size = 8032398 }, + { url = "https://files.pythonhosted.org/packages/c9/b4/680aa700d99b48e8c4393fa08e9ab8c49c0555ee6f4c9c0a5e8ea8dfde5d/matplotlib-3.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae80dc3a4add4665cf2faa90138384a7ffe2a4e37c58d83e115b54287c4f06ef", size = 8587361 }, +] + +[[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 = "more-itertools" +version = "10.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/78/65922308c4248e0eb08ebcbe67c95d48615cc6f27854b6f2e57143e9178f/more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6", size = 121020 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/7e/3a64597054a70f7c86eb0a7d4fc315b8c1ab932f64883a297bdffeb5f967/more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef", size = 60952 }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, +] + +[[package]] +name = "msgpack" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/d0/7555686ae7ff5731205df1012ede15dd9d927f6227ea151e901c7406af4f/msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e", size = 167260 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/f9/a892a6038c861fa849b11a2bb0502c07bc698ab6ea53359e5771397d883b/msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd", size = 150428 }, + { url = "https://files.pythonhosted.org/packages/df/7a/d174cc6a3b6bb85556e6a046d3193294a92f9a8e583cdbd46dc8a1d7e7f4/msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d", size = 84131 }, + { url = "https://files.pythonhosted.org/packages/08/52/bf4fbf72f897a23a56b822997a72c16de07d8d56d7bf273242f884055682/msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5", size = 81215 }, + { url = "https://files.pythonhosted.org/packages/02/95/dc0044b439b518236aaf012da4677c1b8183ce388411ad1b1e63c32d8979/msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5", size = 371229 }, + { url = "https://files.pythonhosted.org/packages/ff/75/09081792db60470bef19d9c2be89f024d366b1e1973c197bb59e6aabc647/msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e", size = 378034 }, + { url = "https://files.pythonhosted.org/packages/32/d3/c152e0c55fead87dd948d4b29879b0f14feeeec92ef1fd2ec21b107c3f49/msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b", size = 363070 }, + { url = "https://files.pythonhosted.org/packages/d9/2c/82e73506dd55f9e43ac8aa007c9dd088c6f0de2aa19e8f7330e6a65879fc/msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f", size = 359863 }, + { url = "https://files.pythonhosted.org/packages/cb/a0/3d093b248837094220e1edc9ec4337de3443b1cfeeb6e0896af8ccc4cc7a/msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68", size = 368166 }, + { url = "https://files.pythonhosted.org/packages/e4/13/7646f14f06838b406cf5a6ddbb7e8dc78b4996d891ab3b93c33d1ccc8678/msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b", size = 370105 }, + { url = "https://files.pythonhosted.org/packages/67/fa/dbbd2443e4578e165192dabbc6a22c0812cda2649261b1264ff515f19f15/msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044", size = 68513 }, + { url = "https://files.pythonhosted.org/packages/24/ce/c2c8fbf0ded750cb63cbcbb61bc1f2dfd69e16dca30a8af8ba80ec182dcd/msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f", size = 74687 }, + { url = "https://files.pythonhosted.org/packages/b7/5e/a4c7154ba65d93be91f2f1e55f90e76c5f91ccadc7efc4341e6f04c8647f/msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7", size = 150803 }, + { url = "https://files.pythonhosted.org/packages/60/c2/687684164698f1d51c41778c838d854965dd284a4b9d3a44beba9265c931/msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa", size = 84343 }, + { url = "https://files.pythonhosted.org/packages/42/ae/d3adea9bb4a1342763556078b5765e666f8fdf242e00f3f6657380920972/msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701", size = 81408 }, + { url = "https://files.pythonhosted.org/packages/dc/17/6313325a6ff40ce9c3207293aee3ba50104aed6c2c1559d20d09e5c1ff54/msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6", size = 396096 }, + { url = "https://files.pythonhosted.org/packages/a8/a1/ad7b84b91ab5a324e707f4c9761633e357820b011a01e34ce658c1dda7cc/msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59", size = 403671 }, + { url = "https://files.pythonhosted.org/packages/bb/0b/fd5b7c0b308bbf1831df0ca04ec76fe2f5bf6319833646b0a4bd5e9dc76d/msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0", size = 387414 }, + { url = "https://files.pythonhosted.org/packages/f0/03/ff8233b7c6e9929a1f5da3c7860eccd847e2523ca2de0d8ef4878d354cfa/msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e", size = 383759 }, + { url = "https://files.pythonhosted.org/packages/1f/1b/eb82e1fed5a16dddd9bc75f0854b6e2fe86c0259c4353666d7fab37d39f4/msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6", size = 394405 }, + { url = "https://files.pythonhosted.org/packages/90/2e/962c6004e373d54ecf33d695fb1402f99b51832631e37c49273cc564ffc5/msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5", size = 396041 }, + { url = "https://files.pythonhosted.org/packages/f8/20/6e03342f629474414860c48aeffcc2f7f50ddaf351d95f20c3f1c67399a8/msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88", size = 68538 }, + { url = "https://files.pythonhosted.org/packages/aa/c4/5a582fc9a87991a3e6f6800e9bb2f3c82972912235eb9539954f3e9997c7/msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788", size = 74871 }, + { url = "https://files.pythonhosted.org/packages/e1/d6/716b7ca1dbde63290d2973d22bbef1b5032ca634c3ff4384a958ec3f093a/msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d", size = 152421 }, + { url = "https://files.pythonhosted.org/packages/70/da/5312b067f6773429cec2f8f08b021c06af416bba340c912c2ec778539ed6/msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2", size = 85277 }, + { url = "https://files.pythonhosted.org/packages/28/51/da7f3ae4462e8bb98af0d5bdf2707f1b8c65a0d4f496e46b6afb06cbc286/msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420", size = 82222 }, + { url = "https://files.pythonhosted.org/packages/33/af/dc95c4b2a49cff17ce47611ca9ba218198806cad7796c0b01d1e332c86bb/msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2", size = 392971 }, + { url = "https://files.pythonhosted.org/packages/f1/54/65af8de681fa8255402c80eda2a501ba467921d5a7a028c9c22a2c2eedb5/msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39", size = 401403 }, + { url = "https://files.pythonhosted.org/packages/97/8c/e333690777bd33919ab7024269dc3c41c76ef5137b211d776fbb404bfead/msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f", size = 385356 }, + { url = "https://files.pythonhosted.org/packages/57/52/406795ba478dc1c890559dd4e89280fa86506608a28ccf3a72fbf45df9f5/msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247", size = 383028 }, + { url = "https://files.pythonhosted.org/packages/e7/69/053b6549bf90a3acadcd8232eae03e2fefc87f066a5b9fbb37e2e608859f/msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c", size = 391100 }, + { url = "https://files.pythonhosted.org/packages/23/f0/d4101d4da054f04274995ddc4086c2715d9b93111eb9ed49686c0f7ccc8a/msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b", size = 394254 }, + { url = "https://files.pythonhosted.org/packages/1c/12/cf07458f35d0d775ff3a2dc5559fa2e1fcd06c46f1ef510e594ebefdca01/msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b", size = 69085 }, + { url = "https://files.pythonhosted.org/packages/73/80/2708a4641f7d553a63bc934a3eb7214806b5b39d200133ca7f7afb0a53e8/msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f", size = 75347 }, + { url = "https://files.pythonhosted.org/packages/c8/b0/380f5f639543a4ac413e969109978feb1f3c66e931068f91ab6ab0f8be00/msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf", size = 151142 }, + { url = "https://files.pythonhosted.org/packages/c8/ee/be57e9702400a6cb2606883d55b05784fada898dfc7fd12608ab1fdb054e/msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330", size = 84523 }, + { url = "https://files.pythonhosted.org/packages/7e/3a/2919f63acca3c119565449681ad08a2f84b2171ddfcff1dba6959db2cceb/msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734", size = 81556 }, + { url = "https://files.pythonhosted.org/packages/7c/43/a11113d9e5c1498c145a8925768ea2d5fce7cbab15c99cda655aa09947ed/msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e", size = 392105 }, + { url = "https://files.pythonhosted.org/packages/2d/7b/2c1d74ca6c94f70a1add74a8393a0138172207dc5de6fc6269483519d048/msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca", size = 399979 }, + { url = "https://files.pythonhosted.org/packages/82/8c/cf64ae518c7b8efc763ca1f1348a96f0e37150061e777a8ea5430b413a74/msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915", size = 383816 }, + { url = "https://files.pythonhosted.org/packages/69/86/a847ef7a0f5ef3fa94ae20f52a4cacf596a4e4a010197fbcc27744eb9a83/msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d", size = 380973 }, + { url = "https://files.pythonhosted.org/packages/aa/90/c74cf6e1126faa93185d3b830ee97246ecc4fe12cf9d2d31318ee4246994/msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434", size = 387435 }, + { url = "https://files.pythonhosted.org/packages/7a/40/631c238f1f338eb09f4acb0f34ab5862c4e9d7eda11c1b685471a4c5ea37/msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c", size = 399082 }, + { url = "https://files.pythonhosted.org/packages/e9/1b/fa8a952be252a1555ed39f97c06778e3aeb9123aa4cccc0fd2acd0b4e315/msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc", size = 69037 }, + { url = "https://files.pythonhosted.org/packages/b6/bc/8bd826dd03e022153bfa1766dcdec4976d6c818865ed54223d71f07862b3/msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f", size = 75140 }, +] + +[[package]] +name = "msgpack-numpy-opentensor" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msgpack" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/69/2a6af13c3be6934a9ba149120a78bf63cf1455ddb1d11ec2cc5e5d6f8186/msgpack-numpy-opentensor-0.5.0.tar.gz", hash = "sha256:213232c20e2efd528ec8a9882b605e8ad87cfc35b57dfcfefe05d33aaaabe574", size = 9661 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/22/590508afb85d5c27ebcb2837410413f4613eebdda6e4e02997fe08ba78e4/msgpack_numpy_opentensor-0.5.0-py2.py3-none-any.whl", hash = "sha256:8a61c597a976425a87094d8e89846aa9528eb1f037e97ff1428fe3cd61a238e7", size = 7209 }, +] + +[[package]] +name = "multidict" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/be/504b89a5e9ca731cd47487e91c469064f8ae5af93b7259758dcfc2b9c848/multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", size = 64002 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/68/259dee7fd14cf56a17c554125e534f6274c2860159692a414d0b402b9a6d/multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60", size = 48628 }, + { url = "https://files.pythonhosted.org/packages/50/79/53ba256069fe5386a4a9e80d4e12857ced9de295baf3e20c68cdda746e04/multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1", size = 29327 }, + { url = "https://files.pythonhosted.org/packages/ff/10/71f1379b05b196dae749b5ac062e87273e3f11634f447ebac12a571d90ae/multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53", size = 29689 }, + { url = "https://files.pythonhosted.org/packages/71/45/70bac4f87438ded36ad4793793c0095de6572d433d98575a5752629ef549/multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5", size = 126639 }, + { url = "https://files.pythonhosted.org/packages/80/cf/17f35b3b9509b4959303c05379c4bfb0d7dd05c3306039fc79cf035bbac0/multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581", size = 134315 }, + { url = "https://files.pythonhosted.org/packages/ef/1f/652d70ab5effb33c031510a3503d4d6efc5ec93153562f1ee0acdc895a57/multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56", size = 129471 }, + { url = "https://files.pythonhosted.org/packages/a6/64/2dd6c4c681688c0165dea3975a6a4eab4944ea30f35000f8b8af1df3148c/multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429", size = 124585 }, + { url = "https://files.pythonhosted.org/packages/87/56/e6ee5459894c7e554b57ba88f7257dc3c3d2d379cb15baaa1e265b8c6165/multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748", size = 116957 }, + { url = "https://files.pythonhosted.org/packages/36/9e/616ce5e8d375c24b84f14fc263c7ef1d8d5e8ef529dbc0f1df8ce71bb5b8/multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db", size = 128609 }, + { url = "https://files.pythonhosted.org/packages/8c/4f/4783e48a38495d000f2124020dc96bacc806a4340345211b1ab6175a6cb4/multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056", size = 123016 }, + { url = "https://files.pythonhosted.org/packages/3e/b3/4950551ab8fc39862ba5e9907dc821f896aa829b4524b4deefd3e12945ab/multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76", size = 133542 }, + { url = "https://files.pythonhosted.org/packages/96/4d/f0ce6ac9914168a2a71df117935bb1f1781916acdecbb43285e225b484b8/multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160", size = 130163 }, + { url = "https://files.pythonhosted.org/packages/be/72/17c9f67e7542a49dd252c5ae50248607dfb780bcc03035907dafefb067e3/multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7", size = 126832 }, + { url = "https://files.pythonhosted.org/packages/71/9f/72d719e248cbd755c8736c6d14780533a1606ffb3fbb0fbd77da9f0372da/multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0", size = 26402 }, + { url = "https://files.pythonhosted.org/packages/04/5a/d88cd5d00a184e1ddffc82aa2e6e915164a6d2641ed3606e766b5d2f275a/multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d", size = 28800 }, + { url = "https://files.pythonhosted.org/packages/93/13/df3505a46d0cd08428e4c8169a196131d1b0c4b515c3649829258843dde6/multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6", size = 48570 }, + { url = "https://files.pythonhosted.org/packages/f0/e1/a215908bfae1343cdb72f805366592bdd60487b4232d039c437fe8f5013d/multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156", size = 29316 }, + { url = "https://files.pythonhosted.org/packages/70/0f/6dc70ddf5d442702ed74f298d69977f904960b82368532c88e854b79f72b/multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb", size = 29640 }, + { url = "https://files.pythonhosted.org/packages/d8/6d/9c87b73a13d1cdea30b321ef4b3824449866bd7f7127eceed066ccb9b9ff/multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b", size = 131067 }, + { url = "https://files.pythonhosted.org/packages/cc/1e/1b34154fef373371fd6c65125b3d42ff5f56c7ccc6bfff91b9b3c60ae9e0/multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72", size = 138507 }, + { url = "https://files.pythonhosted.org/packages/fb/e0/0bc6b2bac6e461822b5f575eae85da6aae76d0e2a79b6665d6206b8e2e48/multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304", size = 133905 }, + { url = "https://files.pythonhosted.org/packages/ba/af/73d13b918071ff9b2205fcf773d316e0f8fefb4ec65354bbcf0b10908cc6/multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351", size = 129004 }, + { url = "https://files.pythonhosted.org/packages/74/21/23960627b00ed39643302d81bcda44c9444ebcdc04ee5bedd0757513f259/multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb", size = 121308 }, + { url = "https://files.pythonhosted.org/packages/8b/5c/cf282263ffce4a596ed0bb2aa1a1dddfe1996d6a62d08842a8d4b33dca13/multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3", size = 132608 }, + { url = "https://files.pythonhosted.org/packages/d7/3e/97e778c041c72063f42b290888daff008d3ab1427f5b09b714f5a8eff294/multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399", size = 127029 }, + { url = "https://files.pythonhosted.org/packages/47/ac/3efb7bfe2f3aefcf8d103e9a7162572f01936155ab2f7ebcc7c255a23212/multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423", size = 137594 }, + { url = "https://files.pythonhosted.org/packages/42/9b/6c6e9e8dc4f915fc90a9b7798c44a30773dea2995fdcb619870e705afe2b/multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3", size = 134556 }, + { url = "https://files.pythonhosted.org/packages/1d/10/8e881743b26aaf718379a14ac58572a240e8293a1c9d68e1418fb11c0f90/multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753", size = 130993 }, + { url = "https://files.pythonhosted.org/packages/45/84/3eb91b4b557442802d058a7579e864b329968c8d0ea57d907e7023c677f2/multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80", size = 26405 }, + { url = "https://files.pythonhosted.org/packages/9f/0b/ad879847ecbf6d27e90a6eabb7eff6b62c129eefe617ea45eae7c1f0aead/multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926", size = 28795 }, + { url = "https://files.pythonhosted.org/packages/fd/16/92057c74ba3b96d5e211b553895cd6dc7cc4d1e43d9ab8fafc727681ef71/multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", size = 48713 }, + { url = "https://files.pythonhosted.org/packages/94/3d/37d1b8893ae79716179540b89fc6a0ee56b4a65fcc0d63535c6f5d96f217/multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", size = 29516 }, + { url = "https://files.pythonhosted.org/packages/a2/12/adb6b3200c363062f805275b4c1e656be2b3681aada66c80129932ff0bae/multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", size = 29557 }, + { url = "https://files.pythonhosted.org/packages/47/e9/604bb05e6e5bce1e6a5cf80a474e0f072e80d8ac105f1b994a53e0b28c42/multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", size = 130170 }, + { url = "https://files.pythonhosted.org/packages/7e/13/9efa50801785eccbf7086b3c83b71a4fb501a4d43549c2f2f80b8787d69f/multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", size = 134836 }, + { url = "https://files.pythonhosted.org/packages/bf/0f/93808b765192780d117814a6dfcc2e75de6dcc610009ad408b8814dca3ba/multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", size = 133475 }, + { url = "https://files.pythonhosted.org/packages/d3/c8/529101d7176fe7dfe1d99604e48d69c5dfdcadb4f06561f465c8ef12b4df/multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", size = 131049 }, + { url = "https://files.pythonhosted.org/packages/ca/0c/fc85b439014d5a58063e19c3a158a889deec399d47b5269a0f3b6a2e28bc/multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", size = 120370 }, + { url = "https://files.pythonhosted.org/packages/db/46/d4416eb20176492d2258fbd47b4abe729ff3b6e9c829ea4236f93c865089/multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", size = 125178 }, + { url = "https://files.pythonhosted.org/packages/5b/46/73697ad7ec521df7de5531a32780bbfd908ded0643cbe457f981a701457c/multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", size = 119567 }, + { url = "https://files.pythonhosted.org/packages/cd/ed/51f060e2cb0e7635329fa6ff930aa5cffa17f4c7f5c6c3ddc3500708e2f2/multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", size = 129822 }, + { url = "https://files.pythonhosted.org/packages/df/9e/ee7d1954b1331da3eddea0c4e08d9142da5f14b1321c7301f5014f49d492/multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", size = 128656 }, + { url = "https://files.pythonhosted.org/packages/77/00/8538f11e3356b5d95fa4b024aa566cde7a38aa7a5f08f4912b32a037c5dc/multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", size = 125360 }, + { url = "https://files.pythonhosted.org/packages/be/05/5d334c1f2462d43fec2363cd00b1c44c93a78c3925d952e9a71caf662e96/multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", size = 26382 }, + { url = "https://files.pythonhosted.org/packages/a3/bf/f332a13486b1ed0496d624bcc7e8357bb8053823e8cd4b9a18edc1d97e73/multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", size = 28529 }, + { url = "https://files.pythonhosted.org/packages/22/67/1c7c0f39fe069aa4e5d794f323be24bf4d33d62d2a348acdb7991f8f30db/multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", size = 48771 }, + { url = "https://files.pythonhosted.org/packages/3c/25/c186ee7b212bdf0df2519eacfb1981a017bda34392c67542c274651daf23/multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", size = 29533 }, + { url = "https://files.pythonhosted.org/packages/67/5e/04575fd837e0958e324ca035b339cea174554f6f641d3fb2b4f2e7ff44a2/multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", size = 29595 }, + { url = "https://files.pythonhosted.org/packages/d3/b2/e56388f86663810c07cfe4a3c3d87227f3811eeb2d08450b9e5d19d78876/multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", size = 130094 }, + { url = "https://files.pythonhosted.org/packages/6c/ee/30ae9b4186a644d284543d55d491fbd4239b015d36b23fea43b4c94f7052/multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", size = 134876 }, + { url = "https://files.pythonhosted.org/packages/84/c7/70461c13ba8ce3c779503c70ec9d0345ae84de04521c1f45a04d5f48943d/multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", size = 133500 }, + { url = "https://files.pythonhosted.org/packages/4a/9f/002af221253f10f99959561123fae676148dd730e2daa2cd053846a58507/multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", size = 131099 }, + { url = "https://files.pythonhosted.org/packages/82/42/d1c7a7301d52af79d88548a97e297f9d99c961ad76bbe6f67442bb77f097/multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", size = 120403 }, + { url = "https://files.pythonhosted.org/packages/68/f3/471985c2c7ac707547553e8f37cff5158030d36bdec4414cb825fbaa5327/multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", size = 125348 }, + { url = "https://files.pythonhosted.org/packages/67/2c/e6df05c77e0e433c214ec1d21ddd203d9a4770a1f2866a8ca40a545869a0/multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", size = 119673 }, + { url = "https://files.pythonhosted.org/packages/c5/cd/bc8608fff06239c9fb333f9db7743a1b2eafe98c2666c9a196e867a3a0a4/multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", size = 129927 }, + { url = "https://files.pythonhosted.org/packages/44/8e/281b69b7bc84fc963a44dc6e0bbcc7150e517b91df368a27834299a526ac/multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", size = 128711 }, + { url = "https://files.pythonhosted.org/packages/12/a4/63e7cd38ed29dd9f1881d5119f272c898ca92536cdb53ffe0843197f6c85/multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", size = 125519 }, + { url = "https://files.pythonhosted.org/packages/38/e0/4f5855037a72cd8a7a2f60a3952d9aa45feedb37ae7831642102604e8a37/multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", size = 26426 }, + { url = "https://files.pythonhosted.org/packages/7e/a5/17ee3a4db1e310b7405f5d25834460073a8ccd86198ce044dfaf69eac073/multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", size = 28531 }, + { url = "https://files.pythonhosted.org/packages/99/b7/b9e70fde2c0f0c9af4cc5277782a89b66d35948ea3369ec9f598358c3ac5/multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", size = 10051 }, +] + +[[package]] +name = "multiprocess" +version = "0.70.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/76/6e712a2623d146d314f17598df5de7224c85c0060ef63fd95cc15a25b3fa/multiprocess-0.70.16-pp310-pypy310_pp73-macosx_10_13_x86_64.whl", hash = "sha256:476887be10e2f59ff183c006af746cb6f1fd0eadcfd4ef49e605cbe2659920ee", size = 134980 }, + { url = "https://files.pythonhosted.org/packages/0f/ab/1e6e8009e380e22254ff539ebe117861e5bdb3bff1fc977920972237c6c7/multiprocess-0.70.16-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d951bed82c8f73929ac82c61f01a7b5ce8f3e5ef40f5b52553b4f547ce2b08ec", size = 134982 }, + { url = "https://files.pythonhosted.org/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02", size = 134824 }, + { url = "https://files.pythonhosted.org/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a", size = 143519 }, + { url = "https://files.pythonhosted.org/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e", size = 146741 }, + { url = "https://files.pythonhosted.org/packages/ea/89/38df130f2c799090c978b366cfdf5b96d08de5b29a4a293df7f7429fa50b/multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435", size = 132628 }, + { url = "https://files.pythonhosted.org/packages/da/d9/f7f9379981e39b8c2511c9e0326d212accacb82f12fbfdc1aa2ce2a7b2b6/multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3", size = 133351 }, +] + +[[package]] +name = "munch" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/a1/ec48010724eedfe2add68eb7592a0d238590e14e08b95a4ffb3c7b2f0808/munch-2.5.0.tar.gz", hash = "sha256:2d735f6f24d4dba3417fa448cae40c6e896ec1fdab6cdb5e6510999758a4dbd2", size = 17015 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/ab/85d8da5c9a45e072301beb37ad7f833cd344e04c817d97e0cc75681d248f/munch-2.5.0-py2.py3-none-any.whl", hash = "sha256:6f44af89a2ce4ed04ff8de41f70b226b984db10a91dcc7b9ac2efc1c77022fdd", size = 10347 }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, +] + +[[package]] +name = "netaddr" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/90/188b2a69654f27b221fba92fda7217778208532c962509e959a9cee5229d/netaddr-1.3.0.tar.gz", hash = "sha256:5c3c3d9895b551b763779ba7db7a03487dc1f8e3b385af819af341ae9ef6e48a", size = 2260504 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/cc/f4fe2c7ce68b92cbf5b2d379ca366e1edae38cccaad00f69f529b460c3ef/netaddr-1.3.0-py3-none-any.whl", hash = "sha256:c2c6a8ebe5554ce33b7d5b3a306b71bbb373e000bbbf2350dd5213cc56e3dbbe", size = 2262023 }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, +] + +[[package]] +name = "nltk" +version = "3.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "joblib" }, + { name = "regex" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/87/db8be88ad32c2d042420b6fd9ffd4a149f9a0d7f0e86b3f543be2eeeedd2/nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868", size = 2904691 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/66/7d9e26593edda06e8cb531874633f7c2372279c3b0f46235539fe546df8b/nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1", size = 1505442 }, +] + +[[package]] +name = "numpy" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245 }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540 }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623 }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774 }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081 }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451 }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572 }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722 }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170 }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558 }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137 }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552 }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957 }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573 }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330 }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895 }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253 }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074 }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640 }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230 }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803 }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835 }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499 }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497 }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158 }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173 }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174 }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701 }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313 }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179 }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.4.5.8" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/7f/7fbae15a3982dc9595e49ce0f19332423b260045d0a6afe93cdbe2f1f624/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0f8aa1706812e00b9f19dfe0cdb3999b092ccb8ca168c0db5b8ea712456fd9b3", size = 363333771 }, + { url = "https://files.pythonhosted.org/packages/ae/71/1c91302526c45ab494c23f61c7a84aa568b8c1f9d196efa5993957faf906/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b", size = 363438805 }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/b5/9fb3d00386d3361b03874246190dfec7b206fd74e6e287b26a8fcb359d95/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:79279b35cf6f91da114182a5ce1864997fd52294a87a16179ce275773799458a", size = 12354556 }, + { url = "https://files.pythonhosted.org/packages/67/42/f4f60238e8194a3106d06a058d494b18e006c10bb2b915655bd9f6ea4cb1/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb", size = 13813957 }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/aa/083b01c427e963ad0b314040565ea396f914349914c298556484f799e61b/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0eedf14185e04b76aa05b1fea04133e59f465b6f960c0cbf4e37c3cb6b0ea198", size = 24133372 }, + { url = "https://files.pythonhosted.org/packages/2c/14/91ae57cd4db3f9ef7aa99f4019cfa8d54cb4caa7e00975df6467e9725a9f/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338", size = 24640306 }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/aa/b656d755f474e2084971e9a297def515938d56b466ab39624012070cb773/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:961fe0e2e716a2a1d967aab7caee97512f71767f852f67432d572e36cb3a11f3", size = 894177 }, + { url = "https://files.pythonhosted.org/packages/ea/27/1795d86fe88ef397885f2e580ac37628ed058a92ed2c39dc8eac3adf0619/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5", size = 883737 }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.1.0.70" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.2.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/8a/0e728f749baca3fbeffad762738276e5df60851958be7783af121a7221e7/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5dad8008fc7f92f5ddfa2101430917ce2ffacd86824914c82e28990ad7f00399", size = 211422548 }, + { url = "https://files.pythonhosted.org/packages/27/94/3266821f65b92b3138631e9c8e7fe1fb513804ac934485a8d05776e1dd43/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9", size = 211459117 }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.5.147" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/9c/a79180e4d70995fdf030c6946991d0171555c6edf95c265c6b2bf7011112/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1f173f09e3e3c76ab084aba0de819c49e56614feae5c12f69883f4ae9bb5fad9", size = 56314811 }, + { url = "https://files.pythonhosted.org/packages/8a/6d/44ad094874c6f1b9c654f8ed939590bdc408349f137f9b98a3a23ccec411/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b", size = 56305206 }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.6.1.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/6b/a5c33cf16af09166845345275c34ad2190944bcc6026797a39f8e0a282e0/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d338f155f174f90724bbde3758b7ac375a70ce8e706d70b018dd3375545fc84e", size = 127634111 }, + { url = "https://files.pythonhosted.org/packages/3a/e1/5b9089a4b2a4790dfdea8b3a006052cfecff58139d5a4e34cb1a51df8d6f/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260", size = 127936057 }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.3.1.170" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/a9/c0d2f83a53d40a4a41be14cea6a0bf9e668ffcf8b004bd65633f433050c0/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9d32f62896231ebe0480efd8a7f702e143c98cfaa0e8a76df3386c1ba2b54df3", size = 207381987 }, + { url = "https://files.pythonhosted.org/packages/db/f7/97a9ea26ed4bbbfc2d470994b8b4f338ef663be97b8f677519ac195e113d/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1", size = 207454763 }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.21.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/99/12cd266d6233f47d00daf3a72739872bdc10267d0383508b0b9c84a18bb6/nvidia_nccl_cu12-2.21.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8579076d30a8c24988834445f8d633c697d42397e92ffc3f63fa26766d25e0a0", size = 188654414 }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/45/239d52c05074898a80a900f49b1615d81c07fceadd5ad6c4f86a987c0bc4/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4abe7fef64914ccfa909bc2ba39739670ecc9e820c83ccc7a6ed414122599b83", size = 20552510 }, + { url = "https://files.pythonhosted.org/packages/ff/ff/847841bacfbefc97a00036e0fce5a0f086b640756dc38caea5e1bb002655/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57", size = 21066810 }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/39/471f581edbb7804b39e8063d92fc8305bdc7a80ae5c07dbe6ea5c50d14a5/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3", size = 100417 }, + { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 }, +] + +[[package]] +name = "openai" +version = "1.59.5" +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/7c/b3/a99ff4f8034383147f853200ff5f6df63a8407a0061d6b3ff47914b94f4c/openai-1.59.5.tar.gz", hash = "sha256:9886e77c02dad9dc6a7b67a11ab372a56842a9b5d376aa476672175ab10e83a0", size = 344773 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/a2/a64f495c016234ca4269005b19eb9193a925dcad01af95eb8fea3de4ee9c/openai-1.59.5-py3-none-any.whl", hash = "sha256:e646b44856b0dda9345d3c43639e056334d792d1690e99690313c0ef7ca4d8cc", size = 454815 }, +] + +[[package]] +name = "opencv-python" +version = "4.10.0.84" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/b70a2d9ab205110d715906fc8ec83fbb00404aeb3a37a0654fdb68eb0c8c/opencv-python-4.10.0.84.tar.gz", hash = "sha256:72d234e4582e9658ffea8e9cae5b63d488ad06994ef12d81dc303b17472f3526", size = 95103981 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/82/564168a349148298aca281e342551404ef5521f33fba17b388ead0a84dc5/opencv_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc182f8f4cda51b45f01c64e4cbedfc2f00aff799debebc305d8d0210c43f251", size = 54835524 }, + { url = "https://files.pythonhosted.org/packages/64/4a/016cda9ad7cf18c58ba074628a4eaae8aa55f3fd06a266398cef8831a5b9/opencv_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:71e575744f1d23f79741450254660442785f45a0797212852ee5199ef12eed98", size = 56475426 }, + { url = "https://files.pythonhosted.org/packages/81/e4/7a987ebecfe5ceaf32db413b67ff18eb3092c598408862fff4d7cc3fd19b/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a332b50488e2dda866a6c5573ee192fe3583239fb26ff2f7f9ceb0bc119ea6", size = 41746971 }, + { url = "https://files.pythonhosted.org/packages/3f/a4/d2537f47fd7fcfba966bd806e3ec18e7ee1681056d4b0a9c8d983983e4d5/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ace140fc6d647fbe1c692bcb2abce768973491222c067c131d80957c595b71f", size = 62548253 }, + { url = "https://files.pythonhosted.org/packages/1e/39/bbf57e7b9dab623e8773f6ff36385456b7ae7fa9357a5e53db732c347eac/opencv_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2db02bb7e50b703f0a2d50c50ced72e95c574e1e5a0bb35a8a86d0b35c98c236", size = 28737688 }, + { url = "https://files.pythonhosted.org/packages/ec/6c/fab8113424af5049f85717e8e527ca3773299a3c6b02506e66436e19874f/opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe", size = 38842521 }, +] + +[[package]] +name = "orjson" +version = "3.10.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/f7/3219b56f47b4f5e864fb11cdf4ac0aaa3de608730ad2dc4c6e16382f35ec/orjson-3.10.14.tar.gz", hash = "sha256:cf31f6f071a6b8e7aa1ead1fa27b935b48d00fbfa6a28ce856cfff2d5dd68eed", size = 5282116 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/62/64348b8b29a14c7342f6aa45c8be0a87fdda2ce7716bc123717376537077/orjson-3.10.14-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:849ea7845a55f09965826e816cdc7689d6cf74fe9223d79d758c714af955bcb6", size = 249439 }, + { url = "https://files.pythonhosted.org/packages/9f/51/48f4dfbca7b4db630316b170db4a150a33cd405650258bd62a2d619b43b4/orjson-3.10.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5947b139dfa33f72eecc63f17e45230a97e741942955a6c9e650069305eb73d", size = 135811 }, + { url = "https://files.pythonhosted.org/packages/a1/1c/e18770843e6d045605c8e00a1be801da5668fa934b323b0492a49c9dee4f/orjson-3.10.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cde6d76910d3179dae70f164466692f4ea36da124d6fb1a61399ca589e81d69a", size = 150154 }, + { url = "https://files.pythonhosted.org/packages/51/1e/3817dc79164f1fc17fc53102f74f62d31f5f4ec042abdd24d94c5e06e51c/orjson-3.10.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6dfbaeb7afa77ca608a50e2770a0461177b63a99520d4928e27591b142c74b1", size = 139740 }, + { url = "https://files.pythonhosted.org/packages/ff/fc/fbf9e25448f7a2d67c1a2b6dad78a9340666bf9fda3339ff59b1e93f0b6f/orjson-3.10.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa45e489ef80f28ff0e5ba0a72812b8cfc7c1ef8b46a694723807d1b07c89ebb", size = 154479 }, + { url = "https://files.pythonhosted.org/packages/d4/df/c8b7ea21ff658f6a9a26d562055631c01d445bda5eb613c02c7d0934607d/orjson-3.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5007abfdbb1d866e2aa8990bd1c465f0f6da71d19e695fc278282be12cffa5", size = 130414 }, + { url = "https://files.pythonhosted.org/packages/df/f7/e29c2d42bef8fbf696a5e54e6339b0b9ea5179326950fee6ae80acf59d09/orjson-3.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1b49e2af011c84c3f2d541bb5cd1e3c7c2df672223e7e3ea608f09cf295e5f8a", size = 138545 }, + { url = "https://files.pythonhosted.org/packages/8e/97/afdf2908fe8eaeecb29e97fa82dc934f275acf330e5271def0b8fbac5478/orjson-3.10.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:164ac155109226b3a2606ee6dda899ccfbe6e7e18b5bdc3fbc00f79cc074157d", size = 130952 }, + { url = "https://files.pythonhosted.org/packages/4a/dd/04e01c1305694f47e9794c60ec7cece02e55fa9d57c5d72081eaaa62ad1d/orjson-3.10.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6b1225024cf0ef5d15934b5ffe9baf860fe8bc68a796513f5ea4f5056de30bca", size = 414673 }, + { url = "https://files.pythonhosted.org/packages/fa/12/28c4d5f6a395ac9693b250f0662366968c47fc99c8f3cd803a65b1f5ba46/orjson-3.10.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d6546e8073dc382e60fcae4a001a5a1bc46da5eab4a4878acc2d12072d6166d5", size = 141002 }, + { url = "https://files.pythonhosted.org/packages/21/f6/357cb167c2d2fd9542251cfd9f68681b67ed4dcdac82aa6ee2f4f3ab952e/orjson-3.10.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f1d2942605c894162252d6259b0121bf1cb493071a1ea8cb35d79cb3e6ac5bc", size = 129626 }, + { url = "https://files.pythonhosted.org/packages/df/07/d9062353500df9db8bfa7c6a5982687c97d0b69a5b158c4166d407ac94e2/orjson-3.10.14-cp310-cp310-win32.whl", hash = "sha256:397083806abd51cf2b3bbbf6c347575374d160331a2d33c5823e22249ad3118b", size = 142429 }, + { url = "https://files.pythonhosted.org/packages/50/ba/6ba2bf69ac0526d143aebe78bc39e6e5fbb51d5336fbc5efb9aab6687cd9/orjson-3.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:fa18f949d3183a8d468367056be989666ac2bef3a72eece0bade9cdb733b3c28", size = 133512 }, + { url = "https://files.pythonhosted.org/packages/bf/18/26721760368e12b691fb6811692ed21ae5275ea918db409ba26866cacbe8/orjson-3.10.14-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f506fd666dd1ecd15a832bebc66c4df45c1902fd47526292836c339f7ba665a9", size = 249437 }, + { url = "https://files.pythonhosted.org/packages/d5/5b/2adfe7cc301edeb3bffc1942956659c19ec00d51a21c53c17c0767bebf47/orjson-3.10.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efe5fd254cfb0eeee13b8ef7ecb20f5d5a56ddda8a587f3852ab2cedfefdb5f6", size = 135812 }, + { url = "https://files.pythonhosted.org/packages/8a/68/07df7787fd9ff6dba815b2d793eec5e039d288fdf150431ed48a660bfcbb/orjson-3.10.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ddc8c866d7467f5ee2991397d2ea94bcf60d0048bdd8ca555740b56f9042725", size = 150153 }, + { url = "https://files.pythonhosted.org/packages/02/71/f68562734461b801b53bacd5365e079dcb3c78656a662f0639494880e522/orjson-3.10.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af8e42ae4363773658b8d578d56dedffb4f05ceeb4d1d4dd3fb504950b45526", size = 139742 }, + { url = "https://files.pythonhosted.org/packages/04/03/1355fb27652582f00d3c62e93a32b982fa42bc31d2e07f0a317867069096/orjson-3.10.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84dd83110503bc10e94322bf3ffab8bc49150176b49b4984dc1cce4c0a993bf9", size = 154479 }, + { url = "https://files.pythonhosted.org/packages/7c/47/1c2a840f27715e8bc2bbafffc851512ede6e53483593eded190919bdcaf4/orjson-3.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36f5bfc0399cd4811bf10ec7a759c7ab0cd18080956af8ee138097d5b5296a95", size = 130413 }, + { url = "https://files.pythonhosted.org/packages/dd/b2/5bb51006cbae85b052d1bbee7ff43ae26fa155bb3d31a71b0c07d384d5e3/orjson-3.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868943660fb2a1e6b6b965b74430c16a79320b665b28dd4511d15ad5038d37d5", size = 138545 }, + { url = "https://files.pythonhosted.org/packages/79/30/7841a5dd46bb46b8e868791d5469c9d4788d3e26b7e69d40256647997baf/orjson-3.10.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33449c67195969b1a677533dee9d76e006001213a24501333624623e13c7cc8e", size = 130953 }, + { url = "https://files.pythonhosted.org/packages/08/49/720e7c2040c0f1df630a36d83d449bd7e4d4471071d5ece47a4f7211d570/orjson-3.10.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e4c9f60f9fb0b5be66e416dcd8c9d94c3eabff3801d875bdb1f8ffc12cf86905", size = 414675 }, + { url = "https://files.pythonhosted.org/packages/50/b0/ca7619f34280e7dcbd50dbc9c5fe5200c12cd7269b8858652beb3887483f/orjson-3.10.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0de4d6315cfdbd9ec803b945c23b3a68207fd47cbe43626036d97e8e9561a436", size = 141004 }, + { url = "https://files.pythonhosted.org/packages/75/1b/7548e3a711543f438e87a4349e00439ab7f37807942e5659f29363f35765/orjson-3.10.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83adda3db595cb1a7e2237029b3249c85afbe5c747d26b41b802e7482cb3933e", size = 129629 }, + { url = "https://files.pythonhosted.org/packages/b0/1e/4930a6ff46debd6be1ff18e869b7bc43a7ad762c865610b7e745038d6f68/orjson-3.10.14-cp311-cp311-win32.whl", hash = "sha256:998019ef74a4997a9d741b1473533cdb8faa31373afc9849b35129b4b8ec048d", size = 142430 }, + { url = "https://files.pythonhosted.org/packages/28/e0/6cc1cd1dfde36555e81ac869f7847e86bb11c27f97b72fde2f1509b12163/orjson-3.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:9d034abdd36f0f0f2240f91492684e5043d46f290525d1117712d5b8137784eb", size = 133516 }, + { url = "https://files.pythonhosted.org/packages/8c/dc/dc5a882be016ee8688bd867ad3b4e3b2ab039d91383099702301a1adb6ac/orjson-3.10.14-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2ad4b7e367efba6dc3f119c9a0fcd41908b7ec0399a696f3cdea7ec477441b09", size = 249396 }, + { url = "https://files.pythonhosted.org/packages/f0/95/4c23ff5c0505cd687928608e0b7910ccb44ce59490079e1c17b7610aa0d0/orjson-3.10.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f496286fc85e93ce0f71cc84fc1c42de2decf1bf494094e188e27a53694777a7", size = 135689 }, + { url = "https://files.pythonhosted.org/packages/ad/39/b4bdd19604dce9d6509c4d86e8e251a1373a24204b4c4169866dcecbe5f5/orjson-3.10.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7f189bbfcded40e41a6969c1068ba305850ba016665be71a217918931416fbf", size = 150136 }, + { url = "https://files.pythonhosted.org/packages/1d/92/7b9bad96353abd3e89947960252dcf1022ce2df7f29056e434de05e18b6d/orjson-3.10.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cc8204f0b75606869c707da331058ddf085de29558b516fc43c73ee5ee2aadb", size = 139766 }, + { url = "https://files.pythonhosted.org/packages/a6/bd/abb13c86540b7a91b40d7d9f8549d03a026bc22d78fa93f71d68b8f4c36e/orjson-3.10.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deaa2899dff7f03ab667e2ec25842d233e2a6a9e333efa484dfe666403f3501c", size = 154533 }, + { url = "https://files.pythonhosted.org/packages/c0/02/0bcb91ec9c7143012359983aca44f567f87df379957cd4af11336217b12f/orjson-3.10.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1c3ea52642c9714dc6e56de8a451a066f6d2707d273e07fe8a9cc1ba073813d", size = 130658 }, + { url = "https://files.pythonhosted.org/packages/b4/1e/b304596bb1f800d47d6e92305bd09f0eef693ed4f7b2095db63f9808b229/orjson-3.10.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d3f9ed72e7458ded9a1fb1b4d4ed4c4fdbaf82030ce3f9274b4dc1bff7ace2b", size = 138546 }, + { url = "https://files.pythonhosted.org/packages/56/c7/65d72b22080186ef618a46afeb9386e20056f3237664090f3a2f8da1cd6d/orjson-3.10.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07520685d408a2aba514c17ccc16199ff2934f9f9e28501e676c557f454a37fe", size = 130774 }, + { url = "https://files.pythonhosted.org/packages/4d/85/1ab35a832f32b37ccd673721e845cf302f23453603112255af611c91d1d1/orjson-3.10.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:76344269b550ea01488d19a2a369ab572c1ac4449a72e9f6ac0d70eb1cbfb953", size = 414649 }, + { url = "https://files.pythonhosted.org/packages/d1/7d/1d6575f779bab8fe698fa6d52e8aa3aa0a9fca4885d0bf6197700455713a/orjson-3.10.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e2979d0f2959990620f7e62da6cd954e4620ee815539bc57a8ae46e2dacf90e3", size = 141060 }, + { url = "https://files.pythonhosted.org/packages/f8/26/68513e28b3bd1d7633318ed2818e86d1bfc8b782c87c520c7b363092837f/orjson-3.10.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03f61ca3674555adcb1aa717b9fc87ae936aa7a63f6aba90a474a88701278780", size = 129798 }, + { url = "https://files.pythonhosted.org/packages/44/ca/020fb99c98ff7267ba18ce798ff0c8c3aa97cd949b611fc76cad3c87e534/orjson-3.10.14-cp312-cp312-win32.whl", hash = "sha256:d5075c54edf1d6ad81d4c6523ce54a748ba1208b542e54b97d8a882ecd810fd1", size = 142524 }, + { url = "https://files.pythonhosted.org/packages/70/7f/f2d346819a273653825e7c92dc26418c8da506003c9fc1dfe8157e733b2e/orjson-3.10.14-cp312-cp312-win_amd64.whl", hash = "sha256:175cafd322e458603e8ce73510a068d16b6e6f389c13f69bf16de0e843d7d406", size = 133663 }, + { url = "https://files.pythonhosted.org/packages/46/bb/f1b037d89f580c79eda0940772384cc226a697be1cb4eb94ae4e792aa34c/orjson-3.10.14-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:0905ca08a10f7e0e0c97d11359609300eb1437490a7f32bbaa349de757e2e0c7", size = 249333 }, + { url = "https://files.pythonhosted.org/packages/e4/72/12958a073cace3f8acef0f9a30739d95f46bbb1544126fecad11527d4508/orjson-3.10.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92d13292249f9f2a3e418cbc307a9fbbef043c65f4bd8ba1eb620bc2aaba3d15", size = 125038 }, + { url = "https://files.pythonhosted.org/packages/c0/ae/461f78b1c98de1bc034af88bc21c6a792cc63373261fbc10a6ee560814fa/orjson-3.10.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90937664e776ad316d64251e2fa2ad69265e4443067668e4727074fe39676414", size = 130604 }, + { url = "https://files.pythonhosted.org/packages/ae/d2/17f50513f56bff7898840fddf7fb88f501305b9b2605d2793ff224789665/orjson-3.10.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9ed3d26c4cb4f6babaf791aa46a029265850e80ec2a566581f5c2ee1a14df4f1", size = 130756 }, + { url = "https://files.pythonhosted.org/packages/fa/bc/673856e4af94c9890dfd8e2054c05dc2ddc16d1728c2aa0c5bd198943105/orjson-3.10.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:56ee546c2bbe9599aba78169f99d1dc33301853e897dbaf642d654248280dc6e", size = 414613 }, + { url = "https://files.pythonhosted.org/packages/09/01/08c5b69b0756dd1790fcffa569d6a28dedcd7b97f825e4b46537b788908c/orjson-3.10.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:901e826cb2f1bdc1fcef3ef59adf0c451e8f7c0b5deb26c1a933fb66fb505eae", size = 141010 }, + { url = "https://files.pythonhosted.org/packages/5b/98/72883bb6cf88fd364996e62d2026622ca79bfb8dbaf96ccdd2018ada25b1/orjson-3.10.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:26336c0d4b2d44636e1e1e6ed1002f03c6aae4a8a9329561c8883f135e9ff010", size = 129732 }, + { url = "https://files.pythonhosted.org/packages/e4/99/347418f7ef56dcb478ba131a6112b8ddd5b747942652b6e77a53155a7e21/orjson-3.10.14-cp313-cp313-win32.whl", hash = "sha256:e2bc525e335a8545c4e48f84dd0328bc46158c9aaeb8a1c2276546e94540ea3d", size = 142504 }, + { url = "https://files.pythonhosted.org/packages/59/ac/5e96cad01083015f7bfdb02ccafa489da8e6caa7f4c519e215f04d2bd856/orjson-3.10.14-cp313-cp313-win_amd64.whl", hash = "sha256:eca04dfd792cedad53dc9a917da1a522486255360cb4e77619343a20d9f35364", size = 133388 }, +] + +[[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 = "password-strength" +version = "0.0.3.post2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/f1/6165ebcca27fca3f1d63f8c3a45805c2ed8568be4d09219a2aa45e792c14/password_strength-0.0.3.post2.tar.gz", hash = "sha256:bf4df10a58fcd3abfa182367307b4fd7b1cec518121dd83bf80c1c42ba796762", size = 12857 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/d6/08fd888c980589e4e27c2a4177e972481e8881600138e63afb785fe52630/password_strength-0.0.3.post2-py2.py3-none-any.whl", hash = "sha256:6739357c2863d707b7c7f247ff7c6882a70904a18d12c9aaf98f8b95da176fb9", size = 12167 }, +] + +[[package]] +name = "peft" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accelerate" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch" }, + { name = "tqdm" }, + { name = "transformers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/33/fb0c31eaa8162c01e9250b21aa65d46a5339f17a818a97c68391db2ff44b/peft-0.14.0.tar.gz", hash = "sha256:546d69af7b42f5ef715a3d3261ed818bc917ae6055e5d7e187ed3f2c76ad72dc", size = 411902 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/05/e58e3aaa36544d30a917814e336fc65a746f708e5874945e92999bc22fa3/peft-0.14.0-py3-none-any.whl", hash = "sha256:2f04f3a870c3baf30f15e7dcaa5dd70d3e54cfdd146d3c6c187735d3ae0a0700", size = 374831 }, +] + +[[package]] +name = "pillow" +version = "11.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/1c/2dcea34ac3d7bc96a1fd1bd0a6e06a57c67167fec2cff8d95d88229a8817/pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8", size = 3229983 }, + { url = "https://files.pythonhosted.org/packages/14/ca/6bec3df25e4c88432681de94a3531cc738bd85dea6c7aa6ab6f81ad8bd11/pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192", size = 3101831 }, + { url = "https://files.pythonhosted.org/packages/d4/2c/668e18e5521e46eb9667b09e501d8e07049eb5bfe39d56be0724a43117e6/pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2", size = 4314074 }, + { url = "https://files.pythonhosted.org/packages/02/80/79f99b714f0fc25f6a8499ecfd1f810df12aec170ea1e32a4f75746051ce/pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26", size = 4394933 }, + { url = "https://files.pythonhosted.org/packages/81/aa/8d4ad25dc11fd10a2001d5b8a80fdc0e564ac33b293bdfe04ed387e0fd95/pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07", size = 4353349 }, + { url = "https://files.pythonhosted.org/packages/84/7a/cd0c3eaf4a28cb2a74bdd19129f7726277a7f30c4f8424cd27a62987d864/pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482", size = 4476532 }, + { url = "https://files.pythonhosted.org/packages/8f/8b/a907fdd3ae8f01c7670dfb1499c53c28e217c338b47a813af8d815e7ce97/pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e", size = 4279789 }, + { url = "https://files.pythonhosted.org/packages/6f/9a/9f139d9e8cccd661c3efbf6898967a9a337eb2e9be2b454ba0a09533100d/pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269", size = 4413131 }, + { url = "https://files.pythonhosted.org/packages/a8/68/0d8d461f42a3f37432203c8e6df94da10ac8081b6d35af1c203bf3111088/pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49", size = 2291213 }, + { url = "https://files.pythonhosted.org/packages/14/81/d0dff759a74ba87715509af9f6cb21fa21d93b02b3316ed43bda83664db9/pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a", size = 2625725 }, + { url = "https://files.pythonhosted.org/packages/ce/1f/8d50c096a1d58ef0584ddc37e6f602828515219e9d2428e14ce50f5ecad1/pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65", size = 2375213 }, + { url = "https://files.pythonhosted.org/packages/dd/d6/2000bfd8d5414fb70cbbe52c8332f2283ff30ed66a9cde42716c8ecbe22c/pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457", size = 3229968 }, + { url = "https://files.pythonhosted.org/packages/d9/45/3fe487010dd9ce0a06adf9b8ff4f273cc0a44536e234b0fad3532a42c15b/pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35", size = 3101806 }, + { url = "https://files.pythonhosted.org/packages/e3/72/776b3629c47d9d5f1c160113158a7a7ad177688d3a1159cd3b62ded5a33a/pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2", size = 4322283 }, + { url = "https://files.pythonhosted.org/packages/e4/c2/e25199e7e4e71d64eeb869f5b72c7ddec70e0a87926398785ab944d92375/pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070", size = 4402945 }, + { url = "https://files.pythonhosted.org/packages/c1/ed/51d6136c9d5911f78632b1b86c45241c712c5a80ed7fa7f9120a5dff1eba/pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6", size = 4361228 }, + { url = "https://files.pythonhosted.org/packages/48/a4/fbfe9d5581d7b111b28f1d8c2762dee92e9821bb209af9fa83c940e507a0/pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1", size = 4484021 }, + { url = "https://files.pythonhosted.org/packages/39/db/0b3c1a5018117f3c1d4df671fb8e47d08937f27519e8614bbe86153b65a5/pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2", size = 4287449 }, + { url = "https://files.pythonhosted.org/packages/d9/58/bc128da7fea8c89fc85e09f773c4901e95b5936000e6f303222490c052f3/pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96", size = 4419972 }, + { url = "https://files.pythonhosted.org/packages/5f/bb/58f34379bde9fe197f51841c5bbe8830c28bbb6d3801f16a83b8f2ad37df/pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f", size = 2291201 }, + { url = "https://files.pythonhosted.org/packages/3a/c6/fce9255272bcf0c39e15abd2f8fd8429a954cf344469eaceb9d0d1366913/pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761", size = 2625686 }, + { url = "https://files.pythonhosted.org/packages/c8/52/8ba066d569d932365509054859f74f2a9abee273edcef5cd75e4bc3e831e/pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71", size = 2375194 }, + { url = "https://files.pythonhosted.org/packages/95/20/9ce6ed62c91c073fcaa23d216e68289e19d95fb8188b9fb7a63d36771db8/pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a", size = 3226818 }, + { url = "https://files.pythonhosted.org/packages/b9/d8/f6004d98579a2596c098d1e30d10b248798cceff82d2b77aa914875bfea1/pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b", size = 3101662 }, + { url = "https://files.pythonhosted.org/packages/08/d9/892e705f90051c7a2574d9f24579c9e100c828700d78a63239676f960b74/pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3", size = 4329317 }, + { url = "https://files.pythonhosted.org/packages/8c/aa/7f29711f26680eab0bcd3ecdd6d23ed6bce180d82e3f6380fb7ae35fcf3b/pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a", size = 4412999 }, + { url = "https://files.pythonhosted.org/packages/c8/c4/8f0fe3b9e0f7196f6d0bbb151f9fba323d72a41da068610c4c960b16632a/pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1", size = 4368819 }, + { url = "https://files.pythonhosted.org/packages/38/0d/84200ed6a871ce386ddc82904bfadc0c6b28b0c0ec78176871a4679e40b3/pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f", size = 4496081 }, + { url = "https://files.pythonhosted.org/packages/84/9c/9bcd66f714d7e25b64118e3952d52841a4babc6d97b6d28e2261c52045d4/pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91", size = 4296513 }, + { url = "https://files.pythonhosted.org/packages/db/61/ada2a226e22da011b45f7104c95ebda1b63dcbb0c378ad0f7c2a710f8fd2/pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c", size = 4431298 }, + { url = "https://files.pythonhosted.org/packages/e7/c4/fc6e86750523f367923522014b821c11ebc5ad402e659d8c9d09b3c9d70c/pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6", size = 2291630 }, + { url = "https://files.pythonhosted.org/packages/08/5c/2104299949b9d504baf3f4d35f73dbd14ef31bbd1ddc2c1b66a5b7dfda44/pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf", size = 2626369 }, + { url = "https://files.pythonhosted.org/packages/37/f3/9b18362206b244167c958984b57c7f70a0289bfb59a530dd8af5f699b910/pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5", size = 2375240 }, + { url = "https://files.pythonhosted.org/packages/b3/31/9ca79cafdce364fd5c980cd3416c20ce1bebd235b470d262f9d24d810184/pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc", size = 3226640 }, + { url = "https://files.pythonhosted.org/packages/ac/0f/ff07ad45a1f172a497aa393b13a9d81a32e1477ef0e869d030e3c1532521/pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0", size = 3101437 }, + { url = "https://files.pythonhosted.org/packages/08/2f/9906fca87a68d29ec4530be1f893149e0cb64a86d1f9f70a7cfcdfe8ae44/pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1", size = 4326605 }, + { url = "https://files.pythonhosted.org/packages/b0/0f/f3547ee15b145bc5c8b336401b2d4c9d9da67da9dcb572d7c0d4103d2c69/pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec", size = 4411173 }, + { url = "https://files.pythonhosted.org/packages/b1/df/bf8176aa5db515c5de584c5e00df9bab0713548fd780c82a86cba2c2fedb/pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5", size = 4369145 }, + { url = "https://files.pythonhosted.org/packages/de/7c/7433122d1cfadc740f577cb55526fdc39129a648ac65ce64db2eb7209277/pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114", size = 4496340 }, + { url = "https://files.pythonhosted.org/packages/25/46/dd94b93ca6bd555588835f2504bd90c00d5438fe131cf01cfa0c5131a19d/pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352", size = 4296906 }, + { url = "https://files.pythonhosted.org/packages/a8/28/2f9d32014dfc7753e586db9add35b8a41b7a3b46540e965cb6d6bc607bd2/pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3", size = 4431759 }, + { url = "https://files.pythonhosted.org/packages/33/48/19c2cbe7403870fbe8b7737d19eb013f46299cdfe4501573367f6396c775/pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9", size = 2291657 }, + { url = "https://files.pythonhosted.org/packages/3b/ad/285c556747d34c399f332ba7c1a595ba245796ef3e22eae190f5364bb62b/pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c", size = 2626304 }, + { url = "https://files.pythonhosted.org/packages/e5/7b/ef35a71163bf36db06e9c8729608f78dedf032fc8313d19bd4be5c2588f3/pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65", size = 2375117 }, + { url = "https://files.pythonhosted.org/packages/79/30/77f54228401e84d6791354888549b45824ab0ffde659bafa67956303a09f/pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861", size = 3230060 }, + { url = "https://files.pythonhosted.org/packages/ce/b1/56723b74b07dd64c1010fee011951ea9c35a43d8020acd03111f14298225/pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081", size = 3106192 }, + { url = "https://files.pythonhosted.org/packages/e1/cd/7bf7180e08f80a4dcc6b4c3a0aa9e0b0ae57168562726a05dc8aa8fa66b0/pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c", size = 4446805 }, + { url = "https://files.pythonhosted.org/packages/97/42/87c856ea30c8ed97e8efbe672b58c8304dee0573f8c7cab62ae9e31db6ae/pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547", size = 4530623 }, + { url = "https://files.pythonhosted.org/packages/ff/41/026879e90c84a88e33fb00cc6bd915ac2743c67e87a18f80270dfe3c2041/pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab", size = 4465191 }, + { url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494 }, + { url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595 }, + { url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651 }, + { url = "https://files.pythonhosted.org/packages/fa/c5/389961578fb677b8b3244fcd934f720ed25a148b9a5cc81c91bdf59d8588/pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90", size = 3198345 }, + { url = "https://files.pythonhosted.org/packages/c4/fa/803c0e50ffee74d4b965229e816af55276eac1d5806712de86f9371858fd/pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb", size = 3072938 }, + { url = "https://files.pythonhosted.org/packages/dc/67/2a3a5f8012b5d8c63fe53958ba906c1b1d0482ebed5618057ef4d22f8076/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442", size = 3400049 }, + { url = "https://files.pythonhosted.org/packages/e5/a0/514f0d317446c98c478d1872497eb92e7cde67003fed74f696441e647446/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83", size = 3422431 }, + { url = "https://files.pythonhosted.org/packages/cd/00/20f40a935514037b7d3f87adfc87d2c538430ea625b63b3af8c3f5578e72/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f", size = 3446208 }, + { url = "https://files.pythonhosted.org/packages/28/3c/7de681727963043e093c72e6c3348411b0185eab3263100d4490234ba2f6/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73", size = 3509746 }, + { url = "https://files.pythonhosted.org/packages/41/67/936f9814bdd74b2dfd4822f1f7725ab5d8ff4103919a1664eb4874c58b2f/pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0", size = 2626353 }, +] + +[[package]] +name = "pip-chill" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/1d/eec0f393fe17675792e302a82cd6c1e77e261d212c7cbf70072727a6e016/pip-chill-1.0.3.tar.gz", hash = "sha256:42c3b888efde0b3dc5d5307b92fae5fb67695dd9c29c9d31891b9505dd8b735a", size = 19455 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/53/6693cc6d71854b024b243139b3fc1f71220abf715e4eb5db94c2a13637c3/pip_chill-1.0.3-py2.py3-none-any.whl", hash = "sha256:452a38edbcdfc333301c438c26ba00a0762d2034fe26a235d8587134453ccdb1", size = 6890 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "playwright" +version = "1.49.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet" }, + { name = "pyee" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/be/01025581052e43eb698092c4328d7497ca62bcb5c83f15a611d4a71b4b92/playwright-1.49.1-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:1041ffb45a0d0bc44d698d3a5aa3ac4b67c9bd03540da43a0b70616ad52592b8", size = 39559859 }, + { url = "https://files.pythonhosted.org/packages/79/25/ef1010a42cc7d576282015d983c5451d73e369b198b6eb32a177fae281f8/playwright-1.49.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9f38ed3d0c1f4e0a6d1c92e73dd9a61f8855133249d6f0cec28648d38a7137be", size = 38808973 }, + { url = "https://files.pythonhosted.org/packages/70/4b/3930cf10f303a10d493a382e4448aaff898b4065698b3b8d92f902e53e08/playwright-1.49.1-py3-none-macosx_11_0_universal2.whl", hash = "sha256:3be48c6d26dc819ca0a26567c1ae36a980a0303dcd4249feb6f59e115aaddfb8", size = 39559863 }, + { url = "https://files.pythonhosted.org/packages/9a/c1/ea765e72a746dc7ec2ce155ffea29d454e7171db78f3c09185e888387246/playwright-1.49.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:753ca90ee31b4b03d165cfd36e477309ebf2b4381953f2a982ff612d85b147d2", size = 44163300 }, + { url = "https://files.pythonhosted.org/packages/5a/52/95efac704bf36b770a2522d88a6dee298042845d10bfb35f7ca0fcc36d91/playwright-1.49.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd9bc8dab37aa25198a01f555f0a2e2c3813fe200fef018ac34dfe86b34994b9", size = 43744353 }, + { url = "https://files.pythonhosted.org/packages/f9/97/a3fccc9aaa6da83890772e9980703b0ea6b1e1ad42042fb50df3aef6c641/playwright-1.49.1-py3-none-win32.whl", hash = "sha256:43b304be67f096058e587dac453ece550eff87b8fbed28de30f4f022cc1745bb", size = 34060663 }, + { url = "https://files.pythonhosted.org/packages/71/a9/bd88ac0bd498c91aab3aba2e393d1fa59f72a7243e9265ccbf4861ca4f64/playwright-1.49.1-py3-none-win_amd64.whl", hash = "sha256:47b23cb346283278f5b4d1e1990bcb6d6302f80c0aa0ca93dd0601a1400191df", size = 34060667 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "primp" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/89/53593df582f3bb35ad5e1a96cb00246c8b6f8b7f116253b9542c8d8c44b9/primp-0.10.0.tar.gz", hash = "sha256:93142590a5a1958240ee5b74faaf2f55185ed499ccaabc622d71cb0cc8a47a0b", size = 84282 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/ef/ecfbcea6a136ef758ba3f5644dc4d06df506f669e9381d52fa09b0952de7/primp-0.10.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:7a91a089bf2962b5b56c8d83d09535eb81cf55b53c09d83208b9e5a715cf2c17", size = 3164015 }, + { url = "https://files.pythonhosted.org/packages/84/15/7fedf1280f04c17fb06095694403d27134758b70bf508948a796ed668f06/primp-0.10.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:0128453cce81552f7aa6ac2bf9b8741b7816cdb2d10536e62c77daaf6483b9af", size = 2929848 }, + { url = "https://files.pythonhosted.org/packages/26/56/dae7ba34f6f41402b1e0a8c17640fa5b44c06012d5d07d69399da2db5cea/primp-0.10.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a959e9a83cff0ae7a85a02cc183e4db636f69ff41dddb7c4e32f997924923417", size = 3251850 }, + { url = "https://files.pythonhosted.org/packages/76/39/20c100140827f0e82e8c3c68284be3292b8345dd5bb7dd5886cd08a2f984/primp-0.10.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8e711cfa019fa9bdc0cba4d5d596f319c884a4329e505bd73e92eee0b024061a", size = 3214061 }, + { url = "https://files.pythonhosted.org/packages/4b/bc/2ce42b5024931c2178999f526a0d6654285e95c178ed5f1c35e3e16ef9bb/primp-0.10.0-cp38-abi3-manylinux_2_34_armv7l.whl", hash = "sha256:b859336d9a35669b68a29c5d8f050e0dca380452dabf6c9667bb8599f010d164", size = 2978905 }, + { url = "https://files.pythonhosted.org/packages/9b/07/c42bc5772e5f1655139d7a2b72f141f8a1fa9686684af50c7aab3d944840/primp-0.10.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dc875cc9a733fe3e6344a37f2b5888e0a9605bb37807fc3009f3b03786408f34", size = 3386043 }, + { url = "https://files.pythonhosted.org/packages/f5/9f/2b6c0322e33b8ee88489e5eac8f5e7dfe4a53cf75cc43cd6e712e76597e0/primp-0.10.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a27c5d997c37bf8237963c11e376eaa66e7eccee39164e3e259a1c3767c304d6", size = 3577429 }, + { url = "https://files.pythonhosted.org/packages/70/cc/8dd693b9e2577690e86fb589478e8788df2920859e04743800eb7d02213c/primp-0.10.0-cp38-abi3-win_amd64.whl", hash = "sha256:7fe94c3164c2efffff08f7f54c018ac445112961b3ce4f4f499315ba0a9d1ef3", size = 3116121 }, +] + +[[package]] +name = "propcache" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/c8/2a13f78d82211490855b2fb303b6721348d0787fdd9a12ac46d99d3acde1/propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64", size = 41735 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a5/0ea64c9426959ef145a938e38c832fc551843481d356713ececa9a8a64e8/propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6", size = 79296 }, + { url = "https://files.pythonhosted.org/packages/76/5a/916db1aba735f55e5eca4733eea4d1973845cf77dfe67c2381a2ca3ce52d/propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2", size = 45622 }, + { url = "https://files.pythonhosted.org/packages/2d/62/685d3cf268b8401ec12b250b925b21d152b9d193b7bffa5fdc4815c392c2/propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea", size = 45133 }, + { url = "https://files.pythonhosted.org/packages/4d/3d/31c9c29ee7192defc05aa4d01624fd85a41cf98e5922aaed206017329944/propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212", size = 204809 }, + { url = "https://files.pythonhosted.org/packages/10/a1/e4050776f4797fc86140ac9a480d5dc069fbfa9d499fe5c5d2fa1ae71f07/propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3", size = 219109 }, + { url = "https://files.pythonhosted.org/packages/c9/c0/e7ae0df76343d5e107d81e59acc085cea5fd36a48aa53ef09add7503e888/propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d", size = 217368 }, + { url = "https://files.pythonhosted.org/packages/fc/e1/e0a2ed6394b5772508868a977d3238f4afb2eebaf9976f0b44a8d347ad63/propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634", size = 205124 }, + { url = "https://files.pythonhosted.org/packages/50/c1/e388c232d15ca10f233c778bbdc1034ba53ede14c207a72008de45b2db2e/propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2", size = 195463 }, + { url = "https://files.pythonhosted.org/packages/0a/fd/71b349b9def426cc73813dbd0f33e266de77305e337c8c12bfb0a2a82bfb/propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958", size = 198358 }, + { url = "https://files.pythonhosted.org/packages/02/f2/d7c497cd148ebfc5b0ae32808e6c1af5922215fe38c7a06e4e722fe937c8/propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c", size = 195560 }, + { url = "https://files.pythonhosted.org/packages/bb/57/f37041bbe5e0dfed80a3f6be2612a3a75b9cfe2652abf2c99bef3455bbad/propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583", size = 196895 }, + { url = "https://files.pythonhosted.org/packages/83/36/ae3cc3e4f310bff2f064e3d2ed5558935cc7778d6f827dce74dcfa125304/propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf", size = 207124 }, + { url = "https://files.pythonhosted.org/packages/8c/c4/811b9f311f10ce9d31a32ff14ce58500458443627e4df4ae9c264defba7f/propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034", size = 210442 }, + { url = "https://files.pythonhosted.org/packages/18/dd/a1670d483a61ecac0d7fc4305d91caaac7a8fc1b200ea3965a01cf03bced/propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b", size = 203219 }, + { url = "https://files.pythonhosted.org/packages/f9/2d/30ced5afde41b099b2dc0c6573b66b45d16d73090e85655f1a30c5a24e07/propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4", size = 40313 }, + { url = "https://files.pythonhosted.org/packages/23/84/bd9b207ac80da237af77aa6e153b08ffa83264b1c7882495984fcbfcf85c/propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba", size = 44428 }, + { url = "https://files.pythonhosted.org/packages/bc/0f/2913b6791ebefb2b25b4efd4bb2299c985e09786b9f5b19184a88e5778dd/propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16", size = 79297 }, + { url = "https://files.pythonhosted.org/packages/cf/73/af2053aeccd40b05d6e19058419ac77674daecdd32478088b79375b9ab54/propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717", size = 45611 }, + { url = "https://files.pythonhosted.org/packages/3c/09/8386115ba7775ea3b9537730e8cf718d83bbf95bffe30757ccf37ec4e5da/propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3", size = 45146 }, + { url = "https://files.pythonhosted.org/packages/03/7a/793aa12f0537b2e520bf09f4c6833706b63170a211ad042ca71cbf79d9cb/propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9", size = 232136 }, + { url = "https://files.pythonhosted.org/packages/f1/38/b921b3168d72111769f648314100558c2ea1d52eb3d1ba7ea5c4aa6f9848/propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787", size = 239706 }, + { url = "https://files.pythonhosted.org/packages/14/29/4636f500c69b5edea7786db3c34eb6166f3384b905665ce312a6e42c720c/propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465", size = 238531 }, + { url = "https://files.pythonhosted.org/packages/85/14/01fe53580a8e1734ebb704a3482b7829a0ef4ea68d356141cf0994d9659b/propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af", size = 231063 }, + { url = "https://files.pythonhosted.org/packages/33/5c/1d961299f3c3b8438301ccfbff0143b69afcc30c05fa28673cface692305/propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7", size = 220134 }, + { url = "https://files.pythonhosted.org/packages/00/d0/ed735e76db279ba67a7d3b45ba4c654e7b02bc2f8050671ec365d8665e21/propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f", size = 220009 }, + { url = "https://files.pythonhosted.org/packages/75/90/ee8fab7304ad6533872fee982cfff5a53b63d095d78140827d93de22e2d4/propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54", size = 212199 }, + { url = "https://files.pythonhosted.org/packages/eb/ec/977ffaf1664f82e90737275873461695d4c9407d52abc2f3c3e24716da13/propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505", size = 214827 }, + { url = "https://files.pythonhosted.org/packages/57/48/031fb87ab6081764054821a71b71942161619549396224cbb242922525e8/propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82", size = 228009 }, + { url = "https://files.pythonhosted.org/packages/1a/06/ef1390f2524850838f2390421b23a8b298f6ce3396a7cc6d39dedd4047b0/propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca", size = 231638 }, + { url = "https://files.pythonhosted.org/packages/38/2a/101e6386d5a93358395da1d41642b79c1ee0f3b12e31727932b069282b1d/propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e", size = 222788 }, + { url = "https://files.pythonhosted.org/packages/db/81/786f687951d0979007e05ad9346cd357e50e3d0b0f1a1d6074df334b1bbb/propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034", size = 40170 }, + { url = "https://files.pythonhosted.org/packages/cf/59/7cc7037b295d5772eceb426358bb1b86e6cab4616d971bd74275395d100d/propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3", size = 44404 }, + { url = "https://files.pythonhosted.org/packages/4c/28/1d205fe49be8b1b4df4c50024e62480a442b1a7b818e734308bb0d17e7fb/propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a", size = 79588 }, + { url = "https://files.pythonhosted.org/packages/21/ee/fc4d893f8d81cd4971affef2a6cb542b36617cd1d8ce56b406112cb80bf7/propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0", size = 45825 }, + { url = "https://files.pythonhosted.org/packages/4a/de/bbe712f94d088da1d237c35d735f675e494a816fd6f54e9db2f61ef4d03f/propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d", size = 45357 }, + { url = "https://files.pythonhosted.org/packages/7f/14/7ae06a6cf2a2f1cb382586d5a99efe66b0b3d0c6f9ac2f759e6f7af9d7cf/propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4", size = 241869 }, + { url = "https://files.pythonhosted.org/packages/cc/59/227a78be960b54a41124e639e2c39e8807ac0c751c735a900e21315f8c2b/propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d", size = 247884 }, + { url = "https://files.pythonhosted.org/packages/84/58/f62b4ffaedf88dc1b17f04d57d8536601e4e030feb26617228ef930c3279/propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5", size = 248486 }, + { url = "https://files.pythonhosted.org/packages/1c/07/ebe102777a830bca91bbb93e3479cd34c2ca5d0361b83be9dbd93104865e/propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24", size = 243649 }, + { url = "https://files.pythonhosted.org/packages/ed/bc/4f7aba7f08f520376c4bb6a20b9a981a581b7f2e385fa0ec9f789bb2d362/propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff", size = 229103 }, + { url = "https://files.pythonhosted.org/packages/fe/d5/04ac9cd4e51a57a96f78795e03c5a0ddb8f23ec098b86f92de028d7f2a6b/propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f", size = 226607 }, + { url = "https://files.pythonhosted.org/packages/e3/f0/24060d959ea41d7a7cc7fdbf68b31852331aabda914a0c63bdb0e22e96d6/propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec", size = 221153 }, + { url = "https://files.pythonhosted.org/packages/77/a7/3ac76045a077b3e4de4859a0753010765e45749bdf53bd02bc4d372da1a0/propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348", size = 222151 }, + { url = "https://files.pythonhosted.org/packages/e7/af/5e29da6f80cebab3f5a4dcd2a3240e7f56f2c4abf51cbfcc99be34e17f0b/propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6", size = 233812 }, + { url = "https://files.pythonhosted.org/packages/8c/89/ebe3ad52642cc5509eaa453e9f4b94b374d81bae3265c59d5c2d98efa1b4/propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6", size = 238829 }, + { url = "https://files.pythonhosted.org/packages/e9/2f/6b32f273fa02e978b7577159eae7471b3cfb88b48563b1c2578b2d7ca0bb/propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518", size = 230704 }, + { url = "https://files.pythonhosted.org/packages/5c/2e/f40ae6ff5624a5f77edd7b8359b208b5455ea113f68309e2b00a2e1426b6/propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246", size = 40050 }, + { url = "https://files.pythonhosted.org/packages/3b/77/a92c3ef994e47180862b9d7d11e37624fb1c00a16d61faf55115d970628b/propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1", size = 44117 }, + { url = "https://files.pythonhosted.org/packages/0f/2a/329e0547cf2def8857157f9477669043e75524cc3e6251cef332b3ff256f/propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc", size = 77002 }, + { url = "https://files.pythonhosted.org/packages/12/2d/c4df5415e2382f840dc2ecbca0eeb2293024bc28e57a80392f2012b4708c/propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9", size = 44639 }, + { url = "https://files.pythonhosted.org/packages/d0/5a/21aaa4ea2f326edaa4e240959ac8b8386ea31dedfdaa636a3544d9e7a408/propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439", size = 44049 }, + { url = "https://files.pythonhosted.org/packages/4e/3e/021b6cd86c0acc90d74784ccbb66808b0bd36067a1bf3e2deb0f3845f618/propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536", size = 224819 }, + { url = "https://files.pythonhosted.org/packages/3c/57/c2fdeed1b3b8918b1770a133ba5c43ad3d78e18285b0c06364861ef5cc38/propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629", size = 229625 }, + { url = "https://files.pythonhosted.org/packages/9d/81/70d4ff57bf2877b5780b466471bebf5892f851a7e2ca0ae7ffd728220281/propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b", size = 232934 }, + { url = "https://files.pythonhosted.org/packages/3c/b9/bb51ea95d73b3fb4100cb95adbd4e1acaf2cbb1fd1083f5468eeb4a099a8/propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052", size = 227361 }, + { url = "https://files.pythonhosted.org/packages/f1/20/3c6d696cd6fd70b29445960cc803b1851a1131e7a2e4ee261ee48e002bcd/propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce", size = 213904 }, + { url = "https://files.pythonhosted.org/packages/a1/cb/1593bfc5ac6d40c010fa823f128056d6bc25b667f5393781e37d62f12005/propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d", size = 212632 }, + { url = "https://files.pythonhosted.org/packages/6d/5c/e95617e222be14a34c709442a0ec179f3207f8a2b900273720501a70ec5e/propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce", size = 207897 }, + { url = "https://files.pythonhosted.org/packages/8e/3b/56c5ab3dc00f6375fbcdeefdede5adf9bee94f1fab04adc8db118f0f9e25/propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95", size = 208118 }, + { url = "https://files.pythonhosted.org/packages/86/25/d7ef738323fbc6ebcbce33eb2a19c5e07a89a3df2fded206065bd5e868a9/propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf", size = 217851 }, + { url = "https://files.pythonhosted.org/packages/b3/77/763e6cef1852cf1ba740590364ec50309b89d1c818e3256d3929eb92fabf/propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f", size = 222630 }, + { url = "https://files.pythonhosted.org/packages/4f/e9/0f86be33602089c701696fbed8d8c4c07b6ee9605c5b7536fd27ed540c5b/propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30", size = 216269 }, + { url = "https://files.pythonhosted.org/packages/cc/02/5ac83217d522394b6a2e81a2e888167e7ca629ef6569a3f09852d6dcb01a/propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6", size = 39472 }, + { url = "https://files.pythonhosted.org/packages/f4/33/d6f5420252a36034bc8a3a01171bc55b4bff5df50d1c63d9caa50693662f/propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1", size = 43363 }, + { url = "https://files.pythonhosted.org/packages/41/b6/c5319caea262f4821995dca2107483b94a3345d4607ad797c76cb9c36bcc/propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54", size = 11818 }, +] + +[[package]] +name = "protobuf" +version = "5.29.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/d1/e0a911544ca9993e0f17ce6d3cc0932752356c1b0a834397f28e63479344/protobuf-5.29.3.tar.gz", hash = "sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620", size = 424945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/7a/1e38f3cafa022f477ca0f57a1f49962f21ad25850c3ca0acd3b9d0091518/protobuf-5.29.3-cp310-abi3-win32.whl", hash = "sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888", size = 422708 }, + { url = "https://files.pythonhosted.org/packages/61/fa/aae8e10512b83de633f2646506a6d835b151edf4b30d18d73afd01447253/protobuf-5.29.3-cp310-abi3-win_amd64.whl", hash = "sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a", size = 434508 }, + { url = "https://files.pythonhosted.org/packages/dd/04/3eaedc2ba17a088961d0e3bd396eac764450f431621b58a04ce898acd126/protobuf-5.29.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e", size = 417825 }, + { url = "https://files.pythonhosted.org/packages/4f/06/7c467744d23c3979ce250397e26d8ad8eeb2bea7b18ca12ad58313c1b8d5/protobuf-5.29.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84", size = 319573 }, + { url = "https://files.pythonhosted.org/packages/a8/45/2ebbde52ad2be18d3675b6bee50e68cd73c9e0654de77d595540b5129df8/protobuf-5.29.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f", size = 319672 }, + { url = "https://files.pythonhosted.org/packages/fd/b2/ab07b09e0f6d143dfb839693aa05765257bceaa13d03bf1a696b78323e7a/protobuf-5.29.3-py3-none-any.whl", hash = "sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f", size = 172550 }, +] + +[[package]] +name = "psutil" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/5a/07871137bb752428aa4b659f910b399ba6f291156bdea939be3e96cae7cb/psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5", size = 508502 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/99/ca79d302be46f7bdd8321089762dd4476ee725fce16fc2b2e1dbba8cac17/psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8", size = 247511 }, + { url = "https://files.pythonhosted.org/packages/0b/6b/73dbde0dd38f3782905d4587049b9be64d76671042fdcaf60e2430c6796d/psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377", size = 248985 }, + { url = "https://files.pythonhosted.org/packages/17/38/c319d31a1d3f88c5b79c68b3116c129e5133f1822157dd6da34043e32ed6/psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003", size = 284488 }, + { url = "https://files.pythonhosted.org/packages/9c/39/0f88a830a1c8a3aba27fededc642da37613c57cbff143412e3536f89784f/psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160", size = 287477 }, + { url = "https://files.pythonhosted.org/packages/47/da/99f4345d4ddf2845cb5b5bd0d93d554e84542d116934fde07a0c50bd4e9f/psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3", size = 289017 }, + { url = "https://files.pythonhosted.org/packages/38/53/bd755c2896f4461fd4f36fa6a6dcb66a88a9e4b9fd4e5b66a77cf9d4a584/psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53", size = 250602 }, + { url = "https://files.pythonhosted.org/packages/7b/d7/7831438e6c3ebbfa6e01a927127a6cb42ad3ab844247f3c5b96bea25d73d/psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649", size = 254444 }, +] + +[[package]] +name = "py" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", size = 207796 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708 }, +] + +[[package]] +name = "py-bip39-bindings" +version = "0.1.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/8a/5e22cbd00b799b33ce0a45ae3715c9ea3fcd263f877544819e7d03753c49/py_bip39_bindings-0.1.11.tar.gz", hash = "sha256:ebc128ccf3a0750d758557e094802f0975c3760a939f8a8b76392d7dbe6b52a1", size = 18103 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/0c/ab1bb098eaca1954c02ff1ef625817e7580907273d0f10de29eb4bac4f31/py_bip39_bindings-0.1.11-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:324a7363f8b49201ebe1cc72d970017ec5139f8a5ddf605fa2774904eb7f08a1", size = 399429 }, + { url = "https://files.pythonhosted.org/packages/c0/87/3d9903a85f9b5b3d57eb04ec484bf148e12a8dcb255160d3071cabb3861c/py_bip39_bindings-0.1.11-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:77173b83c7ade4ca3c91fae0da9c9b1bc5f4c6819baa2276feacd5abec6005fa", size = 792362 }, + { url = "https://files.pythonhosted.org/packages/09/c1/2bda881db1c8fedc009bdd70db5ec6ccc840331f03726946907b417ce9fe/py_bip39_bindings-0.1.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84e5177fb3d3b9607f5d7d526a89f91b35687fcc34b643fc96cd168a0ae025cb", size = 413986 }, + { url = "https://files.pythonhosted.org/packages/7f/df/32db1f9e09757f292c9d88d487b58cdae741ab4bd1567571725c27d4cbf4/py_bip39_bindings-0.1.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ecd1cfb17f0b1bb56f0b1de5c533ff9830a60b5d657846b8cf500ff9fca8b3", size = 1225281 }, + { url = "https://files.pythonhosted.org/packages/c1/0b/b83a9d6d60a5941b1c01ff4df6e4a7994c665f3167038dc26338814d57f1/py_bip39_bindings-0.1.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3408dc0809fca5691f9c02c8292d62590d90de4f02a4b2dcab35817fa857a71", size = 1227053 }, + { url = "https://files.pythonhosted.org/packages/50/45/180a09137e318ff1b41e5a7641e383a7a1605876256b915e2761a3b6c8f8/py_bip39_bindings-0.1.11-cp310-cp310-manylinux_2_28_armv7l.whl", hash = "sha256:d6f0eda277c6d0ef28cc83fd3f59a0f745394ea1e2807f2fea49186084b3d47d", size = 1180932 }, + { url = "https://files.pythonhosted.org/packages/8d/a9/b63d8cddbcbd31bbfa85475bd286779f56c5c313c84b7cd47f0f60a7c204/py_bip39_bindings-0.1.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:963357db40dc7a816d55097a85929cae18c6174c5bedf0410f6e72181270b2b1", size = 1264074 }, + { url = "https://files.pythonhosted.org/packages/69/86/4702ca3849aeb2730311632937219e6fb6d492ff5aef1bad4c1ef23efb33/py_bip39_bindings-0.1.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:be06dc751be86cbd72cd71e318979d3ab27cee12fd84d1e5e4e84575c5c9355d", size = 1252036 }, + { url = "https://files.pythonhosted.org/packages/66/f5/e39751148880e2d422a609608c228734d2f56f36635ad4958fe2f36417d6/py_bip39_bindings-0.1.11-cp310-none-win32.whl", hash = "sha256:b4e05b06831874fa8715bdb128ea776674ad708858a4b3b1a27e5710859b086d", size = 296768 }, + { url = "https://files.pythonhosted.org/packages/d5/67/fc950648f9ea9c3db6bbd55c3384dd6a5a1814b59948ba5f80b8bee7de96/py_bip39_bindings-0.1.11-cp310-none-win_amd64.whl", hash = "sha256:e01a03e858a648d294bcf063368bf09027efa282f5192abddaf7af69c5e2a574", size = 283551 }, + { url = "https://files.pythonhosted.org/packages/44/b6/0bd5bf1c4cb00e000a9c909280aa7f8654208ee136e2cd1f3650a8de59ed/py_bip39_bindings-0.1.11-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:27cce22727e28705a660464689ade6d2cdad4e622bead5bde2ffa53c4f605ee5", size = 399429 }, + { url = "https://files.pythonhosted.org/packages/22/44/b6ffdc17cc499b72821a1d777fb465af842d5b7373f1825a45dce551fedc/py_bip39_bindings-0.1.11-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:cdf35d031587296dcbdb22dbc67f2eaf5b5df9d5036b77fbeb93affbb9eec8d3", size = 792364 }, + { url = "https://files.pythonhosted.org/packages/15/8d/0883d814a26f922b331218c777ecaec61919aebf9c54d4991f919b21ab8a/py_bip39_bindings-0.1.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2fd5b926686207752d5f2e2ff164a9489b3613239d0967362f10c2fbd64eb018", size = 413967 }, + { url = "https://files.pythonhosted.org/packages/72/30/e3c76035b83c9552bbeee90645411a3d52983067badbd8a5854a823701f9/py_bip39_bindings-0.1.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba84c38962bffdaea0e499245731d669cc21d1280f81ace8ff60ed3550024570", size = 1225281 }, + { url = "https://files.pythonhosted.org/packages/38/c9/3b73fe8ffd285387c4fe7b60ccd0072ee16d5153409619c472852ec88acc/py_bip39_bindings-0.1.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9024ec3c4a3db005b355f9a00602cede290dec5e9c7cf7dd06a26f620b0cf99", size = 1227054 }, + { url = "https://files.pythonhosted.org/packages/7e/2f/d096e6e08439e5b3c1f41e95c5828700012c130611a64fe9e82a43b0ca45/py_bip39_bindings-0.1.11-cp311-cp311-manylinux_2_28_armv7l.whl", hash = "sha256:ce028c8aef51dec2a85f298461b2988cca28740bf3cc23472c3469d3f853714e", size = 1180933 }, + { url = "https://files.pythonhosted.org/packages/0b/8f/00c2239452f26e180229d74acd63ac0c027f8eb9a5fb90b879c6c1192102/py_bip39_bindings-0.1.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:51882cd0fa7529173b3543c089c24c775f1876ddf48f10e60f2ed07ad2af5cae", size = 1264074 }, + { url = "https://files.pythonhosted.org/packages/1a/7a/524e38494a0ffb7ca211225acde0324cf216f081dffae7fd55446b009889/py_bip39_bindings-0.1.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ee776f3b33b2d71fee48679951f117e3d1f052449ec2fcb184f3c64a4c77e4f", size = 1252039 }, + { url = "https://files.pythonhosted.org/packages/e9/a0/68bbb9e9326266a9acca2558d6556e22df31bcf4d2235ee1cdaf362add82/py_bip39_bindings-0.1.11-cp311-none-win32.whl", hash = "sha256:d8b722e49562810f94eb61c9efa172f327537c74c37da3e86b161f7f444c51bf", size = 296767 }, + { url = "https://files.pythonhosted.org/packages/0a/47/4c5d0ff9949b725696b1b10b5b87f6c6d3c333d8458b354b7c8536272eef/py_bip39_bindings-0.1.11-cp311-none-win_amd64.whl", hash = "sha256:be934052497f07605768e2c7184e4f4269b3e2e77930131dfc9bdbb791e6fdf4", size = 283550 }, + { url = "https://files.pythonhosted.org/packages/cb/a5/0d29c79ee79475ceca80ca19b5975917827af6ce4dd2711ed197822a12ea/py_bip39_bindings-0.1.11-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:afa9c5762cfaec01141f478a9c3132de01ec3890ff2e5a4013c79d3ba3aff8bb", size = 798236 }, + { url = "https://files.pythonhosted.org/packages/47/fd/a4baff5368ef8be569064e5aef1319c4e75b24a80c70c0f3a871727c6a38/py_bip39_bindings-0.1.11-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a3af7c1f955c6bbd613c6b38d022f7c73896acaf0ecc972ac0dee4b952e14568", size = 406227 }, + { url = "https://files.pythonhosted.org/packages/78/44/fe4a107204690d18691a2db7cacfd6043331f6982dc59962d9e220d46711/py_bip39_bindings-0.1.11-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6aed3e86f105a36676e8dd0c8bc3f611a81b7ba4309b22a77fdc0f63b260e094", size = 1215916 }, + { url = "https://files.pythonhosted.org/packages/0d/53/0cbfe92fde6925244280eaed3ede0f16cb498c8764023acc155225d5f9e4/py_bip39_bindings-0.1.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d202f051cf063abae3acd0b74454d9d7b1dbeaf466ef7cb47a34ccedac845b62", size = 451663 }, + { url = "https://files.pythonhosted.org/packages/44/9b/4c3c8c6decdc7472323a66e98e1d37c43dcbf798c944791eafeb63ff8411/py_bip39_bindings-0.1.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae120b5542fecf97aa3fdb6a526bac1004cb641bc9cc0d0030c6735dc2156072", size = 1206493 }, + { url = "https://files.pythonhosted.org/packages/94/47/71ed526077a4e58ac4ec5dbb43637faa33cc02a0ada912a3fd8f20c193b9/py_bip39_bindings-0.1.11-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:baf896aabb3bec42803015e010c121c8a3210b20184f37aaa6e400ae8e877e60", size = 483935 }, + { url = "https://files.pythonhosted.org/packages/be/e3/7da98b60d113334e2eb95028289410f8a1771e755fa7ad3de1ae2fa9d951/py_bip39_bindings-0.1.11-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e4d45324c598197dbddac10a0298197ca2587fa7b09d1450697517988a29d515", size = 481093 }, + { url = "https://files.pythonhosted.org/packages/c1/38/d54060bda276a062e2327e169b6660b27beb4f75ab7a9e216dd11b9ae703/py_bip39_bindings-0.1.11-cp312-none-win32.whl", hash = "sha256:92abce265b0f2d8c5830441aff06b7b4f9426088a3de39624b12f3f9ff9fc2eb", size = 296429 }, + { url = "https://files.pythonhosted.org/packages/86/12/256aa92f70a8bdf2a00dc84f6c75c86abadeca1c990e02c8345933889952/py_bip39_bindings-0.1.11-cp312-none-win_amd64.whl", hash = "sha256:6794187229eb0b04d0770f0fba936f0c5c598f552848a398ed5af9a61638cacb", size = 284888 }, +] + +[[package]] +name = "py-ed25519-zebra-bindings" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/51/c5f00db791472d4f6c14b383dca0da621db6b68b8c73bef46bf136cb1c93/py_ed25519_zebra_bindings-1.2.0.tar.gz", hash = "sha256:d9ec63d54b1801d5b5bdef0b3096ed94e2e1a7c870c937682afc7b8b25ffc2fc", size = 11851 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/2b/e79646f36bfe281186a6fa88282e32fa124057a14195918bab15e0e01ed5/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d488bf0ac70424514fddb3cf9cca6166ad149b7655970719e9bbef398054e6ad", size = 297590 }, + { url = "https://files.pythonhosted.org/packages/3c/fe/44cd25020aeea4a67b56d580e0ae64ff99dae67623a06afa68edba519470/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c3f97af9b0db7fe2bba1b1ac8d684711fc33e6383c067e1a1fc642e1595282a", size = 324153 }, + { url = "https://files.pythonhosted.org/packages/e7/1c/2fbf3356f9df6865a30fce4802161441a4490ebe8557513f0d5d5e6a80de/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9798a82efe73cfff02eb4c09576af0dc0ca3b41cc3e17cf469179add708c8b40", size = 337730 }, + { url = "https://files.pythonhosted.org/packages/46/7b/25dfb9335256e9e8df2307558da22516e226eeb741d6db2b36ca5b839c29/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0837d10e19e72bb4665c584c89f207bad8b3d29cf2410c0f9ea310c6698f4b26", size = 318874 }, + { url = "https://files.pythonhosted.org/packages/cf/3d/cd917bb2063ff45b2bf084d6ef3a68a5ba905e4fe8c502c04a47c30956b1/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bb278da1728db5259d5c29dcc95717336a69fc6e6159cb7400ac262ee8a96ca", size = 337623 }, + { url = "https://files.pythonhosted.org/packages/0f/18/6578f5418aaa6699d965e802b8953dbb03c56fe58d942f078723e72ecc1b/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5a739e82c82a1f62de54cc0482e9d007b961c84220849ffd86924e34f8db5c9e", size = 474570 }, + { url = "https://files.pythonhosted.org/packages/91/ff/ad829d892424c4bb1135f555ea5f768f98b174eefa506b83c848d762c5be/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d311a44ae162da4b391eb4d47675709b5044b925bef20e4e2209cdfa28ccc1ee", size = 586941 }, + { url = "https://files.pythonhosted.org/packages/75/23/6bffc12b205c21e47dfbcbea76fffb638bf759425ff6da0bec528023538e/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c4a30a6a22f28173de66634294824455ae683163be32565f36fbfa27b8a76495", size = 516467 }, + { url = "https://files.pythonhosted.org/packages/66/10/1e2a675bfc4af6f5396060fb7e57850eb7739a8a4ff92c9b550c34def43a/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:325eb5d0c7a406fd6abbd5b2daeb6d16e4c161a86909bf11a34a3a2c351e7fa0", size = 489615 }, + { url = "https://files.pythonhosted.org/packages/31/ba/53ff308b8ab008606e4de8c24a3511e7c61e14c24b9df90b8aee0ca01ed6/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-win32.whl", hash = "sha256:dcd8f8ecbc3593c54fb3fcc1d0d847d2fdf86c8d2e6840d319d152f4efdef498", size = 186382 }, + { url = "https://files.pythonhosted.org/packages/f1/31/483de0dcc26e8762be7922b924bc889b142591121beb1e827d644f632797/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:b762e13f1e2cedfac4be954a70a75330a5368e2c0ecd64db7ce1e2e9672ed4da", size = 187218 }, + { url = "https://files.pythonhosted.org/packages/5f/41/d98363e8d78919b2340a318f52d6d90f30d67af9e472fdafd30b7003dea5/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dbfe655442b73d49c1ac740f87a480cfee4c013fcb0ba2b639290b20f8dc9bb5", size = 293119 }, + { url = "https://files.pythonhosted.org/packages/86/fa/be0fc2a0340325fd1f8b82f56bd9304d30e694639748a19ef749c8c5e9cf/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b03308c3eb2311b5d308c3df22dbf244073e4c014cda5da2609a562adb4121fc", size = 267743 }, + { url = "https://files.pythonhosted.org/packages/a2/8c/9dfd6b0dec395edb8c8a5475552443320a3085db97f9c7d9332055ba8195/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1828031f38f246d35c7c7b427c17a3525fc311c0402d3b32572510977b9d0f67", size = 297320 }, + { url = "https://files.pythonhosted.org/packages/48/e7/9469f84d868227344240182df25cba274ec3f9d812fc243d27ed2c2ad356/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f88238cf167ba5681e74a556b1e6ce825cb157825ce40c7f757b7d02a7c47dfb", size = 324110 }, + { url = "https://files.pythonhosted.org/packages/91/df/a010af828cb9b3c6864ec74a7d0c54130ef1f557ec0bdfab449881418e29/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b25ca1596ae3be7e6ce6e78252ce7efa570000f9ba5b39cfe8dd10e79f73d50", size = 337742 }, + { url = "https://files.pythonhosted.org/packages/82/73/78e57f453a88345d6481a3eeb96ef9f56b7a7c9ee68a7c577b4385c80405/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a33b1d8af961d28831caf2d481879bb1592f700da79aa5613d845ae6b8153a", size = 318630 }, + { url = "https://files.pythonhosted.org/packages/6a/e2/8b2df28a19e7f552429421c005ec7ae41bf1ed664915fed705f687442220/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:41ee171c18852f6db4a86e68c4fbd622f5415f15c0ab9b40ac1fe66a8ddc3844", size = 337481 }, + { url = "https://files.pythonhosted.org/packages/0d/f3/044b3f5a4c299b53d1beb9e33b3af223bbc6d43707e5acf7edf012b39ff7/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:58623ff56bf1da2581a7d52507d9757ec3b03d49879fc8611646faf666bd0120", size = 474458 }, + { url = "https://files.pythonhosted.org/packages/fe/7d/dff91ff74d992ba0d30e48fda51d715021249ba8f19ae0c5906e4870d6df/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3fdd9cc305dd88562b9fe4d27762070bfdaa1e88647a1509a22fe252e17148d7", size = 586915 }, + { url = "https://files.pythonhosted.org/packages/2e/b3/43d5466fab2025a4213f29242b42f1591cb1dea0ed25098f00809bb6bab2/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:911f068d15159798309dc1895ce156b1bca2f91e34446be3ac5f54f2d3418979", size = 516303 }, + { url = "https://files.pythonhosted.org/packages/5b/7c/8d8b4bffc70ca69fd38b6ec7c61bcad94f2ad1e215b14ecf160de6876563/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9d0fc9c1afbf4b5ff0bc03accf5f07bf53971839eb373d1139eb3bb5a02b3bd0", size = 489278 }, + { url = "https://files.pythonhosted.org/packages/96/33/7cc1f8d6528755c680fa441b67f5010f5f0b2a772dbe0e63cbde86c2d887/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-win32.whl", hash = "sha256:256b96fdf0e264a348bf4176c0fb180a0efc6627ac312cb5e71ec95b347d1ff5", size = 186346 }, + { url = "https://files.pythonhosted.org/packages/20/27/b14ff7fb43a456848492e795d4aeadc5823d1fb8fce7b1ff0d35c467d117/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:efa06b8a0e11c62c10fdf576679ab3039aa6a7254e6cfa4d2d230941799fef5b", size = 187094 }, + { url = "https://files.pythonhosted.org/packages/38/3f/1cbe6c29d5630ab8b29f6f1d52723f8123331d7a3b1a2a5f8070e2f5bc09/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8d63a447d3adac9b431fecd886cf711a6d44200d8b2497598a8ab44ac897f1fb", size = 290728 }, + { url = "https://files.pythonhosted.org/packages/7f/88/fc2759f89c2d07e594455c2b2442bbf6a5ee223af3f87f452a6369e17fce/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5b1c32414a6da709e84d0614e1ed153a5e1dbcbf6d4d17baa31c493fdbd4da4", size = 266106 }, + { url = "https://files.pythonhosted.org/packages/2d/f6/bba44de332b01b048fd739c242829cef0aac776730df2b96d5da0643cb51/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:780073555571390c4b355b5646c0b59c2a90d3393e354d58c4ad904121a2aee2", size = 296312 }, + { url = "https://files.pythonhosted.org/packages/1b/9b/49ff5ab8fc075f2e9395fe604af587bc2d7bdc123db36657f376a35dd5d6/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:677ade8ab3348604a9e4176b068ff19707cf205fd8ee4f1781614b085628fa45", size = 323178 }, + { url = "https://files.pythonhosted.org/packages/75/3c/5f6e8f56c7d59f67f23f16584ebe34c9cc5cf3593c1bf09c96cfa2f7d3a2/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c19c0cc491bc4999245f9d2e904f611354f442710b6dae6d1d6ebc81666124cc", size = 337000 }, + { url = "https://files.pythonhosted.org/packages/bd/34/e30b63c8bfcfaed3c46a68a1493e255f4adb683999360fd5ad81a50703b9/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1317a8af53c658f1e89b346d361edaf10eccd428c937a17d0684b2192fa77c40", size = 317772 }, + { url = "https://files.pythonhosted.org/packages/60/8e/df7e97ab47e9e522b8babe355d7fb5977bc412d4390b07a8f57accde1a7f/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cdc05ade2608707f6c54701e7425d9c00751ccffa57533a48f68f61b0aada9f1", size = 336488 }, + { url = "https://files.pythonhosted.org/packages/c8/eb/a1dcb632754513d1669dfeeaf3ab1eec582cd55cc92c1805af457e6cb8c4/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec1965ed54fd162da564cc33676377888bd1ad14c15680465463d06e14aac74d", size = 473730 }, + { url = "https://files.pythonhosted.org/packages/a5/5c/1bf76d36a0458708e5a20a5489b77dde12859d6969db4ede3658cbe37291/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7386e7cec522ac50e7d81cfc8488e463fe93902d6ba0f7c79d6f6db0fcf71111", size = 586066 }, + { url = "https://files.pythonhosted.org/packages/d1/30/79ad8283c1d686f34079a5e70dc90bdc41e0bcb0e12a66e43ee22ec91325/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b06102b2be52da075f29f0db907bb5a03af942e2f6fb558065ea5717aa567d32", size = 515271 }, + { url = "https://files.pythonhosted.org/packages/6c/90/ed8850e9c73a0595ce661c56cadc5407fcf7fa5e3bce01cc8427bc1c5ee7/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4237cf821f74126077220d5826448c0b68c8807f40db961b1335bb6a66a83af8", size = 488352 }, + { url = "https://files.pythonhosted.org/packages/06/94/1140e74d213d875e21342bffdcc84003d7a7209cf191d044053a37a4da8f/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-win32.whl", hash = "sha256:fe11223695c94040f31b48a2128f1642a1b689aaaa91b5f8ae018d53b1497409", size = 185890 }, + { url = "https://files.pythonhosted.org/packages/51/37/19ad03c6891fb564a9716409da56a2b5977b49410576eac5ae90cdaef8ee/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:87654379855152770974c045099e488b577d86429af609524903b8029b276417", size = 186788 }, + { url = "https://files.pythonhosted.org/packages/b5/8d/7db18ebddff6cd81cf04cbb072b9d8f03b261816e49bc4b44c5cc1499bfc/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2e10a578c1297a9b12a818c5b874d9830afba1592e8cb9df3a44b2afbc241cf0", size = 290758 }, + { url = "https://files.pythonhosted.org/packages/8e/86/bbf541d3acaf91f230560caf0b06c38120531a4b78c79a1069425c9a865f/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f0edbed9d94f5295c4f360baa38e124626296e36f315d6a19bc91f7d8a61627", size = 266143 }, + { url = "https://files.pythonhosted.org/packages/16/b8/60b80117df4af4194038b09729e2b72f01daae30ad3e31a3cf00c3c12742/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe2d0db5c2d4c0575b91373eb0c33b1d222fbb38664e17d807c8845eab268c16", size = 296165 }, + { url = "https://files.pythonhosted.org/packages/85/7a/ccfb0304fcc2286e3e3ecc681ec26da22e408cf1b55ac931f9d32e91b192/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4b371742adbd9be4a5a813e5d920a1a057fe9013620681651a3e7c84fd1f8d8b", size = 323055 }, + { url = "https://files.pythonhosted.org/packages/4e/01/3669026c7600ac78645ea0250ec9381936a4a05c6c21f72fb27726ff7130/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f82a6ae05ac4feb16d077ce1b4a48396c9685bc2b37d3a1ffbcd16023a4f3b8a", size = 336780 }, + { url = "https://files.pythonhosted.org/packages/d7/82/247c3a5c3d3817905f95fdcb5b28a14235e7a7d776482bf968139ff69235/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:446f26b62311db93205507fedb3fa07dae786ae75822182d44dadd28984d7768", size = 317655 }, + { url = "https://files.pythonhosted.org/packages/c8/d2/14223da5008e65d4ef20b80f267dcc9b770b04852a0ececdf614725b588c/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f76ccb64577bbdfdacc543298355747dca9684e74262f844c3d892bd583e023b", size = 336542 }, + { url = "https://files.pythonhosted.org/packages/1b/0f/1748c84528217a9cdddf5ae54564c7c32d74aa4b6f4381d5ca277e115dbc/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c5c95587f93f9cbf73e3609e8befe2b36c488bcf96ccc1c8c63b257212e1b9df", size = 473517 }, + { url = "https://files.pythonhosted.org/packages/5e/e3/4575b55a859933d7819b51d2ac18f4fadfbda3daed8cece11afba68256ef/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f157f87844d5e395380eaf03d9baa2108126ad276088c7edb55869683cc2cfc", size = 585960 }, + { url = "https://files.pythonhosted.org/packages/ec/9c/7dab2229cfcdedf95dfbc65088821f4013d5bd7e7259abb031959d9c4ef9/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:022499a21096d03d90654af2203a5475f6c3c5572245b7bc6a1bbeeb4e42c319", size = 515244 }, + { url = "https://files.pythonhosted.org/packages/37/d4/1aff446495187df2bace76c0a88653b9f9428ac809938841642409d8905a/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7b466ec2de929e38e6f441156a3e108a3c090dbc6b624864f6c1b300cc329f8d", size = 488348 }, + { url = "https://files.pythonhosted.org/packages/3a/25/445680dc6fe7cb4bb8a45219d312b0bee1b63b5cc3467dd0e4fa14e244c3/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:998b5d9c4db1053156a55e8edf06a5dce68ddaa3e928e2861f8ba9a5fe5b6119", size = 296264 }, + { url = "https://files.pythonhosted.org/packages/0d/ca/60e217a0fd3e160f1ed32211a19c93425292fce2d3818a21d2781c547534/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a0fe34c20032f406a78c865c308b49fe3c79c9e1642f6471228cfbc6c513348", size = 322758 }, + { url = "https://files.pythonhosted.org/packages/63/df/5970fab50ce04026c780d48838d5a2c3f96a4e46c69ba81069a24941e18e/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7e3273d73148d983a5e7f9ed3e8b53824dcb7833393aa09dd969dd3e7a1f3c1", size = 337049 }, + { url = "https://files.pythonhosted.org/packages/55/4b/69f7b03c4edd5a8dfa6a64f33c5c99ffbbf63419ec0fe775418be4f930da/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cb5858f54ebd7d37c9d21c6dd80367d0031dbda7bd91b333018c0f243e1284f5", size = 473442 }, + { url = "https://files.pythonhosted.org/packages/30/7a/0d5073188f94fd3b22a836e867e32fae0e26f3b39f734314e3eff5b530f6/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:4fd00c8686b17e31ec29d8e4e7ce97f465fe26227f12c9e111e012b9d0dff4b9", size = 585681 }, + { url = "https://files.pythonhosted.org/packages/ab/99/add86df518d799a17c91763eebf756de68b1a858a5c7977de1b335e886cc/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e4e55fc5be4ba0c723d424cefdbb8d863e74d2ff25fbeadca9539ca60d78cc0f", size = 514835 }, + { url = "https://files.pythonhosted.org/packages/e7/fc/bf32dc80a597501fc7ef8b18638f78e5ee672b0b43cc02373075f9b1f8d4/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:91816ed4cef90d4d08fa9f55fa0c5687c5eba601dc1a44f211adcf1c20d96cc3", size = 488524 }, + { url = "https://files.pythonhosted.org/packages/6a/99/90bcd1e9c7aa0e7ffcab2ca359139d0e2adf47509666c6ac2f7dba69682f/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f76228db22d018a66e858b27d4c074a0111438919a45276ac1a00d397d6daca", size = 297980 }, + { url = "https://files.pythonhosted.org/packages/b1/18/2877f37a621b89447aff5f3ae0f66ccc2bea31f5a681c0bc70eeac813c4e/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f7c0875eda221bfdc1029207d7807c2ae5446bf4aaf5d34def94b8fa2abeace", size = 324793 }, + { url = "https://files.pythonhosted.org/packages/a2/6f/f7d3a6533e4fb3e76b22c9936149e7f622a2789fdd886204a87668fc45c5/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c713b7dba676380e2a1c3208667a71bf4bcc02a67b487894cda35c6103079e9", size = 338015 }, + { url = "https://files.pythonhosted.org/packages/b9/ef/2b9ccc88c971398f430570793eae77bf3531ac001caf40e699511b3ca9ed/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fe2882a1377199cdb656e42adf5e97869d1b04af1f66a7300179f95692603c2", size = 319683 }, + { url = "https://files.pythonhosted.org/packages/cd/ed/505573c349c0a7f45205e31902dc2027cfc277ff39c5980703458fdb1718/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c6afd09a1b831444a5107ca8e48f14db837a2351cac25e70e71f80f976c76ca2", size = 338271 }, + { url = "https://files.pythonhosted.org/packages/cf/88/5e7e30c38f7b4a83a59d45e7a8084d8a1ba17c8c88b0951301c610cc333b/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:91c0627efe7048ce552be5db08c11a99d532b2e115316daed3b53e52ba9f383b", size = 475282 }, + { url = "https://files.pythonhosted.org/packages/ce/2a/481a28104d0f9a287eee558bf8240d571147d894141f825df4377e831c23/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:d6efc48c7c26838044c7f58ba2e7944776ef6eaef21c962a528ddffd3943e1b4", size = 587506 }, + { url = "https://files.pythonhosted.org/packages/dc/59/990991e44c1d91f7b4e70da848573faf23231ba720939342cce1d1f8d9ea/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:7cb8befc4c52c681c4e2f5994adeff28f529f767c979921faaa1fbb84a52afae", size = 516902 }, + { url = "https://files.pythonhosted.org/packages/21/f1/13947be60e9c411fdc21680275b5117becc425bb21e24194be61210c8493/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3b976f2c6053011c08dcde2f5805e285a8ff53eec5a42be0cc24ce93bc5729ac", size = 490091 }, +] + +[[package]] +name = "py-sr25519-bindings" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/55/e5c27d1387f6cb3a6bf7714e1e0c4a62edc3b006710e2d081e8bdfa4123f/py_sr25519_bindings-0.2.1.tar.gz", hash = "sha256:1b96d3dde43adcf86ab427a9fd72b2c6291dca36eb40747df631588c16f01c1a", size = 18439 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/bc/d9c0e997def0884b81ef2fdea5f3f65fae0974dd60ca0639ff0eb04d5ae2/py_sr25519_bindings-0.2.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10489c399768dc4ac91c90a6c8da60aeb77a48b21a81944244d41b0d4c4be2f", size = 331167 }, + { url = "https://files.pythonhosted.org/packages/cd/33/1d9d0345a7c71b6296927f29fb6500e460187c381d7d4fffa57e6ab1f77b/py_sr25519_bindings-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8358a7b3048765008a79733447dfdcafdce3f66859c98634055fee6868252e12", size = 306032 }, + { url = "https://files.pythonhosted.org/packages/68/61/66811bb11031a48c54b7cdfaf75731e86cf136449a10ca3287e9963092bd/py_sr25519_bindings-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:202af5a516614907ddaef073104ae6d0a98ec96743d11cb87faa09d2b235a6b4", size = 340111 }, + { url = "https://files.pythonhosted.org/packages/4c/f0/a0cf86f6b424303c1ab51e55470006da83950b39c35716e9206f2260882e/py_sr25519_bindings-0.2.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0b0d977c9ba6063d7807dda84264f10b1951736ba528b4d4078e5c9989051b1", size = 367989 }, + { url = "https://files.pythonhosted.org/packages/f5/8d/0053fb7afef406ba3aca08ff51d0c0afc31b3c7a3874c824a92c0a5366e8/py_sr25519_bindings-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e6c46cbbb87eb9db3c7deebd71c296d67c0725d9379ee737255e22c15c64bae", size = 384040 }, + { url = "https://files.pythonhosted.org/packages/a6/c6/59ba7e7edb8e496ad8ae2777d102cacb41aae7d5fb05c2ed6972fdd0d55f/py_sr25519_bindings-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9368e9ca0bc1c967db0dd5cfc401f23d364064e99a48d21ea12a068612ccce7e", size = 365611 }, + { url = "https://files.pythonhosted.org/packages/8f/ca/30ab71b4357e9380b4bd8ed65338cf9ec36970798fe8b8e0f3273c3aed71/py_sr25519_bindings-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f1ade92569b0281ff24476bd93333865370d86746b2d7949545f1ca70ac4e14", size = 385332 }, + { url = "https://files.pythonhosted.org/packages/15/bc/1846c1600eb2004e49bf7bd07fbfcb73c61b8f17d5ab6478874b1abd0d94/py_sr25519_bindings-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7286da1662afc300038441620092a0ae527430f7c50b0768e826d46893dd5095", size = 523820 }, + { url = "https://files.pythonhosted.org/packages/b7/8b/bfd727048597181a83cc43282a61f9d7eebc1aed317e4999c58763cdf1dd/py_sr25519_bindings-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1afbf451ecb78d5a1fa3be0f1cafb914aa2d4464ce15374bbff495cc384b1947", size = 627704 }, + { url = "https://files.pythonhosted.org/packages/23/87/fd02d1a53d9287d31ecef4842cfe7df84377a797c297d8497052fa9f5068/py_sr25519_bindings-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:873c0ec12fed805f4086e36ebbb673c95af09e4007ea66d5a9bbd2cc29dfa076", size = 551535 }, + { url = "https://files.pythonhosted.org/packages/14/86/7054e137d105de5408c2510c0d457b69756cdd4d4fbdb77aa2ba71b164b8/py_sr25519_bindings-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5917f8584cf6a81e32f03547d9fbd8c783db2372d49bd9ff8c5c57d969ea1039", size = 529596 }, + { url = "https://files.pythonhosted.org/packages/b0/ef/6701c33c8f243dfc143e55ead788727b1d99865915fe82e72019dfd16109/py_sr25519_bindings-0.2.1-cp310-none-win32.whl", hash = "sha256:09f184393e01d0d2b62d3782a6d18dd0824a225444e0171c08e03f8cf3920e7b", size = 217893 }, + { url = "https://files.pythonhosted.org/packages/70/9c/bca6f55663f573eee619a2100af35797449362e906316e01b8e5ddb612f2/py_sr25519_bindings-0.2.1-cp310-none-win_amd64.whl", hash = "sha256:2d548a8ea057c6f150572059475761101ba8ef15e3b349d2d0cb108652f6aaf8", size = 224183 }, + { url = "https://files.pythonhosted.org/packages/b7/e5/62067ff055a940bcbb02467f7fb63fd85a89cc12153f8c78199ce5c71fb9/py_sr25519_bindings-0.2.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4941e6e0e180f7e72565043ed3ba7190455c9feaa2ab9ee6038904f2b4bb6c5b", size = 331203 }, + { url = "https://files.pythonhosted.org/packages/0a/6c/48a6e1289012b4ab704ccec5315a7c1f1694909b5cc332a36ec87ab03608/py_sr25519_bindings-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b63d7cf5bb4d9b986d7f7012c80b92be70311dc9b75862f7880e03b71a29543d", size = 306083 }, + { url = "https://files.pythonhosted.org/packages/e6/da/b7ab72a15e950779edf376b344b6de43aacc7250e319ff23996ef96cda5b/py_sr25519_bindings-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6752bf3b109446d99f3a368e3ba805812fc5bc09e52ef1c82f5a47e43b19973", size = 340172 }, + { url = "https://files.pythonhosted.org/packages/15/7f/4defee54893a3947936f3b5b8b1fe8cb10bb6d01cf87240345f511636e8d/py_sr25519_bindings-0.2.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0368dcdf5ec8d2bb9c13273c78c3c5b033211d37a70a2f1d2080f29a7d118340", size = 368044 }, + { url = "https://files.pythonhosted.org/packages/44/a9/b6ddb161bb28f7da1b261d8e6d59d9669a15bdbfe8bfff0ff15f9a28f0a6/py_sr25519_bindings-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2618b02b4a3babac07b8bb61fe9550f911f038bb079665682ca76b2e664e5258", size = 384053 }, + { url = "https://files.pythonhosted.org/packages/7a/66/5d4c78ad9766cd46e5439e9fb84cb10bc47b9c4929c8ea99ee880f405f50/py_sr25519_bindings-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab1bc4dc524efefaecf3a85f4a0ff05c1ca9509d4d64056199984550f3c98b3", size = 365700 }, + { url = "https://files.pythonhosted.org/packages/07/ef/f96d4e2472af62768ffd81df2170f643de87b0ab831e405a4572b9959379/py_sr25519_bindings-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7ccdc89d5e3ae0dd163c8150ec76b6bb3291c1cec9746eb79e9544b3423f35f9", size = 385360 }, + { url = "https://files.pythonhosted.org/packages/9e/91/ea5e750e5f2896412fcbbe32da3be8ffab50f4221df7fe3ab367c51a99ac/py_sr25519_bindings-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ae6545c414cfa5d7207c9c77aaa576bb374982fb2105a7a9c2764afa5621f6d4", size = 523867 }, + { url = "https://files.pythonhosted.org/packages/7c/d0/e56f6753b264dd4c3f40364879429af7127c8b235c7a2f6d5fbb69137004/py_sr25519_bindings-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7046774e39e0166d3c12632969c9d1713e6ad9ca8206bbe82923ba6935b0a01f", size = 627828 }, + { url = "https://files.pythonhosted.org/packages/63/19/7a8d5cca0a498da55b0457be98f03e428e4981b563e5d1c8c92dfc7d136e/py_sr25519_bindings-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cba9a8821176895b080ea761e5ab9cd8727660bf401478a6532a30ae3429573d", size = 551658 }, + { url = "https://files.pythonhosted.org/packages/58/4e/083694bded9ce2d8d598f086aa4ca67f2b9c5d9bfd79ca46f04c95e9322b/py_sr25519_bindings-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c31aba05819e5b6b26746dc1b078cf680bd471f135c55e376e95c7774e22e936", size = 529627 }, + { url = "https://files.pythonhosted.org/packages/3d/cc/837b57c938d2b1d0e6f296dc09a3e65b0d762b2387301f8a51452d679391/py_sr25519_bindings-0.2.1-cp311-none-win32.whl", hash = "sha256:d4bfb9c9a5c46563ccf12e74862ee95d2961556ba7aca62c9e4d6e4f7c37b4e0", size = 217894 }, + { url = "https://files.pythonhosted.org/packages/5e/43/3f91ccad4b8d96ddf9a26b00be11de6ad0d260ab26e17ad8f98088512c3a/py_sr25519_bindings-0.2.1-cp311-none-win_amd64.whl", hash = "sha256:4f0d5c065d5e6122e53e771035aa335534363b451358b408d211df1c46773617", size = 224191 }, + { url = "https://files.pythonhosted.org/packages/fa/6f/5dca831fe2617075237d49868d1bd4f025d0dbd23676d7dec3aaf39642cd/py_sr25519_bindings-0.2.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:01ef73c0b3d3f703b54ee69c0f5ff4aa54b4233212c466fd497c7a84d170963a", size = 330633 }, + { url = "https://files.pythonhosted.org/packages/3e/86/569b69e01a962e0c3cd63465e5faad589e54f0c27bfaed5436fef283d56c/py_sr25519_bindings-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7ce8ac85e5ea82825a863f3f6f071e5ead610d7675820eb8ffe772267445ec0b", size = 306030 }, + { url = "https://files.pythonhosted.org/packages/a1/ae/ad0d1fff92966b4ca020abc3ea12e3e1f34c3a937bab28fa0e6bf893d587/py_sr25519_bindings-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f59ac8c03c8ef819db063627f4a8247aab0db11d88b21562abbe371612cf66ab", size = 340266 }, + { url = "https://files.pythonhosted.org/packages/b0/7e/93903b1a0789fe1e7f2bb17f4992b55549dfbc8dd8dc3fa4d57c08b72250/py_sr25519_bindings-0.2.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2c11fc77b57308e3ada9a40e7c343027129b582d3091ebd992c99b1832ac8c1", size = 367790 }, + { url = "https://files.pythonhosted.org/packages/f4/79/842a46cc48c33ff0d08f95db6b327fdd5972fd68d733634322762dd74702/py_sr25519_bindings-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92af2831d6896f0b3fef792d1f2da780fabf6c78dac12535b394cbdb51c0d257", size = 383790 }, + { url = "https://files.pythonhosted.org/packages/0d/33/aeeacf174483ae6163bfb8993c0dabdb15875272e59658123d2dcf55f39a/py_sr25519_bindings-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc99f7f310b7641e510810c1d6a6b51792ab2ccefac3ab288445a9fcbc9a8265", size = 365962 }, + { url = "https://files.pythonhosted.org/packages/85/bb/c41e0115115336acad5b05d577bf463fa69975ed84dcf50011ac4e07eb89/py_sr25519_bindings-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1dc4995a352a6e5851a41cb0ea37d8c9083d173515b7fd2f381b014f57dc1cda", size = 386028 }, + { url = "https://files.pythonhosted.org/packages/cd/d0/48744d7ec55853dc7ec6889f7b85b4f9d21349f09a9ccc8fd988a67f0a46/py_sr25519_bindings-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f103dc5c420057c4447bd6ebf28b2b68ff3ab8da85a5f7ff39c405293de80c78", size = 524320 }, + { url = "https://files.pythonhosted.org/packages/50/4f/9462c0525bd64417c56e788b9543a34c08583bf7eabf81797bf5545b924d/py_sr25519_bindings-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:902ee675497b8d356a2abe2abc4278cd76c503f76d06ef2bcd797c1df59e84b7", size = 628052 }, + { url = "https://files.pythonhosted.org/packages/a7/2a/873f8e7425fd424f9d4aa6eddbbe767889d2aee639372fd9516d6b352c93/py_sr25519_bindings-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5dd9748f4bd9a3bc4d5c1245f6edcc723075b1470b4c36add4474df4c53604e8", size = 552273 }, + { url = "https://files.pythonhosted.org/packages/0e/e2/bb29457851816c1637bdd7176ac419073faeecf452dcfae54b50ddb81bc1/py_sr25519_bindings-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c24bc55699d12948571969c26e65138a942bdaca062171288c40c44b9a4f266", size = 530013 }, + { url = "https://files.pythonhosted.org/packages/4b/70/21d32090ca207738a3979620865e2a48ccbed64871cffafb24c6febe234d/py_sr25519_bindings-0.2.1-cp312-none-win32.whl", hash = "sha256:d4799c9a8f280abdfe564d397bad45da380275c8d22604e059bd7b3d5af404b5", size = 218181 }, + { url = "https://files.pythonhosted.org/packages/bb/df/06a61ef52a6889d6879bfa8a5877688f62854c8eab491ad7af60e797a3ef/py_sr25519_bindings-0.2.1-cp312-none-win_amd64.whl", hash = "sha256:0746befd71d1766d8747910cfeb2cec2be2c859c3b3618eda1dc3cb4a1b85175", size = 224095 }, + { url = "https://files.pythonhosted.org/packages/bf/21/d5a2b0d2b518fa530cd7b08d8906390c9b3eb2476b8d04f3b9eeae848207/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50f8b34fed2c98814dcd414379ef43bf63cd4c05d7d90b83c590cca60fe804d6", size = 340467 }, + { url = "https://files.pythonhosted.org/packages/1e/92/38b9b6ae88eee7ffbe99fd0a577b82ff0ea2b030c92e85696355f1e1f0f4/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:141b0f8fb99cb249984f7c9ec67dd1768aae4d137d47ea0eca027d669503e132", size = 368253 }, + { url = "https://files.pythonhosted.org/packages/d5/c5/90eb85986e929e4e004a5c1f4f1f158bf792426d7ace5a2d59ed3557dc8e/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45cfef18bdfde67d445650a388bfafecbd1844a64c19087e9e4267548998c100", size = 384131 }, + { url = "https://files.pythonhosted.org/packages/31/04/a4575abaaf793836bd43f976288ab637edce45a0e523472d44681a3fe98c/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:639410c0258a543bb84b0518616af724716737054ac5c78daa4d956d17841b17", size = 365591 }, + { url = "https://files.pythonhosted.org/packages/b8/fb/1d32c088d5c297c4c9f6d6887cec43a5f4bb677b058291555cd1ede6d870/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a98e5a395445046f37fc4e365556ce06fa344e3b711de0564ac3fd2b351a1b3e", size = 385688 }, + { url = "https://files.pythonhosted.org/packages/9e/84/f2aa8a575d4dfc9d066dda65f571b814ac662cb81fbacaefd5244bb3df7b/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:7935b79a91aa72db42b5015117018554980c320256e63bc930b8bd148a0765a4", size = 524790 }, + { url = "https://files.pythonhosted.org/packages/33/eb/5f2cba82b804cda52625570875aaea108b9a1c7c01b3ea8a33f34b656420/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:cedf5d0669c23ddab8804982f665c7e99b13e8452db78128f231217b8528c31a", size = 628076 }, + { url = "https://files.pythonhosted.org/packages/c8/87/32b480bc5dfabf8e4ba43ced626d5ce40c0132fbb512ea22fbea63cf6447/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:9ea24db07992f756409729adad1e3ec9aa0a9d4fece5da90768a56ac1563f0f4", size = 552147 }, + { url = "https://files.pythonhosted.org/packages/0b/7f/88d3a788c85727076f512fae958d464f49046cdd9a88958cf39b2b47ceb4/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e8b7e42cd4a5177dd83bbcdef77591fd72d3da02616545011ebcdd872f8cc39d", size = 530561 }, +] + +[[package]] +name = "pyarrow" +version = "18.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/7b/640785a9062bb00314caa8a387abce547d2a420cf09bd6c715fe659ccffb/pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73", size = 1118671 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/bb/8d4a1573f66e0684f190dd2b55fd0b97a7214de8882d58a3867e777bf640/pyarrow-18.1.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e21488d5cfd3d8b500b3238a6c4b075efabc18f0f6d80b29239737ebd69caa6c", size = 29531620 }, + { url = "https://files.pythonhosted.org/packages/30/90/893acfad917533b624a97b9e498c0e8393908508a0a72d624fe935e632bf/pyarrow-18.1.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:b516dad76f258a702f7ca0250885fc93d1fa5ac13ad51258e39d402bd9e2e1e4", size = 30836521 }, + { url = "https://files.pythonhosted.org/packages/a3/2a/526545a7464b5fb2fa6e2c4bad16ca90e59e1843025c534fd907b7f73e5a/pyarrow-18.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f443122c8e31f4c9199cb23dca29ab9427cef990f283f80fe15b8e124bcc49b", size = 39213905 }, + { url = "https://files.pythonhosted.org/packages/8a/77/4b3fab91a30e19e233e738d0c5eca5a8f6dd05758bc349a2ca262c65de79/pyarrow-18.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a03da7f2758645d17b7b4f83c8bffeae5bbb7f974523fe901f36288d2eab71", size = 40128881 }, + { url = "https://files.pythonhosted.org/packages/aa/e2/a88e16c5e45e562449c52305bd3bc2f9d704295322d3434656e7ccac1444/pyarrow-18.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ba17845efe3aa358ec266cf9cc2800fa73038211fb27968bfa88acd09261a470", size = 38627517 }, + { url = "https://files.pythonhosted.org/packages/6d/84/8037c20005ccc7b869726465be0957bd9c29cfc88612962030f08292ad06/pyarrow-18.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:3c35813c11a059056a22a3bef520461310f2f7eea5c8a11ef9de7062a23f8d56", size = 40060187 }, + { url = "https://files.pythonhosted.org/packages/2a/38/d6435c723ff73df8ae74626ea778262fbcc2b9b0d1a4f3db915b61711b05/pyarrow-18.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9736ba3c85129d72aefa21b4f3bd715bc4190fe4426715abfff90481e7d00812", size = 25118314 }, + { url = "https://files.pythonhosted.org/packages/9e/4d/a4988e7d82f4fbc797715db4185939a658eeffb07a25bab7262bed1ea076/pyarrow-18.1.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:eaeabf638408de2772ce3d7793b2668d4bb93807deed1725413b70e3156a7854", size = 29554860 }, + { url = "https://files.pythonhosted.org/packages/59/03/3a42c5c1e4bd4c900ab62aa1ff6b472bdb159ba8f1c3e5deadab7222244f/pyarrow-18.1.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:3b2e2239339c538f3464308fd345113f886ad031ef8266c6f004d49769bb074c", size = 30867076 }, + { url = "https://files.pythonhosted.org/packages/75/7e/332055ac913373e89256dce9d14b7708f55f7bd5be631456c897f0237738/pyarrow-18.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f39a2e0ed32a0970e4e46c262753417a60c43a3246972cfc2d3eb85aedd01b21", size = 39212135 }, + { url = "https://files.pythonhosted.org/packages/8c/64/5099cdb325828722ef7ffeba9a4696f238eb0cdeae227f831c2d77fcf1bd/pyarrow-18.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31e9417ba9c42627574bdbfeada7217ad8a4cbbe45b9d6bdd4b62abbca4c6f6", size = 40125195 }, + { url = "https://files.pythonhosted.org/packages/83/88/1938d783727db1b178ff71bc6a6143d7939e406db83a9ec23cad3dad325c/pyarrow-18.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:01c034b576ce0eef554f7c3d8c341714954be9b3f5d5bc7117006b85fcf302fe", size = 38641884 }, + { url = "https://files.pythonhosted.org/packages/5e/b5/9e14e9f7590e0eaa435ecea84dabb137284a4dbba7b3c337b58b65b76d95/pyarrow-18.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f266a2c0fc31995a06ebd30bcfdb7f615d7278035ec5b1cd71c48d56daaf30b0", size = 40076877 }, + { url = "https://files.pythonhosted.org/packages/4d/a3/817ac7fe0891a2d66e247e223080f3a6a262d8aefd77e11e8c27e6acf4e1/pyarrow-18.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d4f13eee18433f99adefaeb7e01d83b59f73360c231d4782d9ddfaf1c3fbde0a", size = 25119811 }, + { url = "https://files.pythonhosted.org/packages/6a/50/12829e7111b932581e51dda51d5cb39207a056c30fe31ef43f14c63c4d7e/pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d", size = 29514620 }, + { url = "https://files.pythonhosted.org/packages/d1/41/468c944eab157702e96abab3d07b48b8424927d4933541ab43788bb6964d/pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee", size = 30856494 }, + { url = "https://files.pythonhosted.org/packages/68/f9/29fb659b390312a7345aeb858a9d9c157552a8852522f2c8bad437c29c0a/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992", size = 39203624 }, + { url = "https://files.pythonhosted.org/packages/6e/f6/19360dae44200e35753c5c2889dc478154cd78e61b1f738514c9f131734d/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0743e503c55be0fdb5c08e7d44853da27f19dc854531c0570f9f394ec9671d54", size = 40139341 }, + { url = "https://files.pythonhosted.org/packages/bb/e6/9b3afbbcf10cc724312e824af94a2e993d8ace22994d823f5c35324cebf5/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d4b3d2a34780645bed6414e22dda55a92e0fcd1b8a637fba86800ad737057e33", size = 38618629 }, + { url = "https://files.pythonhosted.org/packages/3a/2e/3b99f8a3d9e0ccae0e961978a0d0089b25fb46ebbcfb5ebae3cca179a5b3/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c52f81aa6f6575058d8e2c782bf79d4f9fdc89887f16825ec3a66607a5dd8e30", size = 40078661 }, + { url = "https://files.pythonhosted.org/packages/76/52/f8da04195000099d394012b8d42c503d7041b79f778d854f410e5f05049a/pyarrow-18.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ad4892617e1a6c7a551cfc827e072a633eaff758fa09f21c4ee548c30bcaf99", size = 25092330 }, + { url = "https://files.pythonhosted.org/packages/cb/87/aa4d249732edef6ad88899399047d7e49311a55749d3c373007d034ee471/pyarrow-18.1.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:84e314d22231357d473eabec709d0ba285fa706a72377f9cc8e1cb3c8013813b", size = 29497406 }, + { url = "https://files.pythonhosted.org/packages/3c/c7/ed6adb46d93a3177540e228b5ca30d99fc8ea3b13bdb88b6f8b6467e2cb7/pyarrow-18.1.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f591704ac05dfd0477bb8f8e0bd4b5dc52c1cadf50503858dce3a15db6e46ff2", size = 30835095 }, + { url = "https://files.pythonhosted.org/packages/41/d7/ed85001edfb96200ff606943cff71d64f91926ab42828676c0fc0db98963/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acb7564204d3c40babf93a05624fc6a8ec1ab1def295c363afc40b0c9e66c191", size = 39194527 }, + { url = "https://files.pythonhosted.org/packages/59/16/35e28eab126342fa391593415d79477e89582de411bb95232f28b131a769/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74de649d1d2ccb778f7c3afff6085bd5092aed4c23df9feeb45dd6b16f3811aa", size = 40131443 }, + { url = "https://files.pythonhosted.org/packages/0c/95/e855880614c8da20f4cd74fa85d7268c725cf0013dc754048593a38896a0/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f96bd502cb11abb08efea6dab09c003305161cb6c9eafd432e35e76e7fa9b90c", size = 38608750 }, + { url = "https://files.pythonhosted.org/packages/54/9d/f253554b1457d4fdb3831b7bd5f8f00f1795585a606eabf6fec0a58a9c38/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:36ac22d7782554754a3b50201b607d553a8d71b78cdf03b33c1125be4b52397c", size = 40066690 }, + { url = "https://files.pythonhosted.org/packages/2f/58/8912a2563e6b8273e8aa7b605a345bba5a06204549826f6493065575ebc0/pyarrow-18.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:25dbacab8c5952df0ca6ca0af28f50d45bd31c1ff6fcf79e2d120b4a65ee7181", size = 25081054 }, + { url = "https://files.pythonhosted.org/packages/82/f9/d06ddc06cab1ada0c2f2fd205ac8c25c2701182de1b9c4bf7a0a44844431/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a276190309aba7bc9d5bd2933230458b3521a4317acfefe69a354f2fe59f2bc", size = 29525542 }, + { url = "https://files.pythonhosted.org/packages/ab/94/8917e3b961810587ecbdaa417f8ebac0abb25105ae667b7aa11c05876976/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ad514dbfcffe30124ce655d72771ae070f30bf850b48bc4d9d3b25993ee0e386", size = 30829412 }, + { url = "https://files.pythonhosted.org/packages/5e/e3/3b16c3190f3d71d3b10f6758d2d5f7779ef008c4fd367cedab3ed178a9f7/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aebc13a11ed3032d8dd6e7171eb6e86d40d67a5639d96c35142bd568b9299324", size = 39119106 }, + { url = "https://files.pythonhosted.org/packages/1d/d6/5d704b0d25c3c79532f8c0639f253ec2803b897100f64bcb3f53ced236e5/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6cf5c05f3cee251d80e98726b5c7cc9f21bab9e9783673bac58e6dfab57ecc8", size = 40090940 }, + { url = "https://files.pythonhosted.org/packages/37/29/366bc7e588220d74ec00e497ac6710c2833c9176f0372fe0286929b2d64c/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:11b676cd410cf162d3f6a70b43fb9e1e40affbc542a1e9ed3681895f2962d3d9", size = 38548177 }, + { url = "https://files.pythonhosted.org/packages/c8/11/fabf6ecabb1fe5b7d96889228ca2a9158c4c3bb732e3b8ee3f7f6d40b703/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba", size = 40043567 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pycryptodome" +version = "3.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/52/13b9db4a913eee948152a079fe58d035bd3d1a519584155da8e786f767e6/pycryptodome-3.21.0.tar.gz", hash = "sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297", size = 4818071 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/88/5e83de10450027c96c79dc65ac45e9d0d7a7fef334f39d3789a191f33602/pycryptodome-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2480ec2c72438430da9f601ebc12c518c093c13111a5c1644c82cdfc2e50b1e4", size = 2495937 }, + { url = "https://files.pythonhosted.org/packages/66/e1/8f28cd8cf7f7563319819d1e172879ccce2333781ae38da61c28fe22d6ff/pycryptodome-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:de18954104667f565e2fbb4783b56667f30fb49c4d79b346f52a29cb198d5b6b", size = 1634629 }, + { url = "https://files.pythonhosted.org/packages/6a/c1/f75a1aaff0c20c11df8dc8e2bf8057e7f73296af7dfd8cbb40077d1c930d/pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de4b7263a33947ff440412339cb72b28a5a4c769b5c1ca19e33dd6cd1dcec6e", size = 2168708 }, + { url = "https://files.pythonhosted.org/packages/ea/66/6f2b7ddb457b19f73b82053ecc83ba768680609d56dd457dbc7e902c41aa/pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0714206d467fc911042d01ea3a1847c847bc10884cf674c82e12915cfe1649f8", size = 2254555 }, + { url = "https://files.pythonhosted.org/packages/2c/2b/152c330732a887a86cbf591ed69bd1b489439b5464806adb270f169ec139/pycryptodome-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d85c1b613121ed3dbaa5a97369b3b757909531a959d229406a75b912dd51dd1", size = 2294143 }, + { url = "https://files.pythonhosted.org/packages/55/92/517c5c498c2980c1b6d6b9965dffbe31f3cd7f20f40d00ec4069559c5902/pycryptodome-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8898a66425a57bcf15e25fc19c12490b87bd939800f39a03ea2de2aea5e3611a", size = 2160509 }, + { url = "https://files.pythonhosted.org/packages/39/1f/c74288f54d80a20a78da87df1818c6464ac1041d10988bb7d982c4153fbc/pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:932c905b71a56474bff8a9c014030bc3c882cee696b448af920399f730a650c2", size = 2329480 }, + { url = "https://files.pythonhosted.org/packages/39/1b/d0b013bf7d1af7cf0a6a4fce13f5fe5813ab225313755367b36e714a63f8/pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93", size = 2254397 }, + { url = "https://files.pythonhosted.org/packages/14/71/4cbd3870d3e926c34706f705d6793159ac49d9a213e3ababcdade5864663/pycryptodome-3.21.0-cp36-abi3-win32.whl", hash = "sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764", size = 1775641 }, + { url = "https://files.pythonhosted.org/packages/43/1d/81d59d228381576b92ecede5cd7239762c14001a828bdba30d64896e9778/pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53", size = 1812863 }, + { url = "https://files.pythonhosted.org/packages/25/b3/09ff7072e6d96c9939c24cf51d3c389d7c345bf675420355c22402f71b68/pycryptodome-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca", size = 1691593 }, + { url = "https://files.pythonhosted.org/packages/a8/91/38e43628148f68ba9b68dedbc323cf409e537fd11264031961fd7c744034/pycryptodome-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd", size = 1765997 }, + { url = "https://files.pythonhosted.org/packages/08/16/ae464d4ac338c1dd41f89c41f9488e54f7d2a3acf93bb920bb193b99f8e3/pycryptodome-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d5ebe0763c982f069d3877832254f64974139f4f9655058452603ff559c482e8", size = 1615855 }, + { url = "https://files.pythonhosted.org/packages/1e/8c/b0cee957eee1950ce7655006b26a8894cee1dc4b8747ae913684352786eb/pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee86cbde706be13f2dec5a42b52b1c1d1cbb90c8e405c68d0755134735c8dc6", size = 1650018 }, + { url = "https://files.pythonhosted.org/packages/93/4d/d7138068089b99f6b0368622e60f97a577c936d75f533552a82613060c58/pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fd54003ec3ce4e0f16c484a10bc5d8b9bd77fa662a12b85779a2d2d85d67ee0", size = 1687977 }, + { url = "https://files.pythonhosted.org/packages/96/02/90ae1ac9f28be4df0ed88c127bf4acc1b102b40053e172759d4d1c54d937/pycryptodome-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5dfafca172933506773482b0e18f0cd766fd3920bd03ec85a283df90d8a17bc6", size = 1788273 }, +] + +[[package]] +name = "pydantic" +version = "2.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/7e/fb60e6fee04d0ef8f15e4e01ff187a196fa976eb0f0ab524af4599e5754c/pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06", size = 762094 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/26/3e1bbe954fde7ee22a6e7d31582c642aad9e84ffe4b5fb61e63b87cd326f/pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d", size = 431765 }, +] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938 }, + { url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684 }, + { url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169 }, + { url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227 }, + { url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695 }, + { url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662 }, + { url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370 }, + { url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813 }, + { url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287 }, + { url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414 }, + { url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301 }, + { url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685 }, + { url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876 }, + { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, + { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, + { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, + { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, + { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, + { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, + { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, + { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, + { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, + { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, + { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, + { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, + { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, + { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, + { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, + { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, + { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, + { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, + { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, + { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, + { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, + { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, + { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, + { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, + { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, + { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, + { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, + { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, + { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, + { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, + { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, + { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, + { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, + { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, + { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, + { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, + { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, + { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, + { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, + { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, + { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, + { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, + { url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159 }, + { url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331 }, + { url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467 }, + { url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797 }, + { url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839 }, + { url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861 }, + { url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582 }, + { url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985 }, + { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 }, +] + +[[package]] +name = "pyee" +version = "12.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/a7/8faaa62a488a2a1e0d56969757f087cbd2729e9bcfa508c230299f366b4c/pyee-12.0.0.tar.gz", hash = "sha256:c480603f4aa2927d4766eb41fa82793fe60a82cbfdb8d688e0d08c55a534e145", size = 29675 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/0d/95993c08c721ec68892547f2117e8f9dfbcef2ca71e098533541b4a54d5f/pyee-12.0.0-py3-none-any.whl", hash = "sha256:7b14b74320600049ccc7d0e0b1becd3b4bd0a03c745758225e31a59f4095c990", size = 14831 }, +] + +[[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 = "pynacl" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/22/27582568be639dfe22ddb3902225f91f2f17ceff88ce80e4db396c8986da/PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba", size = 3392854 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/75/0b8ede18506041c0bf23ac4d8e2971b4161cd6ce630b177d0a08eb0d8857/PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1", size = 349920 }, + { url = "https://files.pythonhosted.org/packages/59/bb/fddf10acd09637327a97ef89d2a9d621328850a72f1fdc8c08bdf72e385f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92", size = 601722 }, + { url = "https://files.pythonhosted.org/packages/5d/70/87a065c37cca41a75f2ce113a5a2c2aa7533be648b184ade58971b5f7ccc/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394", size = 680087 }, + { url = "https://files.pythonhosted.org/packages/ee/87/f1bb6a595f14a327e8285b9eb54d41fef76c585a0edef0a45f6fc95de125/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d", size = 856678 }, + { url = "https://files.pythonhosted.org/packages/66/28/ca86676b69bf9f90e710571b67450508484388bfce09acf8a46f0b8c785f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858", size = 1133660 }, + { url = "https://files.pythonhosted.org/packages/3d/85/c262db650e86812585e2bc59e497a8f59948a005325a11bbbc9ecd3fe26b/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b", size = 663824 }, + { url = "https://files.pythonhosted.org/packages/fd/1a/cc308a884bd299b651f1633acb978e8596c71c33ca85e9dc9fa33a5399b9/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff", size = 1117912 }, + { url = "https://files.pythonhosted.org/packages/25/2d/b7df6ddb0c2a33afdb358f8af6ea3b8c4d1196ca45497dd37a56f0c122be/PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543", size = 204624 }, + { url = "https://files.pythonhosted.org/packages/5e/22/d3db169895faaf3e2eda892f005f433a62db2decbcfbc2f61e6517adfa87/PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", size = 212141 }, +] + +[[package]] +name = "pyparsing" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/1a/3544f4f299a47911c2ab3710f534e52fea62a633c96806995da5d25be4b2/pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a", size = 1067694 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/a7/c8a2d361bf89c0d9577c934ebb7421b25dc84bf3a8e3ac0a40aed9acc547/pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1", size = 107716 }, +] + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +] + +[[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 = "python-levenshtein" +version = "0.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "levenshtein" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/72/58d77cb80b3c130d94f53a8204ffad9acfddb925b2fb5818ff9af0b3c832/python_levenshtein-0.26.1.tar.gz", hash = "sha256:24ba578e28058ebb4afa2700057e1678d7adf27e43cd1f17700c09a9009d5d3a", size = 12276 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/d7/03e0453719ed89724664f781f0255949408118093dbf77a2aa2a1198b38e/python_Levenshtein-0.26.1-py3-none-any.whl", hash = "sha256:8ef5e529dd640fb00f05ee62d998d2ee862f19566b641ace775d5ae16167b2ef", size = 9426 }, +] + +[[package]] +name = "python-statemachine" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/91/4f05f3931d1e9b1df71b17dc08c43feddf2bed7dbf13f95323df2cc8e340/python_statemachine-2.5.0.tar.gz", hash = "sha256:ae88cd22e47930b92b983a2176e61d811e571b69897be2568ec812c2885fb93a", size = 403718 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/2d/1c95ebe84df60d630f8e855d1df2c66368805444ac167e9b50f29eabe917/python_statemachine-2.5.0-py3-none-any.whl", hash = "sha256:0ed53846802c17037fcb2a92323f4bc0c833290fa9d17a3587c50886c1541e62", size = 50415 }, +] + +[[package]] +name = "pytz" +version = "2024.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, +] + +[[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 = "rapidfuzz" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/aa/25e5a20131369d82c7b8288c99c2c3011ec47a3f5953ccc9cb8145720be5/rapidfuzz-3.11.0.tar.gz", hash = "sha256:a53ca4d3f52f00b393fab9b5913c5bafb9afc27d030c8a1db1283da6917a860f", size = 57983000 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/70/820ebf9eb22ad97b9e0bb9fd1ad8c6be4c8db5a0974d12ce27b5c9a30db0/rapidfuzz-3.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb8a54543d16ab1b69e2c5ed96cabbff16db044a50eddfc028000138ca9ddf33", size = 1954240 }, + { url = "https://files.pythonhosted.org/packages/41/bc/e39abdc28160d8147ccab0aa922a29be50529dcf149615a68a324ff6f9b1/rapidfuzz-3.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:231c8b2efbd7f8d2ecd1ae900363ba168b8870644bb8f2b5aa96e4a7573bde19", size = 1427139 }, + { url = "https://files.pythonhosted.org/packages/b6/2d/19b8e5d80257b13d73ba994552b78a69ac2ed70f1de716f1b02fcb84d09c/rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54e7f442fb9cca81e9df32333fb075ef729052bcabe05b0afc0441f462299114", size = 1419602 }, + { url = "https://files.pythonhosted.org/packages/8c/82/1fc80cc531ec712872025c19118d78eb23aff09c7144b380c2c4b544b0d1/rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:906f1f2a1b91c06599b3dd1be207449c5d4fc7bd1e1fa2f6aef161ea6223f165", size = 5635370 }, + { url = "https://files.pythonhosted.org/packages/3c/5c/007b90af25f98e301b5f7a095059b09f602701443d555724c9226a45514c/rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed59044aea9eb6c663112170f2399b040d5d7b162828b141f2673e822093fa8", size = 1680848 }, + { url = "https://files.pythonhosted.org/packages/01/04/e481530eff5d1cf337b86a3095dd4de0b758c37291e51eb0d8c4f7d49719/rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cb1965a28b0fa64abdee130c788a0bc0bb3cf9ef7e3a70bf055c086c14a3d7e", size = 1682131 }, + { url = "https://files.pythonhosted.org/packages/10/15/b0ec18edfe6146d8915679644ab7584cd0165724d6a53bcc43bd59f8edb5/rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b488b244931d0291412917e6e46ee9f6a14376625e150056fe7c4426ef28225", size = 3134097 }, + { url = "https://files.pythonhosted.org/packages/8b/0e/cf0a5d62977381bca981fc171fd6c85dc52ca1239eaacf9c1d38978c5866/rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f0ba13557fec9d5ffc0a22826754a7457cc77f1b25145be10b7bb1d143ce84c6", size = 2332928 }, + { url = "https://files.pythonhosted.org/packages/dc/71/568d383eb36586c9e7e13f1327203e2be0938e5ff070c1fa2a99b418808e/rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3871fa7dfcef00bad3c7e8ae8d8fd58089bad6fb21f608d2bf42832267ca9663", size = 6940409 }, + { url = "https://files.pythonhosted.org/packages/ba/23/02972657d69e6d3aae2cdbd67debad080410ff9ef8849d8eab5e580a48a5/rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b2669eafee38c5884a6e7cc9769d25c19428549dcdf57de8541cf9e82822e7db", size = 2715928 }, + { url = "https://files.pythonhosted.org/packages/17/17/d964d770faa4e25e125618c00e31607cf8ce639d518fc29d200edf06cfda/rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ffa1bb0e26297b0f22881b219ffc82a33a3c84ce6174a9d69406239b14575bd5", size = 3265078 }, + { url = "https://files.pythonhosted.org/packages/bc/13/a117412b1e4ed0bb23b9891a45a59812d96fde8c076b8b8b828aa7ca3710/rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:45b15b8a118856ac9caac6877f70f38b8a0d310475d50bc814698659eabc1cdb", size = 4169215 }, + { url = "https://files.pythonhosted.org/packages/9f/0d/89ef496aedf885db4bfe7f46ac6727666afe0d9d8ca5b4f9c7cc8eef0378/rapidfuzz-3.11.0-cp310-cp310-win32.whl", hash = "sha256:22033677982b9c4c49676f215b794b0404073f8974f98739cb7234e4a9ade9ad", size = 1841736 }, + { url = "https://files.pythonhosted.org/packages/47/9a/69019f4e9c8a42e4aca0169dbae71602aba4e0fa4c5e84515f3ed682e59a/rapidfuzz-3.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:be15496e7244361ff0efcd86e52559bacda9cd975eccf19426a0025f9547c792", size = 1614955 }, + { url = "https://files.pythonhosted.org/packages/37/65/6fb036e39d175299ce44e5186ee2d08b9ea02d732ed6dbd70280f63b4eba/rapidfuzz-3.11.0-cp310-cp310-win_arm64.whl", hash = "sha256:714a7ba31ba46b64d30fccfe95f8013ea41a2e6237ba11a805a27cdd3bce2573", size = 863543 }, + { url = "https://files.pythonhosted.org/packages/40/ac/9ca008834104ad138fbfe2d7ae4443ada55e00c4eb4272d288897e8763b8/rapidfuzz-3.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8724a978f8af7059c5323d523870bf272a097478e1471295511cf58b2642ff83", size = 1955019 }, + { url = "https://files.pythonhosted.org/packages/4c/55/d026c01c9312c9c2a413679052a9bb884743fc5655e59339116d83a2125b/rapidfuzz-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b63cb1f2eb371ef20fb155e95efd96e060147bdd4ab9fc400c97325dfee9fe1", size = 1427753 }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5f3fae81dd1efdf47da19641e321ae84b4f49a5a7b2ab3ff78bd04a0ae7f/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82497f244aac10b20710448645f347d862364cc4f7d8b9ba14bd66b5ce4dec18", size = 1411472 }, + { url = "https://files.pythonhosted.org/packages/3c/3f/770b0fca00faf42983fe21fbd379f429dc2600c58d7015f969fb1f73c1db/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:339607394941801e6e3f6c1ecd413a36e18454e7136ed1161388de674f47f9d9", size = 5614973 }, + { url = "https://files.pythonhosted.org/packages/08/6f/e3df1c41adf27f4b8a95c9de947ed49e7311a676cd05bdd62a17bb1f21ec/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84819390a36d6166cec706b9d8f0941f115f700b7faecab5a7e22fc367408bc3", size = 1665667 }, + { url = "https://files.pythonhosted.org/packages/1a/9b/6c91b98dc70270c35913f359c17e30d4185c83663c4721363540f4c03016/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eea8d9e20632d68f653455265b18c35f90965e26f30d4d92f831899d6682149b", size = 1676166 }, + { url = "https://files.pythonhosted.org/packages/59/9d/eec7a1bfd3566fb17617b41bfb19556c483241d6864eea3c01b88efe5459/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b659e1e2ea2784a9a397075a7fc395bfa4fe66424042161c4bcaf6e4f637b38", size = 3130890 }, + { url = "https://files.pythonhosted.org/packages/26/7c/0a4bb5fbb03a362ea3e1409515d3ae641d9bc869c1375d97d8c47e369cc0/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1315cd2a351144572e31fe3df68340d4b83ddec0af8b2e207cd32930c6acd037", size = 2339850 }, + { url = "https://files.pythonhosted.org/packages/f8/c1/6b839db83caaa47721398b76390a3145202beb108fa433e842879b497439/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a7743cca45b4684c54407e8638f6d07b910d8d811347b9d42ff21262c7c23245", size = 6941921 }, + { url = "https://files.pythonhosted.org/packages/cc/c9/eaac43bb5e44f3594afddbbdb1a28d7bc0bcb69f93ed9a2ef0c949a48fb2/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5bb636b0150daa6d3331b738f7c0f8b25eadc47f04a40e5c23c4bfb4c4e20ae3", size = 2717551 }, + { url = "https://files.pythonhosted.org/packages/ef/d3/06ca5ee6b7f030f6527ea1e80fe9a4ab3597e86bc783574e3fc2b05a5265/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:42f4dd264ada7a9aa0805ea0da776dc063533917773cf2df5217f14eb4429eae", size = 3259550 }, + { url = "https://files.pythonhosted.org/packages/74/d8/094e75ee0424cce329901a0ff98c1821fd5d9dbc11bcdc9a3fddd2a09c4c/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51f24cb39e64256221e6952f22545b8ce21cacd59c0d3e367225da8fc4b868d8", size = 4173546 }, + { url = "https://files.pythonhosted.org/packages/d7/81/f263059e3d9f11b076751ac7ef4eba303fa7f11e32155658953f1697c274/rapidfuzz-3.11.0-cp311-cp311-win32.whl", hash = "sha256:aaf391fb6715866bc14681c76dc0308f46877f7c06f61d62cc993b79fc3c4a2a", size = 1842172 }, + { url = "https://files.pythonhosted.org/packages/33/04/dc42c787f02505a4ca0a961172e8353ceee74ea378b795f3e49686e944b7/rapidfuzz-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:ebadd5b8624d8ad503e505a99b8eb26fe3ea9f8e9c2234e805a27b269e585842", size = 1621122 }, + { url = "https://files.pythonhosted.org/packages/4e/0f/461e709bd641922a32bc034976963acbb11d8cf0af28b526f3f35ae07975/rapidfuzz-3.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:d895998fec712544c13cfe833890e0226585cf0391dd3948412441d5d68a2b8c", size = 864792 }, + { url = "https://files.pythonhosted.org/packages/c5/54/954ae2dc7dcb53f5f0953379a4a175d9c2f5e393656ab042843e53780d32/rapidfuzz-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f382fec4a7891d66fb7163c90754454030bb9200a13f82ee7860b6359f3f2fa8", size = 1938694 }, + { url = "https://files.pythonhosted.org/packages/f9/74/4682d3370821db5374c0f192d1e4123598190cb53d88936016187f80f154/rapidfuzz-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dfaefe08af2a928e72344c800dcbaf6508e86a4ed481e28355e8d4b6a6a5230e", size = 1423836 }, + { url = "https://files.pythonhosted.org/packages/e7/78/ce3d72767e186a9deca30dccb5096cfb03ec49e8e3abf2836ab10d1b4f74/rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92ebb7c12f682b5906ed98429f48a3dd80dd0f9721de30c97a01473d1a346576", size = 1393199 }, + { url = "https://files.pythonhosted.org/packages/3c/21/26bdbe846726ff7793789da07e155699cafa3ba3ed3bee86d472b4762121/rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a1b3ebc62d4bcdfdeba110944a25ab40916d5383c5e57e7c4a8dc0b6c17211a", size = 5543400 }, + { url = "https://files.pythonhosted.org/packages/c9/d5/78e922cfbfc67011ecee9f6c2fd630dee68650d23b9ce78316386a3d8c88/rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c6d7fea39cb33e71de86397d38bf7ff1a6273e40367f31d05761662ffda49e4", size = 1642855 }, + { url = "https://files.pythonhosted.org/packages/df/bb/dcf084c03c46968c3fbc52a33f2a725e0b8bb54ed714f0866c7dad747358/rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99aebef8268f2bc0b445b5640fd3312e080bd17efd3fbae4486b20ac00466308", size = 1669853 }, + { url = "https://files.pythonhosted.org/packages/ec/3a/9aa7a2c5b611e8d465e82c1d5f8278be7335769165f68f3ffc5a169f4a23/rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4469307f464ae3089acf3210b8fc279110d26d10f79e576f385a98f4429f7d97", size = 3129941 }, + { url = "https://files.pythonhosted.org/packages/d3/15/2bbab50a2634b25593e36241ab9629be253b8c6ea28a34ba6b856bfea661/rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:eb97c53112b593f89a90b4f6218635a9d1eea1d7f9521a3b7d24864228bbc0aa", size = 2302199 }, + { url = "https://files.pythonhosted.org/packages/c6/7c/e3ed92b89c657348c41708fe3b856ebc982c4b220b47299bdef8da374b20/rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ef8937dae823b889c0273dfa0f0f6c46a3658ac0d851349c464d1b00e7ff4252", size = 6904702 }, + { url = "https://files.pythonhosted.org/packages/bd/4f/eed77097068bffb692d6389ae19a531c52a896275e9f5c00566207767537/rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d95f9e9f3777b96241d8a00d6377cc9c716981d828b5091082d0fe3a2924b43e", size = 2679287 }, + { url = "https://files.pythonhosted.org/packages/1f/dc/d2d5dcd5b33a5b394485c67aa13674c8345826af8d3ba0702c06ab2f6430/rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:b1d67d67f89e4e013a5295e7523bc34a7a96f2dba5dd812c7c8cb65d113cbf28", size = 3224946 }, + { url = "https://files.pythonhosted.org/packages/8f/af/17c0c29ded64e464e626dd43fc2e3028c1fa929d10e8201fb2aec654e5b3/rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d994cf27e2f874069884d9bddf0864f9b90ad201fcc9cb2f5b82bacc17c8d5f2", size = 4144678 }, + { url = "https://files.pythonhosted.org/packages/66/5d/5dc02c87d9a0e64e0abd728d3255ddce8475e06b6be3f732a460f0a360c9/rapidfuzz-3.11.0-cp312-cp312-win32.whl", hash = "sha256:ba26d87fe7fcb56c4a53b549a9e0e9143f6b0df56d35fe6ad800c902447acd5b", size = 1824882 }, + { url = "https://files.pythonhosted.org/packages/b7/da/a37d532cbefd7242191abf18f438b315bf5c72d742f78414a8ec1b7396cf/rapidfuzz-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1f7efdd7b7adb32102c2fa481ad6f11923e2deb191f651274be559d56fc913b", size = 1606419 }, + { url = "https://files.pythonhosted.org/packages/92/d0/1406d6e110aff87303e98f47adc5e76ef2e69d51cdd08b2d463520158cab/rapidfuzz-3.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:ed78c8e94f57b44292c1a0350f580e18d3a3c5c0800e253f1583580c1b417ad2", size = 858655 }, + { url = "https://files.pythonhosted.org/packages/8a/30/984f1013d28b88304386c8e70b5d63db4765c28be8d9ef68d177c9addc77/rapidfuzz-3.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e60814edd0c9b511b5f377d48b9782b88cfe8be07a98f99973669299c8bb318a", size = 1931354 }, + { url = "https://files.pythonhosted.org/packages/a4/8a/41d4f95c5742a8a47c0e96c02957f72f8c34411cecde87fe371d5e09807e/rapidfuzz-3.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f28952da055dbfe75828891cd3c9abf0984edc8640573c18b48c14c68ca5e06", size = 1417918 }, + { url = "https://files.pythonhosted.org/packages/e3/26/031ac8366831da6afc5f25462196eab0e0caf9422c83c007307e23a6f010/rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e8f93bc736020351a6f8e71666e1f486bb8bd5ce8112c443a30c77bfde0eb68", size = 1388327 }, + { url = "https://files.pythonhosted.org/packages/17/1b/927edcd3b540770d3d6d52fe079c6bffdb99e9dfa4b73585bee2a8bd6504/rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76a4a11ba8f678c9e5876a7d465ab86def047a4fcc043617578368755d63a1bc", size = 5513214 }, + { url = "https://files.pythonhosted.org/packages/0d/a2/c1e4f35e7bfbbd97a665f8cd119d8bd4a085f1721366cd76582dc022131b/rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc0e0d41ad8a056a9886bac91ff9d9978e54a244deb61c2972cc76b66752de9c", size = 1638560 }, + { url = "https://files.pythonhosted.org/packages/39/3f/6827972efddb1e357a0b6165ae9e310d7dc5c078af3023893365c212641b/rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e8ea35f2419c7d56b3e75fbde2698766daedb374f20eea28ac9b1f668ef4f74", size = 1667185 }, + { url = "https://files.pythonhosted.org/packages/cc/5d/6902b93e1273e69ea087afd16e7504099bcb8d712a9f69cb649ea05ca7e1/rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd340bbd025302276b5aa221dccfe43040c7babfc32f107c36ad783f2ffd8775", size = 3107466 }, + { url = "https://files.pythonhosted.org/packages/a6/02/bdb2048c9b8edf4cd82c2e8f6a8ed9af0fbdf91810ca2b36d1be6fc996d8/rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:494eef2c68305ab75139034ea25328a04a548d297712d9cf887bf27c158c388b", size = 2302041 }, + { url = "https://files.pythonhosted.org/packages/12/91/0bbe51e3c15c02578487fd10a14692a40677ea974098d8d376bafd627a89/rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5a167344c1d6db06915fb0225592afdc24d8bafaaf02de07d4788ddd37f4bc2f", size = 6899969 }, + { url = "https://files.pythonhosted.org/packages/27/9d/09b85adfd5829f60bd6dbe53ba66dad22f93a281d494a5638b5f20fb6a8a/rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8c7af25bda96ac799378ac8aba54a8ece732835c7b74cfc201b688a87ed11152", size = 2669022 }, + { url = "https://files.pythonhosted.org/packages/cb/07/6fb723963243335c3bf73925914b6998649d642eff550187454d5bb3d077/rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d2a0f7e17f33e7890257367a1662b05fecaf56625f7dbb6446227aaa2b86448b", size = 3229475 }, + { url = "https://files.pythonhosted.org/packages/3a/8e/e9af6da2e235aa29ad2bb0a1fc2472b2949ed8d9ff8fb0f05b4bfbbf7675/rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d0d26c7172bdb64f86ee0765c5b26ea1dc45c52389175888ec073b9b28f4305", size = 4143861 }, + { url = "https://files.pythonhosted.org/packages/fd/d8/4677e36e958b4d95d039d254d597db9c020896c8130911dc36b136373b87/rapidfuzz-3.11.0-cp313-cp313-win32.whl", hash = "sha256:6ad02bab756751c90fa27f3069d7b12146613061341459abf55f8190d899649f", size = 1822624 }, + { url = "https://files.pythonhosted.org/packages/e8/97/1c782140e688ea2c3337d94516c635c575aa39fe62782fd53ad5d2119df4/rapidfuzz-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:b1472986fd9c5d318399a01a0881f4a0bf4950264131bb8e2deba9df6d8c362b", size = 1604273 }, + { url = "https://files.pythonhosted.org/packages/a6/83/8b713d50bec947e945a79be47f772484307fc876c426fb26c6f369098389/rapidfuzz-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c408f09649cbff8da76f8d3ad878b64ba7f7abdad1471efb293d2c075e80c822", size = 857385 }, + { url = "https://files.pythonhosted.org/packages/30/5a/8ac67667663d24cc4d4b76f63783e58ef03e4d4843d02dab6b2f8470ea5e/rapidfuzz-3.11.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f06e3c4c0a8badfc4910b9fd15beb1ad8f3b8fafa8ea82c023e5e607b66a78e4", size = 1853100 }, + { url = "https://files.pythonhosted.org/packages/dc/72/b043c26e93fb1bc5dfab1e5dd0f8d2f6135c2aa48e6db0660d4ecc5b157a/rapidfuzz-3.11.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fe7aaf5a54821d340d21412f7f6e6272a9b17a0cbafc1d68f77f2fc11009dcd5", size = 1361961 }, + { url = "https://files.pythonhosted.org/packages/5c/4a/29916c0dd853d22ef7b988af43f4e34d327581e16f60b4c9b0f229fa306c/rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25398d9ac7294e99876a3027ffc52c6bebeb2d702b1895af6ae9c541ee676702", size = 1354313 }, + { url = "https://files.pythonhosted.org/packages/41/39/f352af4ede7faeeea20bae2537f1fa60c3bbbf2696f0f2f3dda696745239/rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a52eea839e4bdc72c5e60a444d26004da00bb5bc6301e99b3dde18212e41465", size = 5478019 }, + { url = "https://files.pythonhosted.org/packages/99/8e/86f8a11ac0edda63ff5314d992aa1576fff5d8233f4310d46a6bb0551122/rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c87319b0ab9d269ab84f6453601fd49b35d9e4a601bbaef43743f26fabf496c", size = 3056881 }, + { url = "https://files.pythonhosted.org/packages/98/53/222dceb24a83c7d7d76086b6d5bfd3d6aa9988ea73d356d287b5c437c0d5/rapidfuzz-3.11.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3048c6ed29d693fba7d2a7caf165f5e0bb2b9743a0989012a98a47b975355cca", size = 1543944 }, +] + +[[package]] +name = "regex" +version = "2024.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/3c/4651f6b130c6842a8f3df82461a8950f923925db8b6961063e82744bddcc/regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91", size = 482674 }, + { url = "https://files.pythonhosted.org/packages/15/51/9f35d12da8434b489c7b7bffc205c474a0a9432a889457026e9bc06a297a/regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0", size = 287684 }, + { url = "https://files.pythonhosted.org/packages/bd/18/b731f5510d1b8fb63c6b6d3484bfa9a59b84cc578ac8b5172970e05ae07c/regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e", size = 284589 }, + { url = "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde", size = 782511 }, + { url = "https://files.pythonhosted.org/packages/1b/2b/323e72d5d2fd8de0d9baa443e1ed70363ed7e7b2fb526f5950c5cb99c364/regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e", size = 821149 }, + { url = "https://files.pythonhosted.org/packages/90/30/63373b9ea468fbef8a907fd273e5c329b8c9535fee36fc8dba5fecac475d/regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2", size = 809707 }, + { url = "https://files.pythonhosted.org/packages/f2/98/26d3830875b53071f1f0ae6d547f1d98e964dd29ad35cbf94439120bb67a/regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf", size = 781702 }, + { url = "https://files.pythonhosted.org/packages/87/55/eb2a068334274db86208ab9d5599ffa63631b9f0f67ed70ea7c82a69bbc8/regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c", size = 771976 }, + { url = "https://files.pythonhosted.org/packages/74/c0/be707bcfe98254d8f9d2cff55d216e946f4ea48ad2fd8cf1428f8c5332ba/regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86", size = 697397 }, + { url = "https://files.pythonhosted.org/packages/49/dc/bb45572ceb49e0f6509f7596e4ba7031f6819ecb26bc7610979af5a77f45/regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67", size = 768726 }, + { url = "https://files.pythonhosted.org/packages/5a/db/f43fd75dc4c0c2d96d0881967897926942e935d700863666f3c844a72ce6/regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d", size = 775098 }, + { url = "https://files.pythonhosted.org/packages/99/d7/f94154db29ab5a89d69ff893159b19ada89e76b915c1293e98603d39838c/regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2", size = 839325 }, + { url = "https://files.pythonhosted.org/packages/f7/17/3cbfab1f23356fbbf07708220ab438a7efa1e0f34195bf857433f79f1788/regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008", size = 843277 }, + { url = "https://files.pythonhosted.org/packages/7e/f2/48b393b51900456155de3ad001900f94298965e1cad1c772b87f9cfea011/regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62", size = 773197 }, + { url = "https://files.pythonhosted.org/packages/45/3f/ef9589aba93e084cd3f8471fded352826dcae8489b650d0b9b27bc5bba8a/regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e", size = 261714 }, + { url = "https://files.pythonhosted.org/packages/42/7e/5f1b92c8468290c465fd50c5318da64319133231415a8aa6ea5ab995a815/regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519", size = 274042 }, + { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669 }, + { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684 }, + { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589 }, + { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121 }, + { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275 }, + { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257 }, + { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727 }, + { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667 }, + { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963 }, + { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700 }, + { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592 }, + { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929 }, + { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213 }, + { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734 }, + { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052 }, + { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, + { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, + { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, + { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, + { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, + { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, + { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, + { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, + { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, + { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, + { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, + { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, + { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, + { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, + { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, + { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 }, + { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 }, + { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 }, + { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 }, + { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 }, + { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 }, + { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 }, + { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 }, + { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 }, + { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 }, + { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 }, + { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 }, + { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 }, + { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, + { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, +] + +[[package]] +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 = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 }, +] + +[[package]] +name = "resolvelib" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/10/f699366ce577423cbc3df3280063099054c23df70856465080798c6ebad6/resolvelib-1.0.1.tar.gz", hash = "sha256:04ce76cbd63fded2078ce224785da6ecd42b9564b1390793f64ddecbe997b309", size = 21065 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/fc/e9ccf0521607bcd244aa0b3fbd574f71b65e9ce6a112c83af988bbbe2e23/resolvelib-1.0.1-py2.py3-none-any.whl", hash = "sha256:d2da45d1a8dfee81bdd591647783e340ef3bcb104b54c383f70d422ef5cc7dbf", size = 17194 }, +] + +[[package]] +name = "retry" +version = "0.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "decorator" }, + { name = "py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/72/75d0b85443fbc8d9f38d08d2b1b67cc184ce35280e4a3813cda2f445f3a4/retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4", size = 6448 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/0d/53aea75710af4528a25ed6837d71d117602b01946b307a3912cb3cfcbcba/retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606", size = 7986 }, +] + +[[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 = "safetensors" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/4f/2ef9ef1766f8c194b01b67a63a444d2e557c8fe1d82faf3ebd85f370a917/safetensors-0.5.2.tar.gz", hash = "sha256:cb4a8d98ba12fa016f4241932b1fc5e702e5143f5374bba0bbcf7ddc1c4cf2b8", size = 66957 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/d1/017e31e75e274492a11a456a9e7c171f8f7911fe50735b4ec6ff37221220/safetensors-0.5.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:45b6092997ceb8aa3801693781a71a99909ab9cc776fbc3fa9322d29b1d3bef2", size = 427067 }, + { url = "https://files.pythonhosted.org/packages/24/84/e9d3ff57ae50dd0028f301c9ee064e5087fe8b00e55696677a0413c377a7/safetensors-0.5.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6d0d6a8ee2215a440e1296b843edf44fd377b055ba350eaba74655a2fe2c4bae", size = 408856 }, + { url = "https://files.pythonhosted.org/packages/f1/1d/fe95f5dd73db16757b11915e8a5106337663182d0381811c81993e0014a9/safetensors-0.5.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86016d40bcaa3bcc9a56cd74d97e654b5f4f4abe42b038c71e4f00a089c4526c", size = 450088 }, + { url = "https://files.pythonhosted.org/packages/cf/21/e527961b12d5ab528c6e47b92d5f57f33563c28a972750b238b871924e49/safetensors-0.5.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:990833f70a5f9c7d3fc82c94507f03179930ff7d00941c287f73b6fcbf67f19e", size = 458966 }, + { url = "https://files.pythonhosted.org/packages/a5/8b/1a037d7a57f86837c0b41905040369aea7d8ca1ec4b2a77592372b2ec380/safetensors-0.5.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dfa7c2f3fe55db34eba90c29df94bcdac4821043fc391cb5d082d9922013869", size = 509915 }, + { url = "https://files.pythonhosted.org/packages/61/3d/03dd5cfd33839df0ee3f4581a20bd09c40246d169c0e4518f20b21d5f077/safetensors-0.5.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46ff2116150ae70a4e9c490d2ab6b6e1b1b93f25e520e540abe1b81b48560c3a", size = 527664 }, + { url = "https://files.pythonhosted.org/packages/c5/dc/8952caafa9a10a3c0f40fa86bacf3190ae7f55fa5eef87415b97b29cb97f/safetensors-0.5.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab696dfdc060caffb61dbe4066b86419107a24c804a4e373ba59be699ebd8d5", size = 461978 }, + { url = "https://files.pythonhosted.org/packages/60/da/82de1fcf1194e3dbefd4faa92dc98b33c06bed5d67890e0962dd98e18287/safetensors-0.5.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03c937100f38c9ff4c1507abea9928a6a9b02c9c1c9c3609ed4fb2bf413d4975", size = 491253 }, + { url = "https://files.pythonhosted.org/packages/5a/9a/d90e273c25f90c3ba1b0196a972003786f04c39e302fbd6649325b1272bb/safetensors-0.5.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a00e737948791b94dad83cf0eafc09a02c4d8c2171a239e8c8572fe04e25960e", size = 628644 }, + { url = "https://files.pythonhosted.org/packages/70/3c/acb23e05aa34b4f5edd2e7f393f8e6480fbccd10601ab42cd03a57d4ab5f/safetensors-0.5.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:d3a06fae62418ec8e5c635b61a8086032c9e281f16c63c3af46a6efbab33156f", size = 721648 }, + { url = "https://files.pythonhosted.org/packages/71/45/eaa3dba5253a7c6931230dc961641455710ab231f8a89cb3c4c2af70f8c8/safetensors-0.5.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:1506e4c2eda1431099cebe9abf6c76853e95d0b7a95addceaa74c6019c65d8cf", size = 659588 }, + { url = "https://files.pythonhosted.org/packages/b0/71/2f9851164f821064d43b481ddbea0149c2d676c4f4e077b178e7eeaa6660/safetensors-0.5.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5c5b5d9da594f638a259fca766046f44c97244cc7ab8bef161b3e80d04becc76", size = 632533 }, + { url = "https://files.pythonhosted.org/packages/00/f1/5680e2ef61d9c61454fad82c344f0e40b8741a9dbd1e31484f0d31a9b1c3/safetensors-0.5.2-cp38-abi3-win32.whl", hash = "sha256:fe55c039d97090d1f85277d402954dd6ad27f63034fa81985a9cc59655ac3ee2", size = 291167 }, + { url = "https://files.pythonhosted.org/packages/86/ca/aa489392ec6fb59223ffce825461e1f811a3affd417121a2088be7a5758b/safetensors-0.5.2-cp38-abi3-win_amd64.whl", hash = "sha256:78abdddd03a406646107f973c7843276e7b64e5e32623529dc17f3d94a20f589", size = 303756 }, +] + +[[package]] +name = "scalecodec" +version = "1.2.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "base58" }, + { name = "more-itertools" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/7c/703893e7a8751318517a3dd8c0c060b2c30ffa33f4ab5dd6a4ed483f7967/scalecodec-1.2.11.tar.gz", hash = "sha256:99a2cdbfccdcaf22bd86b86da55a730a2855514ad2309faef4a4a93ac6cbeb8d", size = 150260 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/60/2a903fa9ed3dfc842240da22969a25b16ea213ed3ee25b7ba8ae1cba20c7/scalecodec-1.2.11-py3-none-any.whl", hash = "sha256:d15c94965f617caa25096f83a45f5f73031d05e6ee08d6039969f0a64fc35de1", size = 99164 }, +] + +[[package]] +name = "scikit-learn" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/19/5aa2002044afc297ecaf1e3517ed07bba4aece3b5613b5160c1212995fc8/scikit_learn-1.6.0.tar.gz", hash = "sha256:9d58481f9f7499dff4196927aedd4285a0baec8caa3790efbe205f13de37dd6e", size = 7074944 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/97/55060f91a5e7c4df945e5a69b16148b5f2256e6e1ea3f17da8e27edf9953/scikit_learn-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:366fb3fa47dce90afed3d6106183f4978d6f24cfd595c2373424171b915ee718", size = 12060299 }, + { url = "https://files.pythonhosted.org/packages/36/7b/8c5dfc64a8344ebf2ae493d59af4b3650588051f654e164ff4f9952877b3/scikit_learn-1.6.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:59cd96a8d9f8dfd546f5d6e9787e1b989e981388d7803abbc9efdcde61e47460", size = 11105443 }, + { url = "https://files.pythonhosted.org/packages/25/9f/61544f2a5cae1bc27c97f0ec9ffcc9837e469f215817608840a4ccbb277a/scikit_learn-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efa7a579606c73a0b3d210e33ea410ea9e1af7933fe324cb7e6fbafae4ea5948", size = 12637137 }, + { url = "https://files.pythonhosted.org/packages/50/79/d21599fc44d2d497ced440480670b6314ebc00308e3bae0d0ebca44cd481/scikit_learn-1.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a46d3ca0f11a540b8eaddaf5e38172d8cd65a86cb3e3632161ec96c0cffb774c", size = 13490128 }, + { url = "https://files.pythonhosted.org/packages/ff/87/788da20cfefcd261123d4bb015b2de076e49cdd3b811b55e6811acd3cb21/scikit_learn-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:5be4577769c5dde6e1b53de8e6520f9b664ab5861dd57acee47ad119fd7405d6", size = 11118524 }, + { url = "https://files.pythonhosted.org/packages/07/95/070d6e70f735d13f1c10afebb65ba3526125b7d6c6fc7022651a4a061148/scikit_learn-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1f50b4f24cf12a81c3c09958ae3b864d7534934ca66ded3822de4996d25d7285", size = 12095168 }, + { url = "https://files.pythonhosted.org/packages/72/3d/0381e3a59ebd4154e6a61b0ceaf299c3c141035033dd3b868776cd9af02d/scikit_learn-1.6.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:eb9ae21f387826da14b0b9cb1034f5048ddb9182da429c689f5f4a87dc96930b", size = 11108880 }, + { url = "https://files.pythonhosted.org/packages/fe/2d/0999ae3eed2ac67b1b3cd7fc33370bd5ca59a7514ffe43ae2b6f3cd85b9b/scikit_learn-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0baa91eeb8c32632628874a5c91885eaedd23b71504d24227925080da075837a", size = 12585449 }, + { url = "https://files.pythonhosted.org/packages/0e/ec/1b15b59c6cc7a993320a52234369e787f50345a4753e50d5a015a91e1a20/scikit_learn-1.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c716d13ba0a2f8762d96ff78d3e0cde90bc9c9b5c13d6ab6bb9b2d6ca6705fd", size = 13489728 }, + { url = "https://files.pythonhosted.org/packages/96/a2/cbfb5743de748d574ffdfd557e9cb29ba4f8b8a3e07836c6c176f713de2f/scikit_learn-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:9aafd94bafc841b626681e626be27bf1233d5a0f20f0a6fdb4bee1a1963c6643", size = 11132946 }, + { url = "https://files.pythonhosted.org/packages/18/0c/a5de627aa57b028aea7026cb3bbeaf63be3158adc118212d6cc7843d939a/scikit_learn-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:04a5ba45c12a5ff81518aa4f1604e826a45d20e53da47b15871526cda4ff5174", size = 12096999 }, + { url = "https://files.pythonhosted.org/packages/a3/7d/02a96e6fb28ddb213e84b1b4a44148d26ec96fc9db9c74e050277e009892/scikit_learn-1.6.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:21fadfc2ad7a1ce8bd1d90f23d17875b84ec765eecbbfc924ff11fb73db582ce", size = 11160579 }, + { url = "https://files.pythonhosted.org/packages/70/28/77b071f541d75247e6c3403f19aaa634371e972691f6aa1838ca9fd4cc52/scikit_learn-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30f34bb5fde90e020653bb84dcb38b6c83f90c70680dbd8c38bd9becbad7a127", size = 12246543 }, + { url = "https://files.pythonhosted.org/packages/17/0e/e6bb84074f1081245a165c0ee775ecef24beae9d2f2e24bcac0c9f155f13/scikit_learn-1.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dad624cffe3062276a0881d4e441bc9e3b19d02d17757cd6ae79a9d192a0027", size = 13140402 }, + { url = "https://files.pythonhosted.org/packages/21/1d/3df58df8bd425f425df9f90b316618ace62b7f1f838ac1580191025cc735/scikit_learn-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fce7950a3fad85e0a61dc403df0f9345b53432ac0e47c50da210d22c60b6d85", size = 11103596 }, + { url = "https://files.pythonhosted.org/packages/2e/f4/c3b51920cf310169d19d07855a7bdf51a9b065314877d9a58c0c60d08eea/scikit_learn-1.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e5453b2e87ef8accedc5a8a4e6709f887ca01896cd7cc8a174fe39bd4bb00aef", size = 12002532 }, + { url = "https://files.pythonhosted.org/packages/e4/76/cfb0778a84c30df272f1c41fc7b3bd3ffac6e8b02ee6a078a592d35cf73f/scikit_learn-1.6.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5fe11794236fb83bead2af26a87ced5d26e3370b8487430818b915dafab1724e", size = 11088997 }, + { url = "https://files.pythonhosted.org/packages/2b/8d/4563419d742b852e50871fa3494a8dd0304610601359209a2e614e200260/scikit_learn-1.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61fe3dcec0d82ae280877a818ab652f4988371e32dd5451e75251bece79668b1", size = 12203192 }, + { url = "https://files.pythonhosted.org/packages/15/a4/f4fdcdd11d82837804c888097ad02aa6381c4bbd57b9d3074ecf9eba8f42/scikit_learn-1.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b44e3a51e181933bdf9a4953cc69c6025b40d2b49e238233f149b98849beb4bf", size = 13164436 }, + { url = "https://files.pythonhosted.org/packages/1a/e1/32bdcf8f918de5a156da6886aba24a3b5718d267954bd34555be896289f0/scikit_learn-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:a17860a562bac54384454d40b3f6155200c1c737c9399e6a97962c63fce503ac", size = 11064779 }, + { url = "https://files.pythonhosted.org/packages/c6/8d/14464bea220bc02879f9e8d905c4b0a44b5c12afde6c375720b6f41d9407/scikit_learn-1.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:98717d3c152f6842d36a70f21e1468fb2f1a2f8f2624d9a3f382211798516426", size = 11962472 }, + { url = "https://files.pythonhosted.org/packages/b4/69/66899cdc65986188e0e255e52ee93dee5101a72f139ee05f263dfff2053a/scikit_learn-1.6.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:34e20bfac8ff0ebe0ff20fb16a4d6df5dc4cc9ce383e00c2ab67a526a3c67b18", size = 11104864 }, + { url = "https://files.pythonhosted.org/packages/3c/32/2c63bc108cc5438b116a0c6fd25c6126dd14c03118724385f10a3d218ee8/scikit_learn-1.6.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eba06d75815406091419e06dd650b91ebd1c5f836392a0d833ff36447c2b1bfa", size = 12435734 }, + { url = "https://files.pythonhosted.org/packages/0c/f5/9434dff19e04a334bfb30df90511904263c48a422a9952d91d8de5c3aa62/scikit_learn-1.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b6916d1cec1ff163c7d281e699d7a6a709da2f2c5ec7b10547e08cc788ddd3ae", size = 11329803 }, +] + +[[package]] +name = "scipy" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/7b/2b8ac283cf32465ed08bc20a83d559fe7b174a484781702ba8accea001d6/scipy-1.15.0.tar.gz", hash = "sha256:300742e2cc94e36a2880ebe464a1c8b4352a7b0f3e36ec3d2ac006cdbe0219ac", size = 59407226 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/6a/14ce8d4452acdced1b69ea32b0d304b04b00376deb4f1eb65f946aee41af/scipy-1.15.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:aeac60d3562a7bf2f35549bdfdb6b1751c50590f55ce7322b4b2fc821dc27fca", size = 41413763 }, + { url = "https://files.pythonhosted.org/packages/45/12/570ba186d0ae1d528f8f0524b88fb9a263653ce575ac085edd9c1ef29e9c/scipy-1.15.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5abbdc6ede5c5fed7910cf406a948e2c0869231c0db091593a6b2fa78be77e5d", size = 32518980 }, + { url = "https://files.pythonhosted.org/packages/51/5a/b6ac5aa213cfa196d15db5ee159010aa9b94d0bc2bfa917fb99297701628/scipy-1.15.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:eb1533c59f0ec6c55871206f15a5c72d1fae7ad3c0a8ca33ca88f7c309bbbf8c", size = 24792491 }, + { url = "https://files.pythonhosted.org/packages/35/1f/6af575b77b2ee057551643de75a30252ce32098b2d9fd45bcf969a6fa35b/scipy-1.15.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:de112c2dae53107cfeaf65101419662ac0a54e9a088c17958b51c95dac5de56d", size = 27886039 }, + { url = "https://files.pythonhosted.org/packages/6a/7b/0c261d4857f459de6dffe11b3818583944f8d87716ce0b3b5f058aa34ff3/scipy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2240e1fd0782e62e1aacdc7234212ee271d810f67e9cd3b8d521003a82603ef8", size = 38374628 }, + { url = "https://files.pythonhosted.org/packages/99/17/ca390fbbfea5b34e3a00fc819fcb7c22e8b889360882820030b533d26c01/scipy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d35aef233b098e4de88b1eac29f0df378278e7e250a915766786b773309137c4", size = 40599127 }, + { url = "https://files.pythonhosted.org/packages/1d/65/95d93b1360f5defc1b6bf0963ac4e0d3413c95d8e8d6a1624a256506dfd3/scipy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b29e4fc02e155a5fd1165f1e6a73edfdd110470736b0f48bcbe48083f0eee37", size = 42937900 }, + { url = "https://files.pythonhosted.org/packages/51/8c/c2d371111961f737ae08881f654cf54eca796c42ec0429add2a06df97049/scipy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:0e5b34f8894f9904cc578008d1a9467829c1817e9f9cb45e6d6eeb61d2ab7731", size = 43907603 }, + { url = "https://files.pythonhosted.org/packages/b8/53/7f627c180cdaa211fa537650ca05912f58cb68fc33bb2f9af3d29169913e/scipy-1.15.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:46e91b5b16909ff79224b56e19cbad65ca500b3afda69225820aa3afbf9ec020", size = 41423594 }, + { url = "https://files.pythonhosted.org/packages/c9/ab/f848933c6f656f2c7af2d56d0be44511b730498538fe04db70eb03a6ad86/scipy-1.15.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:82bff2eb01ccf7cea8b6ee5274c2dbeadfdac97919da308ee6d8e5bcbe846443", size = 32535797 }, + { url = "https://files.pythonhosted.org/packages/41/93/266693c471ec1e2e7748c1ee5e867299f3d0ac42e0e63f52649430ec1976/scipy-1.15.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:9c8254fe21dd2c6c8f7757035ec0c31daecf3bb3cffd93bc1ca661b731d28136", size = 24809325 }, + { url = "https://files.pythonhosted.org/packages/f3/55/1acc49a48bc11fb95cf625c0763f2749f8710265d2fecbf6ed6dd618fc54/scipy-1.15.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:c9624eeae79b18cab1a31944b5ef87aa14b125d6ab69b71db22f0dbd962caf1e", size = 27917711 }, + { url = "https://files.pythonhosted.org/packages/e2/f5/15f62812b36f2f94b9d1ca31d3d2bbabfb6979e48a0866041bee7031c461/scipy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d13bbc0658c11f3d19df4138336e4bce2c4fbd78c2755be4bf7b8e235481557f", size = 38331850 }, + { url = "https://files.pythonhosted.org/packages/ad/21/6dc57f6f6c8014dc6d07111e4976422580789fa96c4d7ddf63614939cb6c/scipy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdca4c7bb8dc41307e5f39e9e5d19c707d8e20a29845e7533b3bb20a9d4ccba0", size = 40587953 }, + { url = "https://files.pythonhosted.org/packages/da/dd/26db78c2054f8d81b28ae4688da7930ea3c33e5d1885928aadefeec979f9/scipy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f376d7c767731477bac25a85d0118efdc94a572c6b60decb1ee48bf2391a73b", size = 42963920 }, + { url = "https://files.pythonhosted.org/packages/82/89/eb4aaf929be0e2c03bb5e40ed61427aab9c8ba6c0764aebf82d7302bb3d3/scipy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:61513b989ee8d5218fbeb178b2d51534ecaddba050db949ae99eeb3d12f6825d", size = 43894857 }, + { url = "https://files.pythonhosted.org/packages/35/70/fffb90a725dec6056c9059073856fd99de22a253459a874a63b8b8a012db/scipy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5beb0a2200372b7416ec73fdae94fe81a6e85e44eb49c35a11ac356d2b8eccc6", size = 41475240 }, + { url = "https://files.pythonhosted.org/packages/63/ca/6b838a2e5e6718d879e8522d1155a068c2a769be04f7da8c5179ead32a7b/scipy-1.15.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fde0f3104dfa1dfbc1f230f65506532d0558d43188789eaf68f97e106249a913", size = 32595923 }, + { url = "https://files.pythonhosted.org/packages/b1/07/4e69f6f7185915d77719bf226c1d554a4bb99f27cb92161fdd57b1434343/scipy-1.15.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:35c68f7044b4e7ad73a3e68e513dda946989e523df9b062bd3cf401a1a882192", size = 24869617 }, + { url = "https://files.pythonhosted.org/packages/30/22/e3dadf189dcab215be461efe0fd9d288f4c2d99783c4aec2ce80837800b7/scipy-1.15.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:52475011be29dfcbecc3dfe3060e471ac5155d72e9233e8d5616b84e2b542054", size = 28007674 }, + { url = "https://files.pythonhosted.org/packages/51/0f/71c9ee2acaac0660a79e36424d367ed5737e4ef27b885f96cd439f451467/scipy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5972e3f96f7dda4fd3bb85906a17338e65eaddfe47f750e240f22b331c08858e", size = 38066684 }, + { url = "https://files.pythonhosted.org/packages/fb/77/74a1ceecb205f5d46fe2cd10071383748ee8891a96b7824a372391a6291c/scipy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe00169cf875bed0b3c40e4da45b57037dc21d7c7bf0c85ed75f210c281488f1", size = 40250011 }, + { url = "https://files.pythonhosted.org/packages/8c/9f/f1544110a3d31183034e05422836505beb438aa56183f2ccef6dcd3b4e3f/scipy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:161f80a98047c219c257bf5ce1777c574bde36b9d962a46b20d0d7e531f86863", size = 42625471 }, + { url = "https://files.pythonhosted.org/packages/3f/39/a29b75f9c30084cbafd416bfa00933311a5b7a96be6e88750c98521d2ccb/scipy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:327163ad73e54541a675240708244644294cb0a65cca420c9c79baeb9648e479", size = 43622832 }, + { url = "https://files.pythonhosted.org/packages/4d/46/2fa07d5b53092b73c4bb416954d07d883b53be4a5bd6282c67e03c051225/scipy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0fcb16eb04d84670722ce8d93b05257df471704c913cb0ff9dc5a1c31d1e9422", size = 41438080 }, + { url = "https://files.pythonhosted.org/packages/55/05/77778b1127e170ffb484614691fdd8f9d2640dcf951d515f513debe5d0e0/scipy-1.15.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:767e8cf6562931f8312f4faa7ddea412cb783d8df49e62c44d00d89f41f9bbe8", size = 32532932 }, + { url = "https://files.pythonhosted.org/packages/2b/9f/6de4970a2f524785d94a85f423a53b8c53d84917f2df702733ccdc9afd54/scipy-1.15.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:37ce9394cdcd7c5f437583fc6ef91bd290014993900643fdfc7af9b052d1613b", size = 24806488 }, + { url = "https://files.pythonhosted.org/packages/65/ef/b1c1e2499189bbea109a6b022a6147dd4552d72bed19289b4d4e411c4ce7/scipy-1.15.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6d26f17c64abd6c6c2dfb39920f61518cc9e213d034b45b2380e32ba78fde4c0", size = 27930055 }, + { url = "https://files.pythonhosted.org/packages/24/ec/6e4fe2a34a91102c806ecf9f45426f66bd604a5b5f48e951ce2bd770b2fe/scipy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e2448acd79c6374583581a1ded32ac71a00c2b9c62dfa87a40e1dd2520be111", size = 38031212 }, + { url = "https://files.pythonhosted.org/packages/82/4d/ecef655956ce332edbc411ab64ab843d767dd86e646898ac721dbcc7910e/scipy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36be480e512d38db67f377add5b759fb117edd987f4791cdf58e59b26962bee4", size = 40209536 }, + { url = "https://files.pythonhosted.org/packages/c5/ec/3af823fcd86e3155ad7ed2b684634391e4524ff82735c26abed522fc5405/scipy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ccb6248a9987193fe74363a2d73b93bc2c546e0728bd786050b7aef6e17db03c", size = 42584473 }, + { url = "https://files.pythonhosted.org/packages/23/01/f0ec4236ba8a96353e56694160041d7d9bebd9a0231a1c9beedc6e75cd50/scipy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:952d2e9eaa787f0a9e95b6e85da3654791b57a156c3e6609e65cc5176ccfe6f2", size = 43639460 }, + { url = "https://files.pythonhosted.org/packages/e9/02/c8bccc5c4813eccfeeef6ed0effe42e2cf98199d350ca476c22029569edc/scipy-1.15.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b1432102254b6dc7766d081fa92df87832ac25ff0b3d3a940f37276e63eb74ff", size = 41642304 }, + { url = "https://files.pythonhosted.org/packages/27/7a/9191a8b61f5826f08932b6ae47d44fbf4f473beb307d8ca3ed96a216929f/scipy-1.15.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:4e08c6a36f46abaedf765dd2dfcd3698fa4bd7e311a9abb2d80e33d9b2d72c34", size = 32620019 }, + { url = "https://files.pythonhosted.org/packages/e6/17/9c8452c8a59f1ede4a7ba6ba03b8b44703cdd1f1217b649f470c216f3095/scipy-1.15.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ec915cd26d76f6fc7ae8522f74f5b2accf39546f341c771bb2297f3871934a52", size = 24893299 }, + { url = "https://files.pythonhosted.org/packages/db/73/45c8566538bf9252be1e3e36b149714619c6f4d015a901cd76e257f88a37/scipy-1.15.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:351899dd2a801edd3691622172bc8ea01064b1cada794f8641b89a7dc5418db6", size = 27955764 }, + { url = "https://files.pythonhosted.org/packages/9f/4e/8822a2cafcea8727430e9a0bf785e8f0e81aaaac1048dad764d522f0f1ec/scipy-1.15.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9baff912ea4f78a543d183ed6f5b3bea9784509b948227daaf6f10727a0e2e5", size = 39879164 }, + { url = "https://files.pythonhosted.org/packages/b1/27/b55549a4aba515d9a19b6384c2c2f976725cd19d5d41c58ffac9a4d98892/scipy-1.15.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cd9d9198a7fd9a77f0eb5105ea9734df26f41faeb2a88a0e62e5245506f7b6df", size = 42091406 }, + { url = "https://files.pythonhosted.org/packages/79/df/989b2fd3f8ead6bcf89fc683fde94741eb3b291e41a3ce70cec08c80aa36/scipy-1.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:129f899ed275c0515d553b8d31696924e2ca87d1972421e46c376b9eb87de3d2", size = 43188844 }, +] + +[[package]] +name = "sentence-transformers" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "pillow" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "torch" }, + { name = "tqdm" }, + { name = "transformers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/0a/c677efe908b20e7e8d4ed6cce3a3447eebc7dc5e348e458f5f9a44a72b00/sentence_transformers-3.3.1.tar.gz", hash = "sha256:9635dbfb11c6b01d036b9cfcee29f7716ab64cf2407ad9f403a2e607da2ac48b", size = 217914 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/c8/990e22a465e4771338da434d799578865d6d7ef1fdb50bd844b7ecdcfa19/sentence_transformers-3.3.1-py3-none-any.whl", hash = "sha256:abffcc79dab37b7d18d21a26d5914223dd42239cfe18cb5e111c66c54b658ae7", size = 268797 }, +] + +[[package]] +name = "sentry-sdk" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/4a/eccdcb8c2649d53440ae1902447b86e2e2ad1bc84207c80af9696fa07614/sentry_sdk-2.19.2.tar.gz", hash = "sha256:467df6e126ba242d39952375dd816fbee0f217d119bf454a8ce74cf1e7909e8d", size = 299047 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/4d/74597bb6bcc23abc774b8901277652c61331a9d4d0a8d1bdb20679b9bbcb/sentry_sdk-2.19.2-py2.py3-none-any.whl", hash = "sha256:ebdc08228b4d131128e568d696c210d846e5b9d70aa0327dec6b1272d9d40b84", size = 322942 }, +] + +[[package]] +name = "setproctitle" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/4e/b09341b19b9ceb8b4c67298ab4a08ef7a4abdd3016c7bb152e9b6379031d/setproctitle-1.3.4.tar.gz", hash = "sha256:3b40d32a3e1f04e94231ed6dfee0da9e43b4f9c6b5450d53e6dd7754c34e0c50", size = 26456 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/f4/95937eb5c5370324a942ba90174c6d0fc7c5ad2f7f8ea989ccdbd6e1be5e/setproctitle-1.3.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0f6661a69c68349172ba7b4d5dd65fec2b0917abc99002425ad78c3e58cf7595", size = 16855 }, + { url = "https://files.pythonhosted.org/packages/32/a6/d49dbb0d75d02d11db49151469e1fee740efa45de7288bffcc4d88d0c290/setproctitle-1.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:754bac5e470adac7f7ec2239c485cd0b75f8197ca8a5b86ffb20eb3a3676cc42", size = 11627 }, + { url = "https://files.pythonhosted.org/packages/2e/cd/73a0fc913db50c3b736750ce67824f1108c2173e5d043a16ef9874b4f988/setproctitle-1.3.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7bc7088c15150745baf66db62a4ced4507d44419eb66207b609f91b64a682af", size = 31187 }, + { url = "https://files.pythonhosted.org/packages/63/0f/74f9112f7f506acc01f085811c6d135751b6fa42d30207f53b25579d043a/setproctitle-1.3.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a46ef3ecf61e4840fbc1145fdd38acf158d0da7543eda7b773ed2b30f75c2830", size = 32534 }, + { url = "https://files.pythonhosted.org/packages/3b/88/53eec2373745069d4c8a59d41ee2ef4a48949b77cccd0077c270261b238a/setproctitle-1.3.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcb09d5c0ffa043254ec9a734a73f3791fec8bf6333592f906bb2e91ed2af1a", size = 29657 }, + { url = "https://files.pythonhosted.org/packages/50/1c/a4d3d8c20bf3bbafd8c5038e7da09043a9d21450b6a73694ada11c01b58a/setproctitle-1.3.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06c16b7a91cdc5d700271899e4383384a61aae83a3d53d0e2e5a266376083342", size = 30695 }, + { url = "https://files.pythonhosted.org/packages/a2/2a/9f290f0d10ea87a266d63025078eabfa040ad29ea10d815e167a5104de00/setproctitle-1.3.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9f9732e59863eaeedd3feef94b2b216cb86d40dda4fad2d0f0aaec3b31592716", size = 30340 }, + { url = "https://files.pythonhosted.org/packages/38/c4/5bfe02d4cdd16338973d452c7c6042abdd2827d90f7ce4e21bc003f2edb1/setproctitle-1.3.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e152f4ab9ea1632b5fecdd87cee354f2b2eb6e2dfc3aceb0eb36a01c1e12f94c", size = 29352 }, + { url = "https://files.pythonhosted.org/packages/b3/41/0dd85cef0e5a5a332bdda7b55738e950c2edecea3ae45c658990553d50f8/setproctitle-1.3.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:020ea47a79b2bbd7bd7b94b85ca956ba7cb026e82f41b20d2e1dac4008cead25", size = 31819 }, + { url = "https://files.pythonhosted.org/packages/d7/23/fbfacfed8805983a83324099484e47b9028d0d3c07a0fe017123eee3f580/setproctitle-1.3.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8c52b12b10e4057fc302bd09cb3e3f28bb382c30c044eb3396e805179a8260e4", size = 29745 }, + { url = "https://files.pythonhosted.org/packages/68/37/e18c5a00bfd1c4c2c815536d5c63a470e4364b571bd5096d38d0fe277bf5/setproctitle-1.3.4-cp310-cp310-win32.whl", hash = "sha256:a65a147f545f3fac86f11acb2d0b316d3e78139a9372317b7eb50561b2817ba0", size = 11358 }, + { url = "https://files.pythonhosted.org/packages/52/fd/1fae8c4c13af22d8d17816c44421085509a08dfa77f573d31447d6cd540c/setproctitle-1.3.4-cp310-cp310-win_amd64.whl", hash = "sha256:66821fada6426998762a3650a37fba77e814a249a95b1183011070744aff47f6", size = 12072 }, + { url = "https://files.pythonhosted.org/packages/5d/1a/1fb7d622195bcb3ce7b04366a833e51cfa5ad632c5dafe32e0763cd3fdc9/setproctitle-1.3.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0f749f07002c2d6fecf37cedc43207a88e6c651926a470a5f229070cf791879", size = 16851 }, + { url = "https://files.pythonhosted.org/packages/46/54/e3aa4f46eddf795f10452ea878ff85c3496d36409636530f9a37e2de3cbe/setproctitle-1.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:90ea8d302a5d30b948451d146e94674a3c5b020cc0ced9a1c28f8ddb0f203a5d", size = 11620 }, + { url = "https://files.pythonhosted.org/packages/61/47/80988221679dfd93c464248abb71c2a96338f2ca3f8e3288d0ecb7422f4d/setproctitle-1.3.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f859c88193ed466bee4eb9d45fbc29d2253e6aa3ccd9119c9a1d8d95f409a60d", size = 31519 }, + { url = "https://files.pythonhosted.org/packages/2c/72/14984c127f708597e412f1a8cf7cac809b9bca50a267a6b01b221b094330/setproctitle-1.3.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3afa5a0ed08a477ded239c05db14c19af585975194a00adf594d48533b23701", size = 32860 }, + { url = "https://files.pythonhosted.org/packages/16/9d/34ea09295620fddae65cf7caeac81bbfc386a3ae6ce26a4dcadbb54c134d/setproctitle-1.3.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a78fce9018cc3e9a772b6537bbe3fe92380acf656c9f86db2f45e685af376e", size = 30029 }, + { url = "https://files.pythonhosted.org/packages/44/bf/a447a51054ceed23f69d4f7370289044b4508569f11da6db2eec087bc174/setproctitle-1.3.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d758e2eed2643afac5f2881542fbb5aa97640b54be20d0a5ed0691d02f0867d", size = 31017 }, + { url = "https://files.pythonhosted.org/packages/ec/46/adcffde6fb8d95458da0a568afdf0dabbbff6470299d94014676e1ab43c0/setproctitle-1.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ef133a1a2ee378d549048a12d56f4ef0e2b9113b0b25b6b77821e9af94d50634", size = 30762 }, + { url = "https://files.pythonhosted.org/packages/a3/cd/747a67ce1f6ef8fd1fa46b0b13ba0e007b80914bd549318830b8691ab9f6/setproctitle-1.3.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1d2a154b79d5fb42d1eff06e05e22f0e8091261d877dd47b37d31352b74ecc37", size = 29753 }, + { url = "https://files.pythonhosted.org/packages/3d/86/5939546e57238462a7839ae78399a635d1cfc5d125c7a12a28face111730/setproctitle-1.3.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:202eae632815571297833876a0f407d0d9c7ad9d843b38adbe687fe68c5192ee", size = 32161 }, + { url = "https://files.pythonhosted.org/packages/62/83/9194a4baed06e0e90a69e2e4a77a75e5a3ff008046870c79bc36a5c45e1c/setproctitle-1.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2b0080819859e80a7776ac47cf6accb4b7ad313baf55fabac89c000480dcd103", size = 30104 }, + { url = "https://files.pythonhosted.org/packages/ac/cd/08928fec23cbf4dae2a7b245b72d86e6458d64f4e7e6956cd80a9fda8c80/setproctitle-1.3.4-cp311-cp311-win32.whl", hash = "sha256:9c9d7d1267dee8c6627963d9376efa068858cfc8f573c083b1b6a2d297a8710f", size = 11349 }, + { url = "https://files.pythonhosted.org/packages/aa/19/240c4b99d57e045d3b2e2effa5924e810eabb18c56ef9c2336a7746dffe4/setproctitle-1.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:475986ddf6df65d619acd52188336a20f616589403f5a5ceb3fc70cdc137037a", size = 12071 }, + { url = "https://files.pythonhosted.org/packages/94/1f/02fb3c6038c819d86765316d2a911281fc56c7dd3a9355dceb3f26a5bf7b/setproctitle-1.3.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d06990dcfcd41bb3543c18dd25c8476fbfe1f236757f42fef560f6aa03ac8dfc", size = 16842 }, + { url = "https://files.pythonhosted.org/packages/b8/0c/d69e1f91c8f3d3aa74394e9e6ebb818f7d323e2d138ce1127e9462d09ebc/setproctitle-1.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:317218c9d8b17a010ab2d2f0851e8ef584077a38b1ba2b7c55c9e44e79a61e73", size = 11614 }, + { url = "https://files.pythonhosted.org/packages/86/ed/8031871d275302054b2f1b94b7cf5e850212cc412fe968f0979e64c1b838/setproctitle-1.3.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb5fefb53b9d9f334a5d9ec518a36b92a10b936011ac8a6b6dffd60135f16459", size = 31840 }, + { url = "https://files.pythonhosted.org/packages/45/b7/04f5d221cbdcff35d6cdf74e2a852e69dc8d8e746eb1b314be6b57b79c41/setproctitle-1.3.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0855006261635e8669646c7c304b494b6df0a194d2626683520103153ad63cc9", size = 33271 }, + { url = "https://files.pythonhosted.org/packages/25/b2/8dff0d2a72076e5535f117f33458d520538b5a0900b90a9f59a278f0d3f6/setproctitle-1.3.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a88e466fcaee659679c1d64dcb2eddbcb4bfadffeb68ba834d9c173a25b6184", size = 30509 }, + { url = "https://files.pythonhosted.org/packages/4b/cf/4f19cdc7fdff3eaeb3064ce6eeb27c63081dba3123fbf904ac6bf0de440c/setproctitle-1.3.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f963b6ed8ba33eda374a98d979e8a0eaf21f891b6e334701693a2c9510613c4c", size = 31543 }, + { url = "https://files.pythonhosted.org/packages/9b/a7/5f9c3c70dc5573f660f978fb3bb4847cd26ede95a5fc294d3f1cf6779800/setproctitle-1.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:122c2e05697fa91f5d23f00bbe98a9da1bd457b32529192e934095fadb0853f1", size = 31268 }, + { url = "https://files.pythonhosted.org/packages/26/ab/bbde90ea0ed6a062ef94fe1c609b68077f7eb586133a62fa62d0c8dd9f8c/setproctitle-1.3.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1bba0a866f5895d5b769d8c36b161271c7fd407e5065862ab80ff91c29fbe554", size = 30232 }, + { url = "https://files.pythonhosted.org/packages/36/0e/817be9934eda4cf63c96c694c3383cb0d2e5d019a2871af7dbd2202f7a58/setproctitle-1.3.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:97f1f861998e326e640708488c442519ad69046374b2c3fe9bcc9869b387f23c", size = 32739 }, + { url = "https://files.pythonhosted.org/packages/b0/76/9b4877850c9c5f41c4bacae441285dead7c192bebf4fcbf3b3eb0e8033cc/setproctitle-1.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:726aee40357d4bdb70115442cb85ccc8e8bc554fc0bbbaa3a57cbe81df42287d", size = 30778 }, + { url = "https://files.pythonhosted.org/packages/b2/fa/bbc7ab32f253b9700ac20d78ba0d5fbdc4ea5789d33e1adb236cdf20b23a/setproctitle-1.3.4-cp312-cp312-win32.whl", hash = "sha256:04d6ba8b816dbb0bfd62000b0c3e583160893e6e8c4233e1dca1a9ae4d95d924", size = 11355 }, + { url = "https://files.pythonhosted.org/packages/44/5c/6e6665b5fd800206a9e537ab0d2630d7b9b31b4697d931ed468837cc9cf5/setproctitle-1.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:9c76e43cb351ba8887371240b599925cdf3ecececc5dfb7125c71678e7722c55", size = 12069 }, + { url = "https://files.pythonhosted.org/packages/d4/01/51d07ab1dbec8885ebad419d254c06b9e28f4363c163b737a89995a52b75/setproctitle-1.3.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d6e3b177e634aa6bbbfbf66d097b6d1cdb80fc60e912c7d8bace2e45699c07dd", size = 16831 }, + { url = "https://files.pythonhosted.org/packages/30/03/deff7089b525c0d8ec047e06661d2be67c87685a99be6a6aed2890b81c8f/setproctitle-1.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b17655a5f245b416e127e02087ea6347a48821cc4626bc0fd57101bfcd88afc", size = 11607 }, + { url = "https://files.pythonhosted.org/packages/ea/be/cb2950b3f6ba460f530bda2c713828236c75d982d0aa0f62b33429a9b4d0/setproctitle-1.3.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa5057a86df920faab8ee83960b724bace01a3231eb8e3f2c93d78283504d598", size = 31881 }, + { url = "https://files.pythonhosted.org/packages/5c/b4/1f0dba7525a2fbefd08d4086e7e998d9c7581153807fb6b3083d06e0b8e2/setproctitle-1.3.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149fdfb8a26a555780c4ce53c92e6d3c990ef7b30f90a675eca02e83c6d5f76d", size = 33290 }, + { url = "https://files.pythonhosted.org/packages/2d/a8/07a160f9dcd1a7b1cad39ce6cbaf4425837502b0592a400c38cb21f0f247/setproctitle-1.3.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ded03546938a987f463c68ab98d683af87a83db7ac8093bbc179e77680be5ba2", size = 30489 }, + { url = "https://files.pythonhosted.org/packages/83/0c/3d972d9ea4165961a9764df5324d42bf2d059cb8a6ef516c67f068ed4d92/setproctitle-1.3.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab9f5b7f2bbc1754bc6292d9a7312071058e5a891b0391e6d13b226133f36aa", size = 31576 }, + { url = "https://files.pythonhosted.org/packages/7a/c0/c12bdc2c91009defdd1b207ff156ccd691f5b9a6a0aae1ed9126d4ff9a0c/setproctitle-1.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0b19813c852566fa031902124336fa1f080c51e262fc90266a8c3d65ca47b74c", size = 31273 }, + { url = "https://files.pythonhosted.org/packages/4f/83/8d704bee57990b27537adf7c97540f32226ffa3922fb26bdd459da8a4470/setproctitle-1.3.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:db78b645dc63c0ccffca367a498f3b13492fb106a2243a1e998303ba79c996e2", size = 30236 }, + { url = "https://files.pythonhosted.org/packages/d8/42/94e31d1f515f831e1ae43f2405794257eb940a7972b2fbb6283790db2958/setproctitle-1.3.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b669aaac70bd9f03c070270b953f78d9ee56c4af6f0ff9f9cd3e6d1878c10b40", size = 32766 }, + { url = "https://files.pythonhosted.org/packages/83/53/01746ed8fb75239a001ee89d5eb8ad5a3022df510572d1cf60dd04567e13/setproctitle-1.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6dc3d656702791565994e64035a208be56b065675a5bc87b644c657d6d9e2232", size = 30812 }, + { url = "https://files.pythonhosted.org/packages/5f/ea/3ce61e70a6b898e95c0a1e393964c829103dc4ad4b0732cd70c8fc13e54c/setproctitle-1.3.4-cp313-cp313-win32.whl", hash = "sha256:091f682809a4d12291cf0205517619d2e7014986b7b00ebecfde3d76f8ae5a8f", size = 11349 }, + { url = "https://files.pythonhosted.org/packages/e7/1a/8149da1c19db6bd57164d62b1d91c188e7d77e695947cf1ac327c8aea513/setproctitle-1.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:adcd6ba863a315702184d92d3d3bbff290514f24a14695d310f02ae5e28bd1f7", size = 12062 }, + { url = "https://files.pythonhosted.org/packages/2f/d0/775418662081d44b91da236ed4503e10e7008c9c5fd30193e13db388fbef/setproctitle-1.3.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:939d364a187b2adfbf6ae488664277e717d56c7951a4ddeb4f23b281bc50bfe5", size = 11153 }, + { url = "https://files.pythonhosted.org/packages/fd/1f/b3b82633336cd9908bf74cbc06dd533025b3d3c202437c4e3d0bc871ca13/setproctitle-1.3.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb8a6a19be0cbf6da6fcbf3698b76c8af03fe83e4bd77c96c3922be3b88bf7da", size = 13310 }, + { url = "https://files.pythonhosted.org/packages/f5/89/887c6872ceed5ca344d25c8cc8a3f9b99bbcb25613c4b680476b29aabe23/setproctitle-1.3.4-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:779006f9e1aade9522a40e8d9635115ab15dd82b7af8e655967162e9c01e2573", size = 12911 }, + { url = "https://files.pythonhosted.org/packages/b0/8d/9e4a4651b1c5845a9aec0d2c08c65768ba5ca2ec76598124b45d384a5f46/setproctitle-1.3.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5519f2a7b8c535b0f1f77b30441476571373add72008230c81211ee17b423b57", size = 12105 }, +] + +[[package]] +name = "setuptools" +version = "70.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/60/5db2249526c9b453c5bb8b9f6965fcab0ddb7f40ad734420b3b421f7da44/setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0", size = 2265182 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/88/70c5767a0e43eb4451c2200f07d042a4bcd7639276003a9c54a68cfcc1f8/setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4", size = 863432 }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, +] + +[[package]] +name = "shtab" +version = "1.6.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/0e/ce211daf7b28fe685b1c9a21d943b3a1c4f300a07e6c59d8765c5f22eb06/shtab-1.6.5.tar.gz", hash = "sha256:cf4ab120183e84cce041abeb6f620f9560739741dfc31dd466315550c08be9ec", size = 45808 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/ad/7227da64498eaa7abecee4311008f70869e156014b3270cec36e2e70cd31/shtab-1.6.5-py3-none-any.whl", hash = "sha256:3c7be25ab65a324ed41e9c2964f2146236a5da6e6a247355cfea56f65050f220", size = 13932 }, +] + +[[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 = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303 }, +] + +[[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 = "soupsieve" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.36" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/65/9cbc9c4c3287bed2499e05033e207473504dc4df999ce49385fb1f8b058a/sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5", size = 9574485 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/72/14ab694b8b3f0e35ef5beb74a8fea2811aa791ba1611c44dc90cdf46af17/SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72", size = 2092604 }, + { url = "https://files.pythonhosted.org/packages/1e/59/333fcbca58b79f5b8b61853d6137530198823392151fa8fd9425f367519e/SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908", size = 2083796 }, + { url = "https://files.pythonhosted.org/packages/6c/a0/ec3c188d2b0c1bc742262e76408d44104598d7247c23f5b06bb97ee21bfa/SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08", size = 3066165 }, + { url = "https://files.pythonhosted.org/packages/07/15/68ef91de5b8b7f80fb2d2b3b31ed42180c6227fe0a701aed9d01d34f98ec/SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07", size = 3074428 }, + { url = "https://files.pythonhosted.org/packages/e2/4c/9dfea5e63b87325eef6d9cdaac913459aa6a157a05a05ea6ff20004aee8e/SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5", size = 3030477 }, + { url = "https://files.pythonhosted.org/packages/16/a5/fcfde8e74ea5f683b24add22463bfc21e431d4a5531c8a5b55bc6fbea164/SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44", size = 3055942 }, + { url = "https://files.pythonhosted.org/packages/3c/ee/c22c415a771d791ae99146d72ffdb20e43625acd24835ea7fc157436d59f/SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa", size = 2064960 }, + { url = "https://files.pythonhosted.org/packages/aa/af/ad9c25cadc79bd851bdb9d82b68af9bdb91ff05f56d0da2f8a654825974f/SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5", size = 2089078 }, + { url = "https://files.pythonhosted.org/packages/00/4e/5a67963fd7cbc1beb8bd2152e907419f4c940ef04600b10151a751fe9e06/SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c", size = 2093782 }, + { url = "https://files.pythonhosted.org/packages/b3/24/30e33b6389ebb5a17df2a4243b091bc709fb3dfc9a48c8d72f8e037c943d/SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71", size = 2084180 }, + { url = "https://files.pythonhosted.org/packages/10/1e/70e9ed2143a27065246be40f78637ad5160ea0f5fd32f8cab819a31ff54d/SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff", size = 3202469 }, + { url = "https://files.pythonhosted.org/packages/b4/5f/95e0ed74093ac3c0db6acfa944d4d8ac6284ef5e1136b878a327ea1f975a/SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d", size = 3202464 }, + { url = "https://files.pythonhosted.org/packages/91/95/2cf9b85a6bc2ee660e40594dffe04e777e7b8617fd0c6d77a0f782ea96c9/SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb", size = 3139508 }, + { url = "https://files.pythonhosted.org/packages/92/ea/f0c01bc646456e4345c0fb5a3ddef457326285c2dc60435b0eb96b61bf31/SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8", size = 3159837 }, + { url = "https://files.pythonhosted.org/packages/a6/93/c8edbf153ee38fe529773240877bf1332ed95328aceef6254288f446994e/SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f", size = 2064529 }, + { url = "https://files.pythonhosted.org/packages/b1/03/d12b7c1d36fd80150c1d52e121614cf9377dac99e5497af8d8f5b2a8db64/SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959", size = 2089874 }, + { url = "https://files.pythonhosted.org/packages/b8/bf/005dc47f0e57556e14512d5542f3f183b94fde46e15ff1588ec58ca89555/SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4", size = 2092378 }, + { url = "https://files.pythonhosted.org/packages/94/65/f109d5720779a08e6e324ec89a744f5f92c48bd8005edc814bf72fbb24e5/SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855", size = 2082778 }, + { url = "https://files.pythonhosted.org/packages/60/f6/d9aa8c49c44f9b8c9b9dada1f12fa78df3d4c42aa2de437164b83ee1123c/SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53", size = 3232191 }, + { url = "https://files.pythonhosted.org/packages/8a/ab/81d4514527c068670cb1d7ab62a81a185df53a7c379bd2a5636e83d09ede/SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a", size = 3243044 }, + { url = "https://files.pythonhosted.org/packages/35/b4/f87c014ecf5167dc669199cafdb20a7358ff4b1d49ce3622cc48571f811c/SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686", size = 3178511 }, + { url = "https://files.pythonhosted.org/packages/ea/09/badfc9293bc3ccba6ede05e5f2b44a760aa47d84da1fc5a326e963e3d4d9/SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588", size = 3205147 }, + { url = "https://files.pythonhosted.org/packages/c8/60/70e681de02a13c4b27979b7b78da3058c49bacc9858c89ba672e030f03f2/SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e", size = 2062709 }, + { url = "https://files.pythonhosted.org/packages/b7/ed/f6cd9395e41bfe47dd253d74d2dfc3cab34980d4e20c8878cb1117306085/SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5", size = 2088433 }, + { url = "https://files.pythonhosted.org/packages/78/5c/236398ae3678b3237726819b484f15f5c038a9549da01703a771f05a00d6/SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef", size = 2087651 }, + { url = "https://files.pythonhosted.org/packages/a8/14/55c47420c0d23fb67a35af8be4719199b81c59f3084c28d131a7767b0b0b/SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8", size = 2078132 }, + { url = "https://files.pythonhosted.org/packages/3d/97/1e843b36abff8c4a7aa2e37f9bea364f90d021754c2de94d792c2d91405b/SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b", size = 3164559 }, + { url = "https://files.pythonhosted.org/packages/7b/c5/07f18a897b997f6d6b234fab2bf31dccf66d5d16a79fe329aefc95cd7461/SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2", size = 3177897 }, + { url = "https://files.pythonhosted.org/packages/b3/cd/e16f3cbefd82b5c40b33732da634ec67a5f33b587744c7ab41699789d492/SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf", size = 3111289 }, + { url = "https://files.pythonhosted.org/packages/15/85/5b8a3b0bc29c9928aa62b5c91fcc8335f57c1de0a6343873b5f372e3672b/SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c", size = 3139491 }, + { url = "https://files.pythonhosted.org/packages/a1/95/81babb6089938680dfe2cd3f88cd3fd39cccd1543b7cb603b21ad881bff1/SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436", size = 2060439 }, + { url = "https://files.pythonhosted.org/packages/c1/ce/5f7428df55660d6879d0522adc73a3364970b5ef33ec17fa125c5dbcac1d/SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88", size = 2084574 }, + { url = "https://files.pythonhosted.org/packages/b8/49/21633706dd6feb14cd3f7935fc00b60870ea057686035e1a99ae6d9d9d53/SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e", size = 1883787 }, +] + +[[package]] +name = "starlette" +version = "0.37.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/b5/6bceb93ff20bd7ca36e6f7c540581abb18f53130fabb30ba526e26fd819b/starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823", size = 2843736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/18/31fa32ed6c68ba66220204ef0be798c349d0a20c1901f9d4a794e08c76d8/starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee", size = 71908 }, +] + +[[package]] +name = "substrate-interface" +version = "1.7.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "base58" }, + { name = "certifi" }, + { name = "ecdsa" }, + { name = "eth-keys" }, + { name = "eth-utils" }, + { name = "idna" }, + { name = "py-bip39-bindings" }, + { name = "py-ed25519-zebra-bindings" }, + { name = "py-sr25519-bindings" }, + { name = "pycryptodome" }, + { name = "pynacl" }, + { name = "requests" }, + { name = "scalecodec" }, + { name = "websocket-client" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/44/825433c906bdb69ab66fd3967c11fcfbcd953241e9d6257fd6a21c4cdc76/substrate-interface-1.7.11.tar.gz", hash = "sha256:4caa5eacb9996edbe76ad12249521b3542bbd8d9d69b96734087201db1fef8f6", size = 79221 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/e1/37344b7acd260cbfed13563dcbab391c7c4b0c9eca5ec59aba138c5dca9e/substrate_interface-1.7.11-py3-none-any.whl", hash = "sha256:ce19bc97481769238ed23c752db985a3058637918693f2db6aeed2fab3756075", size = 60273 }, +] + +[[package]] +name = "sympy" +version = "1.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/99/5a5b6f19ff9f083671ddf7b9632028436167cd3d33e11015754e41b249a4/sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f", size = 7533040 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/fe/81695a1aa331a842b582453b605175f419fe8540355886031328089d840a/sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8", size = 6189177 }, +] + +[[package]] +name = "tenacity" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/94/91fccdb4b8110642462e653d5dcb27e7b674742ad68efd146367da7bdb10/tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b", size = 47421 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/cb/b86984bed139586d01532a587464b5805f12e397594f19f931c4c2fbfa61/tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539", size = 28169 }, +] + +[[package]] +name = "termcolor" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/72/88311445fd44c455c7d553e61f95412cf89054308a1aa2434ab835075fc5/termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f", size = 13057 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/be/df630c387a0a054815d60be6a97eb4e8f17385d5d6fe660e1c02750062b4/termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8", size = 7755 }, +] + +[[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 = "tiktoken" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/02/576ff3a6639e755c4f70997b2d315f56d6d71e0d046f4fb64cb81a3fb099/tiktoken-0.8.0.tar.gz", hash = "sha256:9ccbb2740f24542534369c5635cfd9b2b3c2490754a78ac8831d99f89f94eeb2", size = 35107 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/ba/a35fad753bbca8ba0cc1b0f3402a70256a110ced7ac332cf84ba89fc87ab/tiktoken-0.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b07e33283463089c81ef1467180e3e00ab00d46c2c4bbcef0acab5f771d6695e", size = 1039905 }, + { url = "https://files.pythonhosted.org/packages/91/05/13dab8fd7460391c387b3e69e14bf1e51ff71fe0a202cd2933cc3ea93fb6/tiktoken-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9269348cb650726f44dd3bbb3f9110ac19a8dcc8f54949ad3ef652ca22a38e21", size = 982417 }, + { url = "https://files.pythonhosted.org/packages/e9/98/18ec4a8351a6cf4537e40cd6e19a422c10cce1ef00a2fcb716e0a96af58b/tiktoken-0.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e13f37bc4ef2d012731e93e0fef21dc3b7aea5bb9009618de9a4026844e560", size = 1144915 }, + { url = "https://files.pythonhosted.org/packages/2e/28/cf3633018cbcc6deb7805b700ccd6085c9a5a7f72b38974ee0bffd56d311/tiktoken-0.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f13d13c981511331eac0d01a59b5df7c0d4060a8be1e378672822213da51e0a2", size = 1177221 }, + { url = "https://files.pythonhosted.org/packages/57/81/8a5be305cbd39d4e83a794f9e80c7f2c84b524587b7feb27c797b2046d51/tiktoken-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6b2ddbc79a22621ce8b1166afa9f9a888a664a579350dc7c09346a3b5de837d9", size = 1237398 }, + { url = "https://files.pythonhosted.org/packages/dc/da/8d1cc3089a83f5cf11c2e489332752981435280285231924557350523a59/tiktoken-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d8c2d0e5ba6453a290b86cd65fc51fedf247e1ba170191715b049dac1f628005", size = 884215 }, + { url = "https://files.pythonhosted.org/packages/f6/1e/ca48e7bfeeccaf76f3a501bd84db1fa28b3c22c9d1a1f41af9fb7579c5f6/tiktoken-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d622d8011e6d6f239297efa42a2657043aaed06c4f68833550cac9e9bc723ef1", size = 1039700 }, + { url = "https://files.pythonhosted.org/packages/8c/f8/f0101d98d661b34534769c3818f5af631e59c36ac6d07268fbfc89e539ce/tiktoken-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2efaf6199717b4485031b4d6edb94075e4d79177a172f38dd934d911b588d54a", size = 982413 }, + { url = "https://files.pythonhosted.org/packages/ac/3c/2b95391d9bd520a73830469f80a96e3790e6c0a5ac2444f80f20b4b31051/tiktoken-0.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5637e425ce1fc49cf716d88df3092048359a4b3bbb7da762840426e937ada06d", size = 1144242 }, + { url = "https://files.pythonhosted.org/packages/01/c4/c4a4360de845217b6aa9709c15773484b50479f36bb50419c443204e5de9/tiktoken-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fb0e352d1dbe15aba082883058b3cce9e48d33101bdaac1eccf66424feb5b47", size = 1176588 }, + { url = "https://files.pythonhosted.org/packages/f8/a3/ef984e976822cd6c2227c854f74d2e60cf4cd6fbfca46251199914746f78/tiktoken-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56edfefe896c8f10aba372ab5706b9e3558e78db39dd497c940b47bf228bc419", size = 1237261 }, + { url = "https://files.pythonhosted.org/packages/1e/86/eea2309dc258fb86c7d9b10db536434fc16420feaa3b6113df18b23db7c2/tiktoken-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:326624128590def898775b722ccc327e90b073714227175ea8febbc920ac0a99", size = 884537 }, + { url = "https://files.pythonhosted.org/packages/c1/22/34b2e136a6f4af186b6640cbfd6f93400783c9ef6cd550d9eab80628d9de/tiktoken-0.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:881839cfeae051b3628d9823b2e56b5cc93a9e2efb435f4cf15f17dc45f21586", size = 1039357 }, + { url = "https://files.pythonhosted.org/packages/04/d2/c793cf49c20f5855fd6ce05d080c0537d7418f22c58e71f392d5e8c8dbf7/tiktoken-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fe9399bdc3f29d428f16a2f86c3c8ec20be3eac5f53693ce4980371c3245729b", size = 982616 }, + { url = "https://files.pythonhosted.org/packages/b3/a1/79846e5ef911cd5d75c844de3fa496a10c91b4b5f550aad695c5df153d72/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a58deb7075d5b69237a3ff4bb51a726670419db6ea62bdcd8bd80c78497d7ab", size = 1144011 }, + { url = "https://files.pythonhosted.org/packages/26/32/e0e3a859136e95c85a572e4806dc58bf1ddf651108ae8b97d5f3ebe1a244/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2908c0d043a7d03ebd80347266b0e58440bdef5564f84f4d29fb235b5df3b04", size = 1175432 }, + { url = "https://files.pythonhosted.org/packages/c7/89/926b66e9025b97e9fbabeaa59048a736fe3c3e4530a204109571104f921c/tiktoken-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:294440d21a2a51e12d4238e68a5972095534fe9878be57d905c476017bff99fc", size = 1236576 }, + { url = "https://files.pythonhosted.org/packages/45/e2/39d4aa02a52bba73b2cd21ba4533c84425ff8786cc63c511d68c8897376e/tiktoken-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:d8f3192733ac4d77977432947d563d7e1b310b96497acd3c196c9bddb36ed9db", size = 883824 }, + { url = "https://files.pythonhosted.org/packages/e3/38/802e79ba0ee5fcbf240cd624143f57744e5d411d2e9d9ad2db70d8395986/tiktoken-0.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:02be1666096aff7da6cbd7cdaa8e7917bfed3467cd64b38b1f112e96d3b06a24", size = 1039648 }, + { url = "https://files.pythonhosted.org/packages/b1/da/24cdbfc302c98663fbea66f5866f7fa1048405c7564ab88483aea97c3b1a/tiktoken-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94ff53c5c74b535b2cbf431d907fc13c678bbd009ee633a2aca269a04389f9a", size = 982763 }, + { url = "https://files.pythonhosted.org/packages/e4/f0/0ecf79a279dfa41fc97d00adccf976ecc2556d3c08ef3e25e45eb31f665b/tiktoken-0.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b231f5e8982c245ee3065cd84a4712d64692348bc609d84467c57b4b72dcbc5", size = 1144417 }, + { url = "https://files.pythonhosted.org/packages/ab/d3/155d2d4514f3471a25dc1d6d20549ef254e2aa9bb5b1060809b1d3b03d3a/tiktoken-0.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4177faa809bd55f699e88c96d9bb4635d22e3f59d635ba6fd9ffedf7150b9953", size = 1175108 }, + { url = "https://files.pythonhosted.org/packages/19/eb/5989e16821ee8300ef8ee13c16effc20dfc26c777d05fbb6825e3c037b81/tiktoken-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5376b6f8dc4753cd81ead935c5f518fa0fbe7e133d9e25f648d8c4dabdd4bad7", size = 1236520 }, + { url = "https://files.pythonhosted.org/packages/40/59/14b20465f1d1cb89cfbc96ec27e5617b2d41c79da12b5e04e96d689be2a7/tiktoken-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:18228d624807d66c87acd8f25fc135665617cab220671eb65b50f5d70fa51f69", size = 883849 }, +] + +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 }, +] + +[[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 = "toml" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/19/5cbd78eac8b1783671c40e34bb0fa83133a06d340a38b55c645076d40094/toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", size = 16719 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/12/ced7105d2de62fa7c8fb5fce92cc4ce66b57c95fb875e9318dba7f8c5db0/toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", size = 25796 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "toolz" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/0b/d80dfa675bf592f636d1ea0b835eab4ec8df6e9415d8cfd766df54456123/toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02", size = 66790 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/98/eb27cc78ad3af8e302c9d8ff4977f5026676e130d28dd7578132a457170c/toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236", size = 56383 }, +] + +[[package]] +name = "torch" +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "sympy" }, + { name = "triton", marker = "python_full_version < '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/ef/834af4a885b31a0b32fff2d80e1e40f771e1566ea8ded55347502440786a/torch-2.5.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:71328e1bbe39d213b8721678f9dcac30dfc452a46d586f1d514a6aa0a99d4744", size = 906446312 }, + { url = "https://files.pythonhosted.org/packages/69/f0/46e74e0d145f43fa506cb336eaefb2d240547e4ce1f496e442711093ab25/torch-2.5.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:34bfa1a852e5714cbfa17f27c49d8ce35e1b7af5608c4bc6e81392c352dbc601", size = 91919522 }, + { url = "https://files.pythonhosted.org/packages/a5/13/1eb674c8efbd04d71e4a157ceba991904f633e009a584dd65dccbafbb648/torch-2.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:32a037bd98a241df6c93e4c789b683335da76a2ac142c0973675b715102dc5fa", size = 203088048 }, + { url = "https://files.pythonhosted.org/packages/a9/9d/e0860474ee0ff8f6ef2c50ec8f71a250f38d78a9b9df9fd241ad3397a65b/torch-2.5.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:23d062bf70776a3d04dbe74db950db2a5245e1ba4f27208a87f0d743b0d06e86", size = 63877046 }, + { url = "https://files.pythonhosted.org/packages/d1/35/e8b2daf02ce933e4518e6f5682c72fd0ed66c15910ea1fb4168f442b71c4/torch-2.5.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:de5b7d6740c4b636ef4db92be922f0edc425b65ed78c5076c43c42d362a45457", size = 906474467 }, + { url = "https://files.pythonhosted.org/packages/40/04/bd91593a4ca178ece93ca55f27e2783aa524aaccbfda66831d59a054c31e/torch-2.5.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:340ce0432cad0d37f5a31be666896e16788f1adf8ad7be481196b503dad675b9", size = 91919450 }, + { url = "https://files.pythonhosted.org/packages/0d/4a/e51420d46cfc90562e85af2fee912237c662ab31140ab179e49bd69401d6/torch-2.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:603c52d2fe06433c18b747d25f5c333f9c1d58615620578c326d66f258686f9a", size = 203098237 }, + { url = "https://files.pythonhosted.org/packages/d0/db/5d9cbfbc7968d79c5c09a0bc0bc3735da079f2fd07cc10498a62b320a480/torch-2.5.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:31f8c39660962f9ae4eeec995e3049b5492eb7360dd4f07377658ef4d728fa4c", size = 63884466 }, + { url = "https://files.pythonhosted.org/packages/8b/5c/36c114d120bfe10f9323ed35061bc5878cc74f3f594003854b0ea298942f/torch-2.5.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:ed231a4b3a5952177fafb661213d690a72caaad97d5824dd4fc17ab9e15cec03", size = 906389343 }, + { url = "https://files.pythonhosted.org/packages/6d/69/d8ada8b6e0a4257556d5b4ddeb4345ea8eeaaef3c98b60d1cca197c7ad8e/torch-2.5.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:3f4b7f10a247e0dcd7ea97dc2d3bfbfc90302ed36d7f3952b0008d0df264e697", size = 91811673 }, + { url = "https://files.pythonhosted.org/packages/5f/ba/607d013b55b9fd805db2a5c2662ec7551f1910b4eef39653eeaba182c5b2/torch-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:73e58e78f7d220917c5dbfad1a40e09df9929d3b95d25e57d9f8558f84c9a11c", size = 203046841 }, + { url = "https://files.pythonhosted.org/packages/57/6c/bf52ff061da33deb9f94f4121fde7ff3058812cb7d2036c97bc167793bd1/torch-2.5.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:8c712df61101964eb11910a846514011f0b6f5920c55dbf567bff8a34163d5b1", size = 63858109 }, + { url = "https://files.pythonhosted.org/packages/69/72/20cb30f3b39a9face296491a86adb6ff8f1a47a897e4d14667e6cf89d5c3/torch-2.5.1-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:9b61edf3b4f6e3b0e0adda8b3960266b9009d02b37555971f4d1c8f7a05afed7", size = 906393265 }, +] + +[[package]] +name = "torchvision" +version = "0.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/59/aea68d755da1451e1a0d894528a7edc9b58eb30d33e274bf21bef28dad1a/torchvision-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4878fefb96ef293d06c27210918adc83c399d9faaf34cda5a63e129f772328f1", size = 1787552 }, + { url = "https://files.pythonhosted.org/packages/a2/f6/7ff89a9f8703f623f5664afd66c8600e3f09fe188e1e0b7e6f9a8617f865/torchvision-0.20.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:8ffbdf8bf5b30eade22d459f5a313329eeadb20dc75efa142987b53c007098c3", size = 7238975 }, + { url = "https://files.pythonhosted.org/packages/f7/ce/4c31e9b96cc4f9fec746b258d2aa35f8d1247f4f58d63f9c505ea5eb254d/torchvision-0.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:75f8a4d51a593c4bab6c9bf7d75bdd88691b00a53b07656678bc55a3a753dd73", size = 14265343 }, + { url = "https://files.pythonhosted.org/packages/17/11/b5ce67715bbbec8798fb48c4a20ac28828aec1710ac01091a3eddcb8e075/torchvision-0.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:22c2fa44e20eb404b85e42b22b453863a14b0927d25e550fd4f84eea97fa5b39", size = 1562413 }, + { url = "https://files.pythonhosted.org/packages/28/57/4d7ad90be612f5ac6c4bdafcb0ff13e818e14a340a88c8ca00d9ed8c2dad/torchvision-0.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:344b339e15e6bbb59ee0700772616d0afefd209920c762b1604368d8c3458322", size = 1787548 }, + { url = "https://files.pythonhosted.org/packages/de/e9/e190ecec448d5a2abad8348cf085fcb39962a491e3f40dcb023721e04feb/torchvision-0.20.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:86f6523dee420000fe14c3527f6c8e0175139fda7d995b187f54a0b0ebec7eb6", size = 7241222 }, + { url = "https://files.pythonhosted.org/packages/b1/a3/cbb8177e5e379f0c040b00c6f80f14d323a97e30495d7115d169b101b2f7/torchvision-0.20.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:a40d766345927639da322c693934e5f91b1ba2218846c7104b868dea2314ce8e", size = 14267510 }, + { url = "https://files.pythonhosted.org/packages/69/55/ce836703ff77bb21582c3098d5311f8ddde7eadc7eab04be9561961f4725/torchvision-0.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:5b501d5c04b034d2ecda96a31ed050e383cf8201352e4c9276ca249cbecfded0", size = 1562402 }, + { url = "https://files.pythonhosted.org/packages/c5/eb/4ba19616378f2bc085999432fded2b7dfdbdccc6dd0fc293203452508100/torchvision-0.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1a31256ff945d64f006bb306813a7c95a531fe16bfb2535c837dd4c104533d7a", size = 1787553 }, + { url = "https://files.pythonhosted.org/packages/d4/75/00a852275ade58d3dc474530f7a7b6bc999a817148f0eb59d4fde12eb955/torchvision-0.20.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:17cd78adddf81dac57d7dccc9277a4d686425b1c55715f308769770cb26cad5c", size = 7240323 }, + { url = "https://files.pythonhosted.org/packages/af/f0/ca1445406eb12cbeb7a41fc833a1941ede78e7c55621198b83ecd7bcfd0f/torchvision-0.20.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:9f853ba4497ac4691815ad41b523ee23cf5ba4f87b1ce869d704052e233ca8b7", size = 14266936 }, + { url = "https://files.pythonhosted.org/packages/c3/18/00993d420b1d6e88582e51d4bc82c824c99a2e9c045d50eaf9b34fff729a/torchvision-0.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:4a330422c36dbfc946d3a6c1caec3489db07ecdf3675d83369adb2e5a0ca17c4", size = 1562392 }, +] + +[[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 = "transformers" +version = "4.47.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/1a/936aeb4f88112f670b604f5748034568dbc2b9bbb457a8d4518b1a15510a/transformers-4.47.1.tar.gz", hash = "sha256:6c29c05a5f595e278481166539202bf8641281536df1c42357ee58a45d0a564a", size = 8707421 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/3a/8bdab26e09c5a242182b7ba9152e216d5ab4ae2d78c4298eb4872549cd35/transformers-4.47.1-py3-none-any.whl", hash = "sha256:d2f5d19bb6283cd66c893ec7e6d931d6370bbf1cc93633326ff1f41a40046c9c", size = 10133598 }, +] + +[[package]] +name = "triton" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/29/69aa56dc0b2eb2602b553881e34243475ea2afd9699be042316842788ff5/triton-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b0dd10a925263abbe9fa37dcde67a5e9b2383fc269fdf59f5657cac38c5d1d8", size = 209460013 }, + { url = "https://files.pythonhosted.org/packages/86/17/d9a5cf4fcf46291856d1e90762e36cbabd2a56c7265da0d1d9508c8e3943/triton-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f34f6e7885d1bf0eaaf7ba875a5f0ce6f3c13ba98f9503651c1e6dc6757ed5c", size = 209506424 }, + { url = "https://files.pythonhosted.org/packages/78/eb/65f5ba83c2a123f6498a3097746607e5b2f16add29e36765305e4ac7fdd8/triton-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8182f42fd8080a7d39d666814fa36c5e30cc00ea7eeeb1a2983dbb4c99a0fdc", size = 209551444 }, +] + +[[package]] +name = "typer" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/dca7b219718afd37a0068f4f2530a727c2b74a8b6e8e0c0080a4c0de4fcd/typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a", size = 99789 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/cc/0a838ba5ca64dc832aa43f727bd586309846b0ffb2ce52422543e6075e8a/typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847", size = 44908 }, +] + +[[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 = "tzdata" +version = "2024.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, +] + +[[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 = "uvicorn" +version = "0.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, +] + +[[package]] +name = "wandb" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "docker-pycreds" }, + { name = "gitpython" }, + { name = "platformdirs" }, + { name = "protobuf" }, + { name = "psutil" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sentry-sdk" }, + { name = "setproctitle" }, + { name = "setuptools" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/cc/3322f2c4d85b84a18cb93e97ecad216fe6a59ec39118a82bdfed7872f660/wandb-0.19.0.tar.gz", hash = "sha256:cfacf2cc323561909e7572e772a4a5f849f28248a4529247b199466171cd84f8", size = 11821728 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/a0/5d8f6268a728fbf008797967587dfad29cd4837a1e3ea33ad2ded074a0a0/wandb-0.19.0-py3-none-any.whl", hash = "sha256:d4dab974f8fd5304ae5af961777d89ba4622d776b18882dc091098a7eace6ca3", size = 6247909 }, + { url = "https://files.pythonhosted.org/packages/3b/23/0e60bee6cf1e738e34204820d19dace42063620dba6786e4293bfabd2166/wandb-0.19.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:ec14280a833263ae828d181b853be38858f933f55ecb77a9040372bf2b09b5e3", size = 20015933 }, + { url = "https://files.pythonhosted.org/packages/59/6e/3bf8590ccbce0eb7c705c0f2b1e3700fad891d48524a93e86f52f7000f67/wandb-0.19.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3d2275ef9d97ce8203b56621d710276b2c023ab3f1a9837dccaf5d75b819ab38", size = 19241348 }, + { url = "https://files.pythonhosted.org/packages/15/93/6b6f4b163a20c70ce4f2792b6ac15c2322dc0cbd1b7377b5aca8c43526df/wandb-0.19.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:65c4fc6fd537d554bcab31a74f28bba82782f83f735b6972702dbab31caaecf1", size = 20040571 }, + { url = "https://files.pythonhosted.org/packages/50/db/efdb733bb98038434ce097ae8178039e81aa4bbc182b3ebc3bb244b29a3c/wandb-0.19.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f0fec8825702ec4ac8453652f2af69b211ee73895272bbdb625bb2721da1f4", size = 18821680 }, + { url = "https://files.pythonhosted.org/packages/45/e0/7ba3b78a74413b7467300cb7a5d486b9871ee464a7cade98ea869d3ca3df/wandb-0.19.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:146b972a0d11442f6b5592e5b53ae37b5add5131206136e5bf0a8c3e3fb8fbd0", size = 20095363 }, + { url = "https://files.pythonhosted.org/packages/0d/9a/828968f7c6256a2440123f9602a403fe2f7730a3286e0e344a84cb9a0821/wandb-0.19.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:370d96c23217cd5a16c1f56e02cda9b0f1e2805f4dd6fa942645a726a0e9b549", size = 20169998 }, + { url = "https://files.pythonhosted.org/packages/43/5b/3f436aa647681bf1b6a3fd694c974a40d33be5c40b492277020da360a5cf/wandb-0.19.0-py3-none-win32.whl", hash = "sha256:ab50cc3233727765fbb7b9266cf824f53637c8de2be47ba107542e3ad21ba307", size = 19563293 }, + { url = "https://files.pythonhosted.org/packages/cd/c8/c778a55aeab47ea918c82023fad837cdf5e686dc2249b67a723d452da390/wandb-0.19.0-py3-none-win_amd64.whl", hash = "sha256:0fe8af679306b959b22260b4a67f22186829433809f76e48e70d25c04c2dcf94", size = 19563296 }, +] + +[[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 }, +] [[package]] name = "web-genie-ai" version = "0.1.0" source = { virtual = "." } +dependencies = [ + { name = "ansible-vault" }, + { name = "beautifulsoup4" }, + { name = "bert-score" }, + { name = "bittensor" }, + { name = "clip" }, + { name = "colormath" }, + { name = "datasets" }, + { name = "ddt" }, + { name = "duckduckgo-search" }, + { name = "einops" }, + { name = "langchain", version = "0.0.27", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "langchain", version = "0.3.14", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "langchain-openai" }, + { name = "lxml" }, + { name = "matplotlib-inline" }, + { name = "nltk" }, + { name = "opencv-python" }, + { name = "peft" }, + { name = "pip-chill" }, + { name = "playwright" }, + { name = "python-dotenv" }, + { name = "scikit-learn" }, + { name = "sentence-transformers" }, + { name = "shtab" }, + { name = "sqlalchemy" }, + { name = "tinycss2" }, + { name = "wandb" }, +] + +[package.metadata] +requires-dist = [ + { name = "ansible-vault", specifier = "==2.1.0" }, + { name = "beautifulsoup4", specifier = "==4.12.3" }, + { name = "bert-score", specifier = "==0.3.13" }, + { name = "bittensor", specifier = "==8.5.1" }, + { name = "clip", git = "https://github.com/openai/CLIP.git" }, + { name = "colormath", specifier = "==3.0.0" }, + { name = "datasets" }, + { name = "datasets", specifier = "==3.2.0" }, + { name = "ddt", specifier = "==1.6.0" }, + { name = "duckduckgo-search" }, + { name = "einops" }, + { name = "langchain" }, + { name = "langchain-openai" }, + { name = "lxml", specifier = "==5.3.0" }, + { name = "matplotlib-inline", specifier = "==0.1.7" }, + { name = "nltk" }, + { name = "opencv-python", specifier = "==4.10.0.84" }, + { name = "peft" }, + { name = "pip-chill", specifier = "==1.0.3" }, + { name = "playwright", specifier = "==1.49.1" }, + { name = "python-dotenv", specifier = "==1.0.1" }, + { name = "scikit-learn", specifier = "==1.6.0" }, + { name = "sentence-transformers" }, + { name = "shtab", specifier = "==1.6.5" }, + { name = "sqlalchemy" }, + { name = "tinycss2", specifier = "==1.4.0" }, + { name = "wandb", specifier = "==0.19.0" }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, +] + +[[package]] +name = "websocket-client" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826 }, +] + +[[package]] +name = "websockets" +version = "14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/1b/380b883ce05bb5f45a905b61790319a28958a9ab1e4b6b95ff5464b60ca1/websockets-14.1.tar.gz", hash = "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8", size = 162840 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/91/b1b375dbd856fd5fff3f117de0e520542343ecaf4e8fc60f1ac1e9f5822c/websockets-14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a0adf84bc2e7c86e8a202537b4fd50e6f7f0e4a6b6bf64d7ccb96c4cd3330b29", size = 161950 }, + { url = "https://files.pythonhosted.org/packages/61/8f/4d52f272d3ebcd35e1325c646e98936099a348374d4a6b83b524bded8116/websockets-14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90b5d9dfbb6d07a84ed3e696012610b6da074d97453bd01e0e30744b472c8179", size = 159601 }, + { url = "https://files.pythonhosted.org/packages/c4/b1/29e87b53eb1937992cdee094a0988aadc94f25cf0b37e90c75eed7123d75/websockets-14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2177ee3901075167f01c5e335a6685e71b162a54a89a56001f1c3e9e3d2ad250", size = 159854 }, + { url = "https://files.pythonhosted.org/packages/3f/e6/752a2f5e8321ae2a613062676c08ff2fccfb37dc837a2ee919178a372e8a/websockets-14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f14a96a0034a27f9d47fd9788913924c89612225878f8078bb9d55f859272b0", size = 168835 }, + { url = "https://files.pythonhosted.org/packages/60/27/ca62de7877596926321b99071639275e94bb2401397130b7cf33dbf2106a/websockets-14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f874ba705deea77bcf64a9da42c1f5fc2466d8f14daf410bc7d4ceae0a9fcb0", size = 167844 }, + { url = "https://files.pythonhosted.org/packages/7e/db/f556a1d06635c680ef376be626c632e3f2bbdb1a0189d1d1bffb061c3b70/websockets-14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199", size = 168157 }, + { url = "https://files.pythonhosted.org/packages/b3/bc/99e5f511838c365ac6ecae19674eb5e94201aa4235bd1af3e6fa92c12905/websockets-14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bea45f19b7ca000380fbd4e02552be86343080120d074b87f25593ce1700ad58", size = 168561 }, + { url = "https://files.pythonhosted.org/packages/c6/e7/251491585bad61c79e525ac60927d96e4e17b18447cc9c3cfab47b2eb1b8/websockets-14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:219c8187b3ceeadbf2afcf0f25a4918d02da7b944d703b97d12fb01510869078", size = 167979 }, + { url = "https://files.pythonhosted.org/packages/ac/98/7ac2e4eeada19bdbc7a3a66a58e3ebdf33648b9e1c5b3f08c3224df168cf/websockets-14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad2ab2547761d79926effe63de21479dfaf29834c50f98c4bf5b5480b5838434", size = 167925 }, + { url = "https://files.pythonhosted.org/packages/ab/3d/09e65c47ee2396b7482968068f6e9b516221e1032b12dcf843b9412a5dfb/websockets-14.1-cp310-cp310-win32.whl", hash = "sha256:1288369a6a84e81b90da5dbed48610cd7e5d60af62df9851ed1d1d23a9069f10", size = 162831 }, + { url = "https://files.pythonhosted.org/packages/8a/67/59828a3d09740e6a485acccfbb66600632f2178b6ed1b61388ee96f17d5a/websockets-14.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0744623852f1497d825a49a99bfbec9bea4f3f946df6eb9d8a2f0c37a2fec2e", size = 163266 }, + { url = "https://files.pythonhosted.org/packages/97/ed/c0d03cb607b7fe1f7ff45e2cd4bb5cd0f9e3299ced79c2c303a6fff44524/websockets-14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:449d77d636f8d9c17952628cc7e3b8faf6e92a17ec581ec0c0256300717e1512", size = 161949 }, + { url = "https://files.pythonhosted.org/packages/06/91/bf0a44e238660d37a2dda1b4896235d20c29a2d0450f3a46cd688f43b239/websockets-14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a35f704be14768cea9790d921c2c1cc4fc52700410b1c10948511039be824aac", size = 159606 }, + { url = "https://files.pythonhosted.org/packages/ff/b8/7185212adad274c2b42b6a24e1ee6b916b7809ed611cbebc33b227e5c215/websockets-14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b1f3628a0510bd58968c0f60447e7a692933589b791a6b572fcef374053ca280", size = 159854 }, + { url = "https://files.pythonhosted.org/packages/5a/8a/0849968d83474be89c183d8ae8dcb7f7ada1a3c24f4d2a0d7333c231a2c3/websockets-14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c3deac3748ec73ef24fc7be0b68220d14d47d6647d2f85b2771cb35ea847aa1", size = 169402 }, + { url = "https://files.pythonhosted.org/packages/bd/4f/ef886e37245ff6b4a736a09b8468dae05d5d5c99de1357f840d54c6f297d/websockets-14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7048eb4415d46368ef29d32133134c513f507fff7d953c18c91104738a68c3b3", size = 168406 }, + { url = "https://files.pythonhosted.org/packages/11/43/e2dbd4401a63e409cebddedc1b63b9834de42f51b3c84db885469e9bdcef/websockets-14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cf0ad281c979306a6a34242b371e90e891bce504509fb6bb5246bbbf31e7b6", size = 168776 }, + { url = "https://files.pythonhosted.org/packages/6d/d6/7063e3f5c1b612e9f70faae20ebaeb2e684ffa36cb959eb0862ee2809b32/websockets-14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cc1fc87428c1d18b643479caa7b15db7d544652e5bf610513d4a3478dbe823d0", size = 169083 }, + { url = "https://files.pythonhosted.org/packages/49/69/e6f3d953f2fa0f8a723cf18cd011d52733bd7f6e045122b24e0e7f49f9b0/websockets-14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f95ba34d71e2fa0c5d225bde3b3bdb152e957150100e75c86bc7f3964c450d89", size = 168529 }, + { url = "https://files.pythonhosted.org/packages/70/ff/f31fa14561fc1d7b8663b0ed719996cf1f581abee32c8fb2f295a472f268/websockets-14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9481a6de29105d73cf4515f2bef8eb71e17ac184c19d0b9918a3701c6c9c4f23", size = 168475 }, + { url = "https://files.pythonhosted.org/packages/f1/15/b72be0e4bf32ff373aa5baef46a4c7521b8ea93ad8b49ca8c6e8e764c083/websockets-14.1-cp311-cp311-win32.whl", hash = "sha256:368a05465f49c5949e27afd6fbe0a77ce53082185bbb2ac096a3a8afaf4de52e", size = 162833 }, + { url = "https://files.pythonhosted.org/packages/bc/ef/2d81679acbe7057ffe2308d422f744497b52009ea8bab34b6d74a2657d1d/websockets-14.1-cp311-cp311-win_amd64.whl", hash = "sha256:6d24fc337fc055c9e83414c94e1ee0dee902a486d19d2a7f0929e49d7d604b09", size = 163263 }, + { url = "https://files.pythonhosted.org/packages/55/64/55698544ce29e877c9188f1aee9093712411a8fc9732cca14985e49a8e9c/websockets-14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed907449fe5e021933e46a3e65d651f641975a768d0649fee59f10c2985529ed", size = 161957 }, + { url = "https://files.pythonhosted.org/packages/a2/b1/b088f67c2b365f2c86c7b48edb8848ac27e508caf910a9d9d831b2f343cb/websockets-14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:87e31011b5c14a33b29f17eb48932e63e1dcd3fa31d72209848652310d3d1f0d", size = 159620 }, + { url = "https://files.pythonhosted.org/packages/c1/89/2a09db1bbb40ba967a1b8225b07b7df89fea44f06de9365f17f684d0f7e6/websockets-14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc6ccf7d54c02ae47a48ddf9414c54d48af9c01076a2e1023e3b486b6e72c707", size = 159852 }, + { url = "https://files.pythonhosted.org/packages/ca/c1/f983138cd56e7d3079f1966e81f77ce6643f230cd309f73aa156bb181749/websockets-14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9777564c0a72a1d457f0848977a1cbe15cfa75fa2f67ce267441e465717dcf1a", size = 169675 }, + { url = "https://files.pythonhosted.org/packages/c1/c8/84191455d8660e2a0bdb33878d4ee5dfa4a2cedbcdc88bbd097303b65bfa/websockets-14.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a655bde548ca98f55b43711b0ceefd2a88a71af6350b0c168aa77562104f3f45", size = 168619 }, + { url = "https://files.pythonhosted.org/packages/8d/a7/62e551fdcd7d44ea74a006dc193aba370505278ad76efd938664531ce9d6/websockets-14.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfff83ca578cada2d19e665e9c8368e1598d4e787422a460ec70e531dbdd58", size = 169042 }, + { url = "https://files.pythonhosted.org/packages/ad/ed/1532786f55922c1e9c4d329608e36a15fdab186def3ca9eb10d7465bc1cc/websockets-14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6a6c9bcf7cdc0fd41cc7b7944447982e8acfd9f0d560ea6d6845428ed0562058", size = 169345 }, + { url = "https://files.pythonhosted.org/packages/ea/fb/160f66960d495df3de63d9bcff78e1b42545b2a123cc611950ffe6468016/websockets-14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4b6caec8576e760f2c7dd878ba817653144d5f369200b6ddf9771d64385b84d4", size = 168725 }, + { url = "https://files.pythonhosted.org/packages/cf/53/1bf0c06618b5ac35f1d7906444b9958f8485682ab0ea40dee7b17a32da1e/websockets-14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb6d38971c800ff02e4a6afd791bbe3b923a9a57ca9aeab7314c21c84bf9ff05", size = 168712 }, + { url = "https://files.pythonhosted.org/packages/e5/22/5ec2f39fff75f44aa626f86fa7f20594524a447d9c3be94d8482cd5572ef/websockets-14.1-cp312-cp312-win32.whl", hash = "sha256:1d045cbe1358d76b24d5e20e7b1878efe578d9897a25c24e6006eef788c0fdf0", size = 162838 }, + { url = "https://files.pythonhosted.org/packages/74/27/28f07df09f2983178db7bf6c9cccc847205d2b92ced986cd79565d68af4f/websockets-14.1-cp312-cp312-win_amd64.whl", hash = "sha256:90f4c7a069c733d95c308380aae314f2cb45bd8a904fb03eb36d1a4983a4993f", size = 163277 }, + { url = "https://files.pythonhosted.org/packages/34/77/812b3ba5110ed8726eddf9257ab55ce9e85d97d4aa016805fdbecc5e5d48/websockets-14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3630b670d5057cd9e08b9c4dab6493670e8e762a24c2c94ef312783870736ab9", size = 161966 }, + { url = "https://files.pythonhosted.org/packages/8d/24/4fcb7aa6986ae7d9f6d083d9d53d580af1483c5ec24bdec0978307a0f6ac/websockets-14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36ebd71db3b89e1f7b1a5deaa341a654852c3518ea7a8ddfdf69cc66acc2db1b", size = 159625 }, + { url = "https://files.pythonhosted.org/packages/f8/47/2a0a3a2fc4965ff5b9ce9324d63220156bd8bedf7f90824ab92a822e65fd/websockets-14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5b918d288958dc3fa1c5a0b9aa3256cb2b2b84c54407f4813c45d52267600cd3", size = 159857 }, + { url = "https://files.pythonhosted.org/packages/dd/c8/d7b425011a15e35e17757e4df75b25e1d0df64c0c315a44550454eaf88fc/websockets-14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00fe5da3f037041da1ee0cf8e308374e236883f9842c7c465aa65098b1c9af59", size = 169635 }, + { url = "https://files.pythonhosted.org/packages/93/39/6e3b5cffa11036c40bd2f13aba2e8e691ab2e01595532c46437b56575678/websockets-14.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8149a0f5a72ca36720981418eeffeb5c2729ea55fa179091c81a0910a114a5d2", size = 168578 }, + { url = "https://files.pythonhosted.org/packages/cf/03/8faa5c9576299b2adf34dcccf278fc6bbbcda8a3efcc4d817369026be421/websockets-14.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77569d19a13015e840b81550922056acabc25e3f52782625bc6843cfa034e1da", size = 169018 }, + { url = "https://files.pythonhosted.org/packages/8c/05/ea1fec05cc3a60defcdf0bb9f760c3c6bd2dd2710eff7ac7f891864a22ba/websockets-14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf5201a04550136ef870aa60ad3d29d2a59e452a7f96b94193bee6d73b8ad9a9", size = 169383 }, + { url = "https://files.pythonhosted.org/packages/21/1d/eac1d9ed787f80754e51228e78855f879ede1172c8b6185aca8cef494911/websockets-14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:88cf9163ef674b5be5736a584c999e98daf3aabac6e536e43286eb74c126b9c7", size = 168773 }, + { url = "https://files.pythonhosted.org/packages/0e/1b/e808685530185915299740d82b3a4af3f2b44e56ccf4389397c7a5d95d39/websockets-14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:836bef7ae338a072e9d1863502026f01b14027250a4545672673057997d5c05a", size = 168757 }, + { url = "https://files.pythonhosted.org/packages/b6/19/6ab716d02a3b068fbbeb6face8a7423156e12c446975312f1c7c0f4badab/websockets-14.1-cp313-cp313-win32.whl", hash = "sha256:0d4290d559d68288da9f444089fd82490c8d2744309113fc26e2da6e48b65da6", size = 162834 }, + { url = "https://files.pythonhosted.org/packages/6c/fd/ab6b7676ba712f2fc89d1347a4b5bdc6aa130de10404071f2b2606450209/websockets-14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8621a07991add373c3c5c2cf89e1d277e49dc82ed72c75e3afc74bd0acc446f0", size = 163277 }, + { url = "https://files.pythonhosted.org/packages/fb/cd/382a05a1ba2a93bd9fb807716a660751295df72e77204fb130a102fcdd36/websockets-14.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5dc25a9dbd1a7f61eca4b7cb04e74ae4b963d658f9e4f9aad9cd00b688692c8", size = 159633 }, + { url = "https://files.pythonhosted.org/packages/b7/a0/fa7c62e2952ef028b422fbf420f9353d9dd4dfaa425de3deae36e98c0784/websockets-14.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:04a97aca96ca2acedf0d1f332c861c5a4486fdcba7bcef35873820f940c4231e", size = 159867 }, + { url = "https://files.pythonhosted.org/packages/c1/94/954b4924f868db31d5f0935893c7a8446515ee4b36bb8ad75a929469e453/websockets-14.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df174ece723b228d3e8734a6f2a6febbd413ddec39b3dc592f5a4aa0aff28098", size = 161121 }, + { url = "https://files.pythonhosted.org/packages/7a/2e/f12bbb41a8f2abb76428ba4fdcd9e67b5b364a3e7fa97c88f4d6950aa2d4/websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:034feb9f4286476f273b9a245fb15f02c34d9586a5bc936aff108c3ba1b21beb", size = 160731 }, + { url = "https://files.pythonhosted.org/packages/13/97/b76979401f2373af1fe3e08f960b265cecab112e7dac803446fb98351a52/websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c308dabd2b380807ab64b62985eaccf923a78ebc572bd485375b9ca2b7dc7", size = 160681 }, + { url = "https://files.pythonhosted.org/packages/39/9c/16916d9a436c109a1d7ba78817e8fee357b78968be3f6e6f517f43afa43d/websockets-14.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a42d3ecbb2db5080fc578314439b1d79eef71d323dc661aa616fb492436af5d", size = 163316 }, + { url = "https://files.pythonhosted.org/packages/b0/0b/c7e5d11020242984d9d37990310520ed663b942333b83a033c2f20191113/websockets-14.1-py3-none-any.whl", hash = "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e", size = 156277 }, +] + +[[package]] +name = "wheel" +version = "0.45.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494 }, +] + +[[package]] +name = "xxhash" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/8a/0e9feca390d512d293afd844d31670e25608c4a901e10202aa98785eab09/xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212", size = 31970 }, + { url = "https://files.pythonhosted.org/packages/16/e6/be5aa49580cd064a18200ab78e29b88b1127e1a8c7955eb8ecf81f2626eb/xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520", size = 30801 }, + { url = "https://files.pythonhosted.org/packages/20/ee/b8a99ebbc6d1113b3a3f09e747fa318c3cde5b04bd9c197688fadf0eeae8/xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680", size = 220927 }, + { url = "https://files.pythonhosted.org/packages/58/62/15d10582ef159283a5c2b47f6d799fc3303fe3911d5bb0bcc820e1ef7ff4/xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da", size = 200360 }, + { url = "https://files.pythonhosted.org/packages/23/41/61202663ea9b1bd8e53673b8ec9e2619989353dba8cfb68e59a9cbd9ffe3/xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23", size = 428528 }, + { url = "https://files.pythonhosted.org/packages/f2/07/d9a3059f702dec5b3b703737afb6dda32f304f6e9da181a229dafd052c29/xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196", size = 194149 }, + { url = "https://files.pythonhosted.org/packages/eb/58/27caadf78226ecf1d62dbd0c01d152ed381c14c1ee4ad01f0d460fc40eac/xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c", size = 207703 }, + { url = "https://files.pythonhosted.org/packages/b1/08/32d558ce23e1e068453c39aed7b3c1cdc690c177873ec0ca3a90d5808765/xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482", size = 216255 }, + { url = "https://files.pythonhosted.org/packages/3f/d4/2b971e2d2b0a61045f842b622ef11e94096cf1f12cd448b6fd426e80e0e2/xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296", size = 202744 }, + { url = "https://files.pythonhosted.org/packages/19/ae/6a6438864a8c4c39915d7b65effd85392ebe22710412902487e51769146d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415", size = 210115 }, + { url = "https://files.pythonhosted.org/packages/48/7d/b3c27c27d1fc868094d02fe4498ccce8cec9fcc591825c01d6bcb0b4fc49/xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198", size = 414247 }, + { url = "https://files.pythonhosted.org/packages/a1/05/918f9e7d2fbbd334b829997045d341d6239b563c44e683b9a7ef8fe50f5d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442", size = 191419 }, + { url = "https://files.pythonhosted.org/packages/08/29/dfe393805b2f86bfc47c290b275f0b7c189dc2f4e136fd4754f32eb18a8d/xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da", size = 30114 }, + { url = "https://files.pythonhosted.org/packages/7b/d7/aa0b22c4ebb7c3ccb993d4c565132abc641cd11164f8952d89eb6a501909/xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9", size = 30003 }, + { url = "https://files.pythonhosted.org/packages/69/12/f969b81541ee91b55f1ce469d7ab55079593c80d04fd01691b550e535000/xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6", size = 26773 }, + { url = "https://files.pythonhosted.org/packages/b8/c7/afed0f131fbda960ff15eee7f304fa0eeb2d58770fade99897984852ef23/xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1", size = 31969 }, + { url = "https://files.pythonhosted.org/packages/8c/0c/7c3bc6d87e5235672fcc2fb42fd5ad79fe1033925f71bf549ee068c7d1ca/xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8", size = 30800 }, + { url = "https://files.pythonhosted.org/packages/04/9e/01067981d98069eec1c20201f8c145367698e9056f8bc295346e4ea32dd1/xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166", size = 221566 }, + { url = "https://files.pythonhosted.org/packages/d4/09/d4996de4059c3ce5342b6e1e6a77c9d6c91acce31f6ed979891872dd162b/xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7", size = 201214 }, + { url = "https://files.pythonhosted.org/packages/62/f5/6d2dc9f8d55a7ce0f5e7bfef916e67536f01b85d32a9fbf137d4cadbee38/xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623", size = 429433 }, + { url = "https://files.pythonhosted.org/packages/d9/72/9256303f10e41ab004799a4aa74b80b3c5977d6383ae4550548b24bd1971/xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a", size = 194822 }, + { url = "https://files.pythonhosted.org/packages/34/92/1a3a29acd08248a34b0e6a94f4e0ed9b8379a4ff471f1668e4dce7bdbaa8/xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88", size = 208538 }, + { url = "https://files.pythonhosted.org/packages/53/ad/7fa1a109663366de42f724a1cdb8e796a260dbac45047bce153bc1e18abf/xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c", size = 216953 }, + { url = "https://files.pythonhosted.org/packages/35/02/137300e24203bf2b2a49b48ce898ecce6fd01789c0fcd9c686c0a002d129/xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2", size = 203594 }, + { url = "https://files.pythonhosted.org/packages/23/03/aeceb273933d7eee248c4322b98b8e971f06cc3880e5f7602c94e5578af5/xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084", size = 210971 }, + { url = "https://files.pythonhosted.org/packages/e3/64/ed82ec09489474cbb35c716b189ddc1521d8b3de12b1b5ab41ce7f70253c/xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d", size = 415050 }, + { url = "https://files.pythonhosted.org/packages/71/43/6db4c02dcb488ad4e03bc86d70506c3d40a384ee73c9b5c93338eb1f3c23/xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839", size = 192216 }, + { url = "https://files.pythonhosted.org/packages/22/6d/db4abec29e7a567455344433d095fdb39c97db6955bb4a2c432e486b4d28/xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da", size = 30120 }, + { url = "https://files.pythonhosted.org/packages/52/1c/fa3b61c0cf03e1da4767213672efe186b1dfa4fc901a4a694fb184a513d1/xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58", size = 30003 }, + { url = "https://files.pythonhosted.org/packages/6b/8e/9e6fc572acf6e1cc7ccb01973c213f895cb8668a9d4c2b58a99350da14b7/xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3", size = 26777 }, + { url = "https://files.pythonhosted.org/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969 }, + { url = "https://files.pythonhosted.org/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787 }, + { url = "https://files.pythonhosted.org/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959 }, + { url = "https://files.pythonhosted.org/packages/fe/86/51258d3e8a8545ff26468c977101964c14d56a8a37f5835bc0082426c672/xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793", size = 200006 }, + { url = "https://files.pythonhosted.org/packages/02/0a/96973bd325412feccf23cf3680fd2246aebf4b789122f938d5557c54a6b2/xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be", size = 428326 }, + { url = "https://files.pythonhosted.org/packages/11/a7/81dba5010f7e733de88af9555725146fc133be97ce36533867f4c7e75066/xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6", size = 194380 }, + { url = "https://files.pythonhosted.org/packages/fb/7d/f29006ab398a173f4501c0e4977ba288f1c621d878ec217b4ff516810c04/xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90", size = 207934 }, + { url = "https://files.pythonhosted.org/packages/8a/6e/6e88b8f24612510e73d4d70d9b0c7dff62a2e78451b9f0d042a5462c8d03/xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27", size = 216301 }, + { url = "https://files.pythonhosted.org/packages/af/51/7862f4fa4b75a25c3b4163c8a873f070532fe5f2d3f9b3fc869c8337a398/xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2", size = 203351 }, + { url = "https://files.pythonhosted.org/packages/22/61/8d6a40f288f791cf79ed5bb113159abf0c81d6efb86e734334f698eb4c59/xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d", size = 210294 }, + { url = "https://files.pythonhosted.org/packages/17/02/215c4698955762d45a8158117190261b2dbefe9ae7e5b906768c09d8bc74/xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab", size = 414674 }, + { url = "https://files.pythonhosted.org/packages/31/5c/b7a8db8a3237cff3d535261325d95de509f6a8ae439a5a7a4ffcff478189/xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e", size = 192022 }, + { url = "https://files.pythonhosted.org/packages/78/e3/dd76659b2811b3fd06892a8beb850e1996b63e9235af5a86ea348f053e9e/xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8", size = 30170 }, + { url = "https://files.pythonhosted.org/packages/d9/6b/1c443fe6cfeb4ad1dcf231cdec96eb94fb43d6498b4469ed8b51f8b59a37/xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e", size = 30040 }, + { url = "https://files.pythonhosted.org/packages/0f/eb/04405305f290173acc0350eba6d2f1a794b57925df0398861a20fbafa415/xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2", size = 26796 }, + { url = "https://files.pythonhosted.org/packages/c9/b8/e4b3ad92d249be5c83fa72916c9091b0965cb0faeff05d9a0a3870ae6bff/xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6", size = 31795 }, + { url = "https://files.pythonhosted.org/packages/fc/d8/b3627a0aebfbfa4c12a41e22af3742cf08c8ea84f5cc3367b5de2d039cce/xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5", size = 30792 }, + { url = "https://files.pythonhosted.org/packages/c3/cc/762312960691da989c7cd0545cb120ba2a4148741c6ba458aa723c00a3f8/xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc", size = 220950 }, + { url = "https://files.pythonhosted.org/packages/fe/e9/cc266f1042c3c13750e86a535496b58beb12bf8c50a915c336136f6168dc/xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3", size = 199980 }, + { url = "https://files.pythonhosted.org/packages/bf/85/a836cd0dc5cc20376de26b346858d0ac9656f8f730998ca4324921a010b9/xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c", size = 428324 }, + { url = "https://files.pythonhosted.org/packages/b4/0e/15c243775342ce840b9ba34aceace06a1148fa1630cd8ca269e3223987f5/xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb", size = 194370 }, + { url = "https://files.pythonhosted.org/packages/87/a1/b028bb02636dfdc190da01951d0703b3d904301ed0ef6094d948983bef0e/xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f", size = 207911 }, + { url = "https://files.pythonhosted.org/packages/80/d5/73c73b03fc0ac73dacf069fdf6036c9abad82de0a47549e9912c955ab449/xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7", size = 216352 }, + { url = "https://files.pythonhosted.org/packages/b6/2a/5043dba5ddbe35b4fe6ea0a111280ad9c3d4ba477dd0f2d1fe1129bda9d0/xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326", size = 203410 }, + { url = "https://files.pythonhosted.org/packages/a2/b2/9a8ded888b7b190aed75b484eb5c853ddd48aa2896e7b59bbfbce442f0a1/xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf", size = 210322 }, + { url = "https://files.pythonhosted.org/packages/98/62/440083fafbc917bf3e4b67c2ade621920dd905517e85631c10aac955c1d2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7", size = 414725 }, + { url = "https://files.pythonhosted.org/packages/75/db/009206f7076ad60a517e016bb0058381d96a007ce3f79fa91d3010f49cc2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c", size = 192070 }, + { url = "https://files.pythonhosted.org/packages/1f/6d/c61e0668943a034abc3a569cdc5aeae37d686d9da7e39cf2ed621d533e36/xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637", size = 30172 }, + { url = "https://files.pythonhosted.org/packages/96/14/8416dce965f35e3d24722cdf79361ae154fa23e2ab730e5323aa98d7919e/xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43", size = 30041 }, + { url = "https://files.pythonhosted.org/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801 }, + { url = "https://files.pythonhosted.org/packages/ab/9a/233606bada5bd6f50b2b72c45de3d9868ad551e83893d2ac86dc7bb8553a/xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c", size = 29732 }, + { url = "https://files.pythonhosted.org/packages/0c/67/f75276ca39e2c6604e3bee6c84e9db8a56a4973fde9bf35989787cf6e8aa/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986", size = 36214 }, + { url = "https://files.pythonhosted.org/packages/0f/f8/f6c61fd794229cc3848d144f73754a0c107854372d7261419dcbbd286299/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6", size = 32020 }, + { url = "https://files.pythonhosted.org/packages/79/d3/c029c99801526f859e6b38d34ab87c08993bf3dcea34b11275775001638a/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b", size = 40515 }, + { url = "https://files.pythonhosted.org/packages/62/e3/bef7b82c1997579c94de9ac5ea7626d01ae5858aa22bf4fcb38bf220cb3e/xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da", size = 30064 }, +] + +[[package]] +name = "yarl" +version = "1.18.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/98/e005bc608765a8a5569f58e650961314873c8469c333616eb40bff19ae97/yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34", size = 141458 }, + { url = "https://files.pythonhosted.org/packages/df/5d/f8106b263b8ae8a866b46d9be869ac01f9b3fb7f2325f3ecb3df8003f796/yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7", size = 94365 }, + { url = "https://files.pythonhosted.org/packages/56/3e/d8637ddb9ba69bf851f765a3ee288676f7cf64fb3be13760c18cbc9d10bd/yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed", size = 92181 }, + { url = "https://files.pythonhosted.org/packages/76/f9/d616a5c2daae281171de10fba41e1c0e2d8207166fc3547252f7d469b4e1/yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde", size = 315349 }, + { url = "https://files.pythonhosted.org/packages/bb/b4/3ea5e7b6f08f698b3769a06054783e434f6d59857181b5c4e145de83f59b/yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b", size = 330494 }, + { url = "https://files.pythonhosted.org/packages/55/f1/e0fc810554877b1b67420568afff51b967baed5b53bcc983ab164eebf9c9/yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5", size = 326927 }, + { url = "https://files.pythonhosted.org/packages/a9/42/b1753949b327b36f210899f2dd0a0947c0c74e42a32de3f8eb5c7d93edca/yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc", size = 319703 }, + { url = "https://files.pythonhosted.org/packages/f0/6d/e87c62dc9635daefb064b56f5c97df55a2e9cc947a2b3afd4fd2f3b841c7/yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd", size = 310246 }, + { url = "https://files.pythonhosted.org/packages/e3/ef/e2e8d1785cdcbd986f7622d7f0098205f3644546da7919c24b95790ec65a/yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990", size = 319730 }, + { url = "https://files.pythonhosted.org/packages/fc/15/8723e22345bc160dfde68c4b3ae8b236e868f9963c74015f1bc8a614101c/yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db", size = 321681 }, + { url = "https://files.pythonhosted.org/packages/86/09/bf764e974f1516efa0ae2801494a5951e959f1610dd41edbfc07e5e0f978/yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62", size = 324812 }, + { url = "https://files.pythonhosted.org/packages/f6/4c/20a0187e3b903c97d857cf0272d687c1b08b03438968ae8ffc50fe78b0d6/yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760", size = 337011 }, + { url = "https://files.pythonhosted.org/packages/c9/71/6244599a6e1cc4c9f73254a627234e0dad3883ece40cc33dce6265977461/yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b", size = 338132 }, + { url = "https://files.pythonhosted.org/packages/af/f5/e0c3efaf74566c4b4a41cb76d27097df424052a064216beccae8d303c90f/yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690", size = 331849 }, + { url = "https://files.pythonhosted.org/packages/8a/b8/3d16209c2014c2f98a8f658850a57b716efb97930aebf1ca0d9325933731/yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6", size = 84309 }, + { url = "https://files.pythonhosted.org/packages/fd/b7/2e9a5b18eb0fe24c3a0e8bae994e812ed9852ab4fd067c0107fadde0d5f0/yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8", size = 90484 }, + { url = "https://files.pythonhosted.org/packages/40/93/282b5f4898d8e8efaf0790ba6d10e2245d2c9f30e199d1a85cae9356098c/yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069", size = 141555 }, + { url = "https://files.pythonhosted.org/packages/6d/9c/0a49af78df099c283ca3444560f10718fadb8a18dc8b3edf8c7bd9fd7d89/yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193", size = 94351 }, + { url = "https://files.pythonhosted.org/packages/5a/a1/205ab51e148fdcedad189ca8dd587794c6f119882437d04c33c01a75dece/yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889", size = 92286 }, + { url = "https://files.pythonhosted.org/packages/ed/fe/88b690b30f3f59275fb674f5f93ddd4a3ae796c2b62e5bb9ece8a4914b83/yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8", size = 340649 }, + { url = "https://files.pythonhosted.org/packages/07/eb/3b65499b568e01f36e847cebdc8d7ccb51fff716dbda1ae83c3cbb8ca1c9/yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca", size = 356623 }, + { url = "https://files.pythonhosted.org/packages/33/46/f559dc184280b745fc76ec6b1954de2c55595f0ec0a7614238b9ebf69618/yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8", size = 354007 }, + { url = "https://files.pythonhosted.org/packages/af/ba/1865d85212351ad160f19fb99808acf23aab9a0f8ff31c8c9f1b4d671fc9/yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae", size = 344145 }, + { url = "https://files.pythonhosted.org/packages/94/cb/5c3e975d77755d7b3d5193e92056b19d83752ea2da7ab394e22260a7b824/yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3", size = 336133 }, + { url = "https://files.pythonhosted.org/packages/19/89/b77d3fd249ab52a5c40859815765d35c91425b6bb82e7427ab2f78f5ff55/yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb", size = 347967 }, + { url = "https://files.pythonhosted.org/packages/35/bd/f6b7630ba2cc06c319c3235634c582a6ab014d52311e7d7c22f9518189b5/yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e", size = 346397 }, + { url = "https://files.pythonhosted.org/packages/18/1a/0b4e367d5a72d1f095318344848e93ea70da728118221f84f1bf6c1e39e7/yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59", size = 350206 }, + { url = "https://files.pythonhosted.org/packages/b5/cf/320fff4367341fb77809a2d8d7fe75b5d323a8e1b35710aafe41fdbf327b/yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d", size = 362089 }, + { url = "https://files.pythonhosted.org/packages/57/cf/aadba261d8b920253204085268bad5e8cdd86b50162fcb1b10c10834885a/yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e", size = 366267 }, + { url = "https://files.pythonhosted.org/packages/54/58/fb4cadd81acdee6dafe14abeb258f876e4dd410518099ae9a35c88d8097c/yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a", size = 359141 }, + { url = "https://files.pythonhosted.org/packages/9a/7a/4c571597589da4cd5c14ed2a0b17ac56ec9ee7ee615013f74653169e702d/yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1", size = 84402 }, + { url = "https://files.pythonhosted.org/packages/ae/7b/8600250b3d89b625f1121d897062f629883c2f45339623b69b1747ec65fa/yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5", size = 91030 }, + { url = "https://files.pythonhosted.org/packages/33/85/bd2e2729752ff4c77338e0102914897512e92496375e079ce0150a6dc306/yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50", size = 142644 }, + { url = "https://files.pythonhosted.org/packages/ff/74/1178322cc0f10288d7eefa6e4a85d8d2e28187ccab13d5b844e8b5d7c88d/yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576", size = 94962 }, + { url = "https://files.pythonhosted.org/packages/be/75/79c6acc0261e2c2ae8a1c41cf12265e91628c8c58ae91f5ff59e29c0787f/yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640", size = 92795 }, + { url = "https://files.pythonhosted.org/packages/6b/32/927b2d67a412c31199e83fefdce6e645247b4fb164aa1ecb35a0f9eb2058/yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2", size = 332368 }, + { url = "https://files.pythonhosted.org/packages/19/e5/859fca07169d6eceeaa4fde1997c91d8abde4e9a7c018e371640c2da2b71/yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75", size = 342314 }, + { url = "https://files.pythonhosted.org/packages/08/75/76b63ccd91c9e03ab213ef27ae6add2e3400e77e5cdddf8ed2dbc36e3f21/yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512", size = 341987 }, + { url = "https://files.pythonhosted.org/packages/1a/e1/a097d5755d3ea8479a42856f51d97eeff7a3a7160593332d98f2709b3580/yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba", size = 336914 }, + { url = "https://files.pythonhosted.org/packages/0b/42/e1b4d0e396b7987feceebe565286c27bc085bf07d61a59508cdaf2d45e63/yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb", size = 325765 }, + { url = "https://files.pythonhosted.org/packages/7e/18/03a5834ccc9177f97ca1bbb245b93c13e58e8225276f01eedc4cc98ab820/yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272", size = 344444 }, + { url = "https://files.pythonhosted.org/packages/c8/03/a713633bdde0640b0472aa197b5b86e90fbc4c5bc05b727b714cd8a40e6d/yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6", size = 340760 }, + { url = "https://files.pythonhosted.org/packages/eb/99/f6567e3f3bbad8fd101886ea0276c68ecb86a2b58be0f64077396cd4b95e/yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e", size = 346484 }, + { url = "https://files.pythonhosted.org/packages/8e/a9/84717c896b2fc6cb15bd4eecd64e34a2f0a9fd6669e69170c73a8b46795a/yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb", size = 359864 }, + { url = "https://files.pythonhosted.org/packages/1e/2e/d0f5f1bef7ee93ed17e739ec8dbcb47794af891f7d165fa6014517b48169/yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393", size = 364537 }, + { url = "https://files.pythonhosted.org/packages/97/8a/568d07c5d4964da5b02621a517532adb8ec5ba181ad1687191fffeda0ab6/yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285", size = 357861 }, + { url = "https://files.pythonhosted.org/packages/7d/e3/924c3f64b6b3077889df9a1ece1ed8947e7b61b0a933f2ec93041990a677/yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2", size = 84097 }, + { url = "https://files.pythonhosted.org/packages/34/45/0e055320daaabfc169b21ff6174567b2c910c45617b0d79c68d7ab349b02/yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477", size = 90399 }, + { url = "https://files.pythonhosted.org/packages/30/c7/c790513d5328a8390be8f47be5d52e141f78b66c6c48f48d241ca6bd5265/yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb", size = 140789 }, + { url = "https://files.pythonhosted.org/packages/30/aa/a2f84e93554a578463e2edaaf2300faa61c8701f0898725842c704ba5444/yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa", size = 94144 }, + { url = "https://files.pythonhosted.org/packages/c6/fc/d68d8f83714b221a85ce7866832cba36d7c04a68fa6a960b908c2c84f325/yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782", size = 91974 }, + { url = "https://files.pythonhosted.org/packages/56/4e/d2563d8323a7e9a414b5b25341b3942af5902a2263d36d20fb17c40411e2/yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0", size = 333587 }, + { url = "https://files.pythonhosted.org/packages/25/c9/cfec0bc0cac8d054be223e9f2c7909d3e8442a856af9dbce7e3442a8ec8d/yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482", size = 344386 }, + { url = "https://files.pythonhosted.org/packages/ab/5d/4c532190113b25f1364d25f4c319322e86232d69175b91f27e3ebc2caf9a/yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186", size = 345421 }, + { url = "https://files.pythonhosted.org/packages/23/d1/6cdd1632da013aa6ba18cee4d750d953104a5e7aac44e249d9410a972bf5/yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58", size = 339384 }, + { url = "https://files.pythonhosted.org/packages/9a/c4/6b3c39bec352e441bd30f432cda6ba51681ab19bb8abe023f0d19777aad1/yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53", size = 326689 }, + { url = "https://files.pythonhosted.org/packages/23/30/07fb088f2eefdc0aa4fc1af4e3ca4eb1a3aadd1ce7d866d74c0f124e6a85/yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2", size = 345453 }, + { url = "https://files.pythonhosted.org/packages/63/09/d54befb48f9cd8eec43797f624ec37783a0266855f4930a91e3d5c7717f8/yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8", size = 341872 }, + { url = "https://files.pythonhosted.org/packages/91/26/fd0ef9bf29dd906a84b59f0cd1281e65b0c3e08c6aa94b57f7d11f593518/yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1", size = 347497 }, + { url = "https://files.pythonhosted.org/packages/d9/b5/14ac7a256d0511b2ac168d50d4b7d744aea1c1aa20c79f620d1059aab8b2/yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a", size = 359981 }, + { url = "https://files.pythonhosted.org/packages/ca/b3/d493221ad5cbd18bc07e642894030437e405e1413c4236dd5db6e46bcec9/yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10", size = 366229 }, + { url = "https://files.pythonhosted.org/packages/04/56/6a3e2a5d9152c56c346df9b8fb8edd2c8888b1e03f96324d457e5cf06d34/yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8", size = 360383 }, + { url = "https://files.pythonhosted.org/packages/fd/b7/4b3c7c7913a278d445cc6284e59b2e62fa25e72758f888b7a7a39eb8423f/yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d", size = 310152 }, + { url = "https://files.pythonhosted.org/packages/f5/d5/688db678e987c3e0fb17867970700b92603cadf36c56e5fb08f23e822a0c/yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c", size = 315723 }, + { url = "https://files.pythonhosted.org/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109 }, +] From d5c35364be750c7005ef842d2e17f21fcf675da2 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 9 Jan 2025 08:37:39 -0600 Subject: [PATCH 167/554] fix: python dependency inconsistency --- pyproject.toml | 1 + uv.lock | 319 +++++++++++++++++++++++++++++-------------------- 2 files changed, 193 insertions(+), 127 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e8924e95..618ac870 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ dependencies = [ "peft", "pip-chill==1.0.3", "playwright==1.49.1", + "pydantic==2.6", "python-dotenv==1.0.1", "scikit-learn==1.6.0", "sentence-transformers", diff --git a/uv.lock b/uv.lock index 386ee570..4cb2c1bb 100644 --- a/uv.lock +++ b/uv.lock @@ -1574,22 +1574,12 @@ wheels = [ name = "langchain" version = "0.0.27" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", -] dependencies = [ - { name = "numpy", marker = "python_full_version < '3.12'" }, - { name = "pydantic", marker = "python_full_version < '3.12'" }, - { name = "pyyaml", marker = "python_full_version < '3.12'" }, - { name = "requests", marker = "python_full_version < '3.12'" }, - { name = "sqlalchemy", marker = "python_full_version < '3.12'" }, + { name = "numpy" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sqlalchemy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/62/53/a975d63f52873daeb2563621240a79ea1368a85a3d94b4231cf750a85048/langchain-0.0.27.tar.gz", hash = "sha256:cdaca3a61eb57a76bef990ce3c4615ac06873e83f100bf9c485f6c36e29ed95c", size = 72304 } wheels = [ @@ -1597,62 +1587,102 @@ wheels = [ ] [[package]] -name = "langchain" -version = "0.3.14" +name = "langchain-core" +version = "0.2.8" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", ] dependencies = [ - { name = "aiohttp", marker = "python_full_version >= '3.12'" }, - { name = "langchain-core", marker = "python_full_version >= '3.12'" }, - { name = "langchain-text-splitters", marker = "python_full_version >= '3.12'" }, - { name = "langsmith", marker = "python_full_version >= '3.12'" }, - { name = "numpy", marker = "python_full_version >= '3.12'" }, - { name = "pydantic", marker = "python_full_version >= '3.12'" }, - { name = "pyyaml", marker = "python_full_version >= '3.12'" }, - { name = "requests", marker = "python_full_version >= '3.12'" }, - { name = "sqlalchemy", marker = "python_full_version >= '3.12'" }, - { name = "tenacity", marker = "python_full_version >= '3.12'" }, + { name = "jsonpatch", marker = "python_full_version >= '3.12.4'" }, + { name = "langsmith", version = "0.1.81", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12.4'" }, + { name = "packaging", marker = "python_full_version >= '3.12.4'" }, + { name = "pydantic", marker = "python_full_version >= '3.12.4'" }, + { name = "pyyaml", marker = "python_full_version >= '3.12.4'" }, + { name = "tenacity", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12.4'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/35/2adb0693acc149e462bc0e7856ecd58096c285f66a78bc44fc2b8ae91ce0/langchain-0.3.14.tar.gz", hash = "sha256:4a5ae817b5832fa0e1fcadc5353fbf74bebd2f8e550294d4dc039f651ddcd3d1", size = 420409 } +sdist = { url = "https://files.pythonhosted.org/packages/5c/40/32338a7cb615e3e4c0e06e7f5ae04fff648364c378a44440f4fb0b296210/langchain_core-0.2.8.tar.gz", hash = "sha256:2db866a4514672c4875b69d5590aa2ed50aa0d144874268bef68d74b5e7f33f9", size = 248197 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/a8/0a8f868615b7a30636b1d15b718e3ea9875bf0dccced03583477c2372495/langchain-0.3.14-py3-none-any.whl", hash = "sha256:5df9031702f7fe6c956e84256b4639a46d5d03a75be1ca4c1bc9479b358061a2", size = 1009213 }, + { url = "https://files.pythonhosted.org/packages/55/08/14620ff398cdcbc30ad0cba0fa71c89f7317a483f726970dc1a33dd84822/langchain_core-0.2.8-py3-none-any.whl", hash = "sha256:172c81c858dc1f3123cc72b7e44e10f44c92f8a761cae18c364081f6c208e9f6", size = 315757 }, ] [[package]] name = "langchain-core" version = "0.3.29" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", +] dependencies = [ - { name = "jsonpatch" }, - { name = "langsmith" }, - { name = "packaging" }, - { name = "pydantic" }, - { name = "pyyaml" }, - { name = "tenacity" }, - { name = "typing-extensions" }, + { name = "jsonpatch", marker = "python_full_version < '3.12.4'" }, + { name = "langsmith", version = "0.2.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12.4'" }, + { name = "packaging", marker = "python_full_version < '3.12.4'" }, + { name = "pydantic", marker = "python_full_version < '3.12.4'" }, + { name = "pyyaml", marker = "python_full_version < '3.12.4'" }, + { name = "tenacity", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12.4'" }, + { name = "typing-extensions", marker = "python_full_version < '3.12.4'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/05/8fc844c2f79a2c30845de2db948de1cf7c17b94f419f7ae7b616d628a54f/langchain_core-0.3.29.tar.gz", hash = "sha256:773d6aeeb612e7ce3d996c0be403433d8c6a91e77bbb7a7461c13e15cfbe5b06", size = 330785 } wheels = [ { url = "https://files.pythonhosted.org/packages/95/4f/fe1de63f6fc1ac7af3ba4ae12d420af1a19f7893b5fcb72856b9fc67f650/langchain_core-0.3.29-py3-none-any.whl", hash = "sha256:817db1474871611a81105594a3e4d11704949661008e455a10e38ca9ff601a1a", size = 411593 }, ] +[[package]] +name = "langchain-openai" +version = "0.1.14" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", +] +dependencies = [ + { name = "langchain-core", version = "0.2.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12.4'" }, + { name = "openai", marker = "python_full_version >= '3.12.4'" }, + { name = "tiktoken", marker = "python_full_version >= '3.12.4'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/19/0e1502ff1812381ea37accb652d88c32c701de449be1b90b7e9cda0912f9/langchain_openai-0.1.14.tar.gz", hash = "sha256:1f13d6041e8bedddf6eb47ccad7416e05af38fa169324f7f1bdf4f385780f8d8", size = 39611 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/06/d200c9e3f8f17bb50a07df9b7c92894ad5e0c1d65729ea0e438a36371858/langchain_openai-0.1.14-py3-none-any.whl", hash = "sha256:fcd34cc5b5713798908a5828d364b4426e3b1afccbd564d344e5477acb86634a", size = 45938 }, +] + [[package]] name = "langchain-openai" version = "0.2.14" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", +] dependencies = [ - { name = "langchain-core" }, - { name = "openai" }, - { name = "tiktoken" }, + { name = "langchain-core", version = "0.3.29", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12.4'" }, + { name = "openai", marker = "python_full_version < '3.12.4'" }, + { name = "tiktoken", marker = "python_full_version < '3.12.4'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e5/fd/8256eba9a159f95a13c5bf7f1f49683de93b3876585b768e6be5dc3a5765/langchain_openai-0.2.14.tar.gz", hash = "sha256:7a514f309e356b182a337c0ed36ab3fbe34d9834a235a3b85cb7f91ae775d978", size = 43647 } wheels = [ @@ -1660,27 +1690,49 @@ wheels = [ ] [[package]] -name = "langchain-text-splitters" -version = "0.3.5" +name = "langsmith" +version = "0.1.81" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", +] dependencies = [ - { name = "langchain-core", marker = "python_full_version >= '3.12'" }, + { name = "orjson", marker = "python_full_version >= '3.12.4'" }, + { name = "pydantic", marker = "python_full_version >= '3.12.4'" }, + { name = "requests", marker = "python_full_version >= '3.12.4'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/35/a6f8d6b1bb0e6e8c00b49bce4d1a115f8b68368b1899f65bb34dbbb44160/langchain_text_splitters-0.3.5.tar.gz", hash = "sha256:11cb7ca3694e5bdd342bc16d3875b7f7381651d4a53cbb91d34f22412ae16443", size = 26318 } +sdist = { url = "https://files.pythonhosted.org/packages/42/46/469638058e3ee19194fc02ca483bb530b1e417f3fb903a9d28951ec87b1d/langsmith-0.1.81.tar.gz", hash = "sha256:585ef3a2251380bd2843a664c9a28da4a7d28432e3ee8bcebf291ffb8e1f0af0", size = 117828 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/83/f8081c3bea416bd9d9f0c26af795c74f42c24f9ad3c4fbf361b7d69de134/langchain_text_splitters-0.3.5-py3-none-any.whl", hash = "sha256:8c9b059827438c5fa8f327b4df857e307828a5ec815163c9b5c9569a3e82c8ee", size = 31620 }, + { url = "https://files.pythonhosted.org/packages/2b/ab/0ea685c54c448e5c7dbdab674c76c36af982859a7011a1b98d67e7d5ab20/langsmith-0.1.81-py3-none-any.whl", hash = "sha256:3251d823225eef23ee541980b9d9e506367eabbb7f985a086b5d09e8f78ba7e9", size = 127090 }, ] [[package]] name = "langsmith" version = "0.2.10" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", +] dependencies = [ - { name = "httpx" }, - { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, - { name = "pydantic" }, - { name = "requests" }, - { name = "requests-toolbelt" }, + { name = "httpx", marker = "python_full_version < '3.12.4'" }, + { name = "orjson", marker = "python_full_version < '3.12.4' and platform_python_implementation != 'PyPy'" }, + { name = "pydantic", marker = "python_full_version < '3.12.4'" }, + { name = "requests", marker = "python_full_version < '3.12.4'" }, + { name = "requests-toolbelt", marker = "python_full_version < '3.12.4'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d4/f8/d642c101267101ddeb44f898a98a5700f90c8959a4119c341a9678fe18cc/langsmith-0.2.10.tar.gz", hash = "sha256:153c7b3ccbd823528ff5bec84801e7e50a164e388919fc583252df5b27dd7830", size = 314154 } wheels = [ @@ -3037,91 +3089,73 @@ wheels = [ [[package]] name = "pydantic" -version = "2.10.4" +version = "2.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/7e/fb60e6fee04d0ef8f15e4e01ff187a196fa976eb0f0ab524af4599e5754c/pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06", size = 762094 } +sdist = { url = "https://files.pythonhosted.org/packages/78/6c/87e7c6e46206e27b3037acdf637906c4be500a0b1dd7ccbb805a72b9f494/pydantic-2.6.0.tar.gz", hash = "sha256:ae887bd94eb404b09d86e4d12f93893bdca79d766e738528c6fa1c849f3c6bcf", size = 677208 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/26/3e1bbe954fde7ee22a6e7d31582c642aad9e84ffe4b5fb61e63b87cd326f/pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d", size = 431765 }, + { url = "https://files.pythonhosted.org/packages/e4/37/3ffe6e7daa1ea1b4bf5228807a92ccbae538cf57c0c50b93564c310c11a8/pydantic-2.6.0-py3-none-any.whl", hash = "sha256:1440966574e1b5b99cf75a13bec7b20e3512e8a61b894ae252f56275e2c465ae", size = 394201 }, ] [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.16.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938 }, - { url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684 }, - { url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169 }, - { url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227 }, - { url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695 }, - { url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662 }, - { url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370 }, - { url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813 }, - { url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287 }, - { url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414 }, - { url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301 }, - { url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685 }, - { url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876 }, - { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, - { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, - { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, - { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, - { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, - { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, - { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, - { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, - { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, - { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, - { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, - { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, - { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, - { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, - { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, - { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, - { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, - { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, - { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, - { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, - { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, - { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, - { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, - { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, - { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, - { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, - { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, - { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, - { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, - { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, - { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, - { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, - { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, - { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, - { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, - { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, - { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, - { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, - { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, - { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, - { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, - { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, - { url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159 }, - { url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331 }, - { url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467 }, - { url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797 }, - { url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839 }, - { url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861 }, - { url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582 }, - { url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985 }, - { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 }, +sdist = { url = "https://files.pythonhosted.org/packages/a0/a7/61d013c73773bb03d02de9de8e4e5b2ed2c100dc98ae7046d54485ecf5d4/pydantic_core-2.16.1.tar.gz", hash = "sha256:daff04257b49ab7f4b3f73f98283d3dbb1a65bf3500d55c7beac3c66c310fe34", size = 368201 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/7c/72819fce116e969f45497206cb84966d99d0b2a8be0173cc3d57b29ee0ff/pydantic_core-2.16.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:300616102fb71241ff477a2cbbc847321dbec49428434a2f17f37528721c4948", size = 1903838 }, + { url = "https://files.pythonhosted.org/packages/a2/a3/142ef9bb5417a67dab782ca5add911fa76a3bef21a5b77ad343683e8c584/pydantic_core-2.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5511f962dd1b9b553e9534c3b9c6a4b0c9ded3d8c2be96e61d56f933feef9e1f", size = 1745896 }, + { url = "https://files.pythonhosted.org/packages/57/08/a6c8392e7d7d08dca77d7cd8531349eeba779b10a0648520ebb23f48fcbf/pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98f0edee7ee9cc7f9221af2e1b95bd02810e1c7a6d115cfd82698803d385b28f", size = 1900593 }, + { url = "https://files.pythonhosted.org/packages/14/03/3f67a97640dd7b252dfecc7f9ba0e4c84cb520885af2f219c40b54e4a297/pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9795f56aa6b2296f05ac79d8a424e94056730c0b860a62b0fdcfe6340b658cc8", size = 1915761 }, + { url = "https://files.pythonhosted.org/packages/f2/49/0f7e0aad58a2620306e190064d316430d455bda1bc40ce35be96252c1346/pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c45f62e4107ebd05166717ac58f6feb44471ed450d07fecd90e5f69d9bf03c48", size = 2065743 }, + { url = "https://files.pythonhosted.org/packages/3f/b6/eb8f5511810d8d7d2c18228ee2a414fe200678a897e938186189dc128b05/pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:462d599299c5971f03c676e2b63aa80fec5ebc572d89ce766cd11ca8bcb56f3f", size = 3259998 }, + { url = "https://files.pythonhosted.org/packages/a0/7e/96bd9a40f6d5edc3b4566951acac7a8f26e423c77001a7dc065d6200052a/pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ebaa4bf6386a3b22eec518da7d679c8363fb7fb70cf6972161e5542f470798", size = 2180287 }, + { url = "https://files.pythonhosted.org/packages/1e/2a/2fd602af15a12dddf0d5b931639ce1a190ca5256e93e68fae209bcb05118/pydantic_core-2.16.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:99f9a50b56713a598d33bc23a9912224fc5d7f9f292444e6664236ae471ddf17", size = 1973184 }, + { url = "https://files.pythonhosted.org/packages/8b/84/8f2ec2a6e961236c202a9ef36f9f498d19ae9305cfbbd4135dab4a8273ed/pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8ec364e280db4235389b5e1e6ee924723c693cbc98e9d28dc1767041ff9bc388", size = 2071546 }, + { url = "https://files.pythonhosted.org/packages/8e/d3/abdf35c0fad643b82cba7cec1d5875b8ffdaf2d775ee12f916c301edcbad/pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:653a5dfd00f601a0ed6654a8b877b18d65ac32c9d9997456e0ab240807be6cf7", size = 2212439 }, + { url = "https://files.pythonhosted.org/packages/85/60/d78c76282beec6260c449331f428e17a228836895a24a8648cbe95aead59/pydantic_core-2.16.1-cp310-none-win32.whl", hash = "sha256:1661c668c1bb67b7cec96914329d9ab66755911d093bb9063c4c8914188af6d4", size = 1761095 }, + { url = "https://files.pythonhosted.org/packages/71/1a/e6a75d2768b8e53eebdc6146a839c0d5629b1f985b3a44d39e4829c8d39e/pydantic_core-2.16.1-cp310-none-win_amd64.whl", hash = "sha256:561be4e3e952c2f9056fba5267b99be4ec2afadc27261505d4992c50b33c513c", size = 1927450 }, + { url = "https://files.pythonhosted.org/packages/25/95/1357b15051f458a15eeaf03d35d0f7466ec8979eb69061dacc6d8f7924d9/pydantic_core-2.16.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:102569d371fadc40d8f8598a59379c37ec60164315884467052830b28cc4e9da", size = 1900014 }, + { url = "https://files.pythonhosted.org/packages/3d/98/11b5400a80f527f5d6c5bdb71a77f4cf3754bef5d512d94471ecbc32e2ff/pydantic_core-2.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:735dceec50fa907a3c314b84ed609dec54b76a814aa14eb90da31d1d36873a5e", size = 1745364 }, + { url = "https://files.pythonhosted.org/packages/20/25/55f41724bd64954fba6e661d6afa3d5a1b3561c0611f5c4e523fee3da640/pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e83ebbf020be727d6e0991c1b192a5c2e7113eb66e3def0cd0c62f9f266247e4", size = 1899615 }, + { url = "https://files.pythonhosted.org/packages/54/5e/6fcf81f868f634f8a163867df89303033cc18985daf693f34475f40117aa/pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:30a8259569fbeec49cfac7fda3ec8123486ef1b729225222f0d41d5f840b476f", size = 1914379 }, + { url = "https://files.pythonhosted.org/packages/2c/8b/930c415a0c1f24ceb602f208b698c21ab80b869a416803ac2a45fdecc006/pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920c4897e55e2881db6a6da151198e5001552c3777cd42b8a4c2f72eedc2ee91", size = 2064696 }, + { url = "https://files.pythonhosted.org/packages/ac/ef/4a84325e7734d8f0b29295b99498ef4c78c8b7ba4b2db51a2b27b6627a17/pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5247a3d74355f8b1d780d0f3b32a23dd9f6d3ff43ef2037c6dcd249f35ecf4c", size = 3258963 }, + { url = "https://files.pythonhosted.org/packages/98/19/955b83b6e33b7ac27914860069a918fe49b29c13bc149dc7bb7c60954812/pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5bea8012df5bb6dda1e67d0563ac50b7f64a5d5858348b5c8cb5043811c19d", size = 2179513 }, + { url = "https://files.pythonhosted.org/packages/a2/14/70e69bcca582a02b939c4eb2ba6df5f474aa0e9c41103986952d3a05da27/pydantic_core-2.16.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ed3025a8a7e5a59817b7494686d449ebfbe301f3e757b852c8d0d1961d6be864", size = 1972273 }, + { url = "https://files.pythonhosted.org/packages/fc/35/994e7001bacfc117d22c64b87426b2fd7539ee9963f7159e7ec60f100720/pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06f0d5a1d9e1b7932477c172cc720b3b23c18762ed7a8efa8398298a59d177c7", size = 2070589 }, + { url = "https://files.pythonhosted.org/packages/e3/e1/7a47532a6046636af04c58710517bc7dc9a76bface7a74edb7913a98950f/pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:150ba5c86f502c040b822777e2e519b5625b47813bd05f9273a8ed169c97d9ae", size = 2211305 }, + { url = "https://files.pythonhosted.org/packages/77/e9/d05dc5c8e7c5698026f01de6affe04015eb719e216a4c4b70cd7946c2785/pydantic_core-2.16.1-cp311-none-win32.whl", hash = "sha256:d6cbdf12ef967a6aa401cf5cdf47850559e59eedad10e781471c960583f25aa1", size = 1760415 }, + { url = "https://files.pythonhosted.org/packages/b2/47/14bf2397a5daa0cc1b99306499a39525966b73aba4d31b17e373e229f07e/pydantic_core-2.16.1-cp311-none-win_amd64.whl", hash = "sha256:afa01d25769af33a8dac0d905d5c7bb2d73c7c3d5161b2dd6f8b5b5eea6a3c4c", size = 1926977 }, + { url = "https://files.pythonhosted.org/packages/df/e0/0b108193cb405c21bdb3a6d95fdf4d0ae65accd187d9df5b8011c484f275/pydantic_core-2.16.1-cp311-none-win_arm64.whl", hash = "sha256:1a2fe7b00a49b51047334d84aafd7e39f80b7675cad0083678c58983662da89b", size = 1844114 }, + { url = "https://files.pythonhosted.org/packages/82/8f/d83953f652b0048fd8be62b6eabed7e3397008b6d050bd080ab78d3e6d14/pydantic_core-2.16.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f478ec204772a5c8218e30eb813ca43e34005dff2eafa03931b3d8caef87d51", size = 1868275 }, + { url = "https://files.pythonhosted.org/packages/c1/33/f627f1d31f7986ade7396237a8b5904c629837878978e9eeb400f85b3e29/pydantic_core-2.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1936ef138bed2165dd8573aa65e3095ef7c2b6247faccd0e15186aabdda7f66", size = 1718240 }, + { url = "https://files.pythonhosted.org/packages/f5/7e/1bcd8ce164868c40d841528f92e5f1f5a1a6cb705a063c425cd00f8b1eef/pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d3a433ef5dc3021c9534a58a3686c88363c591974c16c54a01af7efd741f13", size = 1873433 }, + { url = "https://files.pythonhosted.org/packages/32/aa/f5d9139609e30a6f174c6d6c8f3f64aafaf6f43dab7974b8642a10d08758/pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd88f40f2294440d3f3c6308e50d96a0d3d0973d6f1a5732875d10f569acef49", size = 1875493 }, + { url = "https://files.pythonhosted.org/packages/39/25/46cef345a191d8d6c6458420029ce25edfbe96833075d3a474ae2aeae106/pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fac641bbfa43d5a1bed99d28aa1fded1984d31c670a95aac1bf1d36ac6ce137", size = 2046585 }, + { url = "https://files.pythonhosted.org/packages/62/2d/2c9af3e66486b7159ab2f05712e2d46fc7ee29e2cbd7f4e5e1feaac48e12/pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72bf9308a82b75039b8c8edd2be2924c352eda5da14a920551a8b65d5ee89253", size = 3077749 }, + { url = "https://files.pythonhosted.org/packages/86/61/55607ffc05fc1caac9b6754552bc907c4af469f5585cb3599aa855865923/pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb4363e6c9fc87365c2bc777a1f585a22f2f56642501885ffc7942138499bf54", size = 2179456 }, + { url = "https://files.pythonhosted.org/packages/2b/64/383663f04e58333fe15b30da863fe28a76f00b676dded6f4b6f1c23fc9c5/pydantic_core-2.16.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:20f724a023042588d0f4396bbbcf4cffd0ddd0ad3ed4f0d8e6d4ac4264bae81e", size = 1953184 }, + { url = "https://files.pythonhosted.org/packages/be/cd/3a975ba2bd4493a5bae904891d5239fa3e8dd59be39b1845d3361e1a412e/pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fb4370b15111905bf8b5ba2129b926af9470f014cb0493a67d23e9d7a48348e8", size = 2052616 }, + { url = "https://files.pythonhosted.org/packages/ac/5e/b26ef5ba2266b4dd6fd93964c1923f32032034100cfed167f2f85df7a3da/pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23632132f1fd608034f1a56cc3e484be00854db845b3a4a508834be5a6435a6f", size = 2172342 }, + { url = "https://files.pythonhosted.org/packages/39/81/fce7fcd7f877ab56c2e677366da41abdd3071dcc6592354bc96f0135c43a/pydantic_core-2.16.1-cp312-none-win32.whl", hash = "sha256:b9f3e0bffad6e238f7acc20c393c1ed8fab4371e3b3bc311020dfa6020d99212", size = 1764252 }, + { url = "https://files.pythonhosted.org/packages/ab/de/32c35c9d84652da17673357295d19016cc4768fea0dd071d8c09ca4cacbb/pydantic_core-2.16.1-cp312-none-win_amd64.whl", hash = "sha256:a0b4cfe408cd84c53bab7d83e4209458de676a6ec5e9c623ae914ce1cb79b96f", size = 1881165 }, + { url = "https://files.pythonhosted.org/packages/61/db/78cafc630e4b3193c5a702ae20916349f5f0ef5bdaa918565549f3160059/pydantic_core-2.16.1-cp312-none-win_arm64.whl", hash = "sha256:d195add190abccefc70ad0f9a0141ad7da53e16183048380e688b466702195dd", size = 1853531 }, + { url = "https://files.pythonhosted.org/packages/5f/06/f92cb6971b2163488f2d908095a35bdc38a8b30970674abb1473d407e07e/pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d25ef0c33f22649b7a088035fd65ac1ce6464fa2876578df1adad9472f918a76", size = 1906426 }, + { url = "https://files.pythonhosted.org/packages/18/f1/f4d89d38dc82c2f973e2487504bb3cd30d62340a249e64871f1fe6293049/pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99c095457eea8550c9fa9a7a992e842aeae1429dab6b6b378710f62bfb70b394", size = 1766939 }, + { url = "https://files.pythonhosted.org/packages/4c/10/e714042114547b25a2c792ad72251447407eef9449501573b3fc20b8d02c/pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b49c604ace7a7aa8af31196abbf8f2193be605db6739ed905ecaf62af31ccae0", size = 1911451 }, + { url = "https://files.pythonhosted.org/packages/30/e3/802b56e1207ae8c581bdfb838340c4bd404a7a924a5c26ea3fe615db116e/pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56da23034fe66221f2208c813d8aa509eea34d97328ce2add56e219c3a9f41c", size = 2036878 }, + { url = "https://files.pythonhosted.org/packages/6c/dd/572df4ced42416411c19d427b37e247cbeddbc8c05f5586aea62cce6b753/pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cebf8d56fee3b08ad40d332a807ecccd4153d3f1ba8231e111d9759f02edfd05", size = 1968197 }, + { url = "https://files.pythonhosted.org/packages/87/53/af8141c6ca8b8e2a8e73314bcd313554fc9d01e063f02e13386dff3000b8/pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:1ae8048cba95f382dba56766525abca438328455e35c283bb202964f41a780b0", size = 2083060 }, + { url = "https://files.pythonhosted.org/packages/c8/71/6459ce68d47a30a932b97b49f1c6f66743821c4a410dd10c41b0e347d264/pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:780daad9e35b18d10d7219d24bfb30148ca2afc309928e1d4d53de86822593dc", size = 2216609 }, + { url = "https://files.pythonhosted.org/packages/ec/20/b76edaec257bf631929dcbefaaa7a7c621ede570a99140f404b6278a433b/pydantic_core-2.16.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c94b5537bf6ce66e4d7830c6993152940a188600f6ae044435287753044a8fe2", size = 2027647 }, ] [[package]] @@ -3449,7 +3483,7 @@ name = "requests-toolbelt" version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "requests" }, + { name = "requests", marker = "python_full_version < '3.12.4'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 } wheels = [ @@ -3865,10 +3899,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/fe/81695a1aa331a842b582453b605175f419fe8540355886031328089d840a/sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8", size = 6189177 }, ] +[[package]] +name = "tenacity" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165 }, +] + [[package]] name = "tenacity" version = "9.0.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", +] sdist = { url = "https://files.pythonhosted.org/packages/cd/94/91fccdb4b8110642462e653d5dcb27e7b674742ad68efd146367da7bdb10/tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b", size = 47421 } wheels = [ { url = "https://files.pythonhosted.org/packages/b6/cb/b86984bed139586d01532a587464b5805f12e397594f19f931c4c2fbfa61/tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539", size = 28169 }, @@ -4255,9 +4318,9 @@ dependencies = [ { name = "ddt" }, { name = "duckduckgo-search" }, { name = "einops" }, - { name = "langchain", version = "0.0.27", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "langchain", version = "0.3.14", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "langchain-openai" }, + { name = "langchain" }, + { name = "langchain-openai", version = "0.1.14", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12.4'" }, + { name = "langchain-openai", version = "0.2.14", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12.4'" }, { name = "lxml" }, { name = "matplotlib-inline" }, { name = "nltk" }, @@ -4265,6 +4328,7 @@ dependencies = [ { name = "peft" }, { name = "pip-chill" }, { name = "playwright" }, + { name = "pydantic" }, { name = "python-dotenv" }, { name = "scikit-learn" }, { name = "sentence-transformers" }, @@ -4296,6 +4360,7 @@ requires-dist = [ { name = "peft" }, { name = "pip-chill", specifier = "==1.0.3" }, { name = "playwright", specifier = "==1.49.1" }, + { name = "pydantic", specifier = "==2.6" }, { name = "python-dotenv", specifier = "==1.0.1" }, { name = "scikit-learn", specifier = "==1.6.0" }, { name = "sentence-transformers" }, From 4df5e948c62ce4b49888e7d3b6a6e46dab7f0ba3 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 9 Jan 2025 11:58:57 -0600 Subject: [PATCH 168/554] feat: remove langchain usage --- neurons/miners/openai_miner.py | 94 ++--- pyproject.toml | 2 - tests/test_huggingface_dataset.py | 21 ++ tests/test_llms.py | 34 ++ tests/test_openai_miner.py | 32 ++ uv.lock | 452 ----------------------- webgenie/datasets/huggingface_dataset.py | 19 +- webgenie/datasets/synthetic_dataset.py | 34 +- webgenie/helpers/llms.py | 36 +- webgenie/prompts.py | 6 - webgenie/rewards/quality_reward.py | 35 +- webgenie/rewards/rtc_reward.py | 27 +- 12 files changed, 182 insertions(+), 610 deletions(-) create mode 100644 tests/test_huggingface_dataset.py create mode 100644 tests/test_llms.py create mode 100644 tests/test_openai_miner.py diff --git a/neurons/miners/openai_miner.py b/neurons/miners/openai_miner.py index 165a748b..93498f9e 100644 --- a/neurons/miners/openai_miner.py +++ b/neurons/miners/openai_miner.py @@ -1,47 +1,29 @@ import bittensor as bt - -from langchain.prompts import HumanMessagePromptTemplate, SystemMessagePromptTemplate -from langchain_core.output_parsers import JsonOutputParser -from langchain_core.pydantic_v1 import BaseModel, Field +from pydantic import BaseModel, Field from webgenie.base.neuron import BaseNeuron -from webgenie.helpers.llms import call_llm +from webgenie.helpers.llms import openai_call from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse -class HTMLResponse(BaseModel): - html: str = Field(default="", description="The HTML code for the webpage") +class HTML(BaseModel): + html: str class OpenaiMiner: def __init__(self, neuron: BaseNeuron): self.neuron = neuron - self.html_response_parser = JsonOutputParser(pydantic_object=HTMLResponse) - async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynapse: try: - template = [ - ("system", """You are an expert web developer who specializes in HTML and CSS. A user will provide you with the webpage requirements. You need to return a single html file that uses HTML and CSS to satisfy the requirements. - Include all CSS code in the HTML file itself. - If it involves any images, use "rick.jpg" as the placeholder. - Do not hallucinate any dependencies to external files. You do not need to include JavaScript scripts for dynamic interactions. - Pay attention to things like size, text, position, and color of all the elements, as well as the overall layout. - Respond with the content of the HTML+CSS file: - {instructions}"""), - ("user", "{query}"), - ] - - html_response = await call_llm( - template=template, - params={ - "query": synapse.prompt, - "instructions": self.html_response_parser.get_format_instructions(), - }, - output_parser=self.html_response_parser, + html_response = await openai_call( + messages = [ + {"role": "system", "content": "You are an expert web developer who specializes in HTML and CSS. A user will provide you with the webpage requirements. You need to return a single html file that uses HTML and CSS to satisfy the requirements. Include all CSS code in the HTML file itself. If it involves any images, use 'rick.jpg' as the placeholder. Do not hallucinate any dependencies to external files. You do not need to include JavaScript scripts for dynamic interactions. Pay attention to things like size, text, position, and color of all the elements, as well as the overall layout. Respond with the content of the HTML+CSS file:"}, + {"role": "user", "content": "Create a webpage with a red background and a blue rectangle in the center."}, + ], + response_format = HTML, ) - - synapse.html = html_response["html"] + synapse.html = html_response.html return synapse except Exception as e: bt.logging.error(f"Error in OpenaiMiner forward_text: {e}") @@ -50,35 +32,35 @@ async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynaps async def forward_image(self, synapse: WebgenieImageSynapse) -> WebgenieImageSynapse: try: - prompt_messages = [ - SystemMessagePromptTemplate.from_template(""" - You are an expert web developer who specializes in HTML and CSS. - A user will provide you with a screenshot of a webpage, along with all texts that they want to put on the webpage. - You need to return a single html file that uses HTML and CSS to reproduce the given website. - Include all CSS code in the HTML file itself. - If it involves any images, use "rick.jpg" as the placeholder. - Some images on the webpage are replaced with a blue rectangle as the placeholder, use "rick.jpg" for those as well. - Do not hallucinate any dependencies to external files. You do not need to include JavaScript scripts for dynamic interactions. - Pay attention to things like size, text, position, and color of all the elements, as well as the overall layout. - Respond with the content of the HTML+CSS file: - {instructions}"""), - HumanMessagePromptTemplate.from_template( - template=[ - {"type": "image_url", "image_url": {"url": "{image_url}"}}, - ] - ), - ] - - html_response = await call_llm( - template=prompt_messages, - params={ - "instructions": self.html_response_parser.get_format_instructions(), - "image_url": f"data:image/jpeg;base64,{synapse.base64_image}", - }, - output_parser=self.html_response_parser, + html_response = await openai_call( + messages = [ + { + "role": "system", + "content": """You are an expert web developer who specializes in HTML and CSS. + A user will provide you with a screenshot of a webpage, along with all texts that they want to put on the webpage. + You need to return a single html file that uses HTML and CSS to reproduce the given website. + Include all CSS code in the HTML file itself. + If it involves any images, use "rick.jpg" as the placeholder. + Some images on the webpage are replaced with a blue rectangle as the placeholder, use "rick.jpg" for those as well. + Do not hallucinate any dependencies to external files. You do not need to include JavaScript scripts for dynamic interactions. + Pay attention to things like size, text, position, and color of all the elements, as well as the overall layout. + Respond with the content of the HTML+CSS file: + """ + }, + { + "role": "user", + "content": [{ + "type": "image_url", + "image_url": { + "url": f"data:image/jpeg;base64,{synapse.base64_image}", + }, + }], + }, + ], + response_format = HTML, ) - synapse.html = html_response["html"] + synapse.html = html_response.html return synapse except Exception as e: bt.logging.error(f"Error in OpenaiMiner forward_image: {e}") diff --git a/pyproject.toml b/pyproject.toml index 618ac870..472aeb3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,8 +16,6 @@ dependencies = [ "duckduckgo_search", "datasets", "einops", - "langchain", - "langchain-openai", "lxml==5.3.0", "matplotlib-inline==0.1.7", "nltk", diff --git a/tests/test_huggingface_dataset.py b/tests/test_huggingface_dataset.py new file mode 100644 index 00000000..37745821 --- /dev/null +++ b/tests/test_huggingface_dataset.py @@ -0,0 +1,21 @@ +import sys +import os + +parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(parent_dir) + +from dotenv import load_dotenv, find_dotenv +load_dotenv(find_dotenv(filename=".env.validator")) + +import asyncio + +from webgenie.datasets.huggingface_dataset import HuggingfaceDataset + + +async def test_huggingface_dataset(): + dataset = HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text") + print(await dataset.generate_context()) + + +if __name__ == "__main__": + asyncio.run(test_huggingface_dataset()) \ No newline at end of file diff --git a/tests/test_llms.py b/tests/test_llms.py new file mode 100644 index 00000000..fb0909b9 --- /dev/null +++ b/tests/test_llms.py @@ -0,0 +1,34 @@ +import sys +import os + +parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(parent_dir) + +from dotenv import load_dotenv, find_dotenv +load_dotenv(find_dotenv(filename=".env.validator")) + +import asyncio +from pydantic import BaseModel + +from webgenie.helpers.llms import openai_call + + +class HTML(BaseModel): + html: str + + +async def test_openai_call(): + result = await openai_call( + messages = [ + {"role": "system", "content": "You are an expert web developer who specializes in HTML and CSS. A user will provide you with the webpage requirements. You need to return a single html file that uses HTML and CSS to satisfy the requirements. Include all CSS code in the HTML file itself. If it involves any images, use 'rick.jpg' as the placeholder. Do not hallucinate any dependencies to external files. You do not need to include JavaScript scripts for dynamic interactions. Pay attention to things like size, text, position, and color of all the elements, as well as the overall layout. Respond with the content of the HTML+CSS file:"}, + {"role": "user", "content": "Create a webpage with a red background and a blue rectangle in the center."}, + ], + response_format = HTML, + ) + print(result) + + +if __name__ == "__main__": + asyncio.run(test_openai_call()) + + \ No newline at end of file diff --git a/tests/test_openai_miner.py b/tests/test_openai_miner.py new file mode 100644 index 00000000..f0c7e8e5 --- /dev/null +++ b/tests/test_openai_miner.py @@ -0,0 +1,32 @@ +import sys +import os + +parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(parent_dir) + +from dotenv import load_dotenv, find_dotenv +load_dotenv(find_dotenv(filename=".env.validator")) + +import asyncio + +from webgenie.helpers.images import image_to_base64 +from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse + +from neurons.miners.openai_miner import OpenaiMiner + + +async def test_openai_miner(): + miner = OpenaiMiner(neuron = None) + # result = await miner.forward_text(synapse = WebgenieTextSynapse(prompt = "Create a webpage with a red background and a blue rectangle in the center.")) + # print(result) + + result = await miner.forward_image( + synapse = WebgenieImageSynapse( + base64_image = image_to_base64("debug_images/image_20250106_225751.png"), + prompt = "Create a webpage with a red background and a blue rectangle in the center." + ) + ) + print(result) + +if __name__ == "__main__": + asyncio.run(test_openai_miner()) \ No newline at end of file diff --git a/uv.lock b/uv.lock index 4cb2c1bb..4e8c84fc 100644 --- a/uv.lock +++ b/uv.lock @@ -952,15 +952,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252 }, ] -[[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 = "docker-pycreds" version = "0.4.0" @@ -1318,34 +1309,6 @@ 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 = "huggingface-hub" version = "0.27.1" @@ -1394,65 +1357,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, ] -[[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" @@ -1462,27 +1366,6 @@ 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 = "jsonpatch" -version = "1.33" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jsonpointer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898 }, -] - -[[package]] -name = "jsonpointer" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, -] - [[package]] name = "kiwisolver" version = "1.4.8" @@ -1570,175 +1453,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762 }, ] -[[package]] -name = "langchain" -version = "0.0.27" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "pydantic" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "sqlalchemy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/62/53/a975d63f52873daeb2563621240a79ea1368a85a3d94b4231cf750a85048/langchain-0.0.27.tar.gz", hash = "sha256:cdaca3a61eb57a76bef990ce3c4615ac06873e83f100bf9c485f6c36e29ed95c", size = 72304 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/fd/f2aa39f8e63a6fbacf2e7be820b846c27b1e5830af9c2e2e208801b6c07f/langchain-0.0.27-py3-none-any.whl", hash = "sha256:a39260732b877b223869151db677c08b4b78fb4611c59fc5918f314d0a9d4227", size = 124889 }, -] - -[[package]] -name = "langchain-core" -version = "0.2.8" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", -] -dependencies = [ - { name = "jsonpatch", marker = "python_full_version >= '3.12.4'" }, - { name = "langsmith", version = "0.1.81", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12.4'" }, - { name = "packaging", marker = "python_full_version >= '3.12.4'" }, - { name = "pydantic", marker = "python_full_version >= '3.12.4'" }, - { name = "pyyaml", marker = "python_full_version >= '3.12.4'" }, - { name = "tenacity", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12.4'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5c/40/32338a7cb615e3e4c0e06e7f5ae04fff648364c378a44440f4fb0b296210/langchain_core-0.2.8.tar.gz", hash = "sha256:2db866a4514672c4875b69d5590aa2ed50aa0d144874268bef68d74b5e7f33f9", size = 248197 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/55/08/14620ff398cdcbc30ad0cba0fa71c89f7317a483f726970dc1a33dd84822/langchain_core-0.2.8-py3-none-any.whl", hash = "sha256:172c81c858dc1f3123cc72b7e44e10f44c92f8a761cae18c364081f6c208e9f6", size = 315757 }, -] - -[[package]] -name = "langchain-core" -version = "0.3.29" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", -] -dependencies = [ - { name = "jsonpatch", marker = "python_full_version < '3.12.4'" }, - { name = "langsmith", version = "0.2.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12.4'" }, - { name = "packaging", marker = "python_full_version < '3.12.4'" }, - { name = "pydantic", marker = "python_full_version < '3.12.4'" }, - { name = "pyyaml", marker = "python_full_version < '3.12.4'" }, - { name = "tenacity", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12.4'" }, - { name = "typing-extensions", marker = "python_full_version < '3.12.4'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/05/8fc844c2f79a2c30845de2db948de1cf7c17b94f419f7ae7b616d628a54f/langchain_core-0.3.29.tar.gz", hash = "sha256:773d6aeeb612e7ce3d996c0be403433d8c6a91e77bbb7a7461c13e15cfbe5b06", size = 330785 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/4f/fe1de63f6fc1ac7af3ba4ae12d420af1a19f7893b5fcb72856b9fc67f650/langchain_core-0.3.29-py3-none-any.whl", hash = "sha256:817db1474871611a81105594a3e4d11704949661008e455a10e38ca9ff601a1a", size = 411593 }, -] - -[[package]] -name = "langchain-openai" -version = "0.1.14" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", -] -dependencies = [ - { name = "langchain-core", version = "0.2.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12.4'" }, - { name = "openai", marker = "python_full_version >= '3.12.4'" }, - { name = "tiktoken", marker = "python_full_version >= '3.12.4'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/54/19/0e1502ff1812381ea37accb652d88c32c701de449be1b90b7e9cda0912f9/langchain_openai-0.1.14.tar.gz", hash = "sha256:1f13d6041e8bedddf6eb47ccad7416e05af38fa169324f7f1bdf4f385780f8d8", size = 39611 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/06/d200c9e3f8f17bb50a07df9b7c92894ad5e0c1d65729ea0e438a36371858/langchain_openai-0.1.14-py3-none-any.whl", hash = "sha256:fcd34cc5b5713798908a5828d364b4426e3b1afccbd564d344e5477acb86634a", size = 45938 }, -] - -[[package]] -name = "langchain-openai" -version = "0.2.14" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", -] -dependencies = [ - { name = "langchain-core", version = "0.3.29", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12.4'" }, - { name = "openai", marker = "python_full_version < '3.12.4'" }, - { name = "tiktoken", marker = "python_full_version < '3.12.4'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e5/fd/8256eba9a159f95a13c5bf7f1f49683de93b3876585b768e6be5dc3a5765/langchain_openai-0.2.14.tar.gz", hash = "sha256:7a514f309e356b182a337c0ed36ab3fbe34d9834a235a3b85cb7f91ae775d978", size = 43647 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/54/63c8264d7dbc3bf31ba61bf97740fdd76386b2d4f9a58f58afd3961ce7d7/langchain_openai-0.2.14-py3-none-any.whl", hash = "sha256:d232496662f79ece9a11caf7d798ba863e559c771bc366814f7688e0fe664fe8", size = 50876 }, -] - -[[package]] -name = "langsmith" -version = "0.1.81" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", -] -dependencies = [ - { name = "orjson", marker = "python_full_version >= '3.12.4'" }, - { name = "pydantic", marker = "python_full_version >= '3.12.4'" }, - { name = "requests", marker = "python_full_version >= '3.12.4'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/46/469638058e3ee19194fc02ca483bb530b1e417f3fb903a9d28951ec87b1d/langsmith-0.1.81.tar.gz", hash = "sha256:585ef3a2251380bd2843a664c9a28da4a7d28432e3ee8bcebf291ffb8e1f0af0", size = 117828 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ab/0ea685c54c448e5c7dbdab674c76c36af982859a7011a1b98d67e7d5ab20/langsmith-0.1.81-py3-none-any.whl", hash = "sha256:3251d823225eef23ee541980b9d9e506367eabbb7f985a086b5d09e8f78ba7e9", size = 127090 }, -] - -[[package]] -name = "langsmith" -version = "0.2.10" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", -] -dependencies = [ - { name = "httpx", marker = "python_full_version < '3.12.4'" }, - { name = "orjson", marker = "python_full_version < '3.12.4' and platform_python_implementation != 'PyPy'" }, - { name = "pydantic", marker = "python_full_version < '3.12.4'" }, - { name = "requests", marker = "python_full_version < '3.12.4'" }, - { name = "requests-toolbelt", marker = "python_full_version < '3.12.4'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d4/f8/d642c101267101ddeb44f898a98a5700f90c8959a4119c341a9678fe18cc/langsmith-0.2.10.tar.gz", hash = "sha256:153c7b3ccbd823528ff5bec84801e7e50a164e388919fc583252df5b27dd7830", size = 314154 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/91/e72d13f6b57a0ea9d884ab1d3388f544d7fe3354dbe1d4dd67678693a9fd/langsmith-0.2.10-py3-none-any.whl", hash = "sha256:b02f2f174189ff72e54c88b1aa63343defd6f0f676c396a690c63a4b6495dcc2", size = 326432 }, -] - [[package]] name = "levenshtein" version = "0.26.1" @@ -2426,25 +2140,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 }, ] -[[package]] -name = "openai" -version = "1.59.5" -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/7c/b3/a99ff4f8034383147f853200ff5f6df63a8407a0061d6b3ff47914b94f4c/openai-1.59.5.tar.gz", hash = "sha256:9886e77c02dad9dc6a7b67a11ab372a56842a9b5d376aa476672175ab10e83a0", size = 344773 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/a2/a64f495c016234ca4269005b19eb9193a925dcad01af95eb8fea3de4ee9c/openai-1.59.5-py3-none-any.whl", hash = "sha256:e646b44856b0dda9345d3c43639e056334d792d1690e99690313c0ef7ca4d8cc", size = 454815 }, -] - [[package]] name = "opencv-python" version = "4.10.0.84" @@ -2462,62 +2157,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/6c/fab8113424af5049f85717e8e527ca3773299a3c6b02506e66436e19874f/opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe", size = 38842521 }, ] -[[package]] -name = "orjson" -version = "3.10.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/f7/3219b56f47b4f5e864fb11cdf4ac0aaa3de608730ad2dc4c6e16382f35ec/orjson-3.10.14.tar.gz", hash = "sha256:cf31f6f071a6b8e7aa1ead1fa27b935b48d00fbfa6a28ce856cfff2d5dd68eed", size = 5282116 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/62/64348b8b29a14c7342f6aa45c8be0a87fdda2ce7716bc123717376537077/orjson-3.10.14-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:849ea7845a55f09965826e816cdc7689d6cf74fe9223d79d758c714af955bcb6", size = 249439 }, - { url = "https://files.pythonhosted.org/packages/9f/51/48f4dfbca7b4db630316b170db4a150a33cd405650258bd62a2d619b43b4/orjson-3.10.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5947b139dfa33f72eecc63f17e45230a97e741942955a6c9e650069305eb73d", size = 135811 }, - { url = "https://files.pythonhosted.org/packages/a1/1c/e18770843e6d045605c8e00a1be801da5668fa934b323b0492a49c9dee4f/orjson-3.10.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cde6d76910d3179dae70f164466692f4ea36da124d6fb1a61399ca589e81d69a", size = 150154 }, - { url = "https://files.pythonhosted.org/packages/51/1e/3817dc79164f1fc17fc53102f74f62d31f5f4ec042abdd24d94c5e06e51c/orjson-3.10.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6dfbaeb7afa77ca608a50e2770a0461177b63a99520d4928e27591b142c74b1", size = 139740 }, - { url = "https://files.pythonhosted.org/packages/ff/fc/fbf9e25448f7a2d67c1a2b6dad78a9340666bf9fda3339ff59b1e93f0b6f/orjson-3.10.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa45e489ef80f28ff0e5ba0a72812b8cfc7c1ef8b46a694723807d1b07c89ebb", size = 154479 }, - { url = "https://files.pythonhosted.org/packages/d4/df/c8b7ea21ff658f6a9a26d562055631c01d445bda5eb613c02c7d0934607d/orjson-3.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5007abfdbb1d866e2aa8990bd1c465f0f6da71d19e695fc278282be12cffa5", size = 130414 }, - { url = "https://files.pythonhosted.org/packages/df/f7/e29c2d42bef8fbf696a5e54e6339b0b9ea5179326950fee6ae80acf59d09/orjson-3.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1b49e2af011c84c3f2d541bb5cd1e3c7c2df672223e7e3ea608f09cf295e5f8a", size = 138545 }, - { url = "https://files.pythonhosted.org/packages/8e/97/afdf2908fe8eaeecb29e97fa82dc934f275acf330e5271def0b8fbac5478/orjson-3.10.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:164ac155109226b3a2606ee6dda899ccfbe6e7e18b5bdc3fbc00f79cc074157d", size = 130952 }, - { url = "https://files.pythonhosted.org/packages/4a/dd/04e01c1305694f47e9794c60ec7cece02e55fa9d57c5d72081eaaa62ad1d/orjson-3.10.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6b1225024cf0ef5d15934b5ffe9baf860fe8bc68a796513f5ea4f5056de30bca", size = 414673 }, - { url = "https://files.pythonhosted.org/packages/fa/12/28c4d5f6a395ac9693b250f0662366968c47fc99c8f3cd803a65b1f5ba46/orjson-3.10.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d6546e8073dc382e60fcae4a001a5a1bc46da5eab4a4878acc2d12072d6166d5", size = 141002 }, - { url = "https://files.pythonhosted.org/packages/21/f6/357cb167c2d2fd9542251cfd9f68681b67ed4dcdac82aa6ee2f4f3ab952e/orjson-3.10.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f1d2942605c894162252d6259b0121bf1cb493071a1ea8cb35d79cb3e6ac5bc", size = 129626 }, - { url = "https://files.pythonhosted.org/packages/df/07/d9062353500df9db8bfa7c6a5982687c97d0b69a5b158c4166d407ac94e2/orjson-3.10.14-cp310-cp310-win32.whl", hash = "sha256:397083806abd51cf2b3bbbf6c347575374d160331a2d33c5823e22249ad3118b", size = 142429 }, - { url = "https://files.pythonhosted.org/packages/50/ba/6ba2bf69ac0526d143aebe78bc39e6e5fbb51d5336fbc5efb9aab6687cd9/orjson-3.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:fa18f949d3183a8d468367056be989666ac2bef3a72eece0bade9cdb733b3c28", size = 133512 }, - { url = "https://files.pythonhosted.org/packages/bf/18/26721760368e12b691fb6811692ed21ae5275ea918db409ba26866cacbe8/orjson-3.10.14-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f506fd666dd1ecd15a832bebc66c4df45c1902fd47526292836c339f7ba665a9", size = 249437 }, - { url = "https://files.pythonhosted.org/packages/d5/5b/2adfe7cc301edeb3bffc1942956659c19ec00d51a21c53c17c0767bebf47/orjson-3.10.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efe5fd254cfb0eeee13b8ef7ecb20f5d5a56ddda8a587f3852ab2cedfefdb5f6", size = 135812 }, - { url = "https://files.pythonhosted.org/packages/8a/68/07df7787fd9ff6dba815b2d793eec5e039d288fdf150431ed48a660bfcbb/orjson-3.10.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ddc8c866d7467f5ee2991397d2ea94bcf60d0048bdd8ca555740b56f9042725", size = 150153 }, - { url = "https://files.pythonhosted.org/packages/02/71/f68562734461b801b53bacd5365e079dcb3c78656a662f0639494880e522/orjson-3.10.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af8e42ae4363773658b8d578d56dedffb4f05ceeb4d1d4dd3fb504950b45526", size = 139742 }, - { url = "https://files.pythonhosted.org/packages/04/03/1355fb27652582f00d3c62e93a32b982fa42bc31d2e07f0a317867069096/orjson-3.10.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84dd83110503bc10e94322bf3ffab8bc49150176b49b4984dc1cce4c0a993bf9", size = 154479 }, - { url = "https://files.pythonhosted.org/packages/7c/47/1c2a840f27715e8bc2bbafffc851512ede6e53483593eded190919bdcaf4/orjson-3.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36f5bfc0399cd4811bf10ec7a759c7ab0cd18080956af8ee138097d5b5296a95", size = 130413 }, - { url = "https://files.pythonhosted.org/packages/dd/b2/5bb51006cbae85b052d1bbee7ff43ae26fa155bb3d31a71b0c07d384d5e3/orjson-3.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868943660fb2a1e6b6b965b74430c16a79320b665b28dd4511d15ad5038d37d5", size = 138545 }, - { url = "https://files.pythonhosted.org/packages/79/30/7841a5dd46bb46b8e868791d5469c9d4788d3e26b7e69d40256647997baf/orjson-3.10.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33449c67195969b1a677533dee9d76e006001213a24501333624623e13c7cc8e", size = 130953 }, - { url = "https://files.pythonhosted.org/packages/08/49/720e7c2040c0f1df630a36d83d449bd7e4d4471071d5ece47a4f7211d570/orjson-3.10.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e4c9f60f9fb0b5be66e416dcd8c9d94c3eabff3801d875bdb1f8ffc12cf86905", size = 414675 }, - { url = "https://files.pythonhosted.org/packages/50/b0/ca7619f34280e7dcbd50dbc9c5fe5200c12cd7269b8858652beb3887483f/orjson-3.10.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0de4d6315cfdbd9ec803b945c23b3a68207fd47cbe43626036d97e8e9561a436", size = 141004 }, - { url = "https://files.pythonhosted.org/packages/75/1b/7548e3a711543f438e87a4349e00439ab7f37807942e5659f29363f35765/orjson-3.10.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83adda3db595cb1a7e2237029b3249c85afbe5c747d26b41b802e7482cb3933e", size = 129629 }, - { url = "https://files.pythonhosted.org/packages/b0/1e/4930a6ff46debd6be1ff18e869b7bc43a7ad762c865610b7e745038d6f68/orjson-3.10.14-cp311-cp311-win32.whl", hash = "sha256:998019ef74a4997a9d741b1473533cdb8faa31373afc9849b35129b4b8ec048d", size = 142430 }, - { url = "https://files.pythonhosted.org/packages/28/e0/6cc1cd1dfde36555e81ac869f7847e86bb11c27f97b72fde2f1509b12163/orjson-3.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:9d034abdd36f0f0f2240f91492684e5043d46f290525d1117712d5b8137784eb", size = 133516 }, - { url = "https://files.pythonhosted.org/packages/8c/dc/dc5a882be016ee8688bd867ad3b4e3b2ab039d91383099702301a1adb6ac/orjson-3.10.14-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2ad4b7e367efba6dc3f119c9a0fcd41908b7ec0399a696f3cdea7ec477441b09", size = 249396 }, - { url = "https://files.pythonhosted.org/packages/f0/95/4c23ff5c0505cd687928608e0b7910ccb44ce59490079e1c17b7610aa0d0/orjson-3.10.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f496286fc85e93ce0f71cc84fc1c42de2decf1bf494094e188e27a53694777a7", size = 135689 }, - { url = "https://files.pythonhosted.org/packages/ad/39/b4bdd19604dce9d6509c4d86e8e251a1373a24204b4c4169866dcecbe5f5/orjson-3.10.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7f189bbfcded40e41a6969c1068ba305850ba016665be71a217918931416fbf", size = 150136 }, - { url = "https://files.pythonhosted.org/packages/1d/92/7b9bad96353abd3e89947960252dcf1022ce2df7f29056e434de05e18b6d/orjson-3.10.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cc8204f0b75606869c707da331058ddf085de29558b516fc43c73ee5ee2aadb", size = 139766 }, - { url = "https://files.pythonhosted.org/packages/a6/bd/abb13c86540b7a91b40d7d9f8549d03a026bc22d78fa93f71d68b8f4c36e/orjson-3.10.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deaa2899dff7f03ab667e2ec25842d233e2a6a9e333efa484dfe666403f3501c", size = 154533 }, - { url = "https://files.pythonhosted.org/packages/c0/02/0bcb91ec9c7143012359983aca44f567f87df379957cd4af11336217b12f/orjson-3.10.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1c3ea52642c9714dc6e56de8a451a066f6d2707d273e07fe8a9cc1ba073813d", size = 130658 }, - { url = "https://files.pythonhosted.org/packages/b4/1e/b304596bb1f800d47d6e92305bd09f0eef693ed4f7b2095db63f9808b229/orjson-3.10.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d3f9ed72e7458ded9a1fb1b4d4ed4c4fdbaf82030ce3f9274b4dc1bff7ace2b", size = 138546 }, - { url = "https://files.pythonhosted.org/packages/56/c7/65d72b22080186ef618a46afeb9386e20056f3237664090f3a2f8da1cd6d/orjson-3.10.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07520685d408a2aba514c17ccc16199ff2934f9f9e28501e676c557f454a37fe", size = 130774 }, - { url = "https://files.pythonhosted.org/packages/4d/85/1ab35a832f32b37ccd673721e845cf302f23453603112255af611c91d1d1/orjson-3.10.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:76344269b550ea01488d19a2a369ab572c1ac4449a72e9f6ac0d70eb1cbfb953", size = 414649 }, - { url = "https://files.pythonhosted.org/packages/d1/7d/1d6575f779bab8fe698fa6d52e8aa3aa0a9fca4885d0bf6197700455713a/orjson-3.10.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e2979d0f2959990620f7e62da6cd954e4620ee815539bc57a8ae46e2dacf90e3", size = 141060 }, - { url = "https://files.pythonhosted.org/packages/f8/26/68513e28b3bd1d7633318ed2818e86d1bfc8b782c87c520c7b363092837f/orjson-3.10.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03f61ca3674555adcb1aa717b9fc87ae936aa7a63f6aba90a474a88701278780", size = 129798 }, - { url = "https://files.pythonhosted.org/packages/44/ca/020fb99c98ff7267ba18ce798ff0c8c3aa97cd949b611fc76cad3c87e534/orjson-3.10.14-cp312-cp312-win32.whl", hash = "sha256:d5075c54edf1d6ad81d4c6523ce54a748ba1208b542e54b97d8a882ecd810fd1", size = 142524 }, - { url = "https://files.pythonhosted.org/packages/70/7f/f2d346819a273653825e7c92dc26418c8da506003c9fc1dfe8157e733b2e/orjson-3.10.14-cp312-cp312-win_amd64.whl", hash = "sha256:175cafd322e458603e8ce73510a068d16b6e6f389c13f69bf16de0e843d7d406", size = 133663 }, - { url = "https://files.pythonhosted.org/packages/46/bb/f1b037d89f580c79eda0940772384cc226a697be1cb4eb94ae4e792aa34c/orjson-3.10.14-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:0905ca08a10f7e0e0c97d11359609300eb1437490a7f32bbaa349de757e2e0c7", size = 249333 }, - { url = "https://files.pythonhosted.org/packages/e4/72/12958a073cace3f8acef0f9a30739d95f46bbb1544126fecad11527d4508/orjson-3.10.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92d13292249f9f2a3e418cbc307a9fbbef043c65f4bd8ba1eb620bc2aaba3d15", size = 125038 }, - { url = "https://files.pythonhosted.org/packages/c0/ae/461f78b1c98de1bc034af88bc21c6a792cc63373261fbc10a6ee560814fa/orjson-3.10.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90937664e776ad316d64251e2fa2ad69265e4443067668e4727074fe39676414", size = 130604 }, - { url = "https://files.pythonhosted.org/packages/ae/d2/17f50513f56bff7898840fddf7fb88f501305b9b2605d2793ff224789665/orjson-3.10.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9ed3d26c4cb4f6babaf791aa46a029265850e80ec2a566581f5c2ee1a14df4f1", size = 130756 }, - { url = "https://files.pythonhosted.org/packages/fa/bc/673856e4af94c9890dfd8e2054c05dc2ddc16d1728c2aa0c5bd198943105/orjson-3.10.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:56ee546c2bbe9599aba78169f99d1dc33301853e897dbaf642d654248280dc6e", size = 414613 }, - { url = "https://files.pythonhosted.org/packages/09/01/08c5b69b0756dd1790fcffa569d6a28dedcd7b97f825e4b46537b788908c/orjson-3.10.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:901e826cb2f1bdc1fcef3ef59adf0c451e8f7c0b5deb26c1a933fb66fb505eae", size = 141010 }, - { url = "https://files.pythonhosted.org/packages/5b/98/72883bb6cf88fd364996e62d2026622ca79bfb8dbaf96ccdd2018ada25b1/orjson-3.10.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:26336c0d4b2d44636e1e1e6ed1002f03c6aae4a8a9329561c8883f135e9ff010", size = 129732 }, - { url = "https://files.pythonhosted.org/packages/e4/99/347418f7ef56dcb478ba131a6112b8ddd5b747942652b6e77a53155a7e21/orjson-3.10.14-cp313-cp313-win32.whl", hash = "sha256:e2bc525e335a8545c4e48f84dd0328bc46158c9aaeb8a1c2276546e94540ea3d", size = 142504 }, - { url = "https://files.pythonhosted.org/packages/59/ac/5e96cad01083015f7bfdb02ccafa489da8e6caa7f4c519e215f04d2bd856/orjson-3.10.14-cp313-cp313-win_amd64.whl", hash = "sha256:eca04dfd792cedad53dc9a917da1a522486255360cb4e77619343a20d9f35364", size = 133388 }, -] - [[package]] name = "packaging" version = "24.2" @@ -3478,18 +3117,6 @@ 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 = "requests-toolbelt" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests", marker = "python_full_version < '3.12.4'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 }, -] - [[package]] name = "resolvelib" version = "1.0.1" @@ -3899,44 +3526,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/fe/81695a1aa331a842b582453b605175f419fe8540355886031328089d840a/sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8", size = 6189177 }, ] -[[package]] -name = "tenacity" -version = "8.5.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165 }, -] - -[[package]] -name = "tenacity" -version = "9.0.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/94/91fccdb4b8110642462e653d5dcb27e7b674742ad68efd146367da7bdb10/tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b", size = 47421 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/cb/b86984bed139586d01532a587464b5805f12e397594f19f931c4c2fbfa61/tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539", size = 28169 }, -] - [[package]] name = "termcolor" version = "2.5.0" @@ -3955,42 +3544,6 @@ 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 = "tiktoken" -version = "0.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "regex" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/37/02/576ff3a6639e755c4f70997b2d315f56d6d71e0d046f4fb64cb81a3fb099/tiktoken-0.8.0.tar.gz", hash = "sha256:9ccbb2740f24542534369c5635cfd9b2b3c2490754a78ac8831d99f89f94eeb2", size = 35107 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/ba/a35fad753bbca8ba0cc1b0f3402a70256a110ced7ac332cf84ba89fc87ab/tiktoken-0.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b07e33283463089c81ef1467180e3e00ab00d46c2c4bbcef0acab5f771d6695e", size = 1039905 }, - { url = "https://files.pythonhosted.org/packages/91/05/13dab8fd7460391c387b3e69e14bf1e51ff71fe0a202cd2933cc3ea93fb6/tiktoken-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9269348cb650726f44dd3bbb3f9110ac19a8dcc8f54949ad3ef652ca22a38e21", size = 982417 }, - { url = "https://files.pythonhosted.org/packages/e9/98/18ec4a8351a6cf4537e40cd6e19a422c10cce1ef00a2fcb716e0a96af58b/tiktoken-0.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e13f37bc4ef2d012731e93e0fef21dc3b7aea5bb9009618de9a4026844e560", size = 1144915 }, - { url = "https://files.pythonhosted.org/packages/2e/28/cf3633018cbcc6deb7805b700ccd6085c9a5a7f72b38974ee0bffd56d311/tiktoken-0.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f13d13c981511331eac0d01a59b5df7c0d4060a8be1e378672822213da51e0a2", size = 1177221 }, - { url = "https://files.pythonhosted.org/packages/57/81/8a5be305cbd39d4e83a794f9e80c7f2c84b524587b7feb27c797b2046d51/tiktoken-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6b2ddbc79a22621ce8b1166afa9f9a888a664a579350dc7c09346a3b5de837d9", size = 1237398 }, - { url = "https://files.pythonhosted.org/packages/dc/da/8d1cc3089a83f5cf11c2e489332752981435280285231924557350523a59/tiktoken-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d8c2d0e5ba6453a290b86cd65fc51fedf247e1ba170191715b049dac1f628005", size = 884215 }, - { url = "https://files.pythonhosted.org/packages/f6/1e/ca48e7bfeeccaf76f3a501bd84db1fa28b3c22c9d1a1f41af9fb7579c5f6/tiktoken-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d622d8011e6d6f239297efa42a2657043aaed06c4f68833550cac9e9bc723ef1", size = 1039700 }, - { url = "https://files.pythonhosted.org/packages/8c/f8/f0101d98d661b34534769c3818f5af631e59c36ac6d07268fbfc89e539ce/tiktoken-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2efaf6199717b4485031b4d6edb94075e4d79177a172f38dd934d911b588d54a", size = 982413 }, - { url = "https://files.pythonhosted.org/packages/ac/3c/2b95391d9bd520a73830469f80a96e3790e6c0a5ac2444f80f20b4b31051/tiktoken-0.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5637e425ce1fc49cf716d88df3092048359a4b3bbb7da762840426e937ada06d", size = 1144242 }, - { url = "https://files.pythonhosted.org/packages/01/c4/c4a4360de845217b6aa9709c15773484b50479f36bb50419c443204e5de9/tiktoken-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fb0e352d1dbe15aba082883058b3cce9e48d33101bdaac1eccf66424feb5b47", size = 1176588 }, - { url = "https://files.pythonhosted.org/packages/f8/a3/ef984e976822cd6c2227c854f74d2e60cf4cd6fbfca46251199914746f78/tiktoken-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56edfefe896c8f10aba372ab5706b9e3558e78db39dd497c940b47bf228bc419", size = 1237261 }, - { url = "https://files.pythonhosted.org/packages/1e/86/eea2309dc258fb86c7d9b10db536434fc16420feaa3b6113df18b23db7c2/tiktoken-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:326624128590def898775b722ccc327e90b073714227175ea8febbc920ac0a99", size = 884537 }, - { url = "https://files.pythonhosted.org/packages/c1/22/34b2e136a6f4af186b6640cbfd6f93400783c9ef6cd550d9eab80628d9de/tiktoken-0.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:881839cfeae051b3628d9823b2e56b5cc93a9e2efb435f4cf15f17dc45f21586", size = 1039357 }, - { url = "https://files.pythonhosted.org/packages/04/d2/c793cf49c20f5855fd6ce05d080c0537d7418f22c58e71f392d5e8c8dbf7/tiktoken-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fe9399bdc3f29d428f16a2f86c3c8ec20be3eac5f53693ce4980371c3245729b", size = 982616 }, - { url = "https://files.pythonhosted.org/packages/b3/a1/79846e5ef911cd5d75c844de3fa496a10c91b4b5f550aad695c5df153d72/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a58deb7075d5b69237a3ff4bb51a726670419db6ea62bdcd8bd80c78497d7ab", size = 1144011 }, - { url = "https://files.pythonhosted.org/packages/26/32/e0e3a859136e95c85a572e4806dc58bf1ddf651108ae8b97d5f3ebe1a244/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2908c0d043a7d03ebd80347266b0e58440bdef5564f84f4d29fb235b5df3b04", size = 1175432 }, - { url = "https://files.pythonhosted.org/packages/c7/89/926b66e9025b97e9fbabeaa59048a736fe3c3e4530a204109571104f921c/tiktoken-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:294440d21a2a51e12d4238e68a5972095534fe9878be57d905c476017bff99fc", size = 1236576 }, - { url = "https://files.pythonhosted.org/packages/45/e2/39d4aa02a52bba73b2cd21ba4533c84425ff8786cc63c511d68c8897376e/tiktoken-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:d8f3192733ac4d77977432947d563d7e1b310b96497acd3c196c9bddb36ed9db", size = 883824 }, - { url = "https://files.pythonhosted.org/packages/e3/38/802e79ba0ee5fcbf240cd624143f57744e5d411d2e9d9ad2db70d8395986/tiktoken-0.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:02be1666096aff7da6cbd7cdaa8e7917bfed3467cd64b38b1f112e96d3b06a24", size = 1039648 }, - { url = "https://files.pythonhosted.org/packages/b1/da/24cdbfc302c98663fbea66f5866f7fa1048405c7564ab88483aea97c3b1a/tiktoken-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94ff53c5c74b535b2cbf431d907fc13c678bbd009ee633a2aca269a04389f9a", size = 982763 }, - { url = "https://files.pythonhosted.org/packages/e4/f0/0ecf79a279dfa41fc97d00adccf976ecc2556d3c08ef3e25e45eb31f665b/tiktoken-0.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b231f5e8982c245ee3065cd84a4712d64692348bc609d84467c57b4b72dcbc5", size = 1144417 }, - { url = "https://files.pythonhosted.org/packages/ab/d3/155d2d4514f3471a25dc1d6d20549ef254e2aa9bb5b1060809b1d3b03d3a/tiktoken-0.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4177faa809bd55f699e88c96d9bb4635d22e3f59d635ba6fd9ffedf7150b9953", size = 1175108 }, - { url = "https://files.pythonhosted.org/packages/19/eb/5989e16821ee8300ef8ee13c16effc20dfc26c777d05fbb6825e3c037b81/tiktoken-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5376b6f8dc4753cd81ead935c5f518fa0fbe7e133d9e25f648d8c4dabdd4bad7", size = 1236520 }, - { url = "https://files.pythonhosted.org/packages/40/59/14b20465f1d1cb89cfbc96ec27e5617b2d41c79da12b5e04e96d689be2a7/tiktoken-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:18228d624807d66c87acd8f25fc135665617cab220671eb65b50f5d70fa51f69", size = 883849 }, -] - [[package]] name = "tinycss2" version = "1.4.0" @@ -4318,9 +3871,6 @@ dependencies = [ { name = "ddt" }, { name = "duckduckgo-search" }, { name = "einops" }, - { name = "langchain" }, - { name = "langchain-openai", version = "0.1.14", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12.4'" }, - { name = "langchain-openai", version = "0.2.14", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12.4'" }, { name = "lxml" }, { name = "matplotlib-inline" }, { name = "nltk" }, @@ -4351,8 +3901,6 @@ requires-dist = [ { name = "ddt", specifier = "==1.6.0" }, { name = "duckduckgo-search" }, { name = "einops" }, - { name = "langchain" }, - { name = "langchain-openai" }, { name = "lxml", specifier = "==5.3.0" }, { name = "matplotlib-inline", specifier = "==0.1.7" }, { name = "nltk" }, diff --git a/webgenie/datasets/huggingface_dataset.py b/webgenie/datasets/huggingface_dataset.py index 390f4474..9a3bc7d2 100644 --- a/webgenie/datasets/huggingface_dataset.py +++ b/webgenie/datasets/huggingface_dataset.py @@ -3,12 +3,10 @@ import bittensor as bt import random from datasets import load_dataset - -from langchain_core.output_parsers import JsonOutputParser -from langchain_core.pydantic_v1 import BaseModel, Field +from pydantic import BaseModel, Field from webgenie.datasets.dataset import Dataset, DatasetEntry -from webgenie.helpers.llms import call_llm +from webgenie.helpers.llms import openai_call from webgenie.prompts import PROMPT_MAKE_HTML_COMPLEX @@ -24,18 +22,17 @@ def __init__(self , **kwargs): self.dataset = load_dataset(dataset_name, split=split) self.html_column = html_column - self.output_parser = JsonOutputParser(pydantic_object=HTMLResponse) async def _make_html_complex(self, html: str)->str: bt.logging.info("Making HTML complex") - response = await call_llm( - template=[ - ("system", PROMPT_MAKE_HTML_COMPLEX), + response = await openai_call( + messages = [ + {"role": "system", "content": PROMPT_MAKE_HTML_COMPLEX}, + {"role": "user", "content": html}, ], - params={"html": html, "instructions": self.output_parser.get_format_instructions()}, - output_parser=self.output_parser + response_format = HTMLResponse, ) - return response["complex_html"] + return response.complex_html async def generate_context(self)->DatasetEntry: try: diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index 5e436161..f12e0ade 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -5,12 +5,10 @@ import bittensor as bt from typing import List - -from langchain_core.output_parsers import JsonOutputParser -from langchain_core.pydantic_v1 import BaseModel, Field +from pydantic import BaseModel, Field from webgenie.datasets.dataset import Dataset, DatasetEntry -from webgenie.helpers.llms import call_llm +from webgenie.helpers.llms import openai_call from webgenie.prompts import PROMPT_GEN_CONCEPT, PROMPT_GEN_HTML @@ -25,34 +23,28 @@ class HTMLResponse(BaseModel): class SyntheticDataset(Dataset): def __init__(self, has_ground_truth_html: bool = True): self.has_ground_truth_html = has_ground_truth_html - self.concept_parser = JsonOutputParser(pydantic_object=ConceptResponse) - self.html_parser = JsonOutputParser(pydantic_object=HTMLResponse) self.concepts = [] async def _generate_concepts(self): bt.logging.info("Generating concepts") - response = await call_llm( - template=[ - ("system", PROMPT_GEN_CONCEPT), + response = await openai_call( + messages = [ + {"role": "system", "content": PROMPT_GEN_CONCEPT}, ], - params={"instructions": self.concept_parser.get_format_instructions()}, - output_parser=self.concept_parser, + response_format = ConceptResponse, ) - return response["concepts"] + return response.concepts async def _generate_html(self, concept: str): bt.logging.info("Generating HTML from concept") - response = await call_llm( - template=[ - ("system", PROMPT_GEN_HTML), + response = await openai_call( + messages = [ + {"role": "system", "content": PROMPT_GEN_HTML}, + {"role": "user", "content": concept}, ], - params={ - "concept": concept, - "instructions": self.html_parser.get_format_instructions(), - }, - output_parser=self.html_parser, + response_format = HTMLResponse, ) - return response["html"] + return response.html async def generate_context(self)->DatasetEntry: bt.logging.info("Generating Synthetic context") diff --git a/webgenie/helpers/llms.py b/webgenie/helpers/llms.py index 262ccf0d..7b815878 100644 --- a/webgenie/helpers/llms.py +++ b/webgenie/helpers/llms.py @@ -1,28 +1,30 @@ import os import bittensor as bt -from langchain_core.prompts import ChatPromptTemplate -from langchain_openai import ChatOpenAI +from openai import AsyncOpenAI +model = os.getenv("LLM_MODEL_ID") +api_key = os.getenv("LLM_API_KEY") +base_url = os.getenv("LLM_MODEL_URL") -LLM = ChatOpenAI( - base_url=os.getenv("LLM_MODEL_URL"), - model=os.getenv("LLM_MODEL_ID"), - api_key=os.getenv("LLM_API_KEY"), - temperature=0.7, -) +if not api_key or not base_url or not model: + raise Exception("LLM_API_KEY, LLM_MODEL_URL, and LLM_MODEL_ID must be set") +client = AsyncOpenAI( + api_key=api_key, + base_url=base_url, +) -async def call_llm(template, params, output_parser, retries=3): - if not os.getenv("LLM_API_KEY"): - raise Exception("LLM_API_KEY is not set") - +async def openai_call(messages, response_format, retries=3): for _ in range(retries): try: - prompt = ChatPromptTemplate.from_messages(template) - chain = prompt | LLM | output_parser - return await chain.ainvoke(params) + completion = await client.beta.chat.completions.parse( + model=model, + messages= messages, + response_format=response_format, + ) + return completion.choices[0].message.parsed except Exception as e: - bt.logging.error(f"Error calling LLM: {e}") + bt.logging.error(f"Error calling OpenAI: {e}") continue - raise Exception("Failed to call LLM") \ No newline at end of file + raise Exception("Failed to call OpenAI") \ No newline at end of file diff --git a/webgenie/prompts.py b/webgenie/prompts.py index d830a875..f41200b4 100644 --- a/webgenie/prompts.py +++ b/webgenie/prompts.py @@ -6,8 +6,6 @@ {html} The following is the example of prompt: {prompt} - -{instructions} """ @@ -37,10 +35,6 @@ PROMPT_MAKE_HTML_COMPLEX = """ You are an HTML, CSS expert. I have an HTML code. I want you to make the html code more complex. -The following is the given html code: -{html} - -{instructions} """ diff --git a/webgenie/rewards/quality_reward.py b/webgenie/rewards/quality_reward.py index 8101518a..ce1228e0 100644 --- a/webgenie/rewards/quality_reward.py +++ b/webgenie/rewards/quality_reward.py @@ -3,46 +3,31 @@ import bittensor as bt import numpy as np +from pydantic import BaseModel, Field from typing import List -from langchain_core.output_parsers import JsonOutputParser -from langchain_core.pydantic_v1 import BaseModel, Field - -from webgenie.helpers.llms import call_llm +from webgenie.helpers.llms import openai_call from webgenie.prompts import PROMPT_QUALITY from webgenie.rewards.reward import Reward from webgenie.tasks import Task, Solution class ScoreResponse(BaseModel): - score: float = Field(default=0, description="The score of the html code") + score: float = Field(description="The score of the html code") class QualityReward(Reward): - def __init__(self): - self.prompt_response_parser = JsonOutputParser(pydantic_object=ScoreResponse) - + async def _get_score(self, solution: Solution) -> float: - response = await call_llm( - template=[ - ("system", PROMPT_QUALITY), + response = await openai_call( + messages = [ + {"role": "system", "content": PROMPT_QUALITY.format(html=solution.html)}, ], - params={ - "html": solution.html, - "instructions": self.prompt_response_parser.get_format_instructions(), - }, - output_parser=self.prompt_response_parser, + response_format = ScoreResponse, ) - return float(response["score"]) / 100 + return response.score / 100 async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: bt.logging.debug(f"Rewarding task in quality reward") - scores = [] - for solution in solutions: - score = await self._get_score(solution) - scores.append(score) + scores = [await self._get_score(solution) for solution in solutions] return np.array(scores) - - - - \ No newline at end of file diff --git a/webgenie/rewards/rtc_reward.py b/webgenie/rewards/rtc_reward.py index 331a48a0..62368129 100644 --- a/webgenie/rewards/rtc_reward.py +++ b/webgenie/rewards/rtc_reward.py @@ -5,12 +5,11 @@ import bert_score import os import numpy as np +from pydantic import BaseModel, Field from typing import List -from langchain_core.output_parsers import JsonOutputParser -from langchain_core.pydantic_v1 import BaseModel, Field -from webgenie.helpers.llms import call_llm +from webgenie.helpers.llms import openai_call from webgenie.prompts import PROMPT_RTC from webgenie.rewards.reward import Reward from webgenie.rewards.metrics import s_bert @@ -22,20 +21,13 @@ class PromptResponse(BaseModel): class RtcReward(Reward): - def __init__(self): - self.prompt_response_parser = JsonOutputParser(pydantic_object=PromptResponse) - + async def _get_prompt(self, task: Task, solution: Solution) -> str: - response = await call_llm( - template=[ - ("system", PROMPT_RTC), + response = await openai_call( + messages = [ + {"role": "system", "content": PROMPT_RTC.format(html=solution.html, prompt=task.prompt)}, ], - params={ - "html": solution.html, - "prompt": task.prompt, - "instructions": self.prompt_response_parser.get_format_instructions(), - }, - output_parser=self.prompt_response_parser, + response_format = PromptResponse, ) return response["prompt"] @@ -48,8 +40,3 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: #P, R, F1 = bert_score.score(original_prompts, miner_prompts, lang='en') scores = s_bert.score(original_prompts, miner_prompts) return np.array(scores) - - - - - \ No newline at end of file From bcc9f7a6312402103c666be6ef58c8c1a5493ae8 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 9 Jan 2025 12:00:45 -0600 Subject: [PATCH 169/554] build: add openai --- pyproject.toml | 1 + uv.lock | 117 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 472aeb3f..be8100d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ dependencies = [ "lxml==5.3.0", "matplotlib-inline==0.1.7", "nltk", + "openai", "opencv-python==4.10.0.84", "peft", "pip-chill==1.0.3", diff --git a/uv.lock b/uv.lock index 4e8c84fc..d5f3c804 100644 --- a/uv.lock +++ b/uv.lock @@ -952,6 +952,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252 }, ] +[[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 = "docker-pycreds" version = "0.4.0" @@ -1309,6 +1318,34 @@ 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 = "huggingface-hub" version = "0.27.1" @@ -1357,6 +1394,65 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, ] +[[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" @@ -2140,6 +2236,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 }, ] +[[package]] +name = "openai" +version = "1.59.5" +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/7c/b3/a99ff4f8034383147f853200ff5f6df63a8407a0061d6b3ff47914b94f4c/openai-1.59.5.tar.gz", hash = "sha256:9886e77c02dad9dc6a7b67a11ab372a56842a9b5d376aa476672175ab10e83a0", size = 344773 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/a2/a64f495c016234ca4269005b19eb9193a925dcad01af95eb8fea3de4ee9c/openai-1.59.5-py3-none-any.whl", hash = "sha256:e646b44856b0dda9345d3c43639e056334d792d1690e99690313c0ef7ca4d8cc", size = 454815 }, +] + [[package]] name = "opencv-python" version = "4.10.0.84" @@ -3874,6 +3989,7 @@ dependencies = [ { name = "lxml" }, { name = "matplotlib-inline" }, { name = "nltk" }, + { name = "openai" }, { name = "opencv-python" }, { name = "peft" }, { name = "pip-chill" }, @@ -3904,6 +4020,7 @@ requires-dist = [ { name = "lxml", specifier = "==5.3.0" }, { name = "matplotlib-inline", specifier = "==0.1.7" }, { name = "nltk" }, + { name = "openai" }, { name = "opencv-python", specifier = "==4.10.0.84" }, { name = "peft" }, { name = "pip-chill", specifier = "==1.0.3" }, From bef155a8a0d8a252e9937c401a442882d1284f1b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 9 Jan 2025 19:09:22 -0600 Subject: [PATCH 170/554] feat: implement lighthouse score --- tests/test_lighthouse.py | 26 +++++ webgenie/constants.py | 3 + .../rewards/lighthouse_reward/__init__.py | 1 + .../lighthouse_reward/get_lighthouse_score.py | 105 ++++++++++++++++++ .../lighthouse_reward/lighthouse_reward.py | 27 +++++ 5 files changed, 162 insertions(+) create mode 100644 tests/test_lighthouse.py create mode 100644 webgenie/rewards/lighthouse_reward/__init__.py create mode 100644 webgenie/rewards/lighthouse_reward/get_lighthouse_score.py create mode 100644 webgenie/rewards/lighthouse_reward/lighthouse_reward.py diff --git a/tests/test_lighthouse.py b/tests/test_lighthouse.py new file mode 100644 index 00000000..1011196d --- /dev/null +++ b/tests/test_lighthouse.py @@ -0,0 +1,26 @@ +import asyncio +import sys +import os + +parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(parent_dir) + +from dotenv import load_dotenv, find_dotenv +load_dotenv(find_dotenv(filename=".env.validator")) + +from webgenie.rewards.lighthouse_reward import LighthouseReward +from webgenie.tasks import Task, Solution + +async def test_lighthouse_reward(): + reward = LighthouseReward() + task = Task( + prompt="Create a website that displays a list of 10 random numbers.", + ground_truth_html="", + ) + html = "

Hello, World!

" + html2 = "

Hello, World!

Hello, World!
" + solutions = [Solution(html=html) for _ in range(5)] + [Solution(html=html2) for _ in range(5)] + print(await reward.reward(task, solutions)) + +if __name__ == "__main__": + asyncio.run(test_lighthouse_reward()) diff --git a/webgenie/constants.py b/webgenie/constants.py index 1c68ae3a..06f1f6be 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -7,6 +7,9 @@ # text task timeout TEXT_TASK_TIMEOUT = 100 +# lighthouse server port +LIGHTHOUSE_SERVER_PORT = 8001 + # max competition history size MAX_COMPETETION_HISTORY_SIZE = 30 diff --git a/webgenie/rewards/lighthouse_reward/__init__.py b/webgenie/rewards/lighthouse_reward/__init__.py new file mode 100644 index 00000000..d3792dc4 --- /dev/null +++ b/webgenie/rewards/lighthouse_reward/__init__.py @@ -0,0 +1 @@ +from .lighthouse_reward import LighthouseReward \ No newline at end of file diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py new file mode 100644 index 00000000..a19ac95c --- /dev/null +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -0,0 +1,105 @@ +import bittensor as bt +import json +import subprocess +import threading +import time + +from http.server import SimpleHTTPRequestHandler, HTTPServer +from typing import List, Dict + +from webgenie.constants import LIGHTHOUSE_SERVER_PORT + + +def get_lighthouse_score(htmls: List[str]) -> List[Dict[str, float]]: + class CustomHandler(SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def do_GET(self): + # Add CORS headers + self.send_response(200) + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') + self.send_header('Access-Control-Allow-Headers', 'Content-Type') + if self.path == '/favicon.ico': + self.send_response(200) + self.send_header('Content-type', 'image/x-icon') + self.end_headers() + self.wfile.write(b'') # send a blank byte or actual icon data + elif self.path == '/robots.txt': + self.send_response(200) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write(b'User-agent: *\nDisallow: /') # Example content + elif self.path.startswith('/lighthouse_score'): + print(f"Serving HTML {self.path}") + html_index = int(self.path.split('/')[-1]) + print(f"HTML index: {html_index}") + self.send_header('Content-type', 'text/html') + self.end_headers() + self.wfile.write(htmls[html_index].encode('utf-8')) + else: + self.send_response(404) + self.end_headers() + self.wfile.write(b"Not Found") + + def do_OPTIONS(self): + # Handle preflight requests + self.send_response(200) + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') + self.send_header('Access-Control-Allow-Headers', 'Content-Type') + self.end_headers() + + def run_server(port=8000): + server_address = ('', port) + httpd = HTTPServer(server_address, CustomHandler) + bt.logging.info(f"Starting server on port {port}...") + httpd.serve_forever() + + port = LIGHTHOUSE_SERVER_PORT + server_thread = threading.Thread(target=run_server, args=(port,), daemon=True) + server_thread.start() + + def get_lighthouse_score_from_subprocess(url): + try: + result = subprocess.run( + ['lighthouse', url, '--output=json', '--quiet', '--chrome-flags="--headless --no-sandbox"'], + capture_output=True, text=True, timeout=60 + ) + if result.returncode == 0: + lighthouse_report = json.loads(result.stdout) + scores = { + 'performance': lighthouse_report['categories']['performance']['score'], + 'accessibility': lighthouse_report['categories']['accessibility']['score'], + 'best-practices': lighthouse_report['categories']['best-practices']['score'], + 'seo': lighthouse_report['categories']['seo']['score'] + } + return scores + else: + bt.logging.error(f"Error running Lighthouse: {result.stderr}") + except Exception as e: + bt.logging.error(f"Error running Lighthouse: {e}") + return { + 'performance': 0, + 'accessibility': 0, + 'best-practices': 0, + 'seo': 0 + } + + time.sleep(1) # Give the server time to start + scores = [] + for i in range(len(htmls)): + url = f"http://localhost:{port}/lighthouse_score/{i}" + scores.append(get_lighthouse_score_from_subprocess(url)) + + server_thread.join(timeout=10) + if server_thread.is_alive(): + bt.logging.info("Server did not shut down properly.") + else: + bt.logging.info("Server stopped successfully.") + + return scores + + + \ No newline at end of file diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py new file mode 100644 index 00000000..29aa4f06 --- /dev/null +++ b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py @@ -0,0 +1,27 @@ +# The paper [Unsupervised Evaluation of Code LLMs with Round-Trip Correctness] +# (https://arxiv.org/pdf/2402.08699#page=11&zoom=100,384,458) is our inspiration for this reward. + +import numpy as np +from typing import List + +from webgenie.rewards.reward import Reward +from webgenie.tasks import Task, Solution +from .get_lighthouse_score import get_lighthouse_score + + +class LighthouseReward(Reward): + + async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: + htmls = [solution.html for solution in solutions] + scores_dict = get_lighthouse_score(htmls) + scores = [] + weights = [0.25, 0.25, 0.25, 0.25] + for score_dict in scores_dict: + score = ( + score_dict['performance'] * weights[0] + + score_dict['accessibility'] * weights[1] + + score_dict['best-practices'] * weights[2] + + score_dict['seo'] * weights[3] + ) + scores.append(score) + return np.array(scores) From 7db021af219b1500e9110bc5d00c94858697d898 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 9 Jan 2025 19:16:04 -0600 Subject: [PATCH 171/554] chore: integrate seo metric --- webgenie/competitions/competition.py | 1 + webgenie/competitions/image_task_competition.py | 12 ++++++++++++ webgenie/rewards/__init__.py | 3 ++- .../rewards/lighthouse_reward/lighthouse_reward.py | 2 +- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/webgenie/competitions/competition.py b/webgenie/competitions/competition.py index 3e7979f8..b0707004 100644 --- a/webgenie/competitions/competition.py +++ b/webgenie/competitions/competition.py @@ -7,6 +7,7 @@ ACCURACY_METRIC_NAME = "Accuracy" +SEO_METRIC_NAME = "Seo" QUALITY_METRIC_NAME = "Quality" diff --git a/webgenie/competitions/image_task_competition.py b/webgenie/competitions/image_task_competition.py index 489124ff..436ca2e5 100644 --- a/webgenie/competitions/image_task_competition.py +++ b/webgenie/competitions/image_task_competition.py @@ -7,6 +7,7 @@ Competition, ACCURACY_METRIC_NAME, QUALITY_METRIC_NAME, + SEO_METRIC_NAME, ) from webgenie.constants import IMAGE_TASK_TIMEOUT from webgenie.helpers.htmls import ( @@ -20,6 +21,7 @@ from webgenie.rewards import ( QualityReward, VisualReward, + LighthouseReward, ) from webgenie.datasets import ( RandomWebsiteDataset, @@ -42,6 +44,7 @@ def __init__(self): self.metrics = { ACCURACY_METRIC_NAME: VisualReward(), + SEO_METRIC_NAME: LighthouseReward(), QUALITY_METRIC_NAME: QualityReward(), } @@ -106,3 +109,12 @@ async def calculate_final_scores(self, task: Task, solutions: List[Solution]) -> final_scores = np.where(accuracy_scores > 0.7, quality_scores, 0) return final_scores, scores +class ImageTaskSeoCompetition(ImageTaskCompetition): + COMPETITION_TYPE = "ImageTaskSeoCompetition" + + async def calculate_final_scores(self, task: Task, solutions: List[Solution]) -> np.ndarray: + scores = await self.calculate_scores(task, solutions) + accuracy_scores = scores[ACCURACY_METRIC_NAME] + seo_scores = scores[SEO_METRIC_NAME] + final_scores = np.where(accuracy_scores > 0.7, seo_scores, 0) + return final_scores, scores diff --git a/webgenie/rewards/__init__.py b/webgenie/rewards/__init__.py index dcf724d7..9db13bde 100644 --- a/webgenie/rewards/__init__.py +++ b/webgenie/rewards/__init__.py @@ -2,4 +2,5 @@ from .visual_reward import VisualReward from .quality_reward import QualityReward from .rtc_reward import RtcReward -from .bert_reward import BertReward \ No newline at end of file +from .bert_reward import BertReward +from .lighthouse_reward import LighthouseReward diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py index 29aa4f06..a5b475b7 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py @@ -15,7 +15,7 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: htmls = [solution.html for solution in solutions] scores_dict = get_lighthouse_score(htmls) scores = [] - weights = [0.25, 0.25, 0.25, 0.25] + weights = [0, 0.25, 0.25, 0.5] for score_dict in scores_dict: score = ( score_dict['performance'] * weights[0] + From dc35a241871288c382664c2136e46dc75fbeb2a7 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 9 Jan 2025 19:18:02 -0600 Subject: [PATCH 172/554] chore: remove print log --- webgenie/rewards/lighthouse_reward/get_lighthouse_score.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index a19ac95c..8de84cae 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -32,9 +32,7 @@ def do_GET(self): self.end_headers() self.wfile.write(b'User-agent: *\nDisallow: /') # Example content elif self.path.startswith('/lighthouse_score'): - print(f"Serving HTML {self.path}") html_index = int(self.path.split('/')[-1]) - print(f"HTML index: {html_index}") self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(htmls[html_index].encode('utf-8')) From b98444c4d4191c1bbeb2a9609a89717f8fbf4618 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 9 Jan 2025 19:40:06 -0600 Subject: [PATCH 173/554] refactor: refactor reward --- webgenie/constants.py | 2 +- webgenie/rewards/__init__.py | 2 +- webgenie/rewards/rtc_reward/__init__.py | 1 + webgenie/rewards/{ => rtc_reward}/rtc_reward.py | 0 webgenie/rewards/{metrics => rtc_reward}/s_bert.py | 0 webgenie/rewards/visual_reward/__init__.py | 1 + webgenie/rewards/{ => visual_reward}/metrics/__init__.py | 0 webgenie/rewards/{ => visual_reward}/metrics/dedup_post_gen.py | 0 webgenie/rewards/{ => visual_reward}/metrics/ocr_free_utils.py | 0 .../rewards/{ => visual_reward}/metrics/screenshot_single.py | 0 webgenie/rewards/{ => visual_reward}/metrics/visual_score.py | 0 webgenie/rewards/{ => visual_reward}/visual_reward.py | 2 +- 12 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 webgenie/rewards/rtc_reward/__init__.py rename webgenie/rewards/{ => rtc_reward}/rtc_reward.py (100%) rename webgenie/rewards/{metrics => rtc_reward}/s_bert.py (100%) create mode 100644 webgenie/rewards/visual_reward/__init__.py rename webgenie/rewards/{ => visual_reward}/metrics/__init__.py (100%) rename webgenie/rewards/{ => visual_reward}/metrics/dedup_post_gen.py (100%) rename webgenie/rewards/{ => visual_reward}/metrics/ocr_free_utils.py (100%) rename webgenie/rewards/{ => visual_reward}/metrics/screenshot_single.py (100%) rename webgenie/rewards/{ => visual_reward}/metrics/visual_score.py (100%) rename webgenie/rewards/{ => visual_reward}/visual_reward.py (94%) diff --git a/webgenie/constants.py b/webgenie/constants.py index 06f1f6be..34f606b5 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -26,7 +26,7 @@ PYTHON_CMD = "python" # screenshot script path -SCREENSHOT_SCRIPT_PATH = "webgenie/rewards/metrics/screenshot_single.py" +SCREENSHOT_SCRIPT_PATH = "webgenie/rewards/visual_reward/metrics/screenshot_single.py" # work dir WORK_DIR = "work" diff --git a/webgenie/rewards/__init__.py b/webgenie/rewards/__init__.py index 9db13bde..6f41a10d 100644 --- a/webgenie/rewards/__init__.py +++ b/webgenie/rewards/__init__.py @@ -1,6 +1,6 @@ from .reward import Reward from .visual_reward import VisualReward from .quality_reward import QualityReward -from .rtc_reward import RtcReward +from .rtc_reward.rtc_reward import RtcReward from .bert_reward import BertReward from .lighthouse_reward import LighthouseReward diff --git a/webgenie/rewards/rtc_reward/__init__.py b/webgenie/rewards/rtc_reward/__init__.py new file mode 100644 index 00000000..1265da89 --- /dev/null +++ b/webgenie/rewards/rtc_reward/__init__.py @@ -0,0 +1 @@ +from .rtc_reward import RtcReward \ No newline at end of file diff --git a/webgenie/rewards/rtc_reward.py b/webgenie/rewards/rtc_reward/rtc_reward.py similarity index 100% rename from webgenie/rewards/rtc_reward.py rename to webgenie/rewards/rtc_reward/rtc_reward.py diff --git a/webgenie/rewards/metrics/s_bert.py b/webgenie/rewards/rtc_reward/s_bert.py similarity index 100% rename from webgenie/rewards/metrics/s_bert.py rename to webgenie/rewards/rtc_reward/s_bert.py diff --git a/webgenie/rewards/visual_reward/__init__.py b/webgenie/rewards/visual_reward/__init__.py new file mode 100644 index 00000000..1a17624f --- /dev/null +++ b/webgenie/rewards/visual_reward/__init__.py @@ -0,0 +1 @@ +from .visual_reward import VisualReward \ No newline at end of file diff --git a/webgenie/rewards/metrics/__init__.py b/webgenie/rewards/visual_reward/metrics/__init__.py similarity index 100% rename from webgenie/rewards/metrics/__init__.py rename to webgenie/rewards/visual_reward/metrics/__init__.py diff --git a/webgenie/rewards/metrics/dedup_post_gen.py b/webgenie/rewards/visual_reward/metrics/dedup_post_gen.py similarity index 100% rename from webgenie/rewards/metrics/dedup_post_gen.py rename to webgenie/rewards/visual_reward/metrics/dedup_post_gen.py diff --git a/webgenie/rewards/metrics/ocr_free_utils.py b/webgenie/rewards/visual_reward/metrics/ocr_free_utils.py similarity index 100% rename from webgenie/rewards/metrics/ocr_free_utils.py rename to webgenie/rewards/visual_reward/metrics/ocr_free_utils.py diff --git a/webgenie/rewards/metrics/screenshot_single.py b/webgenie/rewards/visual_reward/metrics/screenshot_single.py similarity index 100% rename from webgenie/rewards/metrics/screenshot_single.py rename to webgenie/rewards/visual_reward/metrics/screenshot_single.py diff --git a/webgenie/rewards/metrics/visual_score.py b/webgenie/rewards/visual_reward/metrics/visual_score.py similarity index 100% rename from webgenie/rewards/metrics/visual_score.py rename to webgenie/rewards/visual_reward/metrics/visual_score.py diff --git a/webgenie/rewards/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py similarity index 94% rename from webgenie/rewards/visual_reward.py rename to webgenie/rewards/visual_reward/visual_reward.py index 34ddc123..534df9f9 100644 --- a/webgenie/rewards/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -8,7 +8,7 @@ from webgenie.constants import WORK_DIR from webgenie.rewards.reward import Reward -from webgenie.rewards.metrics.visual_score import visual_eval_v3_multi +from webgenie.rewards.visual_reward.metrics.visual_score import visual_eval_v3_multi from webgenie.tasks import Task, ImageTask, Solution From e61162182f9b7259ed6a0ebcfac3f9daef8ab014 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 10 Jan 2025 05:16:41 -0600 Subject: [PATCH 174/554] chore: concurrent openai query --- webgenie/rewards/quality_reward.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webgenie/rewards/quality_reward.py b/webgenie/rewards/quality_reward.py index ce1228e0..1b780d2f 100644 --- a/webgenie/rewards/quality_reward.py +++ b/webgenie/rewards/quality_reward.py @@ -2,6 +2,7 @@ # (https://arxiv.org/pdf/2402.08699#page=11&zoom=100,384,458) is our inspiration for this reward. import bittensor as bt +import asyncio import numpy as np from pydantic import BaseModel, Field from typing import List @@ -29,5 +30,6 @@ async def _get_score(self, solution: Solution) -> float: async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: bt.logging.debug(f"Rewarding task in quality reward") - scores = [await self._get_score(solution) for solution in solutions] + get_score_tasks = [self._get_score(solution) for solution in solutions] + scores = await asyncio.gather(*get_score_tasks) return np.array(scores) From c98830065e7d590ad8d3504a67668e530d81104c Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 10 Jan 2025 06:00:20 -0600 Subject: [PATCH 175/554] feat: add html load time --- webgenie/competitions/image_task_competition.py | 4 ++-- webgenie/constants.py | 6 ++++++ webgenie/datasets/random_website_dataset.py | 8 ++++---- webgenie/helpers/htmls.py | 4 ++-- .../visual_reward/metrics/ocr_free_utils.py | 7 ++++--- .../visual_reward/metrics/screenshot_single.py | 9 +++++---- .../rewards/visual_reward/metrics/visual_score.py | 14 +++++++++----- 7 files changed, 32 insertions(+), 20 deletions(-) diff --git a/webgenie/competitions/image_task_competition.py b/webgenie/competitions/image_task_competition.py index 436ca2e5..214cad02 100644 --- a/webgenie/competitions/image_task_competition.py +++ b/webgenie/competitions/image_task_competition.py @@ -9,7 +9,7 @@ QUALITY_METRIC_NAME, SEO_METRIC_NAME, ) -from webgenie.constants import IMAGE_TASK_TIMEOUT +from webgenie.constants import IMAGE_TASK_TIMEOUT, GROUND_TRUTH_HTML_LOAD_TIME from webgenie.helpers.htmls import ( html_to_screenshot, preprocess_html, @@ -61,7 +61,7 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: if is_empty_html(ground_truth_html): raise ValueError("Empty ground truth html") - base64_image = html_to_screenshot(ground_truth_html) + base64_image = html_to_screenshot(ground_truth_html, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) # Save base64_image for debugging purposes import os diff --git a/webgenie/constants.py b/webgenie/constants.py index 34f606b5..c1188499 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -28,6 +28,12 @@ # screenshot script path SCREENSHOT_SCRIPT_PATH = "webgenie/rewards/visual_reward/metrics/screenshot_single.py" +# max page load time +GROUND_TRUTH_HTML_LOAD_TIME = 20000 + +# miner html load time +MINER_HTML_LOAD_TIME = 2000 + # work dir WORK_DIR = "work" diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index 90e1bac7..4c7d3d29 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -1,17 +1,17 @@ import bittensor as bt +import nltk +import random from bs4 import BeautifulSoup from collections import Counter from duckduckgo_search import DDGS -import nltk from nltk.corpus import brown from playwright.async_api import async_playwright from urllib.parse import urljoin -import random from typing import Optional from webgenie.datasets.dataset import Dataset, DatasetEntry - +from webgenie.constants import GROUND_TRUTH_HTML_LOAD_TIME class RandomWebsiteDataset(Dataset): def __init__(self , **kwargs): @@ -42,7 +42,7 @@ async def get_rendered_html(self, url): page = await browser.new_page() await page.goto(url) # Wait for 10 seconds to ensure content loads - await page.wait_for_timeout(10000) + await page.wait_for_timeout(GROUND_TRUTH_HTML_LOAD_TIME) rendered_html = await page.content() # Get the rendered HTML await browser.close() diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index a9625bfb..1e829796 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -69,12 +69,12 @@ def seperate_html_css(html_content: str): return cleaned_html, css -def html_to_screenshot(html: str) -> str: +def html_to_screenshot(html: str, page_load_time: int = 1000) -> str: html_path = f"{WORK_DIR}/screenshot_{uuid.uuid4()}.html" with open(html_path, "w") as f: f.write(html) png_path = f"{WORK_DIR}/screenshot_{uuid.uuid4()}.png" - os.system(f"{PYTHON_CMD} {SCREENSHOT_SCRIPT_PATH} --html {html_path} --png {png_path}") + os.system(f"{PYTHON_CMD} {SCREENSHOT_SCRIPT_PATH} --html {html_path} --png {png_path} --page_load_time {page_load_time}") time.sleep(0.1) base64_image = image_to_base64(png_path) diff --git a/webgenie/rewards/visual_reward/metrics/ocr_free_utils.py b/webgenie/rewards/visual_reward/metrics/ocr_free_utils.py index ccd706a8..2e977fcc 100644 --- a/webgenie/rewards/visual_reward/metrics/ocr_free_utils.py +++ b/webgenie/rewards/visual_reward/metrics/ocr_free_utils.py @@ -5,6 +5,7 @@ from bs4 import BeautifulSoup, NavigableString, Tag, Comment from pathlib import Path +from webgenie.constants import PYTHON_CMD def rgb_to_hex(rgb): """Convert an RGB tuple to hexadecimal format.""" @@ -232,13 +233,13 @@ def get_intersect(arr1, arr2): def get_itermediate_names(name): return name.replace(".png", ".html"), name.replace(".png", "_p.html"), name.replace(".png", "_p_1.html"), name.replace(".png", "_p.png"), name.replace(".png", "_p_1.png") -def get_blocks_ocr_free(image_path): +def get_blocks_ocr_free(image_path, page_load_time): html, p_html, p_html_1, p_png, p_png_1 = get_itermediate_names(image_path) process_html(html, p_html) process_html(html, p_html_1, offset=50) - os.system(f"python3 {Path(__file__).parent}/screenshot_single.py --html {p_html} --png {p_png}") - os.system(f"python3 {Path(__file__).parent}/screenshot_single.py --html {p_html_1} --png {p_png_1}") + os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {p_html} --png {p_png} --page_load_time {page_load_time}") + os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {p_html_1} --png {p_png_1} --page_load_time {page_load_time}") different_pixels = find_different_pixels(p_png, p_png_1) diff --git a/webgenie/rewards/visual_reward/metrics/screenshot_single.py b/webgenie/rewards/visual_reward/metrics/screenshot_single.py index 8f2dd54e..8c4673b8 100644 --- a/webgenie/rewards/visual_reward/metrics/screenshot_single.py +++ b/webgenie/rewards/visual_reward/metrics/screenshot_single.py @@ -1,10 +1,10 @@ import os -from playwright.sync_api import sync_playwright import argparse +from playwright.sync_api import sync_playwright from PIL import Image -def take_screenshot(url, output_file="screenshot.png", do_it_again=False): +def take_screenshot(url, output_file="screenshot.png", page_load_time = 1000, do_it_again=False): # Convert local path to file:// URL if it's a file if os.path.exists(url): url = "file://" + os.path.abspath(url) @@ -23,7 +23,7 @@ def take_screenshot(url, output_file="screenshot.png", do_it_again=False): page.goto(url, timeout=60000) # Wait for 10 seconds to ensure page is fully loaded - page.wait_for_timeout(10000) + page.wait_for_timeout(page_load_time) # Take the screenshot page.screenshot(path=output_file, full_page=True, animations="disabled", timeout=60000) @@ -44,8 +44,9 @@ def take_screenshot(url, output_file="screenshot.png", do_it_again=False): # Define the arguments parser.add_argument('--html', type=str) parser.add_argument('--png', type=str) + parser.add_argument('--page_load_time', type=int, default=1000) # Parse the arguments args = parser.parse_args() - take_screenshot(args.html, args.png, do_it_again=True) + take_screenshot(args.html, args.png, args.page_load_time, do_it_again=True) diff --git a/webgenie/rewards/visual_reward/metrics/visual_score.py b/webgenie/rewards/visual_reward/metrics/visual_score.py index bbc8f055..b320f21a 100644 --- a/webgenie/rewards/visual_reward/metrics/visual_score.py +++ b/webgenie/rewards/visual_reward/metrics/visual_score.py @@ -33,7 +33,11 @@ def patch_asscalar(a): from ocr_free_utils import get_blocks_ocr_free from dedup_post_gen import check_repetitive_content -from webgenie.constants import SCREENSHOT_SCRIPT_PATH, WORK_DIR, PYTHON_CMD +from webgenie.constants import ( + PYTHON_CMD, + GROUND_TRUTH_HTML_LOAD_TIME, + MINER_HTML_LOAD_TIME, +) device = "cuda" if torch.cuda.is_available() else "cpu" model, preprocess = clip.load("ViT-B/32", device=device) @@ -445,13 +449,13 @@ def visual_eval_v3_multi(input_list, debug=False): predict_img = predict_html.replace(".html", ".png") # This will help fix some html syntax error pre_process(predict_html) - os.system(f"{PYTHON_CMD} {SCREENSHOT_SCRIPT_PATH} --html {predict_html} --png {predict_img}") - predict_blocks = get_blocks_ocr_free(predict_img) + os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {predict_html} --png {predict_img} --page_load_time {MINER_HTML_LOAD_TIME}") + predict_blocks = get_blocks_ocr_free(predict_img, page_load_time=MINER_HTML_LOAD_TIME) predict_blocks_list.append(predict_blocks) original_img = original_html.replace(".html", ".png") - os.system(f"{PYTHON_CMD} {SCREENSHOT_SCRIPT_PATH} --html {original_html} --png {original_img}") - original_blocks = get_blocks_ocr_free(original_img) + os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {original_html} --png {original_img} --page_load_time {GROUND_TRUTH_HTML_LOAD_TIME}") + original_blocks = get_blocks_ocr_free(original_img, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) original_blocks = merge_blocks_by_bbox(original_blocks) # Consider context similarity for block matching From 79708ad9bac7418b8d84bc50ab92591c02318a2f Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 10 Jan 2025 06:00:40 -0600 Subject: [PATCH 176/554] style: add line --- neurons/validators/genie_validator.py | 10 ++- .../lighthouse_reward/lighthouse_reward.py | 1 + webgenie/rewards/rtc_reward/rtc_reward.py | 2 +- webgenie/storage/__init__.py | 1 + webgenie/storage/model_gpt.py | 77 +++++++++++++++++++ webgenie/storage/models.py | 45 +++++++++++ webgenie/storage/storage.py | 37 +++++++++ 7 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 webgenie/storage/__init__.py create mode 100644 webgenie/storage/model_gpt.py create mode 100644 webgenie/storage/models.py create mode 100644 webgenie/storage/storage.py diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 5bb5acdc..24644f2b 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -18,7 +18,10 @@ ACCURACY_METRIC_NAME, QUALITY_METRIC_NAME, ) -from webgenie.helpers.dashboard import upload_competition_result +from webgenie.storage import ( + upload_competition, + upload_competition_result, +) from webgenie.helpers.htmls import preprocess_html, validate_resources from webgenie.helpers.images import image_debug_str from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse @@ -97,6 +100,11 @@ async def score(self): solutions.sort(key=lambda solution: solution.process_time) competition = task.competition + upload_competition({ + "competition_type": competition.COMPETITION_TYPE, + "task": task, + }) + miner_uids = [solution.miner_uid for solution in solutions] final_scores, scores = await competition.calculate_final_scores(task, solutions) diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py index a5b475b7..22212974 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py @@ -6,6 +6,7 @@ from webgenie.rewards.reward import Reward from webgenie.tasks import Task, Solution + from .get_lighthouse_score import get_lighthouse_score diff --git a/webgenie/rewards/rtc_reward/rtc_reward.py b/webgenie/rewards/rtc_reward/rtc_reward.py index 62368129..d91a5de5 100644 --- a/webgenie/rewards/rtc_reward/rtc_reward.py +++ b/webgenie/rewards/rtc_reward/rtc_reward.py @@ -12,7 +12,7 @@ from webgenie.helpers.llms import openai_call from webgenie.prompts import PROMPT_RTC from webgenie.rewards.reward import Reward -from webgenie.rewards.metrics import s_bert +from webgenie.rewards.rtc_reward import s_bert from webgenie.tasks import Task, Solution diff --git a/webgenie/storage/__init__.py b/webgenie/storage/__init__.py new file mode 100644 index 00000000..c14e1e33 --- /dev/null +++ b/webgenie/storage/__init__.py @@ -0,0 +1 @@ +from .storage import * \ No newline at end of file diff --git a/webgenie/storage/model_gpt.py b/webgenie/storage/model_gpt.py new file mode 100644 index 00000000..0397fd39 --- /dev/null +++ b/webgenie/storage/model_gpt.py @@ -0,0 +1,77 @@ +from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship, sessionmaker +from datetime import datetime + +Base = declarative_base() + +class Competition(Base): + __tablename__ = 'competitions' + id = Column(Integer, primary_key=True) + uuid = Column(String, unique=True) + type = Column(String) + ground_truth_html = Column(String) + + # Relationship to LeaderboardSessions + sessions = relationship("LeaderboardSession", order_by="LeaderboardSession.id", back_populates="competition") + +class LeaderboardSession(Base): + __tablename__ = 'leaderboard_sessions' + id = Column(Integer, primary_key=True) + competition_uuid = Column(String, ForeignKey('competitions.uuid')) + created_at = Column(DateTime, default=datetime.utcnow) + + # Relationships + competition = relationship("Competition", back_populates="sessions") + solutions = relationship("TaskSolution", back_populates="session") + +class TaskSolution(Base): + __tablename__ = 'task_solutions' + id = Column(Integer, primary_key=True) + session_id = Column(Integer, ForeignKey('leaderboard_sessions.id')) + miner_uid = Column(Integer) + miner_hotkey = Column(String) + validator_hotkey = Column(String) + score = Column(Float) + accuracy_score = Column(Float) + quality_score = Column(Float) + seo_score = Column(Float) + miner_answer = Column(String) + created_at = Column(DateTime, default=datetime.utcnow) + + # Relationship + session = relationship("LeaderboardSession", back_populates="solutions") + +# Database setup +engine = create_engine('sqlite:///competition.db', echo=True) +Base.metadata.create_all(engine) + +# Example function to illustrate usage +def add_competition_data(): + Session = sessionmaker(bind=engine) + session = Session() + + # Create a new competition + competition = Competition(uuid="comp-1234", type="data science") + + # Create a leaderboard session + leaderboard_session = LeaderboardSession(competition_uuid=competition.uuid) + + # Create task solutions + solution1 = TaskSolution(session=leaderboard_session, miner_uid=1, score=95.5) + solution2 = TaskSolution(session=leaderboard_session, miner_uid=2, score=88.0) + + # Create leaderboard entry + leaderboard_entry = LeaderboardEntry(session=leaderboard_session, average_score=91.75, max_score=95.5, min_score=88.0) + + # Add all to session and commit + session.add(competition) + session.add(leaderboard_session) + session.add(solution1) + session.add(solution2) + session.add(leaderboard_entry) + session.commit() + session.close() + +if __name__ == "__main__": + add_competition_data() diff --git a/webgenie/storage/models.py b/webgenie/storage/models.py new file mode 100644 index 00000000..b3a586e2 --- /dev/null +++ b/webgenie/storage/models.py @@ -0,0 +1,45 @@ +from sqlalchemy import Column, Integer, Float, String, DateTime, Boolean, UniqueConstraint, ForeignKey +from sqlalchemy.ext.declarative import declarative_base + + +Base = declarative_base() + + +class Miner(Base): + __tablename__ = 'miners' + + id = Column(Integer, primary_key=True) + uid = Column(Integer) + hotkey = Column(String) + coldkey = Column(String) + is_registered = Column(Boolean) + last_updated = Column(DateTime) + __table_args__ = ( + UniqueConstraint('uid', 'hotkey', name='unique_uid_hotkey'), + ) + + +class Competition(Base): + __tablename__ = 'competitions' + + id = Column(Integer, primary_key=True) + uuid = Column(String) + type = Column(String) + ground_truth_html = Column(String) + last_updated = Column(DateTime) + + +class TaskSolution(Base): + __tablename__ = 'task_solutions' + + id = Column(Integer, primary_key=True) + miner_uid = Column(Integer) + miner_hotkey = Column(String) + validator_hotkey = Column(String) + competition_uuid = Column(String, ForeignKey('competitions.uuid')) + score = Column(Float) + accuracy_score = Column(Float) + quality_score = Column(Float) + seo_score = Column(Float) + miner_answer = Column(String) + last_updated = Column(DateTime) diff --git a/webgenie/storage/storage.py b/webgenie/storage/storage.py new file mode 100644 index 00000000..4a25bd07 --- /dev/null +++ b/webgenie/storage/storage.py @@ -0,0 +1,37 @@ +import bittensor as bt + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from .models import Miner, Base + + +engine = create_engine('sqlite:///webgenie.db') +Session = sessionmaker(bind=engine) +Base.metadata.create_all(engine) + + +def upload_competition(row: dict): + try: + session = Session() + competition = Competition(**row) + session.add(competition) + session.commit() + except Exception as e: + bt.logging.error(f"Database error: {e}") + session.rollback() + finally: + session.close() + + +def upload_competition_result(result: dict): + try: + session = Session() + miner = Miner(**result) + session.add(miner) + session.commit() + except Exception as e: + bt.logging.error(f"Database error: {e}") + session.rollback() + finally: + session.close() From eab585149c7ba9b874bdfe28a234d729df0f8619 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 10 Jan 2025 06:08:43 -0600 Subject: [PATCH 177/554] fix: fix bugs for usage of openai --- webgenie/datasets/synthetic_dataset.py | 3 +-- webgenie/prompts.py | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index f12e0ade..e3cf702c 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -39,8 +39,7 @@ async def _generate_html(self, concept: str): bt.logging.info("Generating HTML from concept") response = await openai_call( messages = [ - {"role": "system", "content": PROMPT_GEN_HTML}, - {"role": "user", "content": concept}, + {"role": "system", "content": PROMPT_GEN_HTML.format(concept=concept)}, ], response_format = HTMLResponse, ) diff --git a/webgenie/prompts.py b/webgenie/prompts.py index f41200b4..7ed13c5c 100644 --- a/webgenie/prompts.py +++ b/webgenie/prompts.py @@ -14,7 +14,6 @@ Examples include: a car company site with a left column, a webpage footer with a centered logo. Explore variations in colors, positions, and company fields. Don’t give any explanations or recognition that you have understood the request, just give the list of 10 ideas, with a line break between each. -{instructions} """ @@ -27,8 +26,6 @@ by the desired width and height, and ‘?keyword‘ by a keyword describing the picture, for example "https://source.unsplash.com/random/300x200/?gym" for an image about gym of size 300x200, or "https://source.unsplash.com/random/100x200/?cake" for an image of a cake of size 100x200. - -{instructions} """ @@ -59,5 +56,4 @@ The following is the given html code: {html} -{instructions} """ \ No newline at end of file From 36121e6141dda45ab3accd404df3f5480e9f46c4 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 10 Jan 2025 06:13:27 -0600 Subject: [PATCH 178/554] feat: add seo competition --- neurons/validators/genie_validator.py | 10 ++++------ webgenie/competitions/__init__.py | 6 +++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 24644f2b..99753384 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -11,9 +11,8 @@ ) from webgenie.competitions import ( ImageTaskAccuracyCompetition, - TextTaskAccuracyCompetition, ImageTaskQualityCompetition, - TextTaskQualityCompetition, + ImageTaskSeoCompetition, RESERVED_WEIGHTS, ACCURACY_METRIC_NAME, QUALITY_METRIC_NAME, @@ -37,10 +36,9 @@ def __init__(self, neuron: BaseNeuron): self.synthetic_tasks = [] self.avail_competitions = [ - (TextTaskAccuracyCompetition(), 0.5), - (TextTaskQualityCompetition(), 0.5), - (ImageTaskAccuracyCompetition(), 0.5), - (ImageTaskQualityCompetition(), 0.5), + (ImageTaskAccuracyCompetition(), 0.4), + (ImageTaskSeoCompetition(), 0.3), + (ImageTaskQualityCompetition(), 0.3), ] self.make_work_dir() diff --git a/webgenie/competitions/__init__.py b/webgenie/competitions/__init__.py index 9fd8f066..1e225fa7 100644 --- a/webgenie/competitions/__init__.py +++ b/webgenie/competitions/__init__.py @@ -12,13 +12,13 @@ ImageTaskCompetition, ImageTaskAccuracyCompetition, ImageTaskQualityCompetition, + ImageTaskSeoCompetition, ) RESERVED_WEIGHTS = { - TextTaskAccuracyCompetition.COMPETITION_TYPE: 50, - TextTaskQualityCompetition.COMPETITION_TYPE: 20, ImageTaskAccuracyCompetition.COMPETITION_TYPE: 90, - ImageTaskQualityCompetition.COMPETITION_TYPE: 10 + ImageTaskQualityCompetition.COMPETITION_TYPE: 10, + ImageTaskSeoCompetition.COMPETITION_TYPE: 20, } From 520e5380f179823018a00271ff6732f2fb8725f2 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 10 Jan 2025 06:14:47 -0600 Subject: [PATCH 179/554] feat: add seo competition --- neurons/validators/genie_validator.py | 2 ++ webgenie/competitions/__init__.py | 1 + 2 files changed, 3 insertions(+) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 99753384..4c4e3561 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -16,6 +16,7 @@ RESERVED_WEIGHTS, ACCURACY_METRIC_NAME, QUALITY_METRIC_NAME, + SEO_METRIC_NAME, ) from webgenie.storage import ( upload_competition, @@ -114,6 +115,7 @@ async def score(self): "final_score": final_scores[i], "accuracy": scores[ACCURACY_METRIC_NAME][i], "quality": scores[QUALITY_METRIC_NAME][i], + "seo": scores[SEO_METRIC_NAME][i], "html": solutions[i].html, }) diff --git a/webgenie/competitions/__init__.py b/webgenie/competitions/__init__.py index 1e225fa7..8c1fe62e 100644 --- a/webgenie/competitions/__init__.py +++ b/webgenie/competitions/__init__.py @@ -2,6 +2,7 @@ Competition, ACCURACY_METRIC_NAME, QUALITY_METRIC_NAME, + SEO_METRIC_NAME, ) from webgenie.competitions.text_task_competition import ( TextTaskCompetition, From fb06389277bc31ed276bfb3ef930ba0bcbef1744 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 10 Jan 2025 06:41:23 -0600 Subject: [PATCH 180/554] doc: description about project --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index be8100d9..0b3a368e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "web-genie-ai" -version = "0.1.0" -description = "Add your description here" +version = "1.0.0" +description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.10" dependencies = [ From aa59f02fcaae87130c2cb429167652bcebaf9767 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 10 Jan 2025 06:49:50 -0600 Subject: [PATCH 181/554] chore: remove dashboard file --- webgenie/helpers/dashboard.py | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 webgenie/helpers/dashboard.py diff --git a/webgenie/helpers/dashboard.py b/webgenie/helpers/dashboard.py deleted file mode 100644 index 6166a0b1..00000000 --- a/webgenie/helpers/dashboard.py +++ /dev/null @@ -1,2 +0,0 @@ -def upload_competition_result(): - pass \ No newline at end of file From fb382cfc730264522ce3d8064064fffb8178bdf1 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 10 Jan 2025 06:58:31 -0600 Subject: [PATCH 182/554] chore: implement contest for organic query --- neurons/validators/genie_validator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 4c4e3561..cb6f07bb 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -153,16 +153,16 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag bt.logging.debug(f"Organic image forward: {image_debug_str(synapse.base64_image)}...") best_miner_uid = get_most_available_uid(self.neuron) + all_miner_uids = get_all_available_uids(self.neuron) try: - axon = self.neuron.metagraph.axons[best_miner_uid] async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: responses = await dendrite( - axons=[axon], + axons=[self.neuron.metagraph.axons[uid] for uid in all_miner_uids], synapse=synapse, timeout=synapse.timeout, ) - processed_synapse = await self.process_synapse(responses[0]) + processed_synapse = await self.process_synapse(responses[best_miner_uid]) if processed_synapse is None: raise Exception(f"No valid solution received") From e4fb54dcf33c7cefc308d980cd7e10c573d1e093 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 10 Jan 2025 07:37:53 -0600 Subject: [PATCH 183/554] style: log info --- neurons/validators/genie_validator.py | 2 +- pyproject.toml | 3 ++- uv.lock | 18 ++++++++++-------- webgenie.db | Bin 0 -> 20480 bytes webgenie/datasets/random_website_dataset.py | 2 +- webgenie/datasets/synthetic_dataset.py | 2 +- webgenie/rewards/bert_reward.py | 2 +- .../lighthouse_reward/get_lighthouse_score.py | 2 +- webgenie/rewards/quality_reward.py | 2 +- webgenie/rewards/rtc_reward/rtc_reward.py | 2 +- .../rewards/visual_reward/visual_reward.py | 2 +- 11 files changed, 20 insertions(+), 17 deletions(-) create mode 100644 webgenie.db diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index cb6f07bb..4067b6b7 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -134,7 +134,7 @@ async def synthensize_task(self): if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: return - bt.logging.debug(f"Synthensize task") + bt.logging.info(f"Synthensize task") competition, _ = random.choices( self.avail_competitions, diff --git a/pyproject.toml b/pyproject.toml index 0b3a368e..bd2efb1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,8 @@ dependencies = [ "ansible-vault==2.1.0", "beautifulsoup4==4.12.3", "bert-score==0.3.13", - "bittensor==8.5.1", + "bittensor-cli==8.2.0rc8", + "bittensor==8.5.1rc5", "clip", "colormath==3.0.0", "datasets==3.2.0", diff --git a/uv.lock b/uv.lock index d5f3c804..c1f762ae 100644 --- a/uv.lock +++ b/uv.lock @@ -344,7 +344,7 @@ wheels = [ [[package]] name = "bittensor" -version = "8.5.1" +version = "8.5.1rc5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -376,14 +376,14 @@ dependencies = [ { name = "websockets" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/b2/09ec0664822d32fcf2f708742c69249f8a70a09fb941cef3a1f4ab8eca02/bittensor-8.5.1.tar.gz", hash = "sha256:f1bb033ba1e2641881d37f9d8cfebdcb7145ae20975861863710bdd17941cce4", size = 210235 } +sdist = { url = "https://files.pythonhosted.org/packages/df/03/b86759459b09456a2eb6a5e84713cf0bd0db10558c0f724ce6ca7940c346/bittensor-8.5.1rc5.tar.gz", hash = "sha256:b4f26b237b45964666fde21d77b5d71fe26270ce9f9450e724b58b8d8bf705e9", size = 217653 } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/34/29f5b570d734b8474b21071756db41b4717c8cbebc3fa5716f0c499a12e8/bittensor-8.5.1-py3-none-any.whl", hash = "sha256:8dbf9c389d10fd043dab5da163377a43ec2ae1b1715e819a3602e07d36304f94", size = 257860 }, + { url = "https://files.pythonhosted.org/packages/01/11/e1b42d98cb955a090df195d004759e4f2a6563513b6d5e1f7dffcd9e2b4e/bittensor-8.5.1rc5-py3-none-any.whl", hash = "sha256:60904f4e8a296ab1a45bfbbba5b39cc62812709562a82f6b4ee16cb38b873a46", size = 265253 }, ] [[package]] name = "bittensor-cli" -version = "8.4.2" +version = "8.2.0rc8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -407,9 +407,9 @@ dependencies = [ { name = "websockets" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/77/a4250b215d7a5dabef2090c8b300843aa962a135a85b8af0ca499b15a23e/bittensor-cli-8.4.2.tar.gz", hash = "sha256:43efc081ed2ecf4357bf5c5322ccd6f7d1a5110eb842cf138c75adb3f21686fd", size = 161405 } +sdist = { url = "https://files.pythonhosted.org/packages/c2/98/bf296a271d460f2912eaad1914197dde5709b88c44ae92784a38e0932b2c/bittensor-cli-8.2.0rc8.tar.gz", hash = "sha256:df7da6ac6344c67a5f4273157c42810ca28dc0564eb7db58160e7d6f754edd01", size = 188330 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/7b/3f43e1e453697aba95734f94a44aeb96d72139a2ca83837ecaf170a58de2/bittensor_cli-8.4.2-py3-none-any.whl", hash = "sha256:e7fc5ff510f039fa0cb9c0c701a56c4eb2b644befb019b1cd0fac29546bfb764", size = 171700 }, + { url = "https://files.pythonhosted.org/packages/55/95/7d77a3a471e759741d19cb3d94b580942c0b2cd586b89a8ea677ba2015c6/bittensor_cli-8.2.0rc8-py3-none-any.whl", hash = "sha256:2108b4158c8719571f1f990f65333a1d7746b1b3dbfefa8b89da11d8cf7ab305", size = 198315 }, ] [[package]] @@ -3973,13 +3973,14 @@ wheels = [ [[package]] name = "web-genie-ai" -version = "0.1.0" +version = "1.0.0" source = { virtual = "." } dependencies = [ { name = "ansible-vault" }, { name = "beautifulsoup4" }, { name = "bert-score" }, { name = "bittensor" }, + { name = "bittensor-cli" }, { name = "clip" }, { name = "colormath" }, { name = "datasets" }, @@ -4009,7 +4010,8 @@ requires-dist = [ { name = "ansible-vault", specifier = "==2.1.0" }, { name = "beautifulsoup4", specifier = "==4.12.3" }, { name = "bert-score", specifier = "==0.3.13" }, - { name = "bittensor", specifier = "==8.5.1" }, + { name = "bittensor", specifier = "==8.5.1rc5" }, + { name = "bittensor-cli", specifier = "==8.2.0rc8" }, { name = "clip", git = "https://github.com/openai/CLIP.git" }, { name = "colormath", specifier = "==3.0.0" }, { name = "datasets" }, diff --git a/webgenie.db b/webgenie.db new file mode 100644 index 0000000000000000000000000000000000000000..34e8234b78ae731ecde3be9c196dfd9a707ece1a GIT binary patch literal 20480 zcmeI%?{CsT7zglnFmc3fyhGmInK=$ z-}=Y+(mNWm!gSed{5ENOKT3Pg=XuhkVQ^(fl9P{VHVY(qX*3MeG+q;87)C|y`)Xfr zx?f#))Y&Yx|JAG*&%cfikAEB0C%JL->-gtULp88L00Izz00bZa0SG_<0{@S|&C_bF z+3A=+rZV^ta~X&!6KR~wC{4s>Z@=qN%csP*&TL9H=SaJL5QU`g`1FE$#BqJ%3~l?A z)DLD+!ZViN&fX4AQaR;I^4{{gZ!Axbe+uF#3}l+IyL01oHs>QSdl2c$G9NLvTjsdv6vm0I)U#QaWI zsVQGmZdW(bQl7adDcgQ>Ip^gtud_5yLMF3ZPS`}w;_~E!4O9%$tTkIL^QOCsVWldH z9jzC}Bb)A9lS*o^4otbHl=g{e24XMgm6rPe|Q7U!M_8v7~wM-*QLjFZ8RLyY~N+rd|LDatasetEntry: website_url = await self.get_random_website_url() if website_url is None: raise Exception("Failed to get a valid website URL") - bt.logging.info(f"Generated website URL: {website_url}") + bt.logging.debug(f"Generated website URL: {website_url}") html = await self.get_rendered_html(website_url) return DatasetEntry( src="random_website", diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index e3cf702c..52c7289f 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -36,7 +36,7 @@ async def _generate_concepts(self): return response.concepts async def _generate_html(self, concept: str): - bt.logging.info("Generating HTML from concept") + bt.logging.debug(f"Generating HTML from concept: {concept}") response = await openai_call( messages = [ {"role": "system", "content": PROMPT_GEN_HTML.format(concept=concept)}, diff --git a/webgenie/rewards/bert_reward.py b/webgenie/rewards/bert_reward.py index e920854c..cc3a2637 100644 --- a/webgenie/rewards/bert_reward.py +++ b/webgenie/rewards/bert_reward.py @@ -15,7 +15,7 @@ def __init__(self): pass async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: - bt.logging.debug(f"Rewarding task in bert reward") + bt.logging.info(f"Rewarding task in bert reward") if not task.ground_truth_html: raise ValueError(f"Ground truth html is empty") diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 8de84cae..cec25096 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -93,7 +93,7 @@ def get_lighthouse_score_from_subprocess(url): server_thread.join(timeout=10) if server_thread.is_alive(): - bt.logging.info("Server did not shut down properly.") + bt.logging.error("Server did not shut down properly.") else: bt.logging.info("Server stopped successfully.") diff --git a/webgenie/rewards/quality_reward.py b/webgenie/rewards/quality_reward.py index 1b780d2f..8becdd0b 100644 --- a/webgenie/rewards/quality_reward.py +++ b/webgenie/rewards/quality_reward.py @@ -29,7 +29,7 @@ async def _get_score(self, solution: Solution) -> float: return response.score / 100 async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: - bt.logging.debug(f"Rewarding task in quality reward") + bt.logging.info(f"Rewarding task in quality reward") get_score_tasks = [self._get_score(solution) for solution in solutions] scores = await asyncio.gather(*get_score_tasks) return np.array(scores) diff --git a/webgenie/rewards/rtc_reward/rtc_reward.py b/webgenie/rewards/rtc_reward/rtc_reward.py index d91a5de5..ec7323f2 100644 --- a/webgenie/rewards/rtc_reward/rtc_reward.py +++ b/webgenie/rewards/rtc_reward/rtc_reward.py @@ -33,7 +33,7 @@ async def _get_prompt(self, task: Task, solution: Solution) -> str: return response["prompt"] async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: - bt.logging.debug(f"Rewarding task in rtc reward") + bt.logging.info(f"Rewarding task in rtc reward") original_prompts = [task.prompt for _ in solutions] miner_prompts = [await self._get_prompt(task, solution) for solution in solutions] diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index 534df9f9..ce2461b6 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -20,7 +20,7 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: if not isinstance(task, ImageTask): raise ValueError(f"Task is not a ImageTask: {type(task)}") - bt.logging.debug(f"Rewarding image task in visual reward") + bt.logging.info(f"Rewarding image task in visual reward") original_html_path = f"{WORK_DIR}/original_{uuid.uuid4()}.html" with open(original_html_path, "w") as f: From 11f20ba9bb1cbb391e56107b306b1d0ba4779608 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 10 Jan 2025 12:08:05 -0600 Subject: [PATCH 184/554] chore: test for dtao --- .gitignore | 2 +- neurons/validators/genie_validator.py | 13 +++++++++++-- webgenie/helpers/dashboard.py | 2 ++ webgenie/utils/uids.py | 9 +++++---- 4 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 webgenie/helpers/dashboard.py diff --git a/.gitignore b/.gitignore index 4ee9a092..130ff60f 100644 --- a/.gitignore +++ b/.gitignore @@ -184,7 +184,7 @@ work_save/ # scripts run_miner.sh -run_miner_2.sh +run_miner2.sh run_validator.sh # developer doc diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 4067b6b7..0c993ba1 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -61,13 +61,18 @@ async def query_miners(self): task, synapse = self.synthetic_tasks.pop(0) miner_uids = get_all_available_uids(self.neuron) + if not miner_uids: + bt.logging.warning("No miners available") + return + bt.logging.debug(f"Querying {len(miner_uids)} miners") async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: all_synapse_results = await dendrite( axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], synapse=synapse, timeout=task.timeout, ) + bt.logging.debug(f"Received {len(all_synapse_results)} synapse results") solutions = [] for synapse, miner_uid in zip(all_synapse_results, miner_uids): @@ -80,7 +85,7 @@ async def query_miners(self): process_time = processed_synapse.dendrite.process_time, ) ) - + bt.logging.info(f"Received {len(solutions)} solutions") self.competitions.append((task, solutions)) except Exception as e: bt.logging.error(f"Error in query_miners: {e}") @@ -155,6 +160,9 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag best_miner_uid = get_most_available_uid(self.neuron) all_miner_uids = get_all_available_uids(self.neuron) try: + if not all_miner_uids: + raise Exception("No miners available") + async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: responses = await dendrite( axons=[self.neuron.metagraph.axons[uid] for uid in all_miner_uids], @@ -162,7 +170,7 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag timeout=synapse.timeout, ) - processed_synapse = await self.process_synapse(responses[best_miner_uid]) + processed_synapse = await self.process_synapse(responses[all_miner_uids.index(best_miner_uid)]) if processed_synapse is None: raise Exception(f"No valid solution received") @@ -173,6 +181,7 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag return synapse async def process_synapse(self, synapse: bt.Synapse) -> bt.Synapse: + bt.logging.debug(f"Processing synapse: {synapse.dendrite.status_code}") if synapse.dendrite.status_code == 200: html = preprocess_html(synapse.html) if not html: diff --git a/webgenie/helpers/dashboard.py b/webgenie/helpers/dashboard.py new file mode 100644 index 00000000..6166a0b1 --- /dev/null +++ b/webgenie/helpers/dashboard.py @@ -0,0 +1,2 @@ +def upload_competition_result(): + pass \ No newline at end of file diff --git a/webgenie/utils/uids.py b/webgenie/utils/uids.py index 4e8edd48..84e72dc7 100644 --- a/webgenie/utils/uids.py +++ b/webgenie/utils/uids.py @@ -19,11 +19,12 @@ def check_uid_availability( if not metagraph.axons[uid].is_serving: return False # Filter validator permit > 1024 stake. - if metagraph.validator_permit[uid]: - if metagraph.S[uid] > vpermit_tao_limit: - return False - # Available otherwise. return True + # if metagraph.validator_permit[uid]: + # if metagraph.S[uid] > vpermit_tao_limit: + # return False + # # Available otherwise. + # return True def get_most_available_uid(self, exclude: List[int] = None) -> int: From 967369233ccf167a06ffa5ef90d91bde04fd9589 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 10 Jan 2025 12:14:25 -0600 Subject: [PATCH 185/554] feat: update organic task logic --- neurons/validators/genie_validator.py | 19 ++++++++++++------- webgenie/utils/uids.py | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 0c993ba1..ac1eee3e 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -1,5 +1,6 @@ import os import bittensor as bt +import numpy as np import random from typing import Union @@ -157,7 +158,6 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag else: bt.logging.debug(f"Organic image forward: {image_debug_str(synapse.base64_image)}...") - best_miner_uid = get_most_available_uid(self.neuron) all_miner_uids = get_all_available_uids(self.neuron) try: if not all_miner_uids: @@ -169,12 +169,17 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag synapse=synapse, timeout=synapse.timeout, ) - - processed_synapse = await self.process_synapse(responses[all_miner_uids.index(best_miner_uid)]) - if processed_synapse is None: - raise Exception(f"No valid solution received") - - return processed_synapse + # Sort miner UIDs and responses by incentive scores + incentives = self.neuron.metagraph.I[all_miner_uids] + sorted_indices = np.argsort(-incentives) # Negative for descending order + all_miner_uids = [all_miner_uids[i] for i in sorted_indices] + responses = [responses[i] for i in sorted_indices] + for response in responses: + processed_synapse = await self.process_synapse(response) + if processed_synapse is None: + continue + return processed_synapse + raise Exception(f"No valid solution received") except Exception as e: bt.logging.error(f"[forward_organic_synapse] Error querying dendrite: {e}") synapse.html = f"Error: {e}" diff --git a/webgenie/utils/uids.py b/webgenie/utils/uids.py index 84e72dc7..8e07094f 100644 --- a/webgenie/utils/uids.py +++ b/webgenie/utils/uids.py @@ -46,7 +46,7 @@ def get_most_available_uid(self, exclude: List[int] = None) -> int: if uid_is_not_excluded: candidate_uids.append(uid) - return candidate_uids[np.argmax(self.metagraph.S[candidate_uids])] + return candidate_uids[np.argmax(self.metagraph.I[candidate_uids])] def get_all_available_uids( self, exclude: List[int] = None From 18b5665b54ea9555b172cbef3a3326a67b41d024 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 11 Jan 2025 06:07:56 -0600 Subject: [PATCH 186/554] test: test reward system --- neurons/validators/genie_validator.py | 2 +- tests/init_test.py | 8 ++ tests/test_reward.py | 76 +++++++++++++++++++ webgenie/constants.py | 3 + .../lighthouse_reward/get_lighthouse_score.py | 7 +- 5 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 tests/init_test.py create mode 100644 tests/test_reward.py diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index ac1eee3e..d0a0f37f 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -62,7 +62,7 @@ async def query_miners(self): task, synapse = self.synthetic_tasks.pop(0) miner_uids = get_all_available_uids(self.neuron) - if not miner_uids: + if len(miner_uids) == 0: bt.logging.warning("No miners available") return diff --git a/tests/init_test.py b/tests/init_test.py new file mode 100644 index 00000000..6613366b --- /dev/null +++ b/tests/init_test.py @@ -0,0 +1,8 @@ +import sys +import os +from dotenv import load_dotenv, find_dotenv + +def init_test(): + parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + sys.path.append(parent_dir) + load_dotenv(find_dotenv(filename=".env.validator")) diff --git a/tests/test_reward.py b/tests/test_reward.py new file mode 100644 index 00000000..2aefa541 --- /dev/null +++ b/tests/test_reward.py @@ -0,0 +1,76 @@ +from init_test import init_test +init_test() + +import asyncio +import numpy as np +from typing import List +import time +from webgenie.tasks import Task, Solution, ImageTask +from webgenie.rewards import ( + LighthouseReward, + QualityReward, + VisualReward, +) + +from webgenie.protocol import WebgenieImageSynapse +from webgenie.competitions.competition import ( + ACCURACY_METRIC_NAME, + SEO_METRIC_NAME, + QUALITY_METRIC_NAME, +) + +from webgenie.helpers.htmls import html_to_screenshot +from neurons.miners.openai_miner import OpenaiMiner + +metrics = { + ACCURACY_METRIC_NAME: VisualReward(), + SEO_METRIC_NAME: LighthouseReward(), + QUALITY_METRIC_NAME: QualityReward(), +} + +async def calculate_scores(task: Task, solutions: List[Solution]) -> dict[str, np.ndarray]: + scores: dict[str, np.ndarray] = {} + for metric_name, reward_model in metrics.items(): + reward_scores = await reward_model.reward(task, solutions) + scores[metric_name] = reward_scores + return scores + + +async def main(): + ground_truth_html_path = "work/original_f7e98ea8-cc04-44ea-8f40-432914b0f0ea.html" + with open(ground_truth_html_path, "r") as f: + ground_truth_html = f.read() + + print("HTML to screenshot") + start_time = time.time() + base64_image = html_to_screenshot(ground_truth_html) + execution_time = time.time() - start_time + print(f"Execution time: {execution_time:.2f} seconds") + + task = ImageTask( + ground_truth_html=ground_truth_html, + ) + + miner = OpenaiMiner(neuron=None) + synapse = WebgenieImageSynapse( + base64_image = base64_image, + ) + + print("Miner forward image") + start_time = time.time() + synapse = await miner.forward_image(synapse) + print(synapse.html) + execution_time = time.time() - start_time + print(f"Execution time: {execution_time:.2f} seconds") + solutions = [Solution(html=synapse.html) for _ in range(256)] + + print("Calculate scores") + start_time = time.time() + scores = await calculate_scores(task, solutions) + execution_time = time.time() - start_time + print(f"Execution time: {execution_time:.2f} seconds") + + print(scores) + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/webgenie/constants.py b/webgenie/constants.py index c1188499..455d097e 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -34,6 +34,9 @@ # miner html load time MINER_HTML_LOAD_TIME = 2000 +# max miner html length +MAX_MINER_HTML_LEN = 1000000 + # work dir WORK_DIR = "work" diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index cec25096..6223764c 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -9,6 +9,7 @@ from webgenie.constants import LIGHTHOUSE_SERVER_PORT +httpd = None def get_lighthouse_score(htmls: List[str]) -> List[Dict[str, float]]: class CustomHandler(SimpleHTTPRequestHandler): @@ -50,6 +51,7 @@ def do_OPTIONS(self): self.end_headers() def run_server(port=8000): + global httpd server_address = ('', port) httpd = HTTPServer(server_address, CustomHandler) bt.logging.info(f"Starting server on port {port}...") @@ -90,8 +92,11 @@ def get_lighthouse_score_from_subprocess(url): for i in range(len(htmls)): url = f"http://localhost:{port}/lighthouse_score/{i}" scores.append(get_lighthouse_score_from_subprocess(url)) - + + if httpd: + httpd.shutdown() server_thread.join(timeout=10) + if server_thread.is_alive(): bt.logging.error("Server did not shut down properly.") else: From 063c283abd06d56000bca632fca4f0c4baab12f7 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 11 Jan 2025 06:08:41 -0600 Subject: [PATCH 187/554] chore: add logging --- webgenie/rewards/lighthouse_reward/lighthouse_reward.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py index 22212974..2f781ea4 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py @@ -1,6 +1,7 @@ # The paper [Unsupervised Evaluation of Code LLMs with Round-Trip Correctness] # (https://arxiv.org/pdf/2402.08699#page=11&zoom=100,384,458) is our inspiration for this reward. +import bittensor as bt import numpy as np from typing import List @@ -13,6 +14,7 @@ class LighthouseReward(Reward): async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: + bt.logging.info(f"Rewarding lighthouse task") htmls = [solution.html for solution in solutions] scores_dict = get_lighthouse_score(htmls) scores = [] From fbbd25c56b792d94c22ef56d778f66757acdbee6 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 11 Jan 2025 06:18:47 -0600 Subject: [PATCH 188/554] chore: add tqdm process --- tests/test_reward.py | 6 ++++++ webgenie/rewards/lighthouse_reward/get_lighthouse_score.py | 3 ++- webgenie/rewards/visual_reward/metrics/visual_score.py | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/test_reward.py b/tests/test_reward.py index 2aefa541..8004bc68 100644 --- a/tests/test_reward.py +++ b/tests/test_reward.py @@ -30,9 +30,15 @@ async def calculate_scores(task: Task, solutions: List[Solution]) -> dict[str, np.ndarray]: scores: dict[str, np.ndarray] = {} + for metric_name, reward_model in metrics.items(): + print(metric_name) + start_time = time.time() reward_scores = await reward_model.reward(task, solutions) + execution_time = time.time() - start_time + print(f"Execution time: {execution_time:.2f} seconds") scores[metric_name] = reward_scores + print(scores[metric_name]) return scores diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 6223764c..73d031e6 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -4,6 +4,7 @@ import threading import time +from tqdm import tqdm from http.server import SimpleHTTPRequestHandler, HTTPServer from typing import List, Dict @@ -89,7 +90,7 @@ def get_lighthouse_score_from_subprocess(url): time.sleep(1) # Give the server time to start scores = [] - for i in range(len(htmls)): + for i in tqdm(range(len(htmls)), desc="Getting lighthouse scores"): url = f"http://localhost:{port}/lighthouse_score/{i}" scores.append(get_lighthouse_score_from_subprocess(url)) diff --git a/webgenie/rewards/visual_reward/metrics/visual_score.py b/webgenie/rewards/visual_reward/metrics/visual_score.py index b320f21a..3f967a16 100644 --- a/webgenie/rewards/visual_reward/metrics/visual_score.py +++ b/webgenie/rewards/visual_reward/metrics/visual_score.py @@ -445,7 +445,7 @@ def visual_eval_v3_multi(input_list, debug=False): predict_img_list = [html.replace(".html", ".png") for html in predict_html_list] # try: predict_blocks_list = [] - for predict_html in predict_html_list: + for predict_html in tqdm(predict_html_list, desc="Screenshot HTML files and get OCR blocks"): predict_img = predict_html.replace(".html", ".png") # This will help fix some html syntax error pre_process(predict_html) @@ -463,7 +463,7 @@ def visual_eval_v3_multi(input_list, debug=False): return_score_list = [] - for k, predict_blocks in enumerate(predict_blocks_list): + for k, predict_blocks in tqdm(enumerate(predict_blocks_list), desc="Processing HTML files"): if len(predict_blocks) == 0: print("[Warning] No detected blocks in: ", predict_img_list[k]) final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) From 604c956432783ce34fc07d4d4d818bad5cd05e25 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 11 Jan 2025 10:58:35 -0600 Subject: [PATCH 189/554] test: calculate scoring time --- tests/test_clip.py | 21 + .../metrics/screenshot_multiple.py | 29 + .../visual_reward/metrics/visual_score.py | 20 +- .../visual_reward/metrics_v2/__init__.py | 0 .../metrics_v2/dedup_post_gen.py | 69 ++ .../metrics_v2/ocr_free_utils.py | 268 ++++++++ .../metrics_v2/screenshot_multiple.py | 29 + .../metrics_v2/screenshot_single.py | 52 ++ .../visual_reward/metrics_v2/visual_score.py | 599 ++++++++++++++++++ 9 files changed, 1077 insertions(+), 10 deletions(-) create mode 100644 tests/test_clip.py create mode 100644 webgenie/rewards/visual_reward/metrics/screenshot_multiple.py create mode 100644 webgenie/rewards/visual_reward/metrics_v2/__init__.py create mode 100644 webgenie/rewards/visual_reward/metrics_v2/dedup_post_gen.py create mode 100644 webgenie/rewards/visual_reward/metrics_v2/ocr_free_utils.py create mode 100644 webgenie/rewards/visual_reward/metrics_v2/screenshot_multiple.py create mode 100644 webgenie/rewards/visual_reward/metrics_v2/screenshot_single.py create mode 100644 webgenie/rewards/visual_reward/metrics_v2/visual_score.py diff --git a/tests/test_clip.py b/tests/test_clip.py new file mode 100644 index 00000000..4fd1b46a --- /dev/null +++ b/tests/test_clip.py @@ -0,0 +1,21 @@ +import clip +import torch +from PIL import Image + +# Load the model +device = "cuda" if torch.cuda.is_available() else "cpu" +model, preprocess = clip.load('ViT-B/32', device) + +# Load and preprocess images +image1 = preprocess(Image.open('path_to_text_image1.jpg')).unsqueeze(0).to(device) +image2 = preprocess(Image.open('path_to_text_image2.jpg')).unsqueeze(0).to(device) + +# Compute features and calculate cosine similarity +with torch.no_grad(): + image_features1 = model.encode_image(image1) + image_features2 = model.encode_image(image2) + image_features1 /= image_features1.norm(dim=-1, keepdim=True) + image_features2 /= image_features2.norm(dim=-1, keepdim=True) + cosine_similarity = (image_features1 @ image_features2.T).cpu().numpy() + +print(f"Cosine similarity: {cosine_similarity.item()}") diff --git a/webgenie/rewards/visual_reward/metrics/screenshot_multiple.py b/webgenie/rewards/visual_reward/metrics/screenshot_multiple.py new file mode 100644 index 00000000..be542a76 --- /dev/null +++ b/webgenie/rewards/visual_reward/metrics/screenshot_multiple.py @@ -0,0 +1,29 @@ +import os +from playwright.sync_api import sync_playwright +from PIL import Image +from tqdm import tqdm + +def take_screenshots(html_files, output_files, page_load_time = 1000, do_it_again=False): + with sync_playwright() as p: + browser = p.chromium.launch() # You can also use 'firefox' or 'webkit' + for html_file, output_file in tqdm(zip(html_files, output_files), desc="Screenshoting HTML files"): + + if os.path.exists(html_file): + html_file = "file://" + os.path.abspath(html_file) + + if os.path.exists(output_file) and not do_it_again: + print(f"{output_file} exists!") + continue + + try: + page = browser.new_page() + page.goto(html_file, timeout=60000) + page.wait_for_timeout(page_load_time) + page.screenshot(path=output_file, full_page=True, animations="disabled", timeout=60000) + page.close() + except Exception as e: + print(f"Failed to take screenshot due to: {e}. Generating a blank image.") + # Generate a blank image + img = Image.new('RGB', (1280, 960), color = 'white') + img.save(output_file) + browser.close() diff --git a/webgenie/rewards/visual_reward/metrics/visual_score.py b/webgenie/rewards/visual_reward/metrics/visual_score.py index 3f967a16..ac0df6b7 100644 --- a/webgenie/rewards/visual_reward/metrics/visual_score.py +++ b/webgenie/rewards/visual_reward/metrics/visual_score.py @@ -464,16 +464,16 @@ def visual_eval_v3_multi(input_list, debug=False): return_score_list = [] for k, predict_blocks in tqdm(enumerate(predict_blocks_list), desc="Processing HTML files"): - if len(predict_blocks) == 0: - print("[Warning] No detected blocks in: ", predict_img_list[k]) - final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) - return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) - continue - elif len(original_blocks) == 0: - print("[Warning] No detected blocks in: ", original_img) - final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) - return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) - continue + # if len(predict_blocks) == 0: + # print("[Warning] No detected blocks in: ", predict_img_list[k]) + # final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) + # return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) + # continue + # elif len(original_blocks) == 0: + # print("[Warning] No detected blocks in: ", original_img) + # final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) + # return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) + # continue if debug: print(predict_blocks) diff --git a/webgenie/rewards/visual_reward/metrics_v2/__init__.py b/webgenie/rewards/visual_reward/metrics_v2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/webgenie/rewards/visual_reward/metrics_v2/dedup_post_gen.py b/webgenie/rewards/visual_reward/metrics_v2/dedup_post_gen.py new file mode 100644 index 00000000..2af9bd43 --- /dev/null +++ b/webgenie/rewards/visual_reward/metrics_v2/dedup_post_gen.py @@ -0,0 +1,69 @@ +import difflib +import os +import re + +def map_positions(clean_text, original_text): + """ + Maps the positions from the clean text back to the original text. + """ + map_clean_to_original = [] + original_idx = 0 + + for clean_char in clean_text: + while original_text[original_idx] != clean_char: + original_idx += 1 + map_clean_to_original.append(original_idx) + original_idx += 1 + + return map_clean_to_original + +def check_repetitive_content(file_path, chunk_size=100, repetition_threshold=5, similarity_threshold=0.8, debug=False): + """ + Checks for repetitive content in a text file, considering both exact and similar chunks, + ignoring HTML tags but keeping the original position reference. + + :param file_path: Path to the text file. + :param chunk_size: The size of each chunk for comparison. + :param repetition_threshold: Minimum number of repetitions to consider it as repetitive content. + :param similarity_threshold: The threshold for considering two chunks as similar (0 to 1). + :return: A tuple indicating if repetitive content was found and the position where it starts in the original file. + """ + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + + # Clean HTML content and keep a map of positions + content_no_html = re.sub('<.*?>', '', content) + position_map = map_positions(content_no_html, content) + + # Split content into chunks + chunks = [content_no_html[i:i + chunk_size] for i in range(0, len(content_no_html), chunk_size)] + + # Check for repetitive and similar chunks + seen = {} + repetitive_start = len(content_no_html) + for i, chunk in enumerate(chunks): + for seen_chunk, indexes in seen.items(): + similarity = difflib.SequenceMatcher(None, chunk, seen_chunk).ratio() + if similarity >= similarity_threshold: + indexes.append(i) + if len(indexes) >= repetition_threshold: + clean_start = min(repetitive_start, indexes[0] * chunk_size) + c_repetitive_start = position_map[clean_start] if clean_start < len(position_map) else len(content) + if c_repetitive_start < repetitive_start: + repetitive_start = c_repetitive_start + break + else: + seen[chunk] = [i] + + repetitive, start_position = repetitive_start != len(content_no_html), repetitive_start + + if repetitive: + print(f"[Warning] Repetitive content found in {file_path}, start at {start_position}") + print(f"[Warning] You might want to manually check whether the automatic repetition removal is correct.") + if not debug: + os.rename(file_path, file_path.replace(".html", "_old.txt")) + with open(file_path, 'w', encoding='utf-8') as file: + file.write(content[:start_position]) + else: + with open(file_path.replace(".html", "_new.html"), 'w', encoding='utf-8') as file: + file.write(content[:start_position]) diff --git a/webgenie/rewards/visual_reward/metrics_v2/ocr_free_utils.py b/webgenie/rewards/visual_reward/metrics_v2/ocr_free_utils.py new file mode 100644 index 00000000..fdffeb41 --- /dev/null +++ b/webgenie/rewards/visual_reward/metrics_v2/ocr_free_utils.py @@ -0,0 +1,268 @@ +import cv2 +import numpy as np +from PIL import Image, ImageColor +import os +from bs4 import BeautifulSoup, NavigableString, Tag, Comment +from pathlib import Path + +from webgenie.constants import PYTHON_CMD + +def rgb_to_hex(rgb): + """Convert an RGB tuple to hexadecimal format.""" + return '{:02X}{:02X}{:02X}'.format(*rgb) + + +class ColorPool: + def __init__(self, offset=0): + + color_values = list(range(10, 251, 16)) + color_list = [((r + offset) % 256, (g + offset) % 256, (b + offset) % 256) for r in color_values for g in color_values for b in color_values] + self.color_pool = [rgb_to_hex(color) for color in color_list] + + def pop_color(self): + if self.color_pool: + return self.color_pool.pop() + else: + raise NotImplementedError + + +def process_html(input_file_path, output_file_path, offset=0): + # Read the input HTML file + with open(input_file_path, 'r') as file: + soup = BeautifulSoup(file, 'html.parser') + + def update_style(element, property_name, value): + # Update the element's style attribute with the given property and value + # Adding !important to ensure the style overrides others + important_value = f"{value} !important" + styles = element.attrs.get('style', '').split(';') + updated_styles = [s for s in styles if not s.strip().startswith(property_name) and len(s.strip()) > 0] + updated_styles.append(f"{property_name}: {important_value}") + element['style'] = '; '.join(updated_styles).strip() + + # Set the background color of all elements to white + for element in soup.find_all(True): + update_style(element, 'background-color', 'rgba(255, 255, 255, 0.0)') + + color_pool = ColorPool(offset) + + # Assign a unique color to text within each text-containing element + text_tags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'span', 'a', 'b', 'li', 'table', 'td', 'th', 'button', 'footer', 'header', 'figcaption'] # Add more tags as needed + for tag in soup.find_all(text_tags): + color = f"#{color_pool.pop_color()}" + update_style(tag, 'color', color) + update_style(tag, 'opacity', 1.0) + + # Write the modified HTML to a new file + with open(output_file_path, 'w') as file: + file.write(str(soup)) + + +def similar(n1, n2): + if abs(n1 - n2) <= 8: + return True + else: + return False + + +def find_different_pixels(image1_path, image2_path): + # Open the images + img1 = Image.open(image1_path) + img2 = Image.open(image2_path) + + # Ensure both images are of the same size + if img1.size != img2.size: + print(f"[Warning] Images are not the same size, {image1_path}, {image2_path}") + return None + + # Convert images to RGB if they are not + img1 = img1.convert('RGB') + img2 = img2.convert('RGB') + + # Get pixel data + pixels1 = img1.load() + pixels2 = img2.load() + + # List to store coordinates of different pixels + different_pixels = [] + + # Iterate through each pixel + for x in range(img1.size[0]): + for y in range(img1.size[1]): + # Compare pixel colors + r1, g1, b1 = pixels1[x, y] + r2, g2, b2 = pixels2[x, y] + if similar((r1 + 50) % 256, r2) and similar((g1 + 50) % 256, g2) and similar((b1 + 50) % 256, b2): + different_pixels.append((y, x)) + + if len(different_pixels) > 0: + return np.stack(different_pixels) + else: + return None + + +def extract_text_with_color(html_file): + def get_color(tag): + if 'style' in tag.attrs: + styles = tag['style'].split(';') + color_style = [s for s in styles if 'color' in s and 'background-color' not in s] + if color_style: + color = color_style[-1].split(':')[1].strip().replace(" !important", "") + if color[0] == "#": + return color + else: + try: + if color.startswith('rgb'): + color = tuple(map(int, color[4:-1].split(','))) # Extract the RGB values + else: + color = ImageColor.getrgb(color) # Convert named color to RGB + return '#{:02x}{:02x}{:02x}'.format(*color) # Convert RGB to hexadecimal + except ValueError: + print(f"Warning: unable to identify or convert color in {html_file}...", color) + return None + return None + + def extract_text_recursive(element, parent_color='#000000'): + if isinstance(element, Comment): + return None + elif isinstance(element, NavigableString): + text = element.strip() + return (text, parent_color) if text else None + + elif isinstance(element, Tag): + current_color = get_color(element) or parent_color + children_texts = filter(None, [extract_text_recursive(child, current_color) for child in element.children]) + return list(children_texts) + + with open(html_file, 'r', encoding='utf-8') as file: + soup = BeautifulSoup(file, 'html.parser') + body = soup.body + return extract_text_recursive(body) if body else [] + + +def flatten_tree(tree): + flat_list = [] + + # Helper function to recursively flatten the tree + def flatten(node): + if isinstance(node, list): + for item in node: + flatten(item) + else: + flat_list.append(node) + + # Flatten the tree + flatten(tree) + return flat_list + + +def average_color(image_path, coordinates): + """ + Calculates the average color of the specified coordinates in the given image. + + :param image: A PIL Image object. + :param coordinates: A 2D numpy array of coordinates, where each row represents [x, y]. + :return: A tuple representing the average color (R, G, B). + """ + # Convert image to numpy array + image_array = np.array(Image.open(image_path).convert('RGB')) + + # Extract colors at the specified coordinates + colors = [image_array[x, y] for x, y in coordinates] + + # Calculate the average color + avg_color = np.mean(colors, axis=0) + + return tuple(avg_color.astype(int)) + + +def get_blocks_from_image_diff_pixels(image_path, html_text_color_tree, different_pixels): + image = cv2.imread(image_path) + x_w = image.shape[0] + y_w = image.shape[1] + + def hex_to_bgr(hex_color): + """ + Converts a hex color string to a BGR color tuple. + """ + hex_color = hex_color.lstrip('#') + rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) + return rgb[::-1] + + + def get_intersect(arr1, arr2): + # Reshape the arrays to 1D + arr1_reshaped = arr1.view([('', arr1.dtype)] * arr1.shape[1]) + arr2_reshaped = arr2.view([('', arr2.dtype)] * arr2.shape[1]) + + # Find the intersection + common_rows = np.intersect1d(arr1_reshaped, arr2_reshaped) + + # Reshape the result back to 2D, if needed + common_rows = common_rows.view(arr1.dtype).reshape(-1, arr1.shape[1]) + return common_rows + + + blocks = [] + for item in html_text_color_tree: + try: + color = np.array(hex_to_bgr(item[1]), dtype="uint8") + except: + continue + + lower = color - 4 + upper = color + 4 + + mask = cv2.inRange(image, lower, upper) + + coords = np.column_stack(np.where(mask > 0)) + + coords = get_intersect(coords, different_pixels) + + if coords.size == 0: + continue + + x_min, y_min = np.min(coords, axis=0) + x_max, y_max = np.max(coords, axis=0) + color = average_color(image_path.replace("_p.png", ".png"), coords) + + blocks.append({'text': item[0].lower(), 'bbox': (y_min / y_w, x_min / x_w, (y_max - y_min + 1) / y_w, (x_max - x_min + 1) / x_w), 'color': color}) + return blocks + + +def get_itermediate_names(name): + return name.replace(".png", ".html"), name.replace(".png", "_p.html"), name.replace(".png", "_p_1.html"), name.replace(".png", "_p.png"), name.replace(".png", "_p_1.png") + +def get_files_to_screenshot(image_path): + html, p_html, p_html_1, p_png, p_png_1 = get_itermediate_names(image_path) + process_html(html, p_html) + process_html(html, p_html_1, offset=50) + + return [p_html, p_html_1], [p_png, p_png_1] + +def get_blocks_ocr_free(image_path, page_load_time, pre_screenshoted = False): + html, p_html, p_html_1, p_png, p_png_1 = get_itermediate_names(image_path) + + if not pre_screenshoted: + process_html(html, p_html) + process_html(html, p_html_1, offset=50) + os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {p_html} --png {p_png} --page_load_time {page_load_time}") + os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {p_html_1} --png {p_png_1} --page_load_time {page_load_time}") + + different_pixels = find_different_pixels(p_png, p_png_1) + + if different_pixels is None: + print(f"[Warning] Unable to get pixels with different colors from {p_png}, {p_png_1}...") + os.system(f"rm {p_html} {p_png} {p_html_1} {p_png_1}") + return [] + + html_text_color_tree = flatten_tree(extract_text_with_color(p_html)) + try: + blocks = get_blocks_from_image_diff_pixels(p_png, html_text_color_tree, different_pixels) + except: + print(f"[Warning] Unable to get blocks from {p_png}...") + os.system(f"rm {p_html} {p_png} {p_html_1} {p_png_1}") + return [] + + os.system(f"rm {p_html} {p_png} {p_html_1} {p_png_1}") + return blocks \ No newline at end of file diff --git a/webgenie/rewards/visual_reward/metrics_v2/screenshot_multiple.py b/webgenie/rewards/visual_reward/metrics_v2/screenshot_multiple.py new file mode 100644 index 00000000..be542a76 --- /dev/null +++ b/webgenie/rewards/visual_reward/metrics_v2/screenshot_multiple.py @@ -0,0 +1,29 @@ +import os +from playwright.sync_api import sync_playwright +from PIL import Image +from tqdm import tqdm + +def take_screenshots(html_files, output_files, page_load_time = 1000, do_it_again=False): + with sync_playwright() as p: + browser = p.chromium.launch() # You can also use 'firefox' or 'webkit' + for html_file, output_file in tqdm(zip(html_files, output_files), desc="Screenshoting HTML files"): + + if os.path.exists(html_file): + html_file = "file://" + os.path.abspath(html_file) + + if os.path.exists(output_file) and not do_it_again: + print(f"{output_file} exists!") + continue + + try: + page = browser.new_page() + page.goto(html_file, timeout=60000) + page.wait_for_timeout(page_load_time) + page.screenshot(path=output_file, full_page=True, animations="disabled", timeout=60000) + page.close() + except Exception as e: + print(f"Failed to take screenshot due to: {e}. Generating a blank image.") + # Generate a blank image + img = Image.new('RGB', (1280, 960), color = 'white') + img.save(output_file) + browser.close() diff --git a/webgenie/rewards/visual_reward/metrics_v2/screenshot_single.py b/webgenie/rewards/visual_reward/metrics_v2/screenshot_single.py new file mode 100644 index 00000000..8c4673b8 --- /dev/null +++ b/webgenie/rewards/visual_reward/metrics_v2/screenshot_single.py @@ -0,0 +1,52 @@ +import os +import argparse +from playwright.sync_api import sync_playwright +from PIL import Image + + +def take_screenshot(url, output_file="screenshot.png", page_load_time = 1000, do_it_again=False): + # Convert local path to file:// URL if it's a file + if os.path.exists(url): + url = "file://" + os.path.abspath(url) + + if os.path.exists(output_file) and not do_it_again: + print(f"{output_file} exists!") + return + + try: + with sync_playwright() as p: + # Choose a browser, e.g., Chromium, Firefox, or WebKit + browser = p.chromium.launch() + page = browser.new_page() + + # Navigate to the URL + page.goto(url, timeout=60000) + + # Wait for 10 seconds to ensure page is fully loaded + page.wait_for_timeout(page_load_time) + + # Take the screenshot + page.screenshot(path=output_file, full_page=True, animations="disabled", timeout=60000) + + browser.close() + except Exception as e: + print(f"Failed to take screenshot due to: {e}. Generating a blank image.") + # Generate a blank image + img = Image.new('RGB', (1280, 960), color = 'white') + img.save(output_file) + + +if __name__ == "__main__": + + # Initialize the parser + parser = argparse.ArgumentParser(description='Process two path strings.') + + # Define the arguments + parser.add_argument('--html', type=str) + parser.add_argument('--png', type=str) + parser.add_argument('--page_load_time', type=int, default=1000) + + # Parse the arguments + args = parser.parse_args() + + take_screenshot(args.html, args.png, args.page_load_time, do_it_again=True) diff --git a/webgenie/rewards/visual_reward/metrics_v2/visual_score.py b/webgenie/rewards/visual_reward/metrics_v2/visual_score.py new file mode 100644 index 00000000..0d46e02b --- /dev/null +++ b/webgenie/rewards/visual_reward/metrics_v2/visual_score.py @@ -0,0 +1,599 @@ +import cv2 +import numpy as np + +import sys +import os +# Add the current file's directory to Python path +current_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(current_dir) + +# This is a patch for color map, which is not updated for newer version of numpy +def patch_asscalar(a): + return a.item() +setattr(np, "asscalar", patch_asscalar) + +import matplotlib.pyplot as plt +from scipy.optimize import linear_sum_assignment +import random +from sklearn.metrics.pairwise import cosine_similarity +from difflib import SequenceMatcher +from tqdm import tqdm +from pathlib import Path +from PIL import Image, ImageDraw +import torch +import clip +from copy import deepcopy +from collections import Counter +from bs4 import BeautifulSoup, NavigableString, Comment +import re +import math +from colormath.color_objects import sRGBColor, LabColor +from colormath.color_conversions import convert_color +from colormath.color_diff import delta_e_cie2000 + +from ocr_free_utils import get_blocks_ocr_free, get_files_to_screenshot +from dedup_post_gen import check_repetitive_content +from screenshot_multiple import take_screenshots +from webgenie.constants import ( + PYTHON_CMD, + GROUND_TRUTH_HTML_LOAD_TIME, + MINER_HTML_LOAD_TIME, +) + +device = "cuda" if torch.cuda.is_available() else "cpu" +model, preprocess = clip.load("ViT-B/32", device=device) + +def calculate_similarity(block1, block2, max_distance=1.42): + text_similarity = SequenceMatcher(None, block1['text'], block2['text']).ratio() + return text_similarity + + +def adjust_cost_for_context(cost_matrix, consecutive_bonus=1.0, window_size=20): + if window_size <= 0: + return cost_matrix + + n, m = cost_matrix.shape + adjusted_cost_matrix = np.copy(cost_matrix) + + for i in range(n): + for j in range(m): + bonus = 0 + if adjusted_cost_matrix[i][j] >= -0.5: + continue + nearby_matrix = cost_matrix[max(0, i - window_size):min(n, i + window_size + 1), max(0, j - window_size):min(m, j + window_size + 1)] + flattened_array = nearby_matrix.flatten() + sorted_array = np.sort(flattened_array)[::-1] + sorted_array = np.delete(sorted_array, np.where(sorted_array == cost_matrix[i, j])[0][0]) + top_k_elements = sorted_array[- window_size * 2:] + sum_top_k = np.sum(top_k_elements) + bonus = consecutive_bonus * sum_top_k + adjusted_cost_matrix[i][j] += bonus + return adjusted_cost_matrix + +def create_cost_matrix(A, B): + n = len(A) + m = len(B) + cost_matrix = np.zeros((n, m)) + for i in range(n): + for j in range(m): + cost_matrix[i, j] = -calculate_similarity(A[i], B[j]) + return cost_matrix + + +def draw_matched_bboxes(img1, img2, matched_bboxes): + # Create copies of images to draw on + img1_drawn = img1.copy() + img2_drawn = img2.copy() + + h1, w1, _ = img1.shape + h2, w2, _ = img2.shape + + + # Iterate over matched bounding boxes + for bbox_pair in matched_bboxes: + # Random color for each pair + color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + + # Ensure that the bounding box coordinates are integers + bbox1 = [int(bbox_pair[0][0] * w1), int(bbox_pair[0][1] * h1), int(bbox_pair[0][2] * w1), int(bbox_pair[0][3] * h1)] + bbox2 = [int(bbox_pair[1][0] * w2), int(bbox_pair[1][1] * h2), int(bbox_pair[1][2] * w2), int(bbox_pair[1][3] * h2)] + + # Draw bbox on the first image + top_left_1 = (bbox1[0], bbox1[1]) + bottom_right_1 = (bbox1[0] + bbox1[2], bbox1[1] + bbox1[3]) + img1_drawn = cv2.rectangle(img1_drawn, top_left_1, bottom_right_1, color, 2) + + # Draw bbox on the second image + top_left_2 = (bbox2[0], bbox2[1]) + bottom_right_2 = (bbox2[0] + bbox2[2], bbox2[1] + bbox2[3]) + img2_drawn = cv2.rectangle(img2_drawn, top_left_2, bottom_right_2, color, 2) + + return img1_drawn, img2_drawn + + +def calculate_distance_max_1d(x1, y1, x2, y2): + distance = max(abs(x2 - x1), abs(y2 - y1)) + return distance + + +def calculate_ratio(h1, h2): + return max(h1, h2) / min(h1, h2) + + +def rgb_to_lab(rgb): + """ + Convert an RGB color to Lab color space. + RGB values should be in the range [0, 255]. + """ + # Create an sRGBColor object from RGB values + rgb_color = sRGBColor(rgb[0], rgb[1], rgb[2], is_upscaled=True) + + # Convert to Lab color space + lab_color = convert_color(rgb_color, LabColor) + + return lab_color + +def color_similarity_ciede2000(rgb1, rgb2): + """ + Calculate the color similarity between two RGB colors using the CIEDE2000 formula. + Returns a similarity score between 0 and 1, where 1 means identical. + """ + # Convert RGB colors to Lab + lab1 = rgb_to_lab(rgb1) + lab2 = rgb_to_lab(rgb2) + + # Calculate the Delta E (CIEDE2000) + delta_e = delta_e_cie2000(lab1, lab2) + + # Normalize the Delta E value to get a similarity score + # Note: The normalization method here is arbitrary and can be adjusted based on your needs. + # A delta_e of 0 means identical colors. Higher values indicate more difference. + # For visualization purposes, we consider a delta_e of 100 to be completely different. + similarity = max(0, 1 - (delta_e / 100)) + + return similarity + + +def calculate_current_cost(cost_matrix, row_ind, col_ind): + return cost_matrix[row_ind, col_ind].sum() + + +def merge_blocks_wo_check(block1, block2): + # Concatenate text + merged_text = block1['text'] + " " + block2['text'] + + # Calculate bounding box + x_min = min(block1['bbox'][0], block2['bbox'][0]) + y_min = min(block1['bbox'][1], block2['bbox'][1]) + x_max = max(block1['bbox'][0] + block1['bbox'][2], block2['bbox'][0] + block2['bbox'][2]) + y_max = max(block1['bbox'][1] + block1['bbox'][3], block2['bbox'][1] + block2['bbox'][3]) + merged_bbox = (x_min, y_min, x_max - x_min, y_max - y_min) + + # Average color + merged_color = tuple( + (color1 + color2) // 2 for color1, color2 in zip(block1['color'], block2['color']) + ) + + return {'text': merged_text, 'bbox': merged_bbox, 'color': merged_color} + + +def calculate_current_cost(cost_matrix, row_ind, col_ind): + return cost_matrix[row_ind, col_ind].tolist() + + +def find_maximum_matching(A, B, consecutive_bonus, window_size): + cost_matrix = create_cost_matrix(A, B) + cost_matrix = adjust_cost_for_context(cost_matrix, consecutive_bonus, window_size) + row_ind, col_ind = linear_sum_assignment(cost_matrix) + current_cost = calculate_current_cost(cost_matrix, row_ind, col_ind) + return list(zip(row_ind, col_ind)), current_cost, cost_matrix + + +def remove_indices(lst, indices): + for index in sorted(indices, reverse=True): + if index < len(lst): + lst.pop(index) + return lst + + +def merge_blocks_by_list(blocks, merge_list): + pop_list = [] + while True: + if len(merge_list) == 0: + remove_indices(blocks, pop_list) + return blocks + + i = merge_list[0][0] + j = merge_list[0][1] + + blocks[i] = merge_blocks_wo_check(blocks[i], blocks[j]) + pop_list.append(j) + + merge_list.pop(0) + if len(merge_list) > 0: + new_merge_list = [] + for k in range(len(merge_list)): + if merge_list[k][0] != i and merge_list[k][1] != i and merge_list[k][0] != j and merge_list[k][1] != j: + new_merge_list.append(merge_list[k]) + merge_list = new_merge_list + + +def print_matching(matching, blocks1, blocks2, cost_matrix): + for i, j in matching: + print(f"{blocks1[i]} matched with {blocks2[j]}, cost {cost_matrix[i][j]}") + + +def difference_of_means(list1, list2): + counter1 = Counter(list1) + counter2 = Counter(list2) + + for element in set(list1) & set(list2): + common_count = min(counter1[element], counter2[element]) + counter1[element] -= common_count + counter2[element] -= common_count + + unique_list1 = [item for item in counter1.elements()] + unique_list2 = [item for item in counter2.elements()] + + mean_list1 = sum(unique_list1) / len(unique_list1) if unique_list1 else 0 + mean_list2 = sum(unique_list2) / len(unique_list2) if unique_list2 else 0 + + if mean_list1 - mean_list2 > 0: + if min(unique_list1) > min(unique_list2): + return mean_list1 - mean_list2 + else: + return 0.0 + else: + return mean_list1 - mean_list2 + + +def find_possible_merge(A, B, consecutive_bonus, window_size, debug=False): + merge_bonus = 0.0 + merge_windows = 1 + + def sortFn(value): + return value[2] + + while True: + A_changed = False + B_changed = False + + matching, current_cost, cost_matrix = find_maximum_matching(A, B, merge_bonus, merge_windows) + if debug: + print("Current cost of the solution:", current_cost) + print_matching(matching, A, B, cost_matrix) + + if len(A) >= 2: + merge_list = [] + for i in range(len(A) - 1): + new_A = deepcopy(A) + new_A[i] = merge_blocks_wo_check(new_A[i], new_A[i + 1]) + new_A.pop(i + 1) + + updated_matching, updated_cost, cost_matrix = find_maximum_matching(new_A, B, merge_bonus, merge_windows) + diff = difference_of_means(current_cost, updated_cost) + if diff > 0.05: + merge_list.append([i, i + 1, diff]) + if debug: + print(new_A[i]['text'], diff) + + merge_list.sort(key=sortFn, reverse=True) + if len(merge_list) > 0: + A_changed = True + A = merge_blocks_by_list(A, merge_list) + matching, current_cost, cost_matrix = find_maximum_matching(A, B, merge_bonus, merge_windows) + if debug: + print("Cost after optimization A:", current_cost) + + if len(B) >= 2: + merge_list = [] + for i in range(len(B) - 1): + new_B = deepcopy(B) + new_B[i] = merge_blocks_wo_check(new_B[i], new_B[i + 1]) + new_B.pop(i + 1) + + updated_matching, updated_cost, cost_matrix = find_maximum_matching(A, new_B, merge_bonus, merge_windows) + diff = difference_of_means(current_cost, updated_cost) + if diff > 0.05: + merge_list.append([i, i + 1, diff]) + if debug: + print(new_B[i]['text'], diff) + + merge_list.sort(key=sortFn, reverse=True) + if len(merge_list) > 0: + B_changed = True + B = merge_blocks_by_list(B, merge_list) + matching, current_cost, cost_matrix = find_maximum_matching(A, B, merge_bonus, merge_windows) + if debug: + print("Cost after optimization B:", current_cost) + + if not A_changed and not B_changed: + break + matching, _, _ = find_maximum_matching(A, B, consecutive_bonus, window_size) + return A, B, matching + + +def merge_blocks_by_bbox(blocks): + merged_blocks = {} + + # Traverse and merge blocks + for block in blocks: + bbox = tuple(block['bbox']) # Convert bbox to tuple for hashability + if bbox in merged_blocks: + # Merge with existing block + existing_block = merged_blocks[bbox] + existing_block['text'] += ' ' + block['text'] + existing_block['color'] = [(ec + c) / 2 for ec, c in zip(existing_block['color'], block['color'])] + else: + # Add new block + merged_blocks[bbox] = block + + return list(merged_blocks.values()) + + +def mask_bounding_boxes_with_inpainting(image, bounding_boxes): + # Convert PIL image to OpenCV format + image_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) + + # Create a black mask + mask = np.zeros(image_cv.shape[:2], dtype=np.uint8) + + height, width = image_cv.shape[:2] + + # Draw white rectangles on the mask + for bbox in bounding_boxes: + x_ratio, y_ratio, w_ratio, h_ratio = bbox + x = int(x_ratio * width) + y = int(y_ratio * height) + w = int(w_ratio * width) + h = int(h_ratio * height) + mask[y:y+h, x:x+w] = 255 + + # Use inpainting + inpainted_image = cv2.inpaint(image_cv, mask, 3, cv2.INPAINT_TELEA) + + # Convert back to PIL format + inpainted_image_pil = Image.fromarray(cv2.cvtColor(inpainted_image, cv2.COLOR_BGR2RGB)) + + return inpainted_image_pil + + +def rescale_and_mask(image_path, blocks): + # Load the image + with Image.open(image_path) as img: + if len(blocks) > 0: + # use inpainting instead of simple mask + img = mask_bounding_boxes_with_inpainting(img, blocks) + + width, height = img.size + + # Determine which side is shorter + if width < height: + # Width is shorter, scale height to match the width + new_size = (width, width) + else: + # Height is shorter, scale width to match the height + new_size = (height, height) + + # Resize the image while maintaining aspect ratio + img_resized = img.resize(new_size, Image.LANCZOS) + + return img_resized + + +def calculate_clip_similarity_with_blocks(image_path1, image_path2, blocks1, blocks2): + # Load and preprocess images + image1 = preprocess(rescale_and_mask(image_path1, [block['bbox'] for block in blocks1])).unsqueeze(0).to(device) + image2 = preprocess(rescale_and_mask(image_path2, [block['bbox'] for block in blocks2])).unsqueeze(0).to(device) + + # Calculate features + with torch.no_grad(): + image_features1 = model.encode_image(image1) + image_features2 = model.encode_image(image2) + + # Normalize features + image_features1 /= image_features1.norm(dim=-1, keepdim=True) + image_features2 /= image_features2.norm(dim=-1, keepdim=True) + + # Calculate cosine similarity + similarity = (image_features1 @ image_features2.T).item() + + return similarity + + +def truncate_repeated_html_elements(soup, max_count=50): + content_counts = {} + + for element in soup.find_all(True): + if isinstance(element, (NavigableString, Comment)): + continue + + try: + element_html = str(element) + except: + element.decompose() + continue + content_counts[element_html] = content_counts.get(element_html, 0) + 1 + + if content_counts[element_html] > max_count: + element.decompose() + + return str(soup) + + +def make_html(filename): + with open(filename, 'r') as file: + content = file.read() + + if not re.search(r']*>', content, re.IGNORECASE): + new_content = f'

{content}

' + with open(filename, 'w') as file: + file.write(new_content) + + +def pre_process(html_file): + #check_repetitive_content(html_file) + make_html(html_file) + with open(html_file, 'r') as file: + soup = BeautifulSoup(file, 'html.parser') + soup_str = truncate_repeated_html_elements(soup) + with open(html_file, 'w') as file: + file.write(soup_str) + + +def visual_eval_v3_multi(input_list, debug=False): + predict_html_list, original_html = input_list[0], input_list[1] + predict_img_list = [html.replace(".html", ".png") for html in predict_html_list] + + predict_blocks_list = [] + + to_screenshot_html_list, to_screenshot_img_list = [], [] + for predict_html in tqdm(predict_html_list, desc="Pre-screenshot HTML files"): + predict_img = predict_html.replace(".html", ".png") + # This will help fix some html syntax error + pre_process(predict_html) + to_screenshot_html_list.append(predict_html) + to_screenshot_img_list.append(predict_img) + + pre_screenshoted_html_list, pre_screenshoted_img_list = get_files_to_screenshot(predict_img) + to_screenshot_html_list.extend(pre_screenshoted_html_list) + to_screenshot_img_list.extend(pre_screenshoted_img_list) + + take_screenshots(to_screenshot_html_list, to_screenshot_img_list) + + for predict_html in tqdm(predict_html_list, desc="Screenshot HTML files and get OCR blocks"): + predict_img = predict_html.replace(".html", ".png") + # This will help fix some html syntax error + predict_blocks = get_blocks_ocr_free(predict_img, page_load_time=MINER_HTML_LOAD_TIME, pre_screenshoted=True) + predict_blocks_list.append(predict_blocks) + + original_img = original_html.replace(".html", ".png") + os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {original_html} --png {original_img} --page_load_time {GROUND_TRUTH_HTML_LOAD_TIME}") + original_blocks = get_blocks_ocr_free(original_img, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME, pre_screenshoted=False) + original_blocks = merge_blocks_by_bbox(original_blocks) + + # Consider context similarity for block matching + consecutive_bonus, window_size = 0.1, 1 + + return_score_list = [] + + for k, predict_blocks in tqdm(enumerate(predict_blocks_list), desc="Processing HTML files"): + if len(predict_blocks) == 0: + print("[Warning] No detected blocks in: ", predict_img_list[k]) + final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) + return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) + continue + elif len(original_blocks) == 0: + print("[Warning] No detected blocks in: ", original_img) + final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) + return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) + continue + + if debug: + print(predict_blocks) + print(original_blocks) + + predict_blocks = merge_blocks_by_bbox(predict_blocks) + predict_blocks_m, original_blocks_m, matching = find_possible_merge(predict_blocks, deepcopy(original_blocks), consecutive_bonus, window_size, debug=debug) + + filtered_matching = [] + for i, j in matching: + text_similarity = SequenceMatcher(None, predict_blocks_m[i]['text'], original_blocks_m[j]['text']).ratio() + # Filter out matching with low similarity + if text_similarity < 0.5: + continue + filtered_matching.append([i, j, text_similarity]) + matching = filtered_matching + + indices1 = [item[0] for item in matching] + indices2 = [item[1] for item in matching] + + matched_list = [] + sum_areas = [] + matched_areas = [] + matched_text_scores = [] + position_scores = [] + text_color_scores = [] + + unmatched_area_1 = 0.0 + for i in range(len(predict_blocks_m)): + if i not in indices1: + unmatched_area_1 += predict_blocks_m[i]['bbox'][2] * predict_blocks_m[i]['bbox'][3] + unmatched_area_2 = 0.0 + for j in range(len(original_blocks_m)): + if j not in indices2: + unmatched_area_2 += original_blocks_m[j]['bbox'][2] * original_blocks_m[j]['bbox'][3] + sum_areas.append(unmatched_area_1 + unmatched_area_2) + + for i, j, text_similarity in matching: + sum_block_area = predict_blocks_m[i]['bbox'][2] * predict_blocks_m[i]['bbox'][3] + original_blocks_m[j]['bbox'][2] * original_blocks_m[j]['bbox'][3] + + # Consider the max postion shift, either horizontally or vertically + position_similarity = 1 - calculate_distance_max_1d(predict_blocks_m[i]['bbox'][0] + predict_blocks_m[i]['bbox'][2] / 2, \ + predict_blocks_m[i]['bbox'][1] + predict_blocks_m[i]['bbox'][3] / 2, \ + original_blocks_m[j]['bbox'][0] + original_blocks_m[j]['bbox'][2] / 2, \ + original_blocks_m[j]['bbox'][1] + original_blocks_m[j]['bbox'][3] / 2) + # Normalized ciede2000 formula + text_color_similarity = color_similarity_ciede2000(predict_blocks_m[i]['color'], original_blocks_m[j]['color']) + matched_list.append([predict_blocks_m[i]['bbox'], original_blocks_m[j]['bbox']]) + + # validation check + if min(predict_blocks_m[i]['bbox'][2], original_blocks_m[j]['bbox'][2], predict_blocks_m[i]['bbox'][3], original_blocks_m[j]['bbox'][3]) == 0: + print(f"{predict_blocks_m[i]} matched with {original_blocks_m[j]}") + assert calculate_ratio(predict_blocks_m[i]['bbox'][2], original_blocks_m[j]['bbox'][2]) > 0 and calculate_ratio(predict_blocks_m[i]['bbox'][3], original_blocks_m[j]['bbox'][3]) > 0, f"{predict_blocks_m[i]} matched with {original_blocks_m[j]}" + + sum_areas.append(sum_block_area) + matched_areas.append(sum_block_area) + matched_text_scores.append(text_similarity) + position_scores.append(position_similarity) + text_color_scores.append(text_color_similarity) + + if debug: + print(f"{predict_blocks_m[i]} matched with {original_blocks_m[j]}") + print(SequenceMatcher(None, predict_blocks_m[i]['text'], original_blocks_m[j]['text']).ratio()) + print("text similarity score", text_similarity) + print("position score", position_similarity) + print("color score", text_color_similarity) + print("----------------------------------") + pass + """ + if debug: + img1 = cv2.imread(predict_img_list[k]) + img2 = cv2.imread(original_img) + img1_with_boxes, img2_with_boxes = draw_matched_bboxes(img1, img2, matched_list) + + plt.figure(figsize=(20, 10)) + plt.subplot(1, 2, 1) + plt.imshow(cv2.cvtColor(img1_with_boxes, cv2.COLOR_BGR2RGB)) + plt.axis('off') + plt.subplot(1, 2, 2) + plt.imshow(cv2.cvtColor(img2_with_boxes, cv2.COLOR_BGR2RGB)) + plt.axis('off') + plt.show() + # """ + + if len(matched_areas) > 0: + sum_sum_areas = np.sum(sum_areas) + + final_size_score = np.sum(matched_areas) / np.sum(sum_areas) + final_matched_text_score = np.mean(matched_text_scores) + final_position_score = np.mean(position_scores) + final_text_color_score = np.mean(text_color_scores) + final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) + final_score = 0.2 * (final_size_score + final_matched_text_score + final_position_score + final_text_color_score + final_clip_score) + return_score_list.append([sum_sum_areas, final_score, (final_size_score, final_matched_text_score, final_position_score, final_text_color_score, final_clip_score)]) + else: + print("[Warning] No matched blocks in: ", predict_img_list[k]) + final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) + return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) + return return_score_list + + # except: + # print("[Warning] Error not handled in: ", input_list) + # return [[0.0, 0.0, (0.0, 0.0, 0.0, 0.0, 0.0)] for _ in range(len(predict_html_list))] + + +if __name__ == "__main__": + print(visual_eval_v3_multi([ + ["work/miner1.html", "work/miner2.html", "work/miner3.html"], + "work/original.html"], debug=True)) From 23495933d46b67a015140cc725f38dfdbe252f39 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 13 Jan 2025 10:58:48 -0600 Subject: [PATCH 190/554] feat: testing the scoring logic --- .python-version | 2 +- neurons/validators/genie_validator.py | 5 + pyproject.toml | 17 +- tests/test_clip.py | 23 +- tests/test_reward.py | 82 - uv.lock | 3009 ++++++++++------- .../lighthouse_reward/get_lighthouse_score.py | 14 +- .../visual_reward/metrics/ocr_free_utils.py | 1 + .../visual_reward/metrics/visual_score.py | 17 +- 9 files changed, 1900 insertions(+), 1270 deletions(-) delete mode 100644 tests/test_reward.py diff --git a/.python-version b/.python-version index c8cfe395..dd6a2206 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.10 +3.12.4 \ No newline at end of file diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index d0a0f37f..07faf7fc 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -92,6 +92,11 @@ async def query_miners(self): bt.logging.error(f"Error in query_miners: {e}") raise e + async def foward(self): + await self.synthensize_task() + await self.query_miners() + await self.score() + async def score(self): if not self.competitions: return diff --git a/pyproject.toml b/pyproject.toml index bd2efb1c..29bc2341 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "web-genie-ai" version = "1.0.0" description = "The first bittensor subnet for web generation" readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.12.4" dependencies = [ "ansible-vault==2.1.0", "beautifulsoup4==4.12.3", @@ -11,7 +11,6 @@ dependencies = [ "bittensor-cli==8.2.0rc8", "bittensor==8.5.1rc5", "clip", - "colormath==3.0.0", "datasets==3.2.0", "ddt==1.6.0", "duckduckgo_search", @@ -32,7 +31,19 @@ dependencies = [ "shtab==1.6.5", "sqlalchemy", "tinycss2==1.4.0", - "wandb==0.19.0" + "wandb==0.19.0", + "numpy>=2.0.2", + "easyocr>=1.7.2", + "colormath>=3.0.0", + "scikit-image>=0.25.0", + "object-detection>=0.0.3", + "tensorflow>=2.18.0", + "protobuf>=5.29.3", + "proto-plus>=1.25.0", + "black>=24.10.0", + "ultralyticsplus>=0.0.28", + "mss>=10.0.0", + "keras-ocr>=0.9.3", ] [project.urls] diff --git a/tests/test_clip.py b/tests/test_clip.py index 4fd1b46a..99437214 100644 --- a/tests/test_clip.py +++ b/tests/test_clip.py @@ -1,21 +1,8 @@ -import clip -import torch -from PIL import Image +from init_test import init_test -# Load the model -device = "cuda" if torch.cuda.is_available() else "cpu" -model, preprocess = clip.load('ViT-B/32', device) +init_test() -# Load and preprocess images -image1 = preprocess(Image.open('path_to_text_image1.jpg')).unsqueeze(0).to(device) -image2 = preprocess(Image.open('path_to_text_image2.jpg')).unsqueeze(0).to(device) +from metrics.text_mask import get_text_contour_image -# Compute features and calculate cosine similarity -with torch.no_grad(): - image_features1 = model.encode_image(image1) - image_features2 = model.encode_image(image2) - image_features1 /= image_features1.norm(dim=-1, keepdim=True) - image_features2 /= image_features2.norm(dim=-1, keepdim=True) - cosine_similarity = (image_features1 @ image_features2.T).cpu().numpy() - -print(f"Cosine similarity: {cosine_similarity.item()}") +mask = get_text_contour_image("tests/data/test.png") +mask.save("tests/data/mask.png") diff --git a/tests/test_reward.py b/tests/test_reward.py deleted file mode 100644 index 8004bc68..00000000 --- a/tests/test_reward.py +++ /dev/null @@ -1,82 +0,0 @@ -from init_test import init_test -init_test() - -import asyncio -import numpy as np -from typing import List -import time -from webgenie.tasks import Task, Solution, ImageTask -from webgenie.rewards import ( - LighthouseReward, - QualityReward, - VisualReward, -) - -from webgenie.protocol import WebgenieImageSynapse -from webgenie.competitions.competition import ( - ACCURACY_METRIC_NAME, - SEO_METRIC_NAME, - QUALITY_METRIC_NAME, -) - -from webgenie.helpers.htmls import html_to_screenshot -from neurons.miners.openai_miner import OpenaiMiner - -metrics = { - ACCURACY_METRIC_NAME: VisualReward(), - SEO_METRIC_NAME: LighthouseReward(), - QUALITY_METRIC_NAME: QualityReward(), -} - -async def calculate_scores(task: Task, solutions: List[Solution]) -> dict[str, np.ndarray]: - scores: dict[str, np.ndarray] = {} - - for metric_name, reward_model in metrics.items(): - print(metric_name) - start_time = time.time() - reward_scores = await reward_model.reward(task, solutions) - execution_time = time.time() - start_time - print(f"Execution time: {execution_time:.2f} seconds") - scores[metric_name] = reward_scores - print(scores[metric_name]) - return scores - - -async def main(): - ground_truth_html_path = "work/original_f7e98ea8-cc04-44ea-8f40-432914b0f0ea.html" - with open(ground_truth_html_path, "r") as f: - ground_truth_html = f.read() - - print("HTML to screenshot") - start_time = time.time() - base64_image = html_to_screenshot(ground_truth_html) - execution_time = time.time() - start_time - print(f"Execution time: {execution_time:.2f} seconds") - - task = ImageTask( - ground_truth_html=ground_truth_html, - ) - - miner = OpenaiMiner(neuron=None) - synapse = WebgenieImageSynapse( - base64_image = base64_image, - ) - - print("Miner forward image") - start_time = time.time() - synapse = await miner.forward_image(synapse) - print(synapse.html) - execution_time = time.time() - start_time - print(f"Execution time: {execution_time:.2f} seconds") - solutions = [Solution(html=synapse.html) for _ in range(256)] - - print("Calculate scores") - start_time = time.time() - scores = await calculate_scores(task, solutions) - execution_time = time.time() - start_time - print(f"Execution time: {execution_time:.2f} seconds") - - print(scores) - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/uv.lock b/uv.lock index c1f762ae..57151925 100644 --- a/uv.lock +++ b/uv.lock @@ -1,22 +1,20 @@ version = 1 -requires-python = ">=3.10" +requires-python = ">=3.12.4" resolution-markers = [ - "python_full_version >= '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", + "sys_platform == 'darwin'", + "platform_machine == 'aarch64' and sys_platform == 'linux'", + "platform_machine != 'aarch64' and sys_platform == 'linux'", + "sys_platform == 'win32'", + "sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", +] + +[[package]] +name = "absl-py" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/8f/fc001b92ecc467cc32ab38398bd0bfb45df46e7523bf33c2ad22a505f06e/absl-py-2.1.0.tar.gz", hash = "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff", size = 118055 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/ad/e0d3c824784ff121c03cc031f944bc7e139a8f1870ffd2845cc2dd76f6c4/absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308", size = 133706 }, ] [[package]] @@ -53,7 +51,6 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, { name = "aiosignal" }, - { name = "async-timeout", marker = "python_full_version < '3.11'" }, { name = "attrs" }, { name = "frozenlist" }, { name = "multidict" }, @@ -61,36 +58,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/25/a8/8e2ba36c6e3278d62e0c88aa42bb92ddbef092ac363b390dab4421da5cf5/aiohttp-3.10.11.tar.gz", hash = "sha256:9dc2b8f3dcab2e39e0fa309c8da50c3b55e6f34ab25f1a71d3288f24924d33a7", size = 7551886 } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/c7/575f9e82d7ef13cb1b45b9db8a5b8fadb35107fb12e33809356ae0155223/aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e", size = 588218 }, - { url = "https://files.pythonhosted.org/packages/12/7b/a800dadbd9a47b7f921bfddcd531371371f39b9cd05786c3638bfe2e1175/aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298", size = 400815 }, - { url = "https://files.pythonhosted.org/packages/cb/28/7dbd53ab10b0ded397feed914880f39ce075bd39393b8dfc322909754a0a/aiohttp-3.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffbfde2443696345e23a3c597049b1dd43049bb65337837574205e7368472177", size = 392099 }, - { url = "https://files.pythonhosted.org/packages/6a/2e/c6390f49e67911711c2229740e261c501685fe7201f7f918d6ff2fd1cfb0/aiohttp-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20b3d9e416774d41813bc02fdc0663379c01817b0874b932b81c7f777f67b217", size = 1224854 }, - { url = "https://files.pythonhosted.org/packages/69/68/c96afae129201bff4edbece52b3e1abf3a8af57529a42700669458b00b9f/aiohttp-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b943011b45ee6bf74b22245c6faab736363678e910504dd7531a58c76c9015a", size = 1259641 }, - { url = "https://files.pythonhosted.org/packages/63/89/bedd01456442747946114a8c2f30ff1b23d3b2ea0c03709f854c4f354a5a/aiohttp-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48bc1d924490f0d0b3658fe5c4b081a4d56ebb58af80a6729d4bd13ea569797a", size = 1295412 }, - { url = "https://files.pythonhosted.org/packages/9b/4d/942198e2939efe7bfa484781590f082135e9931b8bcafb4bba62cf2d8f2f/aiohttp-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e12eb3f4b1f72aaaf6acd27d045753b18101524f72ae071ae1c91c1cd44ef115", size = 1218311 }, - { url = "https://files.pythonhosted.org/packages/a3/5b/8127022912f1fa72dfc39cf37c36f83e0b56afc3b93594b1cf377b6e4ffc/aiohttp-3.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f14ebc419a568c2eff3c1ed35f634435c24ead2fe19c07426af41e7adb68713a", size = 1189448 }, - { url = "https://files.pythonhosted.org/packages/af/12/752878033c8feab3362c0890a4d24e9895921729a53491f6f6fad64d3287/aiohttp-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:72b191cdf35a518bfc7ca87d770d30941decc5aaf897ec8b484eb5cc8c7706f3", size = 1186484 }, - { url = "https://files.pythonhosted.org/packages/61/24/1d91c304fca47d5e5002ca23abab9b2196ac79d5c531258e048195b435b2/aiohttp-3.10.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ab2328a61fdc86424ee540d0aeb8b73bbcad7351fb7cf7a6546fc0bcffa0038", size = 1183864 }, - { url = "https://files.pythonhosted.org/packages/c1/70/022d28b898314dac4cb5dd52ead2a372563c8590b1eaab9c5ed017eefb1e/aiohttp-3.10.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa93063d4af05c49276cf14e419550a3f45258b6b9d1f16403e777f1addf4519", size = 1241460 }, - { url = "https://files.pythonhosted.org/packages/c3/15/2b43853330f82acf180602de0f68be62a2838d25d03d2ed40fecbe82479e/aiohttp-3.10.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30283f9d0ce420363c24c5c2421e71a738a2155f10adbb1a11a4d4d6d2715cfc", size = 1258521 }, - { url = "https://files.pythonhosted.org/packages/28/38/9ef2076cb06dcc155e7f02275f5da403a3e7c9327b6b075e999f0eb73613/aiohttp-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e5358addc8044ee49143c546d2182c15b4ac3a60be01c3209374ace05af5733d", size = 1207329 }, - { url = "https://files.pythonhosted.org/packages/c2/5f/c5329d67a2c83d8ae17a84e11dec14da5773520913bfc191caaf4cd57e50/aiohttp-3.10.11-cp310-cp310-win32.whl", hash = "sha256:e1ffa713d3ea7cdcd4aea9cddccab41edf6882fa9552940344c44e59652e1120", size = 363835 }, - { url = "https://files.pythonhosted.org/packages/0f/c6/ca5d70eea2fdbe283dbc1e7d30649a1a5371b2a2a9150db192446f645789/aiohttp-3.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:778cbd01f18ff78b5dd23c77eb82987ee4ba23408cbed233009fd570dda7e674", size = 382169 }, - { url = "https://files.pythonhosted.org/packages/73/96/221ec59bc38395a6c205cbe8bf72c114ce92694b58abc8c3c6b7250efa7f/aiohttp-3.10.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:80ff08556c7f59a7972b1e8919f62e9c069c33566a6d28586771711e0eea4f07", size = 587742 }, - { url = "https://files.pythonhosted.org/packages/24/17/4e606c969b19de5c31a09b946bd4c37e30c5288ca91d4790aa915518846e/aiohttp-3.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c8f96e9ee19f04c4914e4e7a42a60861066d3e1abf05c726f38d9d0a466e695", size = 400357 }, - { url = "https://files.pythonhosted.org/packages/a2/e5/433f59b87ba69736e446824710dd7f26fcd05b24c6647cb1e76554ea5d02/aiohttp-3.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fb8601394d537da9221947b5d6e62b064c9a43e88a1ecd7414d21a1a6fba9c24", size = 392099 }, - { url = "https://files.pythonhosted.org/packages/d2/a3/3be340f5063970bb9e47f065ee8151edab639d9c2dce0d9605a325ab035d/aiohttp-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea224cf7bc2d8856d6971cea73b1d50c9c51d36971faf1abc169a0d5f85a382", size = 1300367 }, - { url = "https://files.pythonhosted.org/packages/ba/7d/a3043918466cbee9429792ebe795f92f70eeb40aee4ccbca14c38ee8fa4d/aiohttp-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db9503f79e12d5d80b3efd4d01312853565c05367493379df76d2674af881caa", size = 1339448 }, - { url = "https://files.pythonhosted.org/packages/2c/60/192b378bd9d1ae67716b71ae63c3e97c48b134aad7675915a10853a0b7de/aiohttp-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0f449a50cc33f0384f633894d8d3cd020e3ccef81879c6e6245c3c375c448625", size = 1374875 }, - { url = "https://files.pythonhosted.org/packages/e0/d7/cd58bd17f5277d9cc32ecdbb0481ca02c52fc066412de413aa01268dc9b4/aiohttp-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82052be3e6d9e0c123499127782a01a2b224b8af8c62ab46b3f6197035ad94e9", size = 1285626 }, - { url = "https://files.pythonhosted.org/packages/bb/b2/da4953643b7dcdcd29cc99f98209f3653bf02023d95ce8a8fd57ffba0f15/aiohttp-3.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20063c7acf1eec550c8eb098deb5ed9e1bb0521613b03bb93644b810986027ac", size = 1246120 }, - { url = "https://files.pythonhosted.org/packages/6c/22/1217b3c773055f0cb172e3b7108274a74c0fe9900c716362727303931cbb/aiohttp-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:489cced07a4c11488f47aab1f00d0c572506883f877af100a38f1fedaa884c3a", size = 1265177 }, - { url = "https://files.pythonhosted.org/packages/63/5e/3827ad7e61544ed1e73e4fdea7bb87ea35ac59a362d7eb301feb5e859780/aiohttp-3.10.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea9b3bab329aeaa603ed3bf605f1e2a6f36496ad7e0e1aa42025f368ee2dc07b", size = 1257238 }, - { url = "https://files.pythonhosted.org/packages/53/31/951f78751d403da6086b662760e6e8b08201b0dcf5357969f48261b4d0e1/aiohttp-3.10.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ca117819d8ad113413016cb29774b3f6d99ad23c220069789fc050267b786c16", size = 1315944 }, - { url = "https://files.pythonhosted.org/packages/0d/79/06ef7a2a69880649261818b135b245de5a4e89fed5a6987c8645428563fc/aiohttp-3.10.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2dfb612dcbe70fb7cdcf3499e8d483079b89749c857a8f6e80263b021745c730", size = 1332065 }, - { url = "https://files.pythonhosted.org/packages/10/39/a273857c2d0bbf2152a4201fbf776931c2dac74aa399c6683ed4c286d1d1/aiohttp-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9b615d3da0d60e7d53c62e22b4fd1c70f4ae5993a44687b011ea3a2e49051b8", size = 1291882 }, - { url = "https://files.pythonhosted.org/packages/49/39/7aa387f88403febc96e0494101763afaa14d342109329a01b413b2bac075/aiohttp-3.10.11-cp311-cp311-win32.whl", hash = "sha256:29103f9099b6068bbdf44d6a3d090e0a0b2be6d3c9f16a070dd9d0d910ec08f9", size = 363409 }, - { url = "https://files.pythonhosted.org/packages/6f/e9/8eb3dc095ce48499d867ad461d02f1491686b79ad92e4fad4df582f6be7b/aiohttp-3.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:236b28ceb79532da85d59aa9b9bf873b364e27a0acb2ceaba475dc61cffb6f3f", size = 382644 }, { url = "https://files.pythonhosted.org/packages/01/16/077057ef3bd684dbf9a8273a5299e182a8d07b4b252503712ff8b5364fd1/aiohttp-3.10.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7480519f70e32bfb101d71fb9a1f330fbd291655a4c1c922232a48c458c52710", size = 584830 }, { url = "https://files.pythonhosted.org/packages/2c/cf/348b93deb9597c61a51b6682e81f7c7d79290249e886022ef0705d858d90/aiohttp-3.10.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f65267266c9aeb2287a6622ee2bb39490292552f9fbf851baabc04c9f84e048d", size = 397090 }, { url = "https://files.pythonhosted.org/packages/70/bf/903df5cd739dfaf5b827b3d8c9d68ff4fcea16a0ca1aeb948c9da30f56c8/aiohttp-3.10.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7400a93d629a0608dc1d6c55f1e3d6e07f7375745aaa8bd7f085571e4d1cee97", size = 392361 }, @@ -144,96 +111,28 @@ 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 = "ansible" -version = "10.7.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", -] -dependencies = [ - { name = "ansible-core", version = "2.17.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d4/64/29fdff6fe7682342adb54802c1cd90b2272d382e1743089af88f90a1d986/ansible-10.7.0.tar.gz", hash = "sha256:59d29e3de1080e740dfa974517d455217601b16d16880314d9be26145c68dc22", size = 41256974 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/95/cb8944902a2cdd94b1e19ff73695548679a388b9c473dc63c8dc64ffea3a/ansible-10.7.0-py3-none-any.whl", hash = "sha256:0089f08e047ceb70edd011be009f5c6273add613fbe491e9697c0556c989d8ea", size = 51576038 }, -] - [[package]] name = "ansible" version = "11.1.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", -] -dependencies = [ - { name = "ansible-core", version = "2.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +dependencies = [ + { name = "ansible-core" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5c/54/dc559b305948e9c234f79ef00f7aed52d7c127c8616c0c2f3f336103ccdd/ansible-11.1.0.tar.gz", hash = "sha256:d01b425990d960d2a33fc378e1b73dbca1c0e28bc22f4056ab6b3c8e9ae74fba", size = 41299850 } wheels = [ { url = "https://files.pythonhosted.org/packages/d2/75/4c0583e76a3c2eac0c8d878568fd18c8a66f3da8888dca712cae14b195ac/ansible-11.1.0-py3-none-any.whl", hash = "sha256:bbaf7073993f019fc0293fc8b76c7b215081831957c28eb020f12c270a16e8f0", size = 51414573 }, ] -[[package]] -name = "ansible-core" -version = "2.17.7" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux'", -] -dependencies = [ - { name = "cryptography", marker = "python_full_version < '3.11'" }, - { name = "jinja2", marker = "python_full_version < '3.11'" }, - { name = "packaging", marker = "python_full_version < '3.11'" }, - { name = "pyyaml", marker = "python_full_version < '3.11'" }, - { name = "resolvelib", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9d/f3/3621360fee8c4929aa82c62acedab1baaf3f649e435c5dd56a6c5465ceb0/ansible_core-2.17.7.tar.gz", hash = "sha256:3aaab735d6c4e2d6239bc326800dc0ecda2a1490caa8455b41084ec0bc54dacf", size = 3104429 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/4b/e176f8ae78fdff0e2e2d593dabcd5e8fb31cf94b1f655778666c91f91241/ansible_core-2.17.7-py3-none-any.whl", hash = "sha256:64d4f0a006687a5621aa80dca54fd0c5ae75145b7aac8c1b8d7f07a1399c4705", size = 2196119 }, -] - [[package]] name = "ansible-core" version = "2.18.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", - "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux'", - "python_full_version == '3.11.*' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux'", -] -dependencies = [ - { name = "cryptography", marker = "python_full_version >= '3.11'" }, - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, - { name = "resolvelib", marker = "python_full_version >= '3.11'" }, +dependencies = [ + { name = "cryptography" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "resolvelib" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/46/15836f1f48e2682bd5d04f7b3e2cb27e17626bec3cd7a4f2a7b0ccefbbd2/ansible_core-2.18.1.tar.gz", hash = "sha256:14cac1f92bbdae881cb0616eddeb17925e8cb507e486087975e724533d9de74f", size = 3069965 } wheels = [ @@ -245,8 +144,7 @@ name = "ansible-vault" version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ansible", version = "10.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "ansible", version = "11.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "ansible" }, { name = "setuptools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/68/968aea6bc9894fb0e19aeaa03f6b284b573b5815f4f3d3a9daa9e519c3df/ansible-vault-2.1.0.tar.gz", hash = "sha256:5ce8fdb5470f1449b76bf07ae2abc56480dad48356ae405c85b686efb64dbd5e", size = 3519 } @@ -256,7 +154,6 @@ 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'" }, @@ -267,21 +164,98 @@ wheels = [ ] [[package]] -name = "async-property" -version = "0.2.2" +name = "appnope" +version = "0.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/12/900eb34b3af75c11b69d6b78b74ec0fd1ba489376eceb3785f787d1a0a1d/async_property-0.2.2.tar.gz", hash = "sha256:17d9bd6ca67e27915a75d92549df64b5c7174e9dc806b30a3934dc4ff0506380", size = 16523 } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/80/9f608d13b4b3afcebd1dd13baf9551c95fc424d6390e4b1cfd7b1810cd06/async_property-0.2.2-py2.py3-none-any.whl", hash = "sha256:8924d792b5843994537f8ed411165700b27b2bd966cefc4daeefc1253442a9d7", size = 9546 }, + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, +] + +[[package]] +name = "argon2-cffi" +version = "23.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124 }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658 }, + { url = "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583 }, + { url = "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168 }, + { url = "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709 }, + { url = "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613 }, + { url = "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583 }, + { url = "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475 }, + { url = "https://files.pythonhosted.org/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698 }, + { url = "https://files.pythonhosted.org/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817 }, + { url = "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104 }, +] + +[[package]] +name = "arrow" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "types-python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419 }, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 }, +] + +[[package]] +name = "astunparse" +version = "1.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, + { name = "wheel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/af/4182184d3c338792894f34a62672919db7ca008c89abee9b564dd34d8029/astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872", size = 18290 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/03/13dde6512ad7b4557eb792fbcf0c653af6076b81e5941d36ec61f7ce6028/astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8", size = 12732 }, +] + +[[package]] +name = "async-lru" +version = "2.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/e2/2b4651eff771f6fd900d233e175ddc5e2be502c7eb62c0c42f975c6d36cd/async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627", size = 10019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/9f/3c3503693386c4b0f245eaf5ca6198e3b28879ca0a40bde6b0e319793453/async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224", size = 6111 }, ] [[package]] -name = "async-timeout" -version = "5.0.1" +name = "async-property" +version = "0.2.2" 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 } +sdist = { url = "https://files.pythonhosted.org/packages/a7/12/900eb34b3af75c11b69d6b78b74ec0fd1ba489376eceb3785f787d1a0a1d/async_property-0.2.2.tar.gz", hash = "sha256:17d9bd6ca67e27915a75d92549df64b5c7174e9dc806b30a3934dc4ff0506380", size = 16523 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233 }, + { url = "https://files.pythonhosted.org/packages/c7/80/9f608d13b4b3afcebd1dd13baf9551c95fc424d6390e4b1cfd7b1810cd06/async_property-0.2.2-py2.py3-none-any.whl", hash = "sha256:8924d792b5843994537f8ed411165700b27b2bd966cefc4daeefc1253442a9d7", size = 9546 }, ] [[package]] @@ -293,6 +267,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 }, ] +[[package]] +name = "babel" +version = "2.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/74/f1bc80f23eeba13393b7222b11d95ca3af2c1e28edca18af487137eefed9/babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316", size = 9348104 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 }, +] + [[package]] name = "backoff" version = "2.2.1" @@ -418,12 +401,6 @@ version = "0.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/01/93/f6361d6d617f1620f1b642308384d7f22c7917c169b821ddb3a90856a0c9/bittensor_commit_reveal-0.1.0.tar.gz", hash = "sha256:1c8bb8d77f6279988902c5c28361cc460167829c63ffa8d788209f8810933211", size = 23249 } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/53/77d8490726b253e06a9c29a89e3a117494c73a02e05230bbb8f9fa157a58/bittensor_commit_reveal-0.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e628b052ebdbd6893eb996a0d03b4c5e0823e42ee410ff5066018700a35539a", size = 711939 }, - { url = "https://files.pythonhosted.org/packages/56/7a/6cc644423a9e0ac13327b3ff38b168042f85406151e436f8f67485a5be04/bittensor_commit_reveal-0.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:245a7e0defb79f6a45c243f72739ee5c58840cba51342d28322c6d7a73f475b9", size = 552388 }, - { url = "https://files.pythonhosted.org/packages/2d/cf/fcc202fb07594933f759287ceea9e891cbb8ce779f24cc84311af2b50802/bittensor_commit_reveal-0.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2bb23935ac60a981bfb3d83397b83e858c0b69a11806969cf56486f5ebc90943", size = 493021 }, - { url = "https://files.pythonhosted.org/packages/c6/bd/0e438e505036fda9370f352dc9a8f1ff7fa777a8b07479b9874f5742e7b4/bittensor_commit_reveal-0.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4917b215c24b10bd80c84db921113b9cd1346ca7dcaca75e286905ede81a3b18", size = 493236 }, - { url = "https://files.pythonhosted.org/packages/2b/7a/cded935634bf0a077e8f7454b47164e1b3e45064234eaf9722e6a35c1cbf/bittensor_commit_reveal-0.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c46cee3e5fa5fc9e6f6a444793062855f40495c1a00b52df6508e4449ac5e89f", size = 711674 }, - { url = "https://files.pythonhosted.org/packages/06/ab/ea0f20581a786ec4b497bdaab8fb4a046c81d125820fc1ec4bfe79854f96/bittensor_commit_reveal-0.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56407b879dcf82bdde5eaefede43c8891e122fefc03a32c77a063dfc52e0c8", size = 552162 }, { url = "https://files.pythonhosted.org/packages/a8/3a/7705ea18c3d61c8affc4696b8ab483bdb7e3d0bfdfb61ca1583a787ef1e0/bittensor_commit_reveal-0.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8509250549b6f5c475a9150e941b28fc66e82f30b27fe078fd80fa840943bb7b", size = 491259 }, { url = "https://files.pythonhosted.org/packages/80/21/02b400750c7d1d5ed081dc22c740e21e22fd72fbb18b72517d5687eca8bd/bittensor_commit_reveal-0.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bed04f82f162121747cfd7f51bb5d625dda0bf763a0699054565f255d219a9c2", size = 492612 }, { url = "https://files.pythonhosted.org/packages/9a/82/bf02fda4c7bfbe6830709476cf1893ad4e7b591c4e1f62eab2abbfcd0106/bittensor_commit_reveal-0.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25af2d9c82cacc4278095460493430d36070cb2843c0aa54b1c563788d0742eb", size = 712159 }, @@ -432,8 +409,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/05/02329c66db0970569a31779c0effcee67a1f6bb20a12ccbd667123d89f3f/bittensor_commit_reveal-0.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7be8c8f79dea2e137f5add6ee4711447c4f5d43668be26616ab7c1cacf317e07", size = 492469 }, { url = "https://files.pythonhosted.org/packages/f4/47/ca9a347273e6993b8775a2a04e9d3df5569aaab46dc95247bf0c1f1b5ea1/bittensor_commit_reveal-0.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b88ecb6a0989c2200486e29419a8e7d3b3f7918bdbde4ec04dbb4464abdee08f", size = 711920 }, { url = "https://files.pythonhosted.org/packages/fe/87/cbef0fa4b4d3159030d61d09da5a09181c0ca8f25bbb451437cb50627ac7/bittensor_commit_reveal-0.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac015f9eefa9dbddd2875cd7214e3a0bc2e394a2915772e655bdcc5c0af67de", size = 551137 }, - { url = "https://files.pythonhosted.org/packages/11/a4/98ad5a74461960ec4c33334fd65c9445b38a57f2a07a2b3805355cf7039e/bittensor_commit_reveal-0.1.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df0ef502f07804bff843a7e6318f95b9a9ca5bcc9c75e501040202b38d09d8b5", size = 712315 }, - { url = "https://files.pythonhosted.org/packages/be/7e/a4381b45757a67fc61cf7cf16b17c235f64962013d692a78c777140330a9/bittensor_commit_reveal-0.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8706ae43e3455913d210c7ab5fc0110595e45b8fa3964441e2842fc7975aec3", size = 553449 }, ] [[package]] @@ -452,12 +427,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/a4/17/38a9ec85be2167dd2c1aa2e75f0ac7c25ccf7c31859fe9b0d325b474fbbb/bittensor_wallet-2.1.3.tar.gz", hash = "sha256:41927d7e5d68fff1494cef5abd861ede0afc684dff366824b0806cfa3ce13af0", size = 70285 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/40/52e054e1d15901a9eae659a72bd6aaf8d34e9b0a682115b83d937fb8d74c/bittensor_wallet-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07462933ace69992079013ff7497c5f67e94b5d1adbada4ff08c5b18ebc18afe", size = 3146662 }, - { url = "https://files.pythonhosted.org/packages/18/3c/5fd63fd5fcb72629cf705921bd00ff9e8b0382129232f08ceb0bd0a596f5/bittensor_wallet-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:23110aca2d8f3e58c0b7c7bb58a74a66227aea85b30e4fa3eb616f5a13a0f659", size = 2953456 }, - { url = "https://files.pythonhosted.org/packages/62/5d/3b4a4ed5e4d4bbc3575001455dfd5631620147e65ab07f3f3a31891ea56a/bittensor_wallet-2.1.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a5199c84e9d33ccec451294f89d9354b61568a0b623ceee995f588ccdc14ea5c", size = 800061 }, - { url = "https://files.pythonhosted.org/packages/97/7c/8f55e5dfda6c28a74a63ca60cd4d9e860bb798da5e58ea4b88eead124f38/bittensor_wallet-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a34e524f21e8c7bd8edfd54db530480b81f48d2334a0a11b86ea22d9e349137c", size = 752208 }, - { url = "https://files.pythonhosted.org/packages/07/5b/bf271ddda747244ff044d8f7e21e30ff684f24d0a5447662cc020c3c301c/bittensor_wallet-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45a1556e02304e1e8e91059cc11bb8346fa2334ac039f79bb1e6f630fa26657f", size = 3146730 }, - { url = "https://files.pythonhosted.org/packages/c2/97/a74c138b92db1d455d2be371cea3777616fc6cb94ac401cecddd27e4d9d4/bittensor_wallet-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9399c753c37dbe63430c5aff4fba0a038e0349dde0061d623506a24e3b4d2cec", size = 2953376 }, { url = "https://files.pythonhosted.org/packages/a7/b0/a803fb7abe4b004464d67f6812f5067ee0346e7ba0bfb1e3012f569261cd/bittensor_wallet-2.1.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e2f0d03a21a0c54b1f8cd59f34941d7a60df490e9aab7d7776b03f290de6074", size = 797657 }, { url = "https://files.pythonhosted.org/packages/24/35/506d88aed623872fe4ecbcc2d6484ac864dc2c639ef8810141628fd28763/bittensor_wallet-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24c446b0af4c9ffc3ac122f97a1de25b283c877aa49892748ad06a8a61a74e13", size = 752425 }, { url = "https://files.pythonhosted.org/packages/eb/37/c6feb7d6ac75c24bfe170ffabbd42f2d91bc34cc75b99575f2417ec486b1/bittensor_wallet-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eafd9c82720644b3eeac2f68deaa9cec4cf175836b16c89206d98ce22590e8e", size = 3146851 }, @@ -466,8 +435,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e8/de/81744fd99af5339aa196c4c5e559ae3d2dd773d8fc1e39059fd651982b4b/bittensor_wallet-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb7dd2ed4c12e617574b7302a6c20fb8e915477ce2942627f624293b5de9a003", size = 752028 }, { url = "https://files.pythonhosted.org/packages/41/3c/309505722c2390337d417c17cc50040ddcbdaee03cc8fc664a34320f777a/bittensor_wallet-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de47dea7d283e83449465f9780d4dde608fe09da45d6ef8c795806e49ccf4fd2", size = 3145919 }, { url = "https://files.pythonhosted.org/packages/bc/3f/e973420941b0d0b23d944fd60cd95c3bbbca38f5c582d83409f6243880fa/bittensor_wallet-2.1.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e35adc5303b2186df889e07c79bf0bc074df382df49e6c216a8feb27f00453a4", size = 2953541 }, - { url = "https://files.pythonhosted.org/packages/74/f8/91130b3bc6f4def2f08b5bda47b6db8fa65d687ca01f5481f2c67806dbdd/bittensor_wallet-2.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e1ee5be2b8b4c8fa36bc1750da723152dd7c96c4e606121146913adf83cf667", size = 3147414 }, - { url = "https://files.pythonhosted.org/packages/04/f9/dde7ca4ae4ca864345d12447e6f6aae479a238d74762f2b6e9a8edfc90cb/bittensor_wallet-2.1.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b879438249fc70dc2f7b8b579566c526dde68c2baa31d12ee3c4fcd4087f7b9", size = 2955324 }, +] + +[[package]] +name = "black" +version = "24.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/0d/cc2fb42b8c50d80143221515dd7e4766995bd07c56c9a3ed30baf080b6dc/black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", size = 645813 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/04/bf74c71f592bcd761610bbf67e23e6a3cff824780761f536512437f1e655/black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3", size = 1644256 }, + { url = "https://files.pythonhosted.org/packages/4c/ea/a77bab4cf1887f4b2e0bce5516ea0b3ff7d04ba96af21d65024629afedb6/black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65", size = 1448534 }, + { url = "https://files.pythonhosted.org/packages/4e/3e/443ef8bc1fbda78e61f79157f303893f3fddf19ca3c8989b163eb3469a12/black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f", size = 1761892 }, + { url = "https://files.pythonhosted.org/packages/52/93/eac95ff229049a6901bc84fec6908a5124b8a0b7c26ea766b3b8a5debd22/black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8", size = 1434796 }, + { url = "https://files.pythonhosted.org/packages/d0/a0/a993f58d4ecfba035e61fca4e9f64a2ecae838fc9f33ab798c62173ed75c/black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981", size = 1643986 }, + { url = "https://files.pythonhosted.org/packages/37/d5/602d0ef5dfcace3fb4f79c436762f130abd9ee8d950fa2abdbf8bbc555e0/black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b", size = 1448085 }, + { url = "https://files.pythonhosted.org/packages/47/6d/a3a239e938960df1a662b93d6230d4f3e9b4a22982d060fc38c42f45a56b/black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2", size = 1760928 }, + { url = "https://files.pythonhosted.org/packages/dd/cf/af018e13b0eddfb434df4d9cd1b2b7892bab119f7a20123e93f6910982e8/black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b", size = 1436875 }, + { url = "https://files.pythonhosted.org/packages/8d/a7/4b27c50537ebca8bec139b872861f9d2bf501c5ec51fcf897cb924d9e264/black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", size = 206898 }, +] + +[[package]] +name = "bleach" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406 }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, ] [[package]] @@ -479,32 +487,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/78/a9/7449c1073af4ef57520fc01e587a664591ff0331b694a3ec9c1aff3c3133/bt_decode-0.4.0.tar.gz", hash = "sha256:5c7e6286a4f8b9b704f6a0c263ce0e8854fb95d94da5dff6e8835be6de04d508", size = 3496621 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/34/f151606685d868dffec3b02b1086a3ea6bf7f30e7193d0462be351e4c1d6/bt_decode-0.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c176595c23f3d9a632b8a4fe71f8ed74e05be0ff4d447719eab3de686699c6b", size = 603411 }, - { url = "https://files.pythonhosted.org/packages/04/2b/b9cc3583a3a48b0d3fd1bf58247a607838366868d6e526243e8e9c9fd98d/bt_decode-0.4.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a5232cc226d7c537303691dbb27c5c734cabcf51e6c74d641d1721a2d3a119c", size = 600803 }, - { url = "https://files.pythonhosted.org/packages/1c/88/4734639827b1cecb66d698828f4c4d43127f3087fa343b1a03b807897d9b/bt_decode-0.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93dfa1c342a6fb3cbd199b46f511951174503c8405854de484390776ff94228a", size = 669779 }, - { url = "https://files.pythonhosted.org/packages/b7/d2/1924c3de6bada823c7640cc134a762b859280f2fd911a83dc4f86161f7e3/bt_decode-0.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17f6f94d3dee3d9c9909e936b57bc87acef29de9b1b8d4157efd806bc7ff3eee", size = 708492 }, - { url = "https://files.pythonhosted.org/packages/de/f8/07a88e0373a70bc15dd54514ce1ef4b2b8a51723fd1c32371eebb8241931/bt_decode-0.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba2d5f8ef69dde9880db38e45beb4ed965868d660f8de68d8cc7838d6b244295", size = 613639 }, - { url = "https://files.pythonhosted.org/packages/e1/d6/0636e4605376f6145470612a14b6d8a9183705daf6ff3071c266a10658d3/bt_decode-0.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f76a6949edbb7bc9a095f1a732974db04ec39c671e188ee001998901b6cd460", size = 663996 }, - { url = "https://files.pythonhosted.org/packages/b3/97/83ec9bf604fb9086c05a9fa10f5de2ffb470b22322afec421110ade29cd8/bt_decode-0.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6df00582855bc84c1cbb4f7f63900097b456a43fd92fd397466c85943c5ba9f2", size = 781163 }, - { url = "https://files.pythonhosted.org/packages/c5/35/011d29e2e6bbb03d802306535b758d7ac1fff35aea187f7b7f38c995c135/bt_decode-0.4.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:67547de47eb41026f3ec106f2681c45e34fc5d610dd462cbcca9885bf7581af5", size = 861838 }, - { url = "https://files.pythonhosted.org/packages/91/a1/ade95fe5db1cc1a5edc21f61780e41df075e55f2248c2c4fd7bf17e88cf8/bt_decode-0.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fb100ff9d8688c1e5dd98f7aa721279f267408cf7079d8f2ca9ea1abd6c0edfc", size = 819408 }, - { url = "https://files.pythonhosted.org/packages/cd/66/14bae1a496796761121bfaeeb1b934505b531101e5505f11c25279b7e9bd/bt_decode-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:66b599c2af3a7a3f40af22fa3e6304bde56237242120cb37253e4a465dfd419c", size = 784031 }, - { url = "https://files.pythonhosted.org/packages/ac/9b/0e1488e5f8dc7c744525732a40ede44dbb2df5d6449040773e305b3c9b96/bt_decode-0.4.0-cp310-cp310-win32.whl", hash = "sha256:0635af47f0abd4a1c1d9566fb101c4b851c2499a8f8b53e37a496efcd69409da", size = 389613 }, - { url = "https://files.pythonhosted.org/packages/75/c4/cf51cf92b5aa6f9272dafb43e639a2d1f2311475a4838f57e48be550cddc/bt_decode-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:01421093b5e97751624de0113fb3da7fb50a1d70c883887555e73abff081ffcc", size = 416377 }, - { url = "https://files.pythonhosted.org/packages/ad/53/43502e90c428e0ff4946112349a6072a52b3c0e73f770284f1c530f5ad53/bt_decode-0.4.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e2dd446b5956c3c772cdcbfe08fe0d483e68dc07b1606cde5d39c689dffd736c", size = 561621 }, - { url = "https://files.pythonhosted.org/packages/64/f2/a869f4d3bf750a2247a10028b7523e12ba9c62fad072fc88741e64d42236/bt_decode-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcbb0fb758460c5fe7e5276b4406dd15d22ff544d309dd4ebb8fc998ce30d51f", size = 547050 }, - { url = "https://files.pythonhosted.org/packages/b8/c0/d6295ccf4c83dc4b10a19c54a116939a0935350b182d55abf86a36cae7aa/bt_decode-0.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:816f45a75dc78d6beafaf7cc02ab51d73a3dd1c91d4ba0e6b43aae3c637d793d", size = 603391 }, - { url = "https://files.pythonhosted.org/packages/e9/c0/457f63f087b0072e877582e61fac115218b28902df5d9c62d60a42c899d5/bt_decode-0.4.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:39d44102ea27a23644c262d98378ac0ac650e481508f5d6989b8b4e3fd638faf", size = 600597 }, - { url = "https://files.pythonhosted.org/packages/3b/2d/e90271fa86038fcace7eb544923422d91ae36ebf8627291c84ec05d9d22c/bt_decode-0.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:82e959521c60bc48276a91a01bd97726820128a4f4670ae043da35ca11823ca3", size = 669588 }, - { url = "https://files.pythonhosted.org/packages/c0/3e/5d8be99d4d1b3193f526ba12e64fb8c0132511c19859def040f19cdcd2d5/bt_decode-0.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bdea70a4b83e46432999f7743d130dbd49ccf1974c87c87153f7ad3733f5ccea", size = 707978 }, - { url = "https://files.pythonhosted.org/packages/0e/de/2757cab0397594e8547c897696c0983d067c758b1d3ad9cfb944e401bde2/bt_decode-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99b6cc694fe05037c1dca02111d25b2357fd460bea8d8ce9b2432e3ed1d049c", size = 613663 }, - { url = "https://files.pythonhosted.org/packages/7c/15/c0d12ac696b7472f63bb32c61b4b94d75298311840ba315a76b9e2c9a5aa/bt_decode-0.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:645e82838b2e8d7b03686f5cee44e880c56bed3a9dbf2a530c818d1a63544967", size = 664223 }, - { url = "https://files.pythonhosted.org/packages/28/99/c6199f74f1f36279ced846c32d03f245b0d4d8fd2ae1b22842f6cfc4623d/bt_decode-0.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cb32f5c5fda6cada107e3d82b5d760c87cd49075f28105de0900e495ee211659", size = 781056 }, - { url = "https://files.pythonhosted.org/packages/a1/77/896b5f76f4b10d637ffdfd5645f739f5037ff7e7c871cb874528f2c02c40/bt_decode-0.4.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d2ecb71c8b40f3a4abd9c8fda54febffaa298eceafc12a47e9c0cf93e4ccbb8b", size = 861550 }, - { url = "https://files.pythonhosted.org/packages/5a/ea/d2f0b5c8bc2ac59676aa904b4af040f38730caec73fefd8547aabc4222ae/bt_decode-0.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9b7691207021f023485d5adff6758bc0f938f80cf7e1ca05d291189e869217b5", size = 819734 }, - { url = "https://files.pythonhosted.org/packages/dd/82/f7bd11e8b351d5c560daefe87b8884c6e735e1d3eabcd2919684395fb361/bt_decode-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:912957e7373014acf4203f3a701f4b820d9d7f5bee1f710298d7346f12bcff59", size = 783927 }, - { url = "https://files.pythonhosted.org/packages/36/0c/0818b22b21ac168cfa07a9f7a46ca7676b175b1e65956dc5700d12c7f744/bt_decode-0.4.0-cp311-cp311-win32.whl", hash = "sha256:fb47926e13f39663e62b4105b436abc84b913cb27edd621308f441cb405956ac", size = 389847 }, - { url = "https://files.pythonhosted.org/packages/96/60/94e86a68062d69c42f3409a48143407a67c6c4cfbcd428ab46d10993fd0a/bt_decode-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:001995ff6a20438c5542b13ae0af6458845381ccfd0ef484ae5f7e012c6fb383", size = 416482 }, { url = "https://files.pythonhosted.org/packages/29/08/090efa626ad7bb545febf8e47a96dd976effcf6c027ff06cf6e053d83104/bt_decode-0.4.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ee9731ecf76ba4f60e10378b16d15bea826b41183ab208e32a9a7fd86d3b7c21", size = 557364 }, { url = "https://files.pythonhosted.org/packages/6c/53/7e32ff14583db56a9f1ecc2a506a4af9ca6106e2240928d937b0516e0934/bt_decode-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e0ebd9e6f6e710fce9432d448a6add5b266f19af5ec518a2faf19ddd19ce3dc", size = 542812 }, { url = "https://files.pythonhosted.org/packages/30/39/835655b931dd4b7734743bf66caf28bd94cd5067a8141f6ce22bb8e2de91/bt_decode-0.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd898558c915dd9374a1860c1aee944cd6acb25f8e0f33f58d18eb989c49fab", size = 604124 }, @@ -539,16 +521,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/48/387fd8cef96a86c39e6716455b493a759fbe9a67bcaa2dfe39c3d3b6b11b/bt_decode-0.4.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:579aba5010a078831af2025cd03df9d429fa35008ec46bc1561e6147e2c9769e", size = 860601 }, { url = "https://files.pythonhosted.org/packages/12/85/1458d9eaf9a74390ac5e0a1a3be5eaf53550aa4f4c28362fb4f80a94c8a6/bt_decode-0.4.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:039e880688d4c5f2ee090980649811b700593e21eccee520b294c07b85008bce", size = 817941 }, { url = "https://files.pythonhosted.org/packages/70/72/723265284f71fb95556c5b27c83a370b2e38e02666fd17dbb129856fb1f2/bt_decode-0.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a45173a6f0e48b28b190bfb250b6683984d115d70a6d2ff5102a2421d581de6", size = 783857 }, - { url = "https://files.pythonhosted.org/packages/3e/0e/99a9eb10977368489f114b66b7d70fa1573b2e6eef90fd83e7d5553ff533/bt_decode-0.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49cbf7ef7174d57b89c8e72d54749176da7f01926d963846042af7c141fc7c88", size = 604720 }, - { url = "https://files.pythonhosted.org/packages/8a/4a/45fd8f7f058fdf6b46cd8aa333312fafe0b8c7d1c1a92d0c2645f5094bdd/bt_decode-0.4.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f7e85d5dfb4aaefa9dba9ed86b9dfc2efff35322053da2f774942a9da6d50486", size = 601660 }, - { url = "https://files.pythonhosted.org/packages/5b/fc/67fa266990f2be203573cc3184e678bd9a7e31e530badd0e9a75107dc164/bt_decode-0.4.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a061a29489eb9680a01085f87e575e7e69fbfdc2c533d361ab84486d65470986", size = 669550 }, - { url = "https://files.pythonhosted.org/packages/62/06/44bc4f2112573d3e7e8e826c3dae3e591d0c9a16422c61d33a54d23983bf/bt_decode-0.4.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6def48997eac2b9aafde742c4c2a7d159623824e7f9d36bbfa95f12ba6354d5", size = 708913 }, - { url = "https://files.pythonhosted.org/packages/a4/e0/ae0a2e1cd5bd818ae3ddfb954fe7a7fce48071cf2be7d4ecef8a857781b0/bt_decode-0.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a5eee81c7a20bd2739f5867354afc38372b0307211a4c9a580bb99369f84835", size = 614747 }, - { url = "https://files.pythonhosted.org/packages/f5/31/0277f6a1d01730f5826aec952637150d038dce594c842b24e4e60af67a6d/bt_decode-0.4.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8914f5bd5bfe16e79fe6f8f94766d22635f1f4bef1567c545c22ecdf4f150313", size = 663773 }, - { url = "https://files.pythonhosted.org/packages/90/53/1b1c1a0e39c7b02725eae20b072df5044730b5a84d2844d151a1624e3aba/bt_decode-0.4.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3b268f170bcf85e229078f3af589b977c56ed9b696fe9e1198c5d4c9607406f1", size = 782839 }, - { url = "https://files.pythonhosted.org/packages/7a/75/53e83c6834c6c8f1fd2dfd9f97f3badafdd8f73132930eb80c1753c791ff/bt_decode-0.4.0-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:f3c54b14d914bf20669bbeedb97da18b3379c6d7f801404227519416cceda614", size = 862685 }, - { url = "https://files.pythonhosted.org/packages/8d/cc/36fdb495df7b5a48ae72ea4554916e1ac3e1b5b3cbc916cbfeefe9b5a902/bt_decode-0.4.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:a7733ff7bcded3211e3b64fb38a1c917543045a092153999ede98333af766d3c", size = 819340 }, - { url = "https://files.pythonhosted.org/packages/2d/a2/2bcf4bc636933b6c8078ca08b2af8995715a133c327623e0e8d7e111d3a3/bt_decode-0.4.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e1036e0db9f75fb2c2c690bddd2a02d0e94347c13d906eb5dbbf22202f3fa46f", size = 785373 }, ] [[package]] @@ -569,30 +541,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, - { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, - { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, - { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, - { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, - { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, - { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, - { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, - { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, - { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, - { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, - { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, @@ -623,32 +571,6 @@ 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 }, @@ -722,6 +644,27 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/ce/cf/70ea34103a76cc6fb1892289bda321cd0cc73b1a5500ee7fe9ef9f64acef/colormath-3.0.0.tar.gz", hash = "sha256:3d4605af344527da0e4f9f504fad7ddbebda35322c566a6c72e28edb1ff31217", size = 39761 } +[[package]] +name = "comm" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, +] + +[[package]] +name = "contextlib2" +version = "21.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/13/37ea7805ae3057992e96ecb1cffa2fa35c2ef4498543b846f90dd2348d8f/contextlib2-21.6.0.tar.gz", hash = "sha256:ab1e2bfe1d01d968e1b7e8d9023bc51ef3509bba217bb730cee3827e1ee82869", size = 43795 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/56/6d6872f79d14c0cb02f1646cbb4592eef935857c0951a105874b7b62a0c3/contextlib2-21.6.0-py2.py3-none-any.whl", hash = "sha256:3fbdb64466afd23abaf6c977627b75b6139a5a3e8ce38405c5b413aed7a0471f", size = 13277 }, +] + [[package]] name = "contourpy" version = "1.3.1" @@ -731,26 +674,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/25/c2/fc7193cc5383637ff390a712e88e4ded0452c9fbcf84abe3de5ea3df1866/contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699", size = 13465753 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/a3/80937fe3efe0edacf67c9a20b955139a1a622730042c1ea991956f2704ad/contourpy-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab", size = 268466 }, - { url = "https://files.pythonhosted.org/packages/82/1d/e3eaebb4aa2d7311528c048350ca8e99cdacfafd99da87bc0a5f8d81f2c2/contourpy-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124", size = 253314 }, - { url = "https://files.pythonhosted.org/packages/de/f3/d796b22d1a2b587acc8100ba8c07fb7b5e17fde265a7bb05ab967f4c935a/contourpy-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2f926efda994cdf3c8d3fdb40b9962f86edbc4457e739277b961eced3d0b4c1", size = 312003 }, - { url = "https://files.pythonhosted.org/packages/bf/f5/0e67902bc4394daee8daa39c81d4f00b50e063ee1a46cb3938cc65585d36/contourpy-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adce39d67c0edf383647a3a007de0a45fd1b08dedaa5318404f1a73059c2512b", size = 351896 }, - { url = "https://files.pythonhosted.org/packages/1f/d6/e766395723f6256d45d6e67c13bb638dd1fa9dc10ef912dc7dd3dcfc19de/contourpy-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abbb49fb7dac584e5abc6636b7b2a7227111c4f771005853e7d25176daaf8453", size = 320814 }, - { url = "https://files.pythonhosted.org/packages/a9/57/86c500d63b3e26e5b73a28b8291a67c5608d4aa87ebd17bd15bb33c178bc/contourpy-1.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0cffcbede75c059f535725c1680dfb17b6ba8753f0c74b14e6a9c68c29d7ea3", size = 324969 }, - { url = "https://files.pythonhosted.org/packages/b8/62/bb146d1289d6b3450bccc4642e7f4413b92ebffd9bf2e91b0404323704a7/contourpy-1.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab29962927945d89d9b293eabd0d59aea28d887d4f3be6c22deaefbb938a7277", size = 1265162 }, - { url = "https://files.pythonhosted.org/packages/18/04/9f7d132ce49a212c8e767042cc80ae390f728060d2eea47058f55b9eff1c/contourpy-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974d8145f8ca354498005b5b981165b74a195abfae9a8129df3e56771961d595", size = 1324328 }, - { url = "https://files.pythonhosted.org/packages/46/23/196813901be3f97c83ababdab1382e13e0edc0bb4e7b49a7bff15fcf754e/contourpy-1.3.1-cp310-cp310-win32.whl", hash = "sha256:ac4578ac281983f63b400f7fe6c101bedc10651650eef012be1ccffcbacf3697", size = 173861 }, - { url = "https://files.pythonhosted.org/packages/e0/82/c372be3fc000a3b2005061ca623a0d1ecd2eaafb10d9e883a2fc8566e951/contourpy-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:174e758c66bbc1c8576992cec9599ce8b6672b741b5d336b5c74e35ac382b18e", size = 218566 }, - { url = "https://files.pythonhosted.org/packages/12/bb/11250d2906ee2e8b466b5f93e6b19d525f3e0254ac8b445b56e618527718/contourpy-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8b974d8db2c5610fb4e76307e265de0edb655ae8169e8b21f41807ccbeec4b", size = 269555 }, - { url = "https://files.pythonhosted.org/packages/67/71/1e6e95aee21a500415f5d2dbf037bf4567529b6a4e986594d7026ec5ae90/contourpy-1.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20914c8c973f41456337652a6eeca26d2148aa96dd7ac323b74516988bea89fc", size = 254549 }, - { url = "https://files.pythonhosted.org/packages/31/2c/b88986e8d79ac45efe9d8801ae341525f38e087449b6c2f2e6050468a42c/contourpy-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d40d37c1c3a4961b4619dd9d77b12124a453cc3d02bb31a07d58ef684d3d86", size = 313000 }, - { url = "https://files.pythonhosted.org/packages/c4/18/65280989b151fcf33a8352f992eff71e61b968bef7432fbfde3a364f0730/contourpy-1.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:113231fe3825ebf6f15eaa8bc1f5b0ddc19d42b733345eae0934cb291beb88b6", size = 352925 }, - { url = "https://files.pythonhosted.org/packages/f5/c7/5fd0146c93220dbfe1a2e0f98969293b86ca9bc041d6c90c0e065f4619ad/contourpy-1.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dbbc03a40f916a8420e420d63e96a1258d3d1b58cbdfd8d1f07b49fcbd38e85", size = 323693 }, - { url = "https://files.pythonhosted.org/packages/85/fc/7fa5d17daf77306840a4e84668a48ddff09e6bc09ba4e37e85ffc8e4faa3/contourpy-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a04ecd68acbd77fa2d39723ceca4c3197cb2969633836ced1bea14e219d077c", size = 326184 }, - { url = "https://files.pythonhosted.org/packages/ef/e7/104065c8270c7397c9571620d3ab880558957216f2b5ebb7e040f85eeb22/contourpy-1.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c414fc1ed8ee1dbd5da626cf3710c6013d3d27456651d156711fa24f24bd1291", size = 1268031 }, - { url = "https://files.pythonhosted.org/packages/e2/4a/c788d0bdbf32c8113c2354493ed291f924d4793c4a2e85b69e737a21a658/contourpy-1.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:31c1b55c1f34f80557d3830d3dd93ba722ce7e33a0b472cba0ec3b6535684d8f", size = 1325995 }, - { url = "https://files.pythonhosted.org/packages/a6/e6/a2f351a90d955f8b0564caf1ebe4b1451a3f01f83e5e3a414055a5b8bccb/contourpy-1.3.1-cp311-cp311-win32.whl", hash = "sha256:f611e628ef06670df83fce17805c344710ca5cde01edfdc72751311da8585375", size = 174396 }, - { url = "https://files.pythonhosted.org/packages/a8/7e/cd93cab453720a5d6cb75588cc17dcdc08fc3484b9de98b885924ff61900/contourpy-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b2bdca22a27e35f16794cf585832e542123296b4687f9fd96822db6bae17bfc9", size = 219787 }, { url = "https://files.pythonhosted.org/packages/37/6b/175f60227d3e7f5f1549fcb374592be311293132207e451c3d7c654c25fb/contourpy-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509", size = 271494 }, { url = "https://files.pythonhosted.org/packages/6b/6a/7833cfae2c1e63d1d8875a50fd23371394f540ce809d7383550681a1fa64/contourpy-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc", size = 255444 }, { url = "https://files.pythonhosted.org/packages/7f/b3/7859efce66eaca5c14ba7619791b084ed02d868d76b928ff56890d2d059d/contourpy-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454", size = 307628 }, @@ -781,9 +704,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f0/ed/92d86f183a8615f13f6b9cbfc5d4298a509d6ce433432e21da838b4b63f4/contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1", size = 1318403 }, { url = "https://files.pythonhosted.org/packages/b3/0e/c8e4950c77dcfc897c71d61e56690a0a9df39543d2164040301b5df8e67b/contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1", size = 185117 }, { url = "https://files.pythonhosted.org/packages/c1/31/1ae946f11dfbd229222e6d6ad8e7bd1891d3d48bde5fbf7a0beb9491f8e3/contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546", size = 236668 }, - { url = "https://files.pythonhosted.org/packages/3e/4f/e56862e64b52b55b5ddcff4090085521fc228ceb09a88390a2b103dccd1b/contourpy-1.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b457d6430833cee8e4b8e9b6f07aa1c161e5e0d52e118dc102c8f9bd7dd060d6", size = 265605 }, - { url = "https://files.pythonhosted.org/packages/b0/2e/52bfeeaa4541889f23d8eadc6386b442ee2470bd3cff9baa67deb2dd5c57/contourpy-1.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb76c1a154b83991a3cbbf0dfeb26ec2833ad56f95540b442c73950af2013750", size = 315040 }, - { url = "https://files.pythonhosted.org/packages/52/94/86bfae441707205634d80392e873295652fc313dfd93c233c52c4dc07874/contourpy-1.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:44a29502ca9c7b5ba389e620d44f2fbe792b1fb5734e8b931ad307071ec58c53", size = 218221 }, ] [[package]] @@ -813,10 +733,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", size = 4076592 }, { url = "https://files.pythonhosted.org/packages/81/1e/ffcc41b3cebd64ca90b28fd58141c5f68c83d48563c88333ab660e002cd3/cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", size = 2623145 }, { url = "https://files.pythonhosted.org/packages/87/5c/3dab83cc4aba1f4b0e733e3f0c3e7d4386440d660ba5b1e3ff995feb734d/cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", size = 3068026 }, - { url = "https://files.pythonhosted.org/packages/6f/db/d8b8a039483f25fc3b70c90bc8f3e1d4497a99358d610c5067bf3bd4f0af/cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c", size = 3144545 }, - { url = "https://files.pythonhosted.org/packages/93/90/116edd5f8ec23b2dc879f7a42443e073cdad22950d3c8ee834e3b8124543/cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3", size = 3679828 }, - { url = "https://files.pythonhosted.org/packages/d8/32/1e1d78b316aa22c0ba6493cc271c1c309969e5aa5c22c830a1d7ce3471e6/cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83", size = 3908132 }, - { url = "https://files.pythonhosted.org/packages/91/bb/cd2c13be3332e7af3cdf16154147952d39075b9f61ea5e6b5241bf4bf436/cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7", size = 2988811 }, ] [[package]] @@ -828,6 +744,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, ] +[[package]] +name = "cython" +version = "3.0.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/4d/b720d6000f4ca77f030bd70f12550820f0766b568e43f11af7f7ad9061aa/cython-3.0.11.tar.gz", hash = "sha256:7146dd2af8682b4ca61331851e6aebce9fe5158e75300343f80c07ca80b1faff", size = 2755544 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/fbb23239efe2183e4eaf76689270d6f5b3bbcf9be9ad1eb97cc34349e6fc/Cython-3.0.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:11996c40c32abf843ba652a6d53cb15944c88d91f91fc4e6f0028f5df8a8f8a1", size = 3141274 }, + { url = "https://files.pythonhosted.org/packages/87/e5/76379edb21fd5bb9e2aaa1d305492bc35bba96dfb51f5d96867d9863b6df/Cython-3.0.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63f2c892e9f9c1698ecfee78205541623eb31cd3a1b682668be7ac12de94aa8e", size = 3340904 }, + { url = "https://files.pythonhosted.org/packages/9a/ef/44af6aded89444dc45f4466ff207a05d3376c641cf1146c03fd14c55ae64/Cython-3.0.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b14c24f1dc4c4c9d997cca8d1b7fb01187a218aab932328247dcf5694a10102", size = 3514052 }, + { url = "https://files.pythonhosted.org/packages/e0/d5/ef8c7b6aa7a83c508f5c3bf0dfb9eb0a2a9be910c0b1f205f842128269c3/Cython-3.0.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8eed5c015685106db15dd103fd040948ddca9197b1dd02222711815ea782a27", size = 3573721 }, + { url = "https://files.pythonhosted.org/packages/e5/4a/58d6c208563504a35febff94904bb291b368a8b0f28a5e0593c770967caa/Cython-3.0.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780f89c95b8aec1e403005b3bf2f0a2afa060b3eba168c86830f079339adad89", size = 3393594 }, + { url = "https://files.pythonhosted.org/packages/a0/92/a60a400be286dc661609da9db903680bba1423362000b689cf8ef0aec811/Cython-3.0.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a690f2ff460682ea985e8d38ec541be97e0977fa0544aadc21efc116ff8d7579", size = 3601319 }, + { url = "https://files.pythonhosted.org/packages/ac/11/f02fc24d1a071b93e1d07497b0a528687b1f93bb4945c635119480fab3c0/Cython-3.0.11-cp312-cp312-win32.whl", hash = "sha256:2252b5aa57621848e310fe7fa6f7dce5f73aa452884a183d201a8bcebfa05a00", size = 2608335 }, + { url = "https://files.pythonhosted.org/packages/35/00/78ffea3a0ab176267a25ff049518b2582db7ac265bbf27944243d1a81ce2/Cython-3.0.11-cp312-cp312-win_amd64.whl", hash = "sha256:da394654c6da15c1d37f0b7ec5afd325c69a15ceafee2afba14b67a5df8a82c8", size = 2792586 }, + { url = "https://files.pythonhosted.org/packages/eb/19/1d7164b724f62b67c59aa3531a2be8ed1a0c7e4e80afcc6502d8409c4ee3/Cython-3.0.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4341d6a64d47112884e0bcf31e6c075268220ee4cd02223047182d4dda94d637", size = 3134881 }, + { url = "https://files.pythonhosted.org/packages/0a/d7/8d834d7ec4b6e55db857f44e328246d40cb527917040fabf3c48d27609b3/Cython-3.0.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351955559b37e6c98b48aecb178894c311be9d731b297782f2b78d111f0c9015", size = 3330582 }, + { url = "https://files.pythonhosted.org/packages/1c/ae/d520f3cd94a8926bc47275a968e51bbc669a28f27a058cdfc5c3081fbbf7/Cython-3.0.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c02361af9bfa10ff1ccf967fc75159e56b1c8093caf565739ed77a559c1f29f", size = 3503750 }, + { url = "https://files.pythonhosted.org/packages/6e/e4/45c556f4a6d40b6938368d420d3c985bbef9088b7d4a8d8c6648d50e4a94/Cython-3.0.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6823aef13669a32caf18bbb036de56065c485d9f558551a9b55061acf9c4c27f", size = 3566498 }, + { url = "https://files.pythonhosted.org/packages/ce/2d/544f6aa3cab31b99ddb07e7eaaaca6a43db52fe0dc59090195c48fc0b033/Cython-3.0.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6fb68cef33684f8cc97987bee6ae919eee7e18ee6a3ad7ed9516b8386ef95ae6", size = 3389063 }, + { url = "https://files.pythonhosted.org/packages/55/1a/9d871cc1514df273cd2ccfe3efe5ff1df509ce11768c02a834052709f152/Cython-3.0.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:790263b74432cb997740d73665f4d8d00b9cd1cecbdd981d93591ddf993d4f12", size = 3596353 }, + { url = "https://files.pythonhosted.org/packages/47/4e/4db412f595de4b2224a81ea5332ce107ce3e93bf87275c78648f2e3e37b8/Cython-3.0.11-cp313-cp313-win32.whl", hash = "sha256:e6dd395d1a704e34a9fac00b25f0036dce6654c6b898be6f872ac2bb4f2eda48", size = 2602768 }, + { url = "https://files.pythonhosted.org/packages/e7/91/8a29e1bce2f8a893a4c24874943b64e8ede14fac9990bd4a3f13a46c2720/Cython-3.0.11-cp313-cp313-win_amd64.whl", hash = "sha256:52186101d51497519e99b60d955fd5cb3bf747c67f00d742e70ab913f1e42d31", size = 2784414 }, + { url = "https://files.pythonhosted.org/packages/43/39/bdbec9142bc46605b54d674bf158a78b191c2b75be527c6dcf3e6dfe90b8/Cython-3.0.11-py2.py3-none-any.whl", hash = "sha256:0e25f6425ad4a700d7f77cd468da9161e63658837d1bc34861a9861a4ef6346d", size = 1171267 }, +] + [[package]] name = "cytoolz" version = "1.0.1" @@ -837,34 +778,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/a7/f9/3243eed3a6545c2a33a21f74f655e3fcb5d2192613cd3db81a93369eb339/cytoolz-1.0.1.tar.gz", hash = "sha256:89cc3161b89e1bb3ed7636f74ed2e55984fd35516904fc878cae216e42b2c7d6", size = 626652 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/d9/f13d66c16cff1fa1cb6c234698029877c456f35f577ef274aba3b86e7c51/cytoolz-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cec9af61f71fc3853eb5dca3d42eb07d1f48a4599fa502cbe92adde85f74b042", size = 403515 }, - { url = "https://files.pythonhosted.org/packages/4b/2d/4cdf848a69300c7d44984f2ebbebb3b8576e5449c8dea157298f3bdc4da3/cytoolz-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:140bbd649dbda01e91add7642149a5987a7c3ccc251f2263de894b89f50b6608", size = 383936 }, - { url = "https://files.pythonhosted.org/packages/72/a4/ccfdd3f0ed9cc818f734b424261f6018fc61e3ec833bf85225a9aca0d994/cytoolz-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e90124bdc42ff58b88cdea1d24a6bc5f776414a314cc4d94f25c88badb3a16d1", size = 1934569 }, - { url = "https://files.pythonhosted.org/packages/50/fc/38d5344fa595683ad10dc819cfc1d8b9d2b3391ccf3e8cb7bab4899a01f5/cytoolz-1.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e74801b751e28f7c5cc3ad264c123954a051f546f2fdfe089f5aa7a12ccfa6da", size = 2015129 }, - { url = "https://files.pythonhosted.org/packages/28/29/75261748dc54a20a927f33641f4e9aac674cfc6d3fbd4f332e10d0b37639/cytoolz-1.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:582dad4545ddfb5127494ef23f3fa4855f1673a35d50c66f7638e9fb49805089", size = 2000506 }, - { url = "https://files.pythonhosted.org/packages/00/ae/e4ead004cc2698281d153c4a5388638d67cdb5544d6d6cc1e5b3db2bd2a3/cytoolz-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd7bd0618e16efe03bd12f19c2a26a27e6e6b75d7105adb7be1cd2a53fa755d8", size = 1957537 }, - { url = "https://files.pythonhosted.org/packages/4a/ff/4f3aa07f4f47701f7f63df60ce0a5669fa09c256c3d4a33503a9414ea5cc/cytoolz-1.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d74cca6acf1c4af58b2e4a89cc565ed61c5e201de2e434748c93e5a0f5c541a5", size = 1863331 }, - { url = "https://files.pythonhosted.org/packages/a2/29/654f57f2a9b8e9765a4ab876765f64f94530b61fc6471a07feea42ece6d4/cytoolz-1.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:823a3763828d8d457f542b2a45d75d6b4ced5e470b5c7cf2ed66a02f508ed442", size = 1849938 }, - { url = "https://files.pythonhosted.org/packages/bc/7b/11f457db6b291060a98315ab2c7198077d8bddeeebe5f7126d9dad98cc54/cytoolz-1.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:51633a14e6844c61db1d68c1ffd077cf949f5c99c60ed5f1e265b9e2966f1b52", size = 1852345 }, - { url = "https://files.pythonhosted.org/packages/6b/92/0dccc96ce0323be236d404f5084479b79b747fa0e74e43a270e95868b5f9/cytoolz-1.0.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3ec9b01c45348f1d0d712507d54c2bfd69c62fbd7c9ef555c9d8298693c2432", size = 1989877 }, - { url = "https://files.pythonhosted.org/packages/a3/c8/1c5203a81200bae51aa8f7b5fad613f695bf1afa03f16251ca23ecb2ef9f/cytoolz-1.0.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1855022b712a9c7a5bce354517ab4727a38095f81e2d23d3eabaf1daeb6a3b3c", size = 1994492 }, - { url = "https://files.pythonhosted.org/packages/e2/8a/04bc193c4d7ced8ef6bb62cdcd0bf40b5e5eb26586ed2cfb4433ec7dfd0a/cytoolz-1.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9930f7288c4866a1dc1cc87174f0c6ff4cad1671eb1f6306808aa6c445857d78", size = 1896077 }, - { url = "https://files.pythonhosted.org/packages/21/a5/bee63a58f51d2c74856db66e6119a014464ff8cb1c9387fa4bd2d94e49b0/cytoolz-1.0.1-cp310-cp310-win32.whl", hash = "sha256:a9baad795d72fadc3445ccd0f122abfdbdf94269157e6d6d4835636dad318804", size = 322135 }, - { url = "https://files.pythonhosted.org/packages/e8/16/7abfb1685e8b7f2838264551ee33651748994813f566ac4c3d737dfe90e5/cytoolz-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:ad95b386a84e18e1f6136f6d343d2509d4c3aae9f5a536f3dc96808fcc56a8cf", size = 363599 }, - { url = "https://files.pythonhosted.org/packages/dc/ea/8131ae39119820b8867cddc23716fa9f681f2b3bbce6f693e68dfb36b55b/cytoolz-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2d958d4f04d9d7018e5c1850790d9d8e68b31c9a2deebca74b903706fdddd2b6", size = 406162 }, - { url = "https://files.pythonhosted.org/packages/26/18/3d9bd4c146f6ea6e51300c242b20cb416966b21d481dac230e1304f1e54b/cytoolz-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0f445b8b731fc0ecb1865b8e68a070084eb95d735d04f5b6c851db2daf3048ab", size = 384961 }, - { url = "https://files.pythonhosted.org/packages/e4/73/9034827907c7f85c7c484c9494e905d022fb8174526004e9ef332570349e/cytoolz-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f546a96460a7e28eb2ec439f4664fa646c9b3e51c6ebad9a59d3922bbe65e30", size = 2091698 }, - { url = "https://files.pythonhosted.org/packages/74/af/d5c2733b0fde1a08254ff1a8a8d567874040c9eb1606363cfebc0713c73f/cytoolz-1.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0317681dd065532d21836f860b0563b199ee716f55d0c1f10de3ce7100c78a3b", size = 2188452 }, - { url = "https://files.pythonhosted.org/packages/6a/bb/77c71fa9c217260b4056a732d754748903423c2cdd82a673d6064741e375/cytoolz-1.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c0ef52febd5a7821a3fd8d10f21d460d1a3d2992f724ba9c91fbd7a96745d41", size = 2174203 }, - { url = "https://files.pythonhosted.org/packages/fc/a9/a5b4a3ff5d22faa1b60293bfe97362e2caf4a830c26d37ab5557f60d04b2/cytoolz-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebaf419acf2de73b643cf96108702b8aef8e825cf4f63209ceb078d5fbbbfd", size = 2099831 }, - { url = "https://files.pythonhosted.org/packages/35/08/7f6869ea1ff31ce5289a7d58d0e7090acfe7058baa2764473048ff61ea3c/cytoolz-1.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f7f04eeb4088947585c92d6185a618b25ad4a0f8f66ea30c8db83cf94a425e3", size = 1996744 }, - { url = "https://files.pythonhosted.org/packages/46/b4/9ac424c994b51763fd1bbed62d95f8fba8fa0e45c8c3c583904fdaf8f51d/cytoolz-1.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f61928803bb501c17914b82d457c6f50fe838b173fb40d39c38d5961185bd6c7", size = 2013733 }, - { url = "https://files.pythonhosted.org/packages/3e/99/03009765c4b87d742d5b5a8670abb56a8c7ede033c2cdaa4be8662d3b001/cytoolz-1.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d2960cb4fa01ccb985ad1280db41f90dc97a80b397af970a15d5a5de403c8c61", size = 1994850 }, - { url = "https://files.pythonhosted.org/packages/40/9a/8458af9a5557e177ea42f8cf7e477bede518b0bbef564e28c4151feaa52c/cytoolz-1.0.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2b407cc3e9defa8df5eb46644f6f136586f70ba49eba96f43de67b9a0984fd3", size = 2155352 }, - { url = "https://files.pythonhosted.org/packages/5e/5c/2a701423e001fcbec288b4f3fc2bf67557d114c2388237fc1ae67e1e2686/cytoolz-1.0.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8245f929144d4d3bd7b972c9593300195c6cea246b81b4c46053c48b3f044580", size = 2163515 }, - { url = "https://files.pythonhosted.org/packages/36/16/ee2e06e65d9d533bc05cd52a0b355ba9072fc8f60d77289e529c6d2e3750/cytoolz-1.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e37385db03af65763933befe89fa70faf25301effc3b0485fec1c15d4ce4f052", size = 2054431 }, - { url = "https://files.pythonhosted.org/packages/d8/d5/2fac8315f210fa1bc7106e27c19e1211580aa25bb7fa17dfd79505e5baf2/cytoolz-1.0.1-cp311-cp311-win32.whl", hash = "sha256:50f9c530f83e3e574fc95c264c3350adde8145f4f8fc8099f65f00cc595e5ead", size = 322004 }, - { url = "https://files.pythonhosted.org/packages/a9/9e/0b70b641850a95f9ff90adde9d094a4b1d81ec54dadfd97fec0a2aaf440e/cytoolz-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:b7f6b617454b4326af7bd3c7c49b0fc80767f134eb9fd6449917a058d17a0e3c", size = 365358 }, { url = "https://files.pythonhosted.org/packages/d8/e8/218098344ed2cb5f8441fade9b2428e435e7073962374a9c71e59ac141a7/cytoolz-1.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fcb8f7d0d65db1269022e7e0428471edee8c937bc288ebdcb72f13eaa67c2fe4", size = 414121 }, { url = "https://files.pythonhosted.org/packages/de/27/4d729a5653718109262b758fec1a959aa9facb74c15460d9074dc76d6635/cytoolz-1.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:207d4e4b445e087e65556196ff472ff134370d9a275d591724142e255f384662", size = 390904 }, { url = "https://files.pythonhosted.org/packages/72/c0/cbabfa788bab9c6038953bf9478adaec06e88903a726946ea7c88092f5c4/cytoolz-1.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21cdf6bac6fd843f3b20280a66fd8df20dea4c58eb7214a2cd8957ec176f0bb3", size = 2090734 }, @@ -893,11 +806,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d8/03/dbb9d47556ee54337e7e0ac209d17ceff2d2a197c34de08005abc7a7449b/cytoolz-1.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:738b2350f340ff8af883eb301054eb724997f795d20d90daec7911c389d61581", size = 2069785 }, { url = "https://files.pythonhosted.org/packages/ea/f8/11bb7b8947002231faae3ec2342df5896afbc19eb783a332cce6d219ff79/cytoolz-1.0.1-cp313-cp313-win32.whl", hash = "sha256:9cbd9c103df54fcca42be55ef40e7baea624ac30ee0b8bf1149f21146d1078d9", size = 320685 }, { url = "https://files.pythonhosted.org/packages/40/eb/dde173cf2357084ca9423950be1f2f11ab11d65d8bd30165bfb8fd4213e9/cytoolz-1.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:90e577e08d3a4308186d9e1ec06876d4756b1e8164b92971c69739ea17e15297", size = 362898 }, - { url = "https://files.pythonhosted.org/packages/d9/f7/ef2a10daaec5c0f7d781d50758c6187eee484256e356ae8ef178d6c48497/cytoolz-1.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:83d19d55738ad9c60763b94f3f6d3c6e4de979aeb8d76841c1401081e0e58d96", size = 345702 }, - { url = "https://files.pythonhosted.org/packages/c8/14/53c84adddedb67ff1546abb86fea04d26e24298c3ceab8436d20122ed0b9/cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f112a71fad6ea824578e6393765ce5c054603afe1471a5c753ff6c67fd872d10", size = 385695 }, - { url = "https://files.pythonhosted.org/packages/bd/80/3ae356c5e7b8d7dc7d1adb52f6932fee85cd748ed4e1217c269d2dfd610f/cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a515df8f8aa6e1eaaf397761a6e4aff2eef73b5f920aedf271416d5471ae5ee", size = 406261 }, - { url = "https://files.pythonhosted.org/packages/0c/31/8e43761ffc82d90bf9cab7e0959712eedcd1e33c211397e143dd42d7af57/cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c398e7b7023460bea2edffe5fcd0a76029580f06c3f6938ac3d198b47156f3", size = 397207 }, - { url = "https://files.pythonhosted.org/packages/d1/b9/fe9da37090b6444c65f848a83e390f87d8cb43d6a4df46de1556ad7e5ceb/cytoolz-1.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3237e56211e03b13df47435b2369f5df281e02b04ad80a948ebd199b7bc10a47", size = 343358 }, ] [[package]] @@ -934,6 +842,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/05/310e94d212fa6654261e40887de1d155afb72e3dadf7b625550dd5c71678/ddt-1.6.0-py2.py3-none-any.whl", hash = "sha256:e3c93b961a108b4f4d5a6c7f2263513d928baf3bb5b32af8e1c804bfb041141d", size = 7095 }, ] +[[package]] +name = "debugpy" +version = "1.8.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/e7/666f4c9b0e24796af50aadc28d36d21c2e01e831a934535f956e09b3650c/debugpy-1.8.11.tar.gz", hash = "sha256:6ad2688b69235c43b020e04fecccdf6a96c8943ca9c2fb340b8adc103c655e57", size = 1640124 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ae/2cf26f3111e9d94384d9c01e9d6170188b0aeda15b60a4ac6457f7c8a26f/debugpy-1.8.11-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:84e511a7545d11683d32cdb8f809ef63fc17ea2a00455cc62d0a4dbb4ed1c308", size = 2498756 }, + { url = "https://files.pythonhosted.org/packages/b0/16/ec551789d547541a46831a19aa15c147741133da188e7e6acf77510545a7/debugpy-1.8.11-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce291a5aca4985d82875d6779f61375e959208cdf09fcec40001e65fb0a54768", size = 4219136 }, + { url = "https://files.pythonhosted.org/packages/72/6f/b2b3ce673c55f882d27a6eb04a5f0c68bcad6b742ac08a86d8392ae58030/debugpy-1.8.11-cp312-cp312-win32.whl", hash = "sha256:28e45b3f827d3bf2592f3cf7ae63282e859f3259db44ed2b129093ca0ac7940b", size = 5224440 }, + { url = "https://files.pythonhosted.org/packages/77/09/b1f05be802c1caef5b3efc042fc6a7cadd13d8118b072afd04a9b9e91e06/debugpy-1.8.11-cp312-cp312-win_amd64.whl", hash = "sha256:44b1b8e6253bceada11f714acf4309ffb98bfa9ac55e4fce14f9e5d4484287a1", size = 5264578 }, + { url = "https://files.pythonhosted.org/packages/2e/66/931dc2479aa8fbf362dc6dcee707d895a84b0b2d7b64020135f20b8db1ed/debugpy-1.8.11-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:8988f7163e4381b0da7696f37eec7aca19deb02e500245df68a7159739bbd0d3", size = 2483651 }, + { url = "https://files.pythonhosted.org/packages/10/07/6c171d0fe6b8d237e35598b742f20ba062511b3a4631938cc78eefbbf847/debugpy-1.8.11-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c1f6a173d1140e557347419767d2b14ac1c9cd847e0b4c5444c7f3144697e4e", size = 4213770 }, + { url = "https://files.pythonhosted.org/packages/89/f1/0711da6ac250d4fe3bf7b3e9b14b4a86e82a98b7825075c07e19bab8da3d/debugpy-1.8.11-cp313-cp313-win32.whl", hash = "sha256:bb3b15e25891f38da3ca0740271e63ab9db61f41d4d8541745cfc1824252cb28", size = 5223911 }, + { url = "https://files.pythonhosted.org/packages/56/98/5e27fa39050749ed460025bcd0034a0a5e78a580a14079b164cc3abdeb98/debugpy-1.8.11-cp313-cp313-win_amd64.whl", hash = "sha256:d8768edcbeb34da9e11bcb8b5c2e0958d25218df7a6e56adf415ef262cd7b6d1", size = 5264166 }, + { url = "https://files.pythonhosted.org/packages/77/0a/d29a5aacf47b4383ed569b8478c02d59ee3a01ad91224d2cff8562410e43/debugpy-1.8.11-py2.py3-none-any.whl", hash = "sha256:0e22f846f4211383e6a416d04b4c13ed174d24cc5d43f5fd52e7821d0ebc8920", size = 5226874 }, +] + [[package]] name = "decorator" version = "5.1.1" @@ -943,6 +868,15 @@ 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 = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + [[package]] name = "dill" version = "0.3.8" @@ -987,6 +921,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/8f/ee72af555cd58feb928ff0fd3977913f4ecd0ce8ad92cf4031c36de91776/duckduckgo_search-7.2.1-py3-none-any.whl", hash = "sha256:72ebbf6ad8759e3c3c79521cd66256e7a4ac741c522fd9342db94de91745ef87", size = 19720 }, ] +[[package]] +name = "easyocr" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ninja" }, + { name = "numpy" }, + { name = "opencv-python-headless" }, + { name = "pillow" }, + { name = "pyclipper" }, + { name = "python-bidi" }, + { name = "pyyaml" }, + { name = "scikit-image" }, + { name = "scipy" }, + { name = "shapely" }, + { name = "torch" }, + { name = "torchvision" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/84/4a2cab0e6adde6a85e7ba543862e5fc0250c51f3ac721a078a55cdcff250/easyocr-1.7.2-py3-none-any.whl", hash = "sha256:5be12f9b0e595d443c9c3d10b0542074b50f0ec2d98b141a109cd961fd1c177c", size = 2870178 }, +] + [[package]] name = "ecdsa" version = "0.19.0" @@ -999,6 +955,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/e7/ed3243b30d1bec41675b6394a1daae46349dc2b855cb83be846a5a918238/ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a", size = 149266 }, ] +[[package]] +name = "editdistance" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/18/9f4f975ca87a390832b1c22478f3702fcdf739f83211e24d054b7551270d/editdistance-0.8.1.tar.gz", hash = "sha256:d1cdf80a5d5014b0c9126a69a42ce55a457b457f6986ff69ca98e4fe4d2d8fed", size = 50006 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/4c/7f195588949b4e72436dc7fc902632381f96e586af829685b56daebb38b8/editdistance-0.8.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04af61b3fcdd287a07c15b6ae3b02af01c5e3e9c3aca76b8c1d13bd266b6f57", size = 106723 }, + { url = "https://files.pythonhosted.org/packages/8d/82/31dc1640d830cd7d36865098329f34e4dad3b77f31cfb9404b347e700196/editdistance-0.8.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:18fc8b6eaae01bfd9cf999af726c1e8dcf667d120e81aa7dbd515bea7427f62f", size = 80998 }, + { url = "https://files.pythonhosted.org/packages/ea/2a/6b823e71cef694d6f070a1d82be2842706fa193541aab8856a8f42044cd0/editdistance-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a87839450a5987028738d061ffa5ef6a68bac2ddc68c9147a8aae9806629c7f", size = 79248 }, + { url = "https://files.pythonhosted.org/packages/e1/31/bfb8e590f922089dc3471ed7828a6da2fc9453eba38c332efa9ee8749fd7/editdistance-0.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24b5f9c9673c823d91b5973d0af8b39f883f414a55ade2b9d097138acd10f31e", size = 415262 }, + { url = "https://files.pythonhosted.org/packages/a9/c7/57423942b2f847cdbbb46494568d00cd8a45500904ea026f0aad6ca01bc7/editdistance-0.8.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c59248eabfad603f0fba47b0c263d5dc728fb01c2b6b50fb6ca187cec547fdb3", size = 418905 }, + { url = "https://files.pythonhosted.org/packages/1b/05/dfa4cdcce063596cbf0d7a32c46cd0f4fa70980311b7da64d35f33ad02a0/editdistance-0.8.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e239d88ff52821cf64023fabd06a1d9a07654f364b64bf1284577fd3a79d0e", size = 412511 }, + { url = "https://files.pythonhosted.org/packages/0e/14/39608ff724a9523f187c4e28926d78bc68f2798f74777ac6757981108345/editdistance-0.8.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2f7f71698f83e8c83839ac0d876a0f4ef996c86c5460aebd26d85568d4afd0db", size = 917293 }, + { url = "https://files.pythonhosted.org/packages/df/92/4a1c61d72da40dedfd0ff950fdc71ae83f478330c58a8bccfd776518bd67/editdistance-0.8.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:04e229d6f4ce0c12abc9f4cd4023a5b5fa9620226e0207b119c3c2778b036250", size = 975580 }, + { url = "https://files.pythonhosted.org/packages/47/3d/9877566e724c8a37f2228a84ec5cbf66dbfd0673515baf68a0fe07caff40/editdistance-0.8.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e16721636da6d6b68a2c09eaced35a94f4a4a704ec09f45756d4fd5e128ed18d", size = 929121 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/8c50757d198b8ca30ddb91e8b8f0247a8dca04ff2ec30755245f0ab1ff0c/editdistance-0.8.1-cp312-cp312-win32.whl", hash = "sha256:87533cf2ebc3777088d991947274cd7e1014b9c861a8aa65257bcdc0ee492526", size = 81039 }, + { url = "https://files.pythonhosted.org/packages/28/f0/65101e51dc7c850e7b7581a5d8fa8721a1d7479a0dca6c08386328e19882/editdistance-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:09f01ed51746d90178af7dd7ea4ebb41497ef19f53c7f327e864421743dffb0a", size = 79853 }, +] + +[[package]] +name = "efficientnet" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "keras-applications" }, + { name = "scikit-image" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/94/0af58e74b6e3f1f217a84289e9fd5a11e75276ac675d5c24d512f6a56720/efficientnet-1.0.0.tar.gz", hash = "sha256:868715f6f5467186c0fa67ee8c9d50260b22f3a1bfb5919acd9911358be54df9", size = 15153 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/82/f3ae07316f0461417dc54affab6e86ab188a5a22f33176d35271628b96e0/efficientnet-1.0.0-py3-none-any.whl", hash = "sha256:c6fd035d856c9f1c409f3ac19e7655e54a13851046c821e9d4417fd12bcbaae4", size = 17685 }, +] + [[package]] name = "einops" version = "0.8.0" @@ -1008,6 +996,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/5a/f0b9ad6c0a9017e62d4735daaeb11ba3b6c009d69a26141b258cd37b5588/einops-0.8.0-py3-none-any.whl", hash = "sha256:9572fb63046264a862693b0a87088af3bdc8c068fde03de63453cbbde245465f", size = 43223 }, ] +[[package]] +name = "essential-generators" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/ed/8f1b16654c55b9ba09a4a5058d85debd0acb89f8eb000071522c6c071952/essential_generators-1.0.tar.gz", hash = "sha256:a4e015b01ea45cf32b9afe7fdfd239e9ad13654d1bfe2e4d9008204e62a1743d", size = 9231075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/f4/20863402b45c3475c8ace4274c5ec870ba6df7259e9a4e3e1436c964f176/essential_generators-1.0-py3-none-any.whl", hash = "sha256:6a0f4f589cf4feb0431485e7c5abf58088e1d28ce2981b4c5a7142723646b172", size = 9452110 }, +] + [[package]] name = "eth-hash" version = "0.7.0" @@ -1058,12 +1055,12 @@ wheels = [ ] [[package]] -name = "exceptiongroup" -version = "1.2.2" +name = "executing" +version = "2.1.0" 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 } +sdist = { url = "https://files.pythonhosted.org/packages/8c/e3/7d45f492c2c4a0e8e0fad57d081a7c8a0286cdd86372b070cca1ec0caa1e/executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab", size = 977485 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, + { url = "https://files.pythonhosted.org/packages/b5/fd/afcd0496feca3276f509df3dbd5dae726fcc756f1a08d9e25abe1733f962/executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf", size = 25805 }, ] [[package]] @@ -1080,6 +1077,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/d1/5958526c3bdbed74f88bf69b86506db5b25a600207f0f688473667690de6/fastapi-0.110.3-py3-none-any.whl", hash = "sha256:fd7600612f755e4050beb74001310b5a7e1796d149c2ee363124abdfa0289d32", size = 91834 }, ] +[[package]] +name = "fastjsonschema" +version = "2.21.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/50/4b769ce1ac4071a1ef6d86b1a3fb56cdc3a37615e8c5519e1af96cdac366/fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4", size = 373939 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/2b/0817a2b257fe88725c25589d89aec060581aabf668707a8d03b2e9e0cb2a/fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667", size = 23924 }, +] + [[package]] name = "filelock" version = "3.16.1" @@ -1089,28 +1095,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, ] +[[package]] +name = "fire" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "termcolor" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/b6/82c7e601d6d3c3278c40b7bd35e17e82aa227f050aa9f66cb7b7fce29471/fire-0.7.0.tar.gz", hash = "sha256:961550f07936eaf65ad1dc8360f2b2bf8408fad46abbfa4d2a3794f8d2a95cdf", size = 87189 } + +[[package]] +name = "flatbuffers" +version = "24.12.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/83/9ae01534f7e92a0c04f86586a0d62a4a0266e51d8bb2bfd5b8ea8165abba/flatbuffers-24.12.23.tar.gz", hash = "sha256:2910b0bc6ae9b6db78dd2b18d0b7a0709ba240fb5585f286a3a2b30785c22dac", size = 22164 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/b4/31c461eef98b96b8ab736d97274548eaf2b2e349bf09e4de3902f7d53084/flatbuffers-24.12.23-py2.py3-none-any.whl", hash = "sha256:c418e0d48890f4142b92fd3e343e73a48f194e1f80075ddcc5793779b3585444", size = 30962 }, +] + [[package]] name = "fonttools" version = "4.55.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/76/61/a300d1574dc381393424047c0396a0e213db212e28361123af9830d71a8d/fonttools-4.55.3.tar.gz", hash = "sha256:3983313c2a04d6cc1fe9251f8fc647754cf49a61dac6cb1e7249ae67afaafc45", size = 3498155 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/f3/9ac8c6705e4a0ff3c29e524df1caeee6f2987b02fb630129f21cc99a8212/fonttools-4.55.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1dcc07934a2165ccdc3a5a608db56fb3c24b609658a5b340aee4ecf3ba679dc0", size = 2769857 }, - { url = "https://files.pythonhosted.org/packages/d8/24/e8b8edd280bdb7d0ecc88a5d952b1dec2ee2335be71cc5a33c64871cdfe8/fonttools-4.55.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7d66c15ba875432a2d2fb419523f5d3d347f91f48f57b8b08a2dfc3c39b8a3f", size = 2299705 }, - { url = "https://files.pythonhosted.org/packages/f8/9e/e1ba20bd3b71870207fd45ca3b90208a7edd8ae3b001081dc31c45adb017/fonttools-4.55.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e4ae3592e62eba83cd2c4ccd9462dcfa603ff78e09110680a5444c6925d841", size = 4576104 }, - { url = "https://files.pythonhosted.org/packages/34/db/d423bc646e6703fe3e6aea0edd22a2df47b9d188c5f7f1b49070be4d2205/fonttools-4.55.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62d65a3022c35e404d19ca14f291c89cc5890032ff04f6c17af0bd1927299674", size = 4618282 }, - { url = "https://files.pythonhosted.org/packages/75/a0/e5062ac960a385b984ba74e7b55132e7f2c65e449e8330ab0f595407a3de/fonttools-4.55.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d342e88764fb201286d185093781bf6628bbe380a913c24adf772d901baa8276", size = 4570539 }, - { url = "https://files.pythonhosted.org/packages/1f/33/0d744ff518ebe50020b63e5018b8b278efd6a930c1d2eedda7defc42153b/fonttools-4.55.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd68c87a2bfe37c5b33bcda0fba39b65a353876d3b9006fde3adae31f97b3ef5", size = 4742411 }, - { url = "https://files.pythonhosted.org/packages/7e/6c/2f768652dba6b801f1567fc5d1829cda369bcd6e95e315a91e628f91c702/fonttools-4.55.3-cp310-cp310-win32.whl", hash = "sha256:1bc7ad24ff98846282eef1cbeac05d013c2154f977a79886bb943015d2b1b261", size = 2175132 }, - { url = "https://files.pythonhosted.org/packages/19/d1/4dcd865360fb2c499749a913fe80e41c26e8ae18629d87dfffa3de27e831/fonttools-4.55.3-cp310-cp310-win_amd64.whl", hash = "sha256:b54baf65c52952db65df39fcd4820668d0ef4766c0ccdf32879b77f7c804d5c5", size = 2219430 }, - { url = "https://files.pythonhosted.org/packages/4b/18/14be25545600bd100e5b74a3ac39089b7c1cb403dc513b7ca348be3381bf/fonttools-4.55.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c4491699bad88efe95772543cd49870cf756b019ad56294f6498982408ab03e", size = 2771005 }, - { url = "https://files.pythonhosted.org/packages/b2/51/2e1a5d3871cd7c2ae2054b54e92604e7d6abc3fd3656e9583c399648fe1c/fonttools-4.55.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5323a22eabddf4b24f66d26894f1229261021dacd9d29e89f7872dd8c63f0b8b", size = 2300654 }, - { url = "https://files.pythonhosted.org/packages/73/1a/50109bb2703bc6f774b52ea081db21edf2a9fa4b6d7485faadf9d1b997e9/fonttools-4.55.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5480673f599ad410695ca2ddef2dfefe9df779a9a5cda89503881e503c9c7d90", size = 4877541 }, - { url = "https://files.pythonhosted.org/packages/5d/52/c0b9857fa075da1b8806c5dc2d8342918a8cc2065fd14fbddb3303282693/fonttools-4.55.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da9da6d65cd7aa6b0f806556f4985bcbf603bf0c5c590e61b43aa3e5a0f822d0", size = 4906304 }, - { url = "https://files.pythonhosted.org/packages/0b/1b/55f85c7e962d295e456d5209581c919620ee3e877b95cd86245187a5050f/fonttools-4.55.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e894b5bd60d9f473bed7a8f506515549cc194de08064d829464088d23097331b", size = 4888087 }, - { url = "https://files.pythonhosted.org/packages/83/13/6f2809c612ea2ac51391f92468ff861c63473601530fca96458b453212bf/fonttools-4.55.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aee3b57643827e237ff6ec6d28d9ff9766bd8b21e08cd13bff479e13d4b14765", size = 5056958 }, - { url = "https://files.pythonhosted.org/packages/c1/28/d0ea9e872fa4208b9dfca686e1dd9ca22f6c9ef33ecff2f0ebc2dbe7c29b/fonttools-4.55.3-cp311-cp311-win32.whl", hash = "sha256:eb6ca911c4c17eb51853143624d8dc87cdcdf12a711fc38bf5bd21521e79715f", size = 2173939 }, - { url = "https://files.pythonhosted.org/packages/be/36/d74ae1020bc41a1dff3e6f5a99f646563beecb97e386d27abdac3ba07650/fonttools-4.55.3-cp311-cp311-win_amd64.whl", hash = "sha256:6314bf82c54c53c71805318fcf6786d986461622dd926d92a465199ff54b1b72", size = 2220363 }, { url = "https://files.pythonhosted.org/packages/89/58/fbcf5dff7e3ea844bb00c4d806ca1e339e1f2dce5529633bf4842c0c9a1f/fonttools-4.55.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f9e736f60f4911061235603a6119e72053073a12c6d7904011df2d8fad2c0e35", size = 2765380 }, { url = "https://files.pythonhosted.org/packages/81/dd/da6e329e51919b4f421c8738f3497e2ab08c168e76aaef7b6d5351862bdf/fonttools-4.55.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a8aa2c5e5b8b3bcb2e4538d929f6589a5c6bdb84fd16e2ed92649fb5454f11c", size = 2297940 }, { url = "https://files.pythonhosted.org/packages/00/44/f5ee560858425c99ef07e04919e736db09d6416408e5a8d3bbfb4a6623fd/fonttools-4.55.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07f8288aacf0a38d174445fc78377a97fb0b83cfe352a90c9d9c1400571963c7", size = 4793327 }, @@ -1130,42 +1138,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/3b/406d17b1f63e04a82aa621936e6e1c53a8c05458abd66300ac85ea7f9ae9/fonttools-4.55.3-py3-none-any.whl", hash = "sha256:f412604ccbeee81b091b420272841e5ec5ef68967a9790e80bffd0e30b8e2977", size = 1111638 }, ] +[[package]] +name = "fqdn" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121 }, +] + [[package]] name = "frozenlist" version = "1.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/79/29d44c4af36b2b240725dce566b20f63f9b36ef267aaaa64ee7466f4f2f8/frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a", size = 94451 }, - { url = "https://files.pythonhosted.org/packages/47/47/0c999aeace6ead8a44441b4f4173e2261b18219e4ad1fe9a479871ca02fc/frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb", size = 54301 }, - { url = "https://files.pythonhosted.org/packages/8d/60/107a38c1e54176d12e06e9d4b5d755b677d71d1219217cee063911b1384f/frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec", size = 52213 }, - { url = "https://files.pythonhosted.org/packages/17/62/594a6829ac5679c25755362a9dc93486a8a45241394564309641425d3ff6/frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5", size = 240946 }, - { url = "https://files.pythonhosted.org/packages/7e/75/6c8419d8f92c80dd0ee3f63bdde2702ce6398b0ac8410ff459f9b6f2f9cb/frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76", size = 264608 }, - { url = "https://files.pythonhosted.org/packages/88/3e/82a6f0b84bc6fb7e0be240e52863c6d4ab6098cd62e4f5b972cd31e002e8/frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17", size = 261361 }, - { url = "https://files.pythonhosted.org/packages/fd/85/14e5f9ccac1b64ff2f10c927b3ffdf88772aea875882406f9ba0cec8ad84/frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba", size = 231649 }, - { url = "https://files.pythonhosted.org/packages/ee/59/928322800306f6529d1852323014ee9008551e9bb027cc38d276cbc0b0e7/frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d", size = 241853 }, - { url = "https://files.pythonhosted.org/packages/7d/bd/e01fa4f146a6f6c18c5d34cab8abdc4013774a26c4ff851128cd1bd3008e/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2", size = 243652 }, - { url = "https://files.pythonhosted.org/packages/a5/bd/e4771fd18a8ec6757033f0fa903e447aecc3fbba54e3630397b61596acf0/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f", size = 241734 }, - { url = "https://files.pythonhosted.org/packages/21/13/c83821fa5544af4f60c5d3a65d054af3213c26b14d3f5f48e43e5fb48556/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c", size = 260959 }, - { url = "https://files.pythonhosted.org/packages/71/f3/1f91c9a9bf7ed0e8edcf52698d23f3c211d8d00291a53c9f115ceb977ab1/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab", size = 262706 }, - { url = "https://files.pythonhosted.org/packages/4c/22/4a256fdf5d9bcb3ae32622c796ee5ff9451b3a13a68cfe3f68e2c95588ce/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5", size = 250401 }, - { url = "https://files.pythonhosted.org/packages/af/89/c48ebe1f7991bd2be6d5f4ed202d94960c01b3017a03d6954dd5fa9ea1e8/frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb", size = 45498 }, - { url = "https://files.pythonhosted.org/packages/28/2f/cc27d5f43e023d21fe5c19538e08894db3d7e081cbf582ad5ed366c24446/frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4", size = 51622 }, - { url = "https://files.pythonhosted.org/packages/79/43/0bed28bf5eb1c9e4301003b74453b8e7aa85fb293b31dde352aac528dafc/frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30", size = 94987 }, - { url = "https://files.pythonhosted.org/packages/bb/bf/b74e38f09a246e8abbe1e90eb65787ed745ccab6eaa58b9c9308e052323d/frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5", size = 54584 }, - { url = "https://files.pythonhosted.org/packages/2c/31/ab01375682f14f7613a1ade30149f684c84f9b8823a4391ed950c8285656/frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778", size = 52499 }, - { url = "https://files.pythonhosted.org/packages/98/a8/d0ac0b9276e1404f58fec3ab6e90a4f76b778a49373ccaf6a563f100dfbc/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a", size = 276357 }, - { url = "https://files.pythonhosted.org/packages/ad/c9/c7761084fa822f07dac38ac29f841d4587570dd211e2262544aa0b791d21/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869", size = 287516 }, - { url = "https://files.pythonhosted.org/packages/a1/ff/cd7479e703c39df7bdab431798cef89dc75010d8aa0ca2514c5b9321db27/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d", size = 283131 }, - { url = "https://files.pythonhosted.org/packages/59/a0/370941beb47d237eca4fbf27e4e91389fd68699e6f4b0ebcc95da463835b/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45", size = 261320 }, - { url = "https://files.pythonhosted.org/packages/b8/5f/c10123e8d64867bc9b4f2f510a32042a306ff5fcd7e2e09e5ae5100ee333/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d", size = 274877 }, - { url = "https://files.pythonhosted.org/packages/fa/79/38c505601ae29d4348f21706c5d89755ceded02a745016ba2f58bd5f1ea6/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3", size = 269592 }, - { url = "https://files.pythonhosted.org/packages/19/e2/39f3a53191b8204ba9f0bb574b926b73dd2efba2a2b9d2d730517e8f7622/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a", size = 265934 }, - { url = "https://files.pythonhosted.org/packages/d5/c9/3075eb7f7f3a91f1a6b00284af4de0a65a9ae47084930916f5528144c9dd/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9", size = 283859 }, - { url = "https://files.pythonhosted.org/packages/05/f5/549f44d314c29408b962fa2b0e69a1a67c59379fb143b92a0a065ffd1f0f/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2", size = 287560 }, - { url = "https://files.pythonhosted.org/packages/9d/f8/cb09b3c24a3eac02c4c07a9558e11e9e244fb02bf62c85ac2106d1eb0c0b/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf", size = 277150 }, - { url = "https://files.pythonhosted.org/packages/37/48/38c2db3f54d1501e692d6fe058f45b6ad1b358d82cd19436efab80cfc965/frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942", size = 45244 }, - { url = "https://files.pythonhosted.org/packages/ca/8c/2ddffeb8b60a4bce3b196c32fcc30d8830d4615e7b492ec2071da801b8ad/frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d", size = 51634 }, { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, @@ -1234,6 +1221,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/ff/74f23998ad2f93b945c0309f825be92e04e0348e062026998b5eefef4c33/fuzzywuzzy-0.18.0-py2.py3-none-any.whl", hash = "sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993", size = 18272 }, ] +[[package]] +name = "gast" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/14/c566f5ca00c115db7725263408ff952b8ae6d6a4e792ef9c84e77d9af7a1/gast-0.6.0.tar.gz", hash = "sha256:88fc5300d32c7ac6ca7b515310862f71e6fdf2c029bbec7c66c0f5dd47b6b1fb", size = 27708 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/61/8001b38461d751cd1a0c3a6ae84346796a5758123f3ed97a1b121dfbf4f3/gast-0.6.0-py3-none-any.whl", hash = "sha256:52b182313f7330389f72b069ba00f174cfe2a06411099547288839c6cbafbd54", size = 21173 }, +] + [[package]] name = "gitdb" version = "4.0.12" @@ -1258,30 +1254,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599 }, ] +[[package]] +name = "google-pasta" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/4a/0bd53b36ff0323d10d5f24ebd67af2de10a1117f5cf4d7add90df92756f1/google-pasta-0.2.0.tar.gz", hash = "sha256:c9f2c8dfc8f96d0d5808299920721be30c9eec37f2389f28904f454565c8a16e", size = 40430 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/de/c648ef6835192e6e2cc03f40b19eeda4382c49b5bafb43d88b931c4c74ac/google_pasta-0.2.0-py3-none-any.whl", hash = "sha256:b32482794a366b5366a32c92a9a9201b107821889935a02b3e51f6b432ea84ed", size = 57471 }, +] + [[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 }, @@ -1309,6 +1299,32 @@ wheels = [ { 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 = "grpcio" +version = "1.69.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/87/06a145284cbe86c91ca517fe6b57be5efbb733c0d6374b407f0992054d18/grpcio-1.69.0.tar.gz", hash = "sha256:936fa44241b5379c5afc344e1260d467bee495747eaf478de825bab2791da6f5", size = 12738244 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/1d/8f28f147d7f3f5d6b6082f14e1e0f40d58e50bc2bd30d2377c730c57a286/grpcio-1.69.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:fc18a4de8c33491ad6f70022af5c460b39611e39578a4d84de0fe92f12d5d47b", size = 5161414 }, + { url = "https://files.pythonhosted.org/packages/35/4b/9ab8ea65e515e1844feced1ef9e7a5d8359c48d986c93f3d2a2006fbdb63/grpcio-1.69.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:0f0270bd9ffbff6961fe1da487bdcd594407ad390cc7960e738725d4807b18c4", size = 11108909 }, + { url = "https://files.pythonhosted.org/packages/99/68/1856fde2b3c3162bdfb9845978608deef3606e6907fdc2c87443fce6ecd0/grpcio-1.69.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:dc48f99cc05e0698e689b51a05933253c69a8c8559a47f605cff83801b03af0e", size = 5658302 }, + { url = "https://files.pythonhosted.org/packages/3e/21/3fa78d38dc5080d0d677103fad3a8cd55091635cc2069a7c06c7a54e6c4d/grpcio-1.69.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e925954b18d41aeb5ae250262116d0970893b38232689c4240024e4333ac084", size = 6306201 }, + { url = "https://files.pythonhosted.org/packages/f3/cb/5c47b82fd1baf43dba973ae399095d51aaf0085ab0439838b4cbb1e87e3c/grpcio-1.69.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d222569273720366f68a99cb62e6194681eb763ee1d3b1005840678d4884f9", size = 5919649 }, + { url = "https://files.pythonhosted.org/packages/c6/67/59d1a56a0f9508a29ea03e1ce800bdfacc1f32b4f6b15274b2e057bf8758/grpcio-1.69.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b62b0f41e6e01a3e5082000b612064c87c93a49b05f7602fe1b7aa9fd5171a1d", size = 6648974 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/ca70c14d98c6400095f19a0f4df8273d09c2106189751b564b26019f1dbe/grpcio-1.69.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:db6f9fd2578dbe37db4b2994c94a1d9c93552ed77dca80e1657bb8a05b898b55", size = 6215144 }, + { url = "https://files.pythonhosted.org/packages/b3/94/b2b0a9fd487fc8262e20e6dd0ec90d9fa462c82a43b4855285620f6e9d01/grpcio-1.69.0-cp312-cp312-win32.whl", hash = "sha256:b192b81076073ed46f4b4dd612b8897d9a1e39d4eabd822e5da7b38497ed77e1", size = 3644552 }, + { url = "https://files.pythonhosted.org/packages/93/99/81aec9f85412e3255a591ae2ccb799238e074be774e5f741abae08a23418/grpcio-1.69.0-cp312-cp312-win_amd64.whl", hash = "sha256:1227ff7836f7b3a4ab04e5754f1d001fa52a730685d3dc894ed8bc262cc96c01", size = 4399532 }, + { url = "https://files.pythonhosted.org/packages/54/47/3ff4501365f56b7cc16617695dbd4fd838c5e362bc7fa9fee09d592f7d78/grpcio-1.69.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:a78a06911d4081a24a1761d16215a08e9b6d4d29cdbb7e427e6c7e17b06bcc5d", size = 5162928 }, + { url = "https://files.pythonhosted.org/packages/c0/63/437174c5fa951052c9ecc5f373f62af6f3baf25f3f5ef35cbf561806b371/grpcio-1.69.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:dc5a351927d605b2721cbb46158e431dd49ce66ffbacb03e709dc07a491dde35", size = 11103027 }, + { url = "https://files.pythonhosted.org/packages/53/df/53566a6fdc26b6d1f0585896e1cc4825961039bca5a6a314ff29d79b5d5b/grpcio-1.69.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:3629d8a8185f5139869a6a17865d03113a260e311e78fbe313f1a71603617589", size = 5659277 }, + { url = "https://files.pythonhosted.org/packages/e6/4c/b8a0c4f71498b6f9be5ca6d290d576cf2af9d95fd9827c47364f023969ad/grpcio-1.69.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9a281878feeb9ae26db0622a19add03922a028d4db684658f16d546601a4870", size = 6305255 }, + { url = "https://files.pythonhosted.org/packages/ef/55/d9aa05eb3dfcf6aa946aaf986740ec07fc5189f20e2cbeb8c5d278ffd00f/grpcio-1.69.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cc614e895177ab7e4b70f154d1a7c97e152577ea101d76026d132b7aaba003b", size = 5920240 }, + { url = "https://files.pythonhosted.org/packages/ea/eb/774b27c51e3e386dfe6c491a710f6f87ffdb20d88ec6c3581e047d9354a2/grpcio-1.69.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:1ee76cd7e2e49cf9264f6812d8c9ac1b85dda0eaea063af07292400f9191750e", size = 6652974 }, + { url = "https://files.pythonhosted.org/packages/59/98/96de14e6e7d89123813d58c246d9b0f1fbd24f9277f5295264e60861d9d6/grpcio-1.69.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:0470fa911c503af59ec8bc4c82b371ee4303ececbbdc055f55ce48e38b20fd67", size = 6215757 }, + { url = "https://files.pythonhosted.org/packages/7d/5b/ce922e0785910b10756fabc51fd294260384a44bea41651dadc4e47ddc82/grpcio-1.69.0-cp313-cp313-win32.whl", hash = "sha256:b650f34aceac8b2d08a4c8d7dc3e8a593f4d9e26d86751ebf74ebf5107d927de", size = 3642488 }, + { url = "https://files.pythonhosted.org/packages/5d/04/11329e6ca1ceeb276df2d9c316b5e170835a687a4d0f778dba8294657e36/grpcio-1.69.0-cp313-cp313-win_amd64.whl", hash = "sha256:028337786f11fecb5d7b7fa660475a06aabf7e5e52b5ac2df47414878c0ce7ea", size = 4399968 }, +] + [[package]] name = "h11" version = "0.14.0" @@ -1318,6 +1334,27 @@ 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 = "h5py" +version = "3.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/0c/5c2b0a88158682aeafb10c1c2b735df5bc31f165bfe192f2ee9f2a23b5f1/h5py-3.12.1.tar.gz", hash = "sha256:326d70b53d31baa61f00b8aa5f95c2fcb9621a3ee8365d770c551a13dbbcbfdf", size = 411457 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/e1/ea9bfe18a3075cdc873f0588ff26ce394726047653557876d7101bf0c74e/h5py-3.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06a903a4e4e9e3ebbc8b548959c3c2552ca2d70dac14fcfa650d9261c66939ed", size = 3372538 }, + { url = "https://files.pythonhosted.org/packages/0d/74/1009b663387c025e8fa5f3ee3cf3cd0d99b1ad5c72eeb70e75366b1ce878/h5py-3.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b3b8f3b48717e46c6a790e3128d39c61ab595ae0a7237f06dfad6a3b51d5351", size = 2868104 }, + { url = "https://files.pythonhosted.org/packages/af/52/c604adc06280c15a29037d4aa79a24fe54d8d0b51085e81ed24b2fa995f7/h5py-3.12.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:050a4f2c9126054515169c49cb900949814987f0c7ae74c341b0c9f9b5056834", size = 5194606 }, + { url = "https://files.pythonhosted.org/packages/fa/63/eeaacff417b393491beebabb8a3dc5342950409eb6d7b39d437289abdbae/h5py-3.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c4b41d1019322a5afc5082864dfd6359f8935ecd37c11ac0029be78c5d112c9", size = 5413256 }, + { url = "https://files.pythonhosted.org/packages/86/f7/bb465dcb92ca3521a15cbe1031f6d18234dbf1fb52a6796a00bfaa846ebf/h5py-3.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4d51919110a030913201422fb07987db4338eba5ec8c5a15d6fab8e03d443fc", size = 2993055 }, + { url = "https://files.pythonhosted.org/packages/23/1c/ecdd0efab52c24f2a9bf2324289828b860e8dd1e3c5ada3cf0889e14fdc1/h5py-3.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:513171e90ed92236fc2ca363ce7a2fc6f2827375efcbb0cc7fbdd7fe11fecafc", size = 3346239 }, + { url = "https://files.pythonhosted.org/packages/93/cd/5b6f574bf3e318bbe305bc93ba45181676550eb44ba35e006d2e98004eaa/h5py-3.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59400f88343b79655a242068a9c900001a34b63e3afb040bd7cdf717e440f653", size = 2843416 }, + { url = "https://files.pythonhosted.org/packages/8a/4f/b74332f313bfbe94ba03fff784219b9db385e6139708e55b11490149f90a/h5py-3.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3e465aee0ec353949f0f46bf6c6f9790a2006af896cee7c178a8c3e5090aa32", size = 5154390 }, + { url = "https://files.pythonhosted.org/packages/1a/57/93ea9e10a6457ea8d3b867207deb29a527e966a08a84c57ffd954e32152a/h5py-3.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba51c0c5e029bb5420a343586ff79d56e7455d496d18a30309616fdbeed1068f", size = 5378244 }, + { url = "https://files.pythonhosted.org/packages/50/51/0bbf3663062b2eeee78aa51da71e065f8a0a6e3cb950cc7020b4444999e6/h5py-3.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:52ab036c6c97055b85b2a242cb540ff9590bacfda0c03dd0cf0661b311f522f8", size = 2979760 }, +] + [[package]] name = "httpcore" version = "1.0.7" @@ -1374,62 +1411,155 @@ wheels = [ ] [[package]] -name = "iniconfig" -version = "2.0.0" +name = "imageio" +version = "2.36.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/aa/2e7a49259339e691ff2b477ae0696b1784a09313c5872700bbbdd00a3030/imageio-2.36.1.tar.gz", hash = "sha256:e4e1d231f47f9a9e16100b0f7ce1a86e8856fb4d1c0fa2c4365a316f1746be62", size = 389522 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, + { url = "https://files.pythonhosted.org/packages/5c/f9/f78e7f5ac8077c481bf6b43b8bc736605363034b3d5eb3ce8eb79f53f5f1/imageio-2.36.1-py3-none-any.whl", hash = "sha256:20abd2cae58e55ca1af8a8dcf43293336a59adf0391f1917bf8518633cfc2cdf", size = 315435 }, ] [[package]] -name = "jinja2" -version = "3.1.5" +name = "imgaug" +version = "0.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markupsafe" }, + { name = "imageio" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "opencv-python" }, + { name = "pillow" }, + { name = "scikit-image" }, + { name = "scipy" }, + { name = "shapely" }, + { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } +sdist = { url = "https://files.pythonhosted.org/packages/25/7d/820295b8fdaf06dce9688ef2fdeb5a317896d3276db7723e5a94e85e1253/imgaug-0.4.0.tar.gz", hash = "sha256:46bab63ed38f8980630ff721a09ca2281b7dbd4d8c11258818b6ebcc69ea46c7", size = 937254 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, + { url = "https://files.pythonhosted.org/packages/66/b1/af3142c4a85cba6da9f4ebb5ff4e21e2616309552caca5e8acefe9840622/imgaug-0.4.0-py2.py3-none-any.whl", hash = "sha256:ce61e65b4eb7405fc62c1b0a79d2fa92fd47f763aaecb65152d29243592111f9", size = 948018 }, ] [[package]] -name = "jiter" -version = "0.8.2" +name = "iniconfig" +version = "2.0.0" 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 } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } 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/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "ipykernel" +version = "6.29.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/67594cb0c7055dc50814b21731c22a601101ea3b1b50a9a1b090e11f5d0f/ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215", size = 163367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173 }, +] + +[[package]] +name = "ipython" +version = "8.31.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { 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" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/35/6f90fdddff7a08b7b715fccbd2427b5212c9525cd043d26fdc45bee0708d/ipython-8.31.0.tar.gz", hash = "sha256:b6a2274606bec6166405ff05e54932ed6e5cfecaca1fc05f2cacde7bb074d70b", size = 5501011 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/60/d0feb6b6d9fe4ab89fe8fe5b47cbf6cd936bfd9f1e7ffa9d0015425aeed6/ipython-8.31.0-py3-none-any.whl", hash = "sha256:46ec58f8d3d076a61d128fe517a51eb730e3aaf0c184ea8c17d16e366660c6a6", size = 821583 }, +] + +[[package]] +name = "ipywidgets" +version = "8.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "comm" }, + { name = "ipython" }, + { name = "jupyterlab-widgets" }, + { name = "traitlets" }, + { name = "widgetsnbextension" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/4c/dab2a281b07596a5fc220d49827fe6c794c66f1493d7a74f1df0640f2cc5/ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17", size = 116723 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/2d/9c0b76f2f9cc0ebede1b9371b6f317243028ed60b90705863d493bae622e/ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245", size = 139767 }, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321 }, +] + +[[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 = "jinja2" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, +] + +[[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/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 }, @@ -1462,42 +1592,319 @@ 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 = "json5" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/3d/bbe62f3d0c05a689c711cff57b2e3ac3d3e526380adb7c781989f075115c/json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559", size = 48202 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/42/797895b952b682c3dafe23b1834507ee7f02f4d6299b65aaa61425763278/json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa", size = 34049 }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, +] + +[package.optional-dependencies] +format-nongpl = [ + { name = "fqdn" }, + { name = "idna" }, + { name = "isoduration" }, + { name = "jsonpointer" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "uri-template" }, + { name = "webcolors" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, +] + +[[package]] +name = "jupyter" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "ipywidgets" }, + { name = "jupyter-console" }, + { name = "jupyterlab" }, + { name = "nbconvert" }, + { name = "notebook" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/f3/af28ea964ab8bc1e472dba2e82627d36d470c51f5cd38c37502eeffaa25e/jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a", size = 5714959 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83", size = 2657 }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105 }, +] + +[[package]] +name = "jupyter-console" +version = "6.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "pyzmq" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/2d/e2fd31e2fc41c14e2bcb6c976ab732597e907523f6b2420305f9fc7fdbdb/jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539", size = 34363 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/77/71d78d58f15c22db16328a476426f7ac4a60d3a5a7ba3b9627ee2f7903d4/jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485", size = 24510 }, +] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/11/b56381fa6c3f4cc5d2cf54a7dbf98ad9aa0b339ef7a601d6053538b079a7/jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9", size = 87629 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/fb/108ecd1fe961941959ad0ee4e12ee7b8b1477247f30b1fdfd83ceaf017f0/jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", size = 28965 }, +] + +[[package]] +name = "jupyter-events" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema", extra = ["format-nongpl"] }, + { name = "python-json-logger" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/65/5791c8a979b5646ca29ea50e42b6708908b789f7ff389d1a03c1b93a1c54/jupyter_events-0.11.0.tar.gz", hash = "sha256:c0bc56a37aac29c1fbc3bcfbddb8c8c49533f9cf11f1c4e6adadba936574ab90", size = 62039 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/8c/9b65cb2cd4ea32d885993d5542244641590530836802a2e8c7449a4c61c9/jupyter_events-0.11.0-py3-none-any.whl", hash = "sha256:36399b41ce1ca45fe8b8271067d6a140ffa54cec4028e95491c93b78a855cacf", size = 19423 }, +] + +[[package]] +name = "jupyter-lsp" +version = "2.2.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/b4/3200b0b09c12bc3b72d943d923323c398eff382d1dcc7c0dbc8b74630e40/jupyter-lsp-2.2.5.tar.gz", hash = "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001", size = 48741 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/e0/7bd7cff65594fd9936e2f9385701e44574fc7d721331ff676ce440b14100/jupyter_lsp-2.2.5-py3-none-any.whl", hash = "sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da", size = 69146 }, +] + +[[package]] +name = "jupyter-server" +version = "2.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "argon2-cffi" }, + { name = "jinja2" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "jupyter-events" }, + { name = "jupyter-server-terminals" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "overrides" }, + { name = "packaging" }, + { name = "prometheus-client" }, + { name = "pywinpty", marker = "os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux'" }, + { name = "pyzmq" }, + { name = "send2trash" }, + { name = "terminado" }, + { name = "tornado" }, + { name = "traitlets" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/8c/df09d4ab646141f130f9977b32b206ba8615d1969b2eba6a2e84b7f89137/jupyter_server-2.15.0.tar.gz", hash = "sha256:9d446b8697b4f7337a1b7cdcac40778babdd93ba614b6d68ab1c0c918f1c4084", size = 725227 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/a2/89eeaf0bb954a123a909859fa507fa86f96eb61b62dc30667b60dbd5fdaf/jupyter_server-2.15.0-py3-none-any.whl", hash = "sha256:872d989becf83517012ee669f09604aa4a28097c0bd90b2f424310156c2cdae3", size = 385826 }, +] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywinpty", marker = "os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux'" }, + { name = "terminado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656 }, +] + +[[package]] +name = "jupyterlab" +version = "4.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-lru" }, + { name = "httpx" }, + { name = "ipykernel" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyter-lsp" }, + { name = "jupyter-server" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "packaging" }, + { name = "setuptools" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/45/1052f842e066902b1d78126df7e2269b1b9408991e1344e167b2e429f9e1/jupyterlab-4.3.4.tar.gz", hash = "sha256:f0bb9b09a04766e3423cccc2fc23169aa2ffedcdf8713e9e0fb33cac0b6859d0", size = 21797583 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/48/af57263e53cfc220e522de047aa0993f53bab734fe812af1e03e33ac6d7c/jupyterlab-4.3.4-py3-none-any.whl", hash = "sha256:b754c2601c5be6adf87cb5a1d8495d653ffb945f021939f77776acaa94dae952", size = 11665373 }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884 }, +] + +[[package]] +name = "jupyterlab-server" +version = "2.27.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "jinja2" }, + { name = "json5" }, + { name = "jsonschema" }, + { name = "jupyter-server" }, + { name = "packaging" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/c9/a883ce65eb27905ce77ace410d83587c82ea64dc85a48d1f7ed52bcfa68d/jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4", size = 76173 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4", size = 59700 }, +] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/73/fa26bbb747a9ea4fca6b01453aa22990d52ab62dd61384f1ac0dc9d4e7ba/jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed", size = 203556 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/93/858e87edc634d628e5d752ba944c2833133a28fa87bb093e6832ced36a3e/jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54", size = 214392 }, +] + +[[package]] +name = "keras" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "h5py" }, + { name = "ml-dtypes" }, + { name = "namex" }, + { name = "numpy" }, + { name = "optree" }, + { name = "packaging" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/97/8b0b420e14008100a330d30e78df9bce04fd1845edc5d29b0a6f4d8ad061/keras-3.8.0.tar.gz", hash = "sha256:6289006e6f6cb2b68a563b58cf8ae5a45569449c5a791df6b2f54c1877f3f344", size = 975959 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/cf/aea9087c4d7fafe956a0cc0ff6c3327d10fb8442cda50f992a2186921fa0/keras-3.8.0-py3-none-any.whl", hash = "sha256:b65d125976b0f8bf8ad1e93311a98e7dfb334ff6023627a59a52b35499165ec3", size = 1301880 }, +] + +[[package]] +name = "keras-applications" +version = "1.0.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h5py" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/56/4bcec5a8d9503a87e58e814c4e32ac2b32c37c685672c30bc8c54c6e478a/Keras_Applications-1.0.8.tar.gz", hash = "sha256:5579f9a12bcde9748f4a12233925a59b93b73ae6947409ff34aa2ba258189fe5", size = 289095 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/e3/19762fdfc62877ae9102edf6342d71b28fbfd9dea3d2f96a882ce099b03f/Keras_Applications-1.0.8-py3-none-any.whl", hash = "sha256:df4323692b8c1174af821bf906f1e442e63fa7589bf0f1230a0b6bdc5a810c95", size = 50704 }, +] + +[[package]] +name = "keras-ocr" +version = "0.9.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "editdistance" }, + { name = "efficientnet" }, + { name = "essential-generators" }, + { name = "fonttools" }, + { name = "imgaug" }, + { name = "pyclipper" }, + { name = "shapely" }, + { name = "tqdm" }, + { name = "validators" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/f3/7ad1edb975c6c485d73146438dfff188f1c22b798d3a076e4f644e7bdce1/keras_ocr-0.9.3-py3-none-any.whl", hash = "sha256:8fdbb9044a814910e86d3853f5e7f13085c98ac210f27204fe75f4f8c4ac6262", size = 42313 }, +] + [[package]] name = "kiwisolver" version = "1.4.8" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538 } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/5f/4d8e9e852d98ecd26cdf8eaf7ed8bc33174033bba5e07001b289f07308fd/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db", size = 124623 }, - { url = "https://files.pythonhosted.org/packages/1d/70/7f5af2a18a76fe92ea14675f8bd88ce53ee79e37900fa5f1a1d8e0b42998/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b", size = 66720 }, - { url = "https://files.pythonhosted.org/packages/c6/13/e15f804a142353aefd089fadc8f1d985561a15358c97aca27b0979cb0785/kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d", size = 65413 }, - { url = "https://files.pythonhosted.org/packages/ce/6d/67d36c4d2054e83fb875c6b59d0809d5c530de8148846b1370475eeeece9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d", size = 1650826 }, - { url = "https://files.pythonhosted.org/packages/de/c6/7b9bb8044e150d4d1558423a1568e4f227193662a02231064e3824f37e0a/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c", size = 1628231 }, - { url = "https://files.pythonhosted.org/packages/b6/38/ad10d437563063eaaedbe2c3540a71101fc7fb07a7e71f855e93ea4de605/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3", size = 1408938 }, - { url = "https://files.pythonhosted.org/packages/52/ce/c0106b3bd7f9e665c5f5bc1e07cc95b5dabd4e08e3dad42dbe2faad467e7/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed", size = 1422799 }, - { url = "https://files.pythonhosted.org/packages/d0/87/efb704b1d75dc9758087ba374c0f23d3254505edaedd09cf9d247f7878b9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f", size = 1354362 }, - { url = "https://files.pythonhosted.org/packages/eb/b3/fd760dc214ec9a8f208b99e42e8f0130ff4b384eca8b29dd0efc62052176/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff", size = 2222695 }, - { url = "https://files.pythonhosted.org/packages/a2/09/a27fb36cca3fc01700687cc45dae7a6a5f8eeb5f657b9f710f788748e10d/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d", size = 2370802 }, - { url = "https://files.pythonhosted.org/packages/3d/c3/ba0a0346db35fe4dc1f2f2cf8b99362fbb922d7562e5f911f7ce7a7b60fa/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c", size = 2334646 }, - { url = "https://files.pythonhosted.org/packages/41/52/942cf69e562f5ed253ac67d5c92a693745f0bed3c81f49fc0cbebe4d6b00/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605", size = 2467260 }, - { url = "https://files.pythonhosted.org/packages/32/26/2d9668f30d8a494b0411d4d7d4ea1345ba12deb6a75274d58dd6ea01e951/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e", size = 2288633 }, - { url = "https://files.pythonhosted.org/packages/98/99/0dd05071654aa44fe5d5e350729961e7bb535372935a45ac89a8924316e6/kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751", size = 71885 }, - { url = "https://files.pythonhosted.org/packages/6c/fc/822e532262a97442989335394d441cd1d0448c2e46d26d3e04efca84df22/kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271", size = 65175 }, - { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635 }, - { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717 }, - { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413 }, - { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994 }, - { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804 }, - { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690 }, - { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839 }, - { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109 }, - { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269 }, - { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468 }, - { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394 }, - { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901 }, - { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306 }, - { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966 }, - { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311 }, { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152 }, { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555 }, { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067 }, @@ -1541,12 +1948,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661 }, { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710 }, { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213 }, - { url = "https://files.pythonhosted.org/packages/1f/f9/ae81c47a43e33b93b0a9819cac6723257f5da2a5a60daf46aa5c7226ea85/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a", size = 60403 }, - { url = "https://files.pythonhosted.org/packages/58/ca/f92b5cb6f4ce0c1ebfcfe3e2e42b96917e16f7090e45b21102941924f18f/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8", size = 58657 }, - { url = "https://files.pythonhosted.org/packages/80/28/ae0240f732f0484d3a4dc885d055653c47144bdf59b670aae0ec3c65a7c8/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0", size = 84948 }, - { url = "https://files.pythonhosted.org/packages/5d/eb/78d50346c51db22c7203c1611f9b513075f35c4e0e4877c5dde378d66043/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c", size = 81186 }, - { url = "https://files.pythonhosted.org/packages/43/f8/7259f18c77adca88d5f64f9a522792e178b2691f3748817a8750c2d216ef/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b", size = 80279 }, - { url = "https://files.pythonhosted.org/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762 }, +] + +[[package]] +name = "lazy-loader" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097 }, ] [[package]] @@ -1558,36 +1971,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/97/e6/79807d3b59a67dd78bb77072ca6a28d8db0935161fecf935e6c38c5f6825/levenshtein-0.26.1.tar.gz", hash = "sha256:0d19ba22330d50609b2349021ec3cf7d905c6fe21195a2d0d876a146e7ed2575", size = 374307 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/ae/af5f9e9f06052719df6af46d7a7fee3675fd2dea0e2845cc0f4968cf853f/levenshtein-0.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8dc4a4aecad538d944a1264c12769c99e3c0bf8e741fc5e454cc954913befb2e", size = 177032 }, - { url = "https://files.pythonhosted.org/packages/bb/a6/be36c1d43cccd032b359ba2fa66dd299bac0cd226f263672332738535553/levenshtein-0.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec108f368c12b25787c8b1a4537a1452bc53861c3ee4abc810cc74098278edcd", size = 157539 }, - { url = "https://files.pythonhosted.org/packages/d1/76/13df26b47c53db1cf01c40bae1483b13919d6eab12cede3b93b018927229/levenshtein-0.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69229d651c97ed5b55b7ce92481ed00635cdbb80fbfb282a22636e6945dc52d5", size = 153298 }, - { url = "https://files.pythonhosted.org/packages/f2/d9/c02fd7ec98d55df51c643d0475b859fab19a974eb44e5ca72f642dbfeffd/levenshtein-0.26.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79dcd157046d62482a7719b08ba9e3ce9ed3fc5b015af8ea989c734c702aedd4", size = 186766 }, - { url = "https://files.pythonhosted.org/packages/7a/71/44adaafadc5c93845048b88426ab5e2a8414efce7026478cad115fd08f92/levenshtein-0.26.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f53f9173ae21b650b4ed8aef1d0ad0c37821f367c221a982f4d2922b3044e0d", size = 187546 }, - { url = "https://files.pythonhosted.org/packages/2d/7e/24593d50e9e0911c96631a123760b96d1dabbcf1fc55a300648d4f0240dd/levenshtein-0.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3956f3c5c229257dbeabe0b6aacd2c083ebcc1e335842a6ff2217fe6cc03b6b", size = 162601 }, - { url = "https://files.pythonhosted.org/packages/54/98/2285860f07c519af3bb1af29cc4a51c3fd8c028836887615c776f6bb28d4/levenshtein-0.26.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1e83af732726987d2c4cd736f415dae8b966ba17b7a2239c8b7ffe70bfb5543", size = 249164 }, - { url = "https://files.pythonhosted.org/packages/28/f7/87008ca57377f2f296a3b9b87b46fa80a4a471c1d3de3ea4ff37acc65b5a/levenshtein-0.26.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4f052c55046c2a9c9b5f742f39e02fa6e8db8039048b8c1c9e9fdd27c8a240a1", size = 1077613 }, - { url = "https://files.pythonhosted.org/packages/7d/ca/5f2b3c4b181f4e97805ee839c47cb99c8048bf7934358af8c3d6a07fb6c2/levenshtein-0.26.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9895b3a98f6709e293615fde0dcd1bb0982364278fa2072361a1a31b3e388b7a", size = 1331030 }, - { url = "https://files.pythonhosted.org/packages/b3/f4/de5a779d178e489906fd39d7b2bdb782f80a98affc57e9d40a723b9ee89c/levenshtein-0.26.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a3777de1d8bfca054465229beed23994f926311ce666f5a392c8859bb2722f16", size = 1207001 }, - { url = "https://files.pythonhosted.org/packages/f8/61/78b25ef514a23735ae0baf230af668f16d6f5e1466c4db72a4de0e233768/levenshtein-0.26.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:81c57e1135c38c5e6e3675b5e2077d8a8d3be32bf0a46c57276c092b1dffc697", size = 1355999 }, - { url = "https://files.pythonhosted.org/packages/b9/e8/a488dbb99726e08ac05ad3359e7db79e35c2c4e4bafbaaf081ae140c7de3/levenshtein-0.26.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:91d5e7d984891df3eff7ea9fec8cf06fdfacc03cd074fd1a410435706f73b079", size = 1135174 }, - { url = "https://files.pythonhosted.org/packages/52/c1/79693b33ab4c5ba04df8b4d116c2ae4cfaa71e08b2cf2b8cd93d5fa37b07/levenshtein-0.26.1-cp310-cp310-win32.whl", hash = "sha256:f48abff54054b4142ad03b323e80aa89b1d15cabc48ff49eb7a6ff7621829a56", size = 87111 }, - { url = "https://files.pythonhosted.org/packages/e6/ed/5250c0891f6a99e41e715ce379b77863d66356eae7519e3626514f2729b6/levenshtein-0.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:79dd6ad799784ea7b23edd56e3bf94b3ca866c4c6dee845658ee75bb4aefdabf", size = 98062 }, - { url = "https://files.pythonhosted.org/packages/4f/b3/58f69cbd9f21fe7ec54a71059b3e8fdb37c43781b31a36f49c973bd387c5/levenshtein-0.26.1-cp310-cp310-win_arm64.whl", hash = "sha256:3351ddb105ef010cc2ce474894c5d213c83dddb7abb96400beaa4926b0b745bd", size = 87976 }, - { url = "https://files.pythonhosted.org/packages/af/b4/86e447173ca8d936b7ef270d21952a0053e799040e73b843a4a5ac9a15a1/levenshtein-0.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:44c51f5d33b3cfb9db518b36f1288437a509edd82da94c4400f6a681758e0cb6", size = 177037 }, - { url = "https://files.pythonhosted.org/packages/27/b3/e15e14e5836dfc23ed014c21b307cbf77b3c6fd75e11d0675ce9a0d43b31/levenshtein-0.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56b93203e725f9df660e2afe3d26ba07d71871b6d6e05b8b767e688e23dfb076", size = 157478 }, - { url = "https://files.pythonhosted.org/packages/32/f1/f4d0904c5074e4e9d33dcaf304144e02eae9eec9d61b63bf17b1108ce228/levenshtein-0.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:270d36c5da04a0d89990660aea8542227cbd8f5bc34e9fdfadd34916ff904520", size = 153873 }, - { url = "https://files.pythonhosted.org/packages/f9/0d/cd5abe809421ce0d4a2cae60fd2fdf62cb43890068515a8a0069e2b17894/levenshtein-0.26.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:480674c05077eeb0b0f748546d4fcbb386d7c737f9fff0010400da3e8b552942", size = 186850 }, - { url = "https://files.pythonhosted.org/packages/a8/69/03f4266ad83781f2602b1976a2e5a98785c148f9bfc77c343e5aa1840f64/levenshtein-0.26.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13946e37323728695ba7a22f3345c2e907d23f4600bc700bf9b4352fb0c72a48", size = 187527 }, - { url = "https://files.pythonhosted.org/packages/36/fa/ec3be1162b1a757f80e713220470fe5b4db22e23f886f50ac59a48f0a84d/levenshtein-0.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceb673f572d1d0dc9b1cd75792bb8bad2ae8eb78a7c6721e23a3867d318cb6f2", size = 162673 }, - { url = "https://files.pythonhosted.org/packages/9e/d6/dc8358b6a4174f413532aa27463dc4d167ac25742826f58916bb6e6417b1/levenshtein-0.26.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42d6fa242e3b310ce6bfd5af0c83e65ef10b608b885b3bb69863c01fb2fcff98", size = 250413 }, - { url = "https://files.pythonhosted.org/packages/57/5e/a87bf39686482a1df000fdc265fdd812f0cd316d5fb0a25f52654504a82b/levenshtein-0.26.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b8b68295808893a81e0a1dbc2274c30dd90880f14d23078e8eb4325ee615fc68", size = 1078713 }, - { url = "https://files.pythonhosted.org/packages/c5/04/30ab2f27c4ff7d6d98b3bb6bf8541521535ad2d05e50ac8fd00ab701c080/levenshtein-0.26.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b01061d377d1944eb67bc40bef5d4d2f762c6ab01598efd9297ce5d0047eb1b5", size = 1331174 }, - { url = "https://files.pythonhosted.org/packages/e4/68/9c7f60ccb097a86420d058dcc3f575e6b3d663b3a5cde3651443f7087e14/levenshtein-0.26.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9d12c8390f156745e533d01b30773b9753e41d8bbf8bf9dac4b97628cdf16314", size = 1207733 }, - { url = "https://files.pythonhosted.org/packages/64/21/222f54a1a654eca1c1cd015d32d972d70529eb218d469d516f13eac2149d/levenshtein-0.26.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:48825c9f967f922061329d1481b70e9fee937fc68322d6979bc623f69f75bc91", size = 1356116 }, - { url = "https://files.pythonhosted.org/packages/6f/65/681dced2fa798ea7882bff5682ab566689a4920006ed9aca4fd8d1edb2d2/levenshtein-0.26.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d8ec137170b95736842f99c0e7a9fd8f5641d0c1b63b08ce027198545d983e2b", size = 1135459 }, - { url = "https://files.pythonhosted.org/packages/a1/e8/1ff8a634c428ed908d20482f77491cca08fa16c96738ad82d9219da138a1/levenshtein-0.26.1-cp311-cp311-win32.whl", hash = "sha256:798f2b525a2e90562f1ba9da21010dde0d73730e277acaa5c52d2a6364fd3e2a", size = 87265 }, - { url = "https://files.pythonhosted.org/packages/8f/fb/44e9747558a7381ea6736e10ac2f871414007915afb94efac423e68cf441/levenshtein-0.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:55b1024516c59df55f1cf1a8651659a568f2c5929d863d3da1ce8893753153bd", size = 98518 }, - { url = "https://files.pythonhosted.org/packages/04/90/c476a74d8ec25d680b9cbf51966d638623a82a2fd4e99b988a383f22a681/levenshtein-0.26.1-cp311-cp311-win_arm64.whl", hash = "sha256:e52575cbc6b9764ea138a6f82d73d3b1bc685fe62e207ff46a963d4c773799f6", size = 88086 }, { url = "https://files.pythonhosted.org/packages/4c/53/3685ee7fbe9b8eb4b82d8045255e59dd6943f94e8091697ef3808e7ecf63/levenshtein-0.26.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cc741ca406d3704dc331a69c04b061fc952509a069b79cab8287413f434684bd", size = 176447 }, { url = "https://files.pythonhosted.org/packages/82/7f/7d6fe9b76bd030200f8f9b162f3de862d597804d292af292ec3ce9ae8bee/levenshtein-0.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:821ace3b4e1c2e02b43cf5dc61aac2ea43bdb39837ac890919c225a2c3f2fea4", size = 157589 }, { url = "https://files.pythonhosted.org/packages/bc/d3/44539e952df93c5d88a95a0edff34af38e4f87330a76e8335bfe2c0f31bf/levenshtein-0.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92694c9396f55d4c91087efacf81297bef152893806fc54c289fc0254b45384", size = 153306 }, @@ -1618,12 +2001,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/90/cbcfa3dd86023e82036662a19fec2fcb48782d3f9fa322d44dc898d95a5d/levenshtein-0.26.1-cp313-cp313-win32.whl", hash = "sha256:9fb859da90262eb474c190b3ca1e61dee83add022c676520f5c05fdd60df902a", size = 87318 }, { url = "https://files.pythonhosted.org/packages/83/73/372edebc79fd09a8b2382cf1244d279ada5b795124f1e1c4fc73d9fbb00f/levenshtein-0.26.1-cp313-cp313-win_amd64.whl", hash = "sha256:8adcc90e3a5bfb0a463581d85e599d950fe3c2938ac6247b29388b64997f6e2d", size = 98418 }, { url = "https://files.pythonhosted.org/packages/b2/6d/f0160ea5a7bb7a62b3b3d56e9fc5024b440cb59555a90be2347abf2e7888/levenshtein-0.26.1-cp313-cp313-win_arm64.whl", hash = "sha256:c2599407e029865dc66d210b8804c7768cbdbf60f061d993bb488d5242b0b73e", size = 87792 }, - { url = "https://files.pythonhosted.org/packages/c9/40/11a601baf1731d6b6927890bb7107f6cf77357dec8a22f269cd8f4ab8631/levenshtein-0.26.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6cf8f1efaf90ca585640c5d418c30b7d66d9ac215cee114593957161f63acde0", size = 172550 }, - { url = "https://files.pythonhosted.org/packages/74/1c/070757904b9fb4dfddaf9f43da8e8d9fb6feabd660631cc9e4cb49364d2b/levenshtein-0.26.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d5b2953978b8c158dd5cd93af8216a5cfddbf9de66cf5481c2955f44bb20767a", size = 154546 }, - { url = "https://files.pythonhosted.org/packages/31/7e/ef5538895aa96d6f59b5a6ed3c40c3db3b1b0df45807bd23eae250f380b8/levenshtein-0.26.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b952b3732c4631c49917d4b15d78cb4a2aa006c1d5c12e2a23ba8e18a307a055", size = 152897 }, - { url = "https://files.pythonhosted.org/packages/94/65/28fb5c59871a673f93e72c00c33c43bcc27eff6f9be5e515252e6da28a7f/levenshtein-0.26.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07227281e12071168e6ae59238918a56d2a0682e529f747b5431664f302c0b42", size = 160411 }, - { url = "https://files.pythonhosted.org/packages/4c/c7/b8fe968f92ed672cd346d38f4077586eb7ff63bade2e8d7c93a9259573c4/levenshtein-0.26.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8191241cd8934feaf4d05d0cc0e5e72877cbb17c53bbf8c92af9f1aedaa247e9", size = 247483 }, - { url = "https://files.pythonhosted.org/packages/f3/98/c119974fdce4808afdf3622230759c871bc4c73287cf34b338db2be936b8/levenshtein-0.26.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9e70d7ee157a9b698c73014f6e2b160830e7d2d64d2e342fefc3079af3c356fc", size = 95854 }, +] + +[[package]] +name = "libclang" +version = "18.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/5c/ca35e19a4f142adffa27e3d652196b7362fa612243e2b916845d801454fc/libclang-18.1.1.tar.gz", hash = "sha256:a1214966d08d73d971287fc3ead8dfaf82eb07fb197680d8b3859dbbbbf78250", size = 39612 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/49/f5e3e7e1419872b69f6f5e82ba56e33955a74bd537d8a1f5f1eff2f3668a/libclang-18.1.1-1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:0b2e143f0fac830156feb56f9231ff8338c20aecfe72b4ffe96f19e5a1dbb69a", size = 25836045 }, + { url = "https://files.pythonhosted.org/packages/e2/e5/fc61bbded91a8830ccce94c5294ecd6e88e496cc85f6704bf350c0634b70/libclang-18.1.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:6f14c3f194704e5d09769108f03185fce7acaf1d1ae4bbb2f30a72c2400cb7c5", size = 26502641 }, + { url = "https://files.pythonhosted.org/packages/db/ed/1df62b44db2583375f6a8a5e2ca5432bbdc3edb477942b9b7c848c720055/libclang-18.1.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:83ce5045d101b669ac38e6da8e58765f12da2d3aafb3b9b98d88b286a60964d8", size = 26420207 }, + { url = "https://files.pythonhosted.org/packages/1d/fc/716c1e62e512ef1c160e7984a73a5fc7df45166f2ff3f254e71c58076f7c/libclang-18.1.1-py2.py3-none-manylinux2010_x86_64.whl", hash = "sha256:c533091d8a3bbf7460a00cb6c1a71da93bffe148f172c7d03b1c31fbf8aa2a0b", size = 24515943 }, + { url = "https://files.pythonhosted.org/packages/3c/3d/f0ac1150280d8d20d059608cf2d5ff61b7c3b7f7bcf9c0f425ab92df769a/libclang-18.1.1-py2.py3-none-manylinux2014_aarch64.whl", hash = "sha256:54dda940a4a0491a9d1532bf071ea3ef26e6dbaf03b5000ed94dd7174e8f9592", size = 23784972 }, + { url = "https://files.pythonhosted.org/packages/fe/2f/d920822c2b1ce9326a4c78c0c2b4aa3fde610c7ee9f631b600acb5376c26/libclang-18.1.1-py2.py3-none-manylinux2014_armv7l.whl", hash = "sha256:cf4a99b05376513717ab5d82a0db832c56ccea4fd61a69dbb7bccf2dfb207dbe", size = 20259606 }, + { url = "https://files.pythonhosted.org/packages/2d/c2/de1db8c6d413597076a4259cea409b83459b2db997c003578affdd32bf66/libclang-18.1.1-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:69f8eb8f65c279e765ffd28aaa7e9e364c776c17618af8bff22a8df58677ff4f", size = 24921494 }, + { url = "https://files.pythonhosted.org/packages/0b/2d/3f480b1e1d31eb3d6de5e3ef641954e5c67430d5ac93b7fa7e07589576c7/libclang-18.1.1-py2.py3-none-win_amd64.whl", hash = "sha256:4dd2d3b82fab35e2bf9ca717d7b63ac990a3519c7e312f19fa8e86dcc712f7fb", size = 26415083 }, + { url = "https://files.pythonhosted.org/packages/71/cf/e01dc4cc79779cd82d77888a88ae2fa424d93b445ad4f6c02bfc18335b70/libclang-18.1.1-py2.py3-none-win_arm64.whl", hash = "sha256:3f0e1f49f04d3cd198985fea0511576b0aee16f9ff0e0f0cad7f9c57ec3c20e8", size = 22361112 }, ] [[package]] @@ -1632,40 +2026,6 @@ version = "5.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e7/6b/20c3a4b24751377aaa6307eb230b66701024012c29dd374999cc92983269/lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f", size = 3679318 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ce/2789e39eddf2b13fac29878bfa465f0910eb6b0096e29090e5176bc8cf43/lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656", size = 8124570 }, - { url = "https://files.pythonhosted.org/packages/24/a8/f4010166a25d41715527129af2675981a50d3bbf7df09c5d9ab8ca24fbf9/lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d", size = 4413042 }, - { url = "https://files.pythonhosted.org/packages/41/a4/7e45756cecdd7577ddf67a68b69c1db0f5ddbf0c9f65021ee769165ffc5a/lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a", size = 5139213 }, - { url = "https://files.pythonhosted.org/packages/02/e2/ecf845b12323c92748077e1818b64e8b4dba509a4cb12920b3762ebe7552/lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8", size = 4838814 }, - { url = "https://files.pythonhosted.org/packages/12/91/619f9fb72cf75e9ceb8700706f7276f23995f6ad757e6d400fbe35ca4990/lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330", size = 5425084 }, - { url = "https://files.pythonhosted.org/packages/25/3b/162a85a8f0fd2a3032ec3f936636911c6e9523a8e263fffcfd581ce98b54/lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965", size = 4875993 }, - { url = "https://files.pythonhosted.org/packages/43/af/dd3f58cc7d946da6ae42909629a2b1d5dd2d1b583334d4af9396697d6863/lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22", size = 5012462 }, - { url = "https://files.pythonhosted.org/packages/69/c1/5ea46b2d4c98f5bf5c83fffab8a0ad293c9bc74df9ecfbafef10f77f7201/lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b", size = 4815288 }, - { url = "https://files.pythonhosted.org/packages/1d/51/a0acca077ad35da458f4d3f729ef98effd2b90f003440d35fc36323f8ae6/lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7", size = 5472435 }, - { url = "https://files.pythonhosted.org/packages/4d/6b/0989c9368986961a6b0f55b46c80404c4b758417acdb6d87bfc3bd5f4967/lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8", size = 4976354 }, - { url = "https://files.pythonhosted.org/packages/05/9e/87492d03ff604fbf656ed2bf3e2e8d28f5d58ea1f00ff27ac27b06509079/lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32", size = 5029973 }, - { url = "https://files.pythonhosted.org/packages/f9/cc/9ae1baf5472af88e19e2c454b3710c1be9ecafb20eb474eeabcd88a055d2/lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86", size = 4888837 }, - { url = "https://files.pythonhosted.org/packages/d2/10/5594ffaec8c120d75b17e3ad23439b740a51549a9b5fd7484b2179adfe8f/lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5", size = 5530555 }, - { url = "https://files.pythonhosted.org/packages/ea/9b/de17f05377c8833343b629905571fb06cff2028f15a6f58ae2267662e341/lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03", size = 5405314 }, - { url = "https://files.pythonhosted.org/packages/8a/b4/227be0f1f3cca8255925985164c3838b8b36e441ff0cc10c1d3c6bdba031/lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7", size = 5079303 }, - { url = "https://files.pythonhosted.org/packages/5c/ee/19abcebb7fc40319bb71cd6adefa1ad94d09b5660228715854d6cc420713/lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80", size = 3475126 }, - { url = "https://files.pythonhosted.org/packages/a1/35/183d32551447e280032b2331738cd850da435a42f850b71ebeaab42c1313/lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3", size = 3805065 }, - { url = "https://files.pythonhosted.org/packages/5c/a8/449faa2a3cbe6a99f8d38dcd51a3ee8844c17862841a6f769ea7c2a9cd0f/lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b", size = 8141056 }, - { url = "https://files.pythonhosted.org/packages/ac/8a/ae6325e994e2052de92f894363b038351c50ee38749d30cc6b6d96aaf90f/lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18", size = 4425238 }, - { url = "https://files.pythonhosted.org/packages/f8/fb/128dddb7f9086236bce0eeae2bfb316d138b49b159f50bc681d56c1bdd19/lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442", size = 5095197 }, - { url = "https://files.pythonhosted.org/packages/b4/f9/a181a8ef106e41e3086629c8bdb2d21a942f14c84a0e77452c22d6b22091/lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4", size = 4809809 }, - { url = "https://files.pythonhosted.org/packages/25/2f/b20565e808f7f6868aacea48ddcdd7e9e9fb4c799287f21f1a6c7c2e8b71/lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f", size = 5407593 }, - { url = "https://files.pythonhosted.org/packages/23/0e/caac672ec246d3189a16c4d364ed4f7d6bf856c080215382c06764058c08/lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e", size = 4866657 }, - { url = "https://files.pythonhosted.org/packages/67/a4/1f5fbd3f58d4069000522196b0b776a014f3feec1796da03e495cf23532d/lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c", size = 4967017 }, - { url = "https://files.pythonhosted.org/packages/ee/73/623ecea6ca3c530dd0a4ed0d00d9702e0e85cd5624e2d5b93b005fe00abd/lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16", size = 4810730 }, - { url = "https://files.pythonhosted.org/packages/1d/ce/fb84fb8e3c298f3a245ae3ea6221c2426f1bbaa82d10a88787412a498145/lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79", size = 5455154 }, - { url = "https://files.pythonhosted.org/packages/b1/72/4d1ad363748a72c7c0411c28be2b0dc7150d91e823eadad3b91a4514cbea/lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080", size = 4969416 }, - { url = "https://files.pythonhosted.org/packages/42/07/b29571a58a3a80681722ea8ed0ba569211d9bb8531ad49b5cacf6d409185/lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654", size = 5013672 }, - { url = "https://files.pythonhosted.org/packages/b9/93/bde740d5a58cf04cbd38e3dd93ad1e36c2f95553bbf7d57807bc6815d926/lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d", size = 4878644 }, - { url = "https://files.pythonhosted.org/packages/56/b5/645c8c02721d49927c93181de4017164ec0e141413577687c3df8ff0800f/lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763", size = 5511531 }, - { url = "https://files.pythonhosted.org/packages/85/3f/6a99a12d9438316f4fc86ef88c5d4c8fb674247b17f3173ecadd8346b671/lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec", size = 5402065 }, - { url = "https://files.pythonhosted.org/packages/80/8a/df47bff6ad5ac57335bf552babfb2408f9eb680c074ec1ba412a1a6af2c5/lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be", size = 5069775 }, - { url = "https://files.pythonhosted.org/packages/08/ae/e7ad0f0fbe4b6368c5ee1e3ef0c3365098d806d42379c46c1ba2802a52f7/lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9", size = 3474226 }, - { url = "https://files.pythonhosted.org/packages/c3/b5/91c2249bfac02ee514ab135e9304b89d55967be7e53e94a879b74eec7a5c/lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1", size = 3814971 }, { url = "https://files.pythonhosted.org/packages/eb/6d/d1f1c5e40c64bf62afd7a3f9b34ce18a586a1cccbf71e783cd0a6d8e8971/lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859", size = 8171753 }, { url = "https://files.pythonhosted.org/packages/bd/83/26b1864921869784355459f374896dcf8b44d4af3b15d7697e9156cb2de9/lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e", size = 4441955 }, { url = "https://files.pythonhosted.org/packages/e0/d2/e9bff9fb359226c25cda3538f664f54f2804f4b37b0d7c944639e1a51f69/lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f", size = 5050778 }, @@ -1700,12 +2060,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b6/17/71e9984cf0570cd202ac0a1c9ed5c1b8889b0fc8dc736f5ef0ffb181c284/lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b", size = 5011053 }, { url = "https://files.pythonhosted.org/packages/69/68/9f7e6d3312a91e30829368c2b3217e750adef12a6f8eb10498249f4e8d72/lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957", size = 3485634 }, { url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 }, - { url = "https://files.pythonhosted.org/packages/99/f7/b73a431c8500565aa500e99e60b448d305eaf7c0b4c893c7c5a8a69cc595/lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c", size = 3925431 }, - { url = "https://files.pythonhosted.org/packages/db/48/4a206623c0d093d0e3b15f415ffb4345b0bdf661a3d0b15a112948c033c7/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a", size = 4216683 }, - { url = "https://files.pythonhosted.org/packages/54/47/577820c45dd954523ae8453b632d91e76da94ca6d9ee40d8c98dd86f916b/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005", size = 4326732 }, - { url = "https://files.pythonhosted.org/packages/68/de/96cb6d3269bc994b4f5ede8ca7bf0840f5de0a278bc6e50cb317ff71cafa/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce", size = 4218377 }, - { url = "https://files.pythonhosted.org/packages/a5/43/19b1ef6cbffa4244a217f95cc5f41a6cb4720fed33510a49670b03c5f1a0/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83", size = 4351237 }, - { url = "https://files.pythonhosted.org/packages/ba/b2/6a22fb5c0885da3b00e116aee81f0b829ec9ac8f736cd414b4a09413fc7d/lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba", size = 3487557 }, +] + +[[package]] +name = "markdown" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, ] [[package]] @@ -1726,26 +2089,6 @@ version = "3.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, - { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, - { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, - { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, - { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, - { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, - { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, - { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, - { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, - { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, @@ -1795,18 +2138,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/68/dd/fa2e1a45fce2d09f4aea3cee169760e672c8262325aa5796c49d543dc7e6/matplotlib-3.10.0.tar.gz", hash = "sha256:b886d02a581b96704c9d1ffe55709e49b4d2d52709ccebc4be42db856e511278", size = 36686418 } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/ec/3cdff7b5239adaaacefcc4f77c316dfbbdf853c4ed2beec467e0fec31b9f/matplotlib-3.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2c5829a5a1dd5a71f0e31e6e8bb449bc0ee9dbfb05ad28fc0c6b55101b3a4be6", size = 8160551 }, - { url = "https://files.pythonhosted.org/packages/41/f2/b518f2c7f29895c9b167bf79f8529c63383ae94eaf49a247a4528e9a148d/matplotlib-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2a43cbefe22d653ab34bb55d42384ed30f611bcbdea1f8d7f431011a2e1c62e", size = 8034853 }, - { url = "https://files.pythonhosted.org/packages/ed/8d/45754b4affdb8f0d1a44e4e2bcd932cdf35b256b60d5eda9f455bb293ed0/matplotlib-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:607b16c8a73943df110f99ee2e940b8a1cbf9714b65307c040d422558397dac5", size = 8446724 }, - { url = "https://files.pythonhosted.org/packages/09/5a/a113495110ae3e3395c72d82d7bc4802902e46dc797f6b041e572f195c56/matplotlib-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01d2b19f13aeec2e759414d3bfe19ddfb16b13a1250add08d46d5ff6f9be83c6", size = 8583905 }, - { url = "https://files.pythonhosted.org/packages/12/b1/8b1655b4c9ed4600c817c419f7eaaf70082630efd7556a5b2e77a8a3cdaf/matplotlib-3.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e6c6461e1fc63df30bf6f80f0b93f5b6784299f721bc28530477acd51bfc3d1", size = 9395223 }, - { url = "https://files.pythonhosted.org/packages/5a/85/b9a54d64585a6b8737a78a61897450403c30f39e0bd3214270bb0b96f002/matplotlib-3.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:994c07b9d9fe8d25951e3202a68c17900679274dadfc1248738dcfa1bd40d7f3", size = 8025355 }, - { url = "https://files.pythonhosted.org/packages/0c/f1/e37f6c84d252867d7ddc418fff70fc661cfd363179263b08e52e8b748e30/matplotlib-3.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:fd44fc75522f58612ec4a33958a7e5552562b7705b42ef1b4f8c0818e304a363", size = 8171677 }, - { url = "https://files.pythonhosted.org/packages/c7/8b/92e9da1f28310a1f6572b5c55097b0c0ceb5e27486d85fb73b54f5a9b939/matplotlib-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c58a9622d5dbeb668f407f35f4e6bfac34bb9ecdcc81680c04d0258169747997", size = 8044945 }, - { url = "https://files.pythonhosted.org/packages/c5/cb/49e83f0fd066937a5bd3bc5c5d63093703f3637b2824df8d856e0558beef/matplotlib-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:845d96568ec873be63f25fa80e9e7fae4be854a66a7e2f0c8ccc99e94a8bd4ef", size = 8458269 }, - { url = "https://files.pythonhosted.org/packages/b2/7d/2d873209536b9ee17340754118a2a17988bc18981b5b56e6715ee07373ac/matplotlib-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5439f4c5a3e2e8eab18e2f8c3ef929772fd5641876db71f08127eed95ab64683", size = 8599369 }, - { url = "https://files.pythonhosted.org/packages/b8/03/57d6cbbe85c61fe4cbb7c94b54dce443d68c21961830833a1f34d056e5ea/matplotlib-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4673ff67a36152c48ddeaf1135e74ce0d4bce1bbf836ae40ed39c29edf7e2765", size = 9405992 }, - { url = "https://files.pythonhosted.org/packages/14/cf/e382598f98be11bf51dd0bc60eca44a517f6793e3dc8b9d53634a144620c/matplotlib-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e8632baebb058555ac0cde75db885c61f1212e47723d63921879806b40bec6a", size = 8034580 }, { url = "https://files.pythonhosted.org/packages/44/c7/6b2d8cb7cc251d53c976799cacd3200add56351c175ba89ab9cbd7c1e68a/matplotlib-3.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4659665bc7c9b58f8c00317c3c2a299f7f258eeae5a5d56b4c64226fca2f7c59", size = 8172465 }, { url = "https://files.pythonhosted.org/packages/42/2a/6d66d0fba41e13e9ca6512a0a51170f43e7e7ed3a8dfa036324100775612/matplotlib-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d44cb942af1693cced2604c33a9abcef6205601c445f6d0dc531d813af8a2f5a", size = 8043300 }, { url = "https://files.pythonhosted.org/packages/90/60/2a60342b27b90a16bada939a85e29589902b41073f59668b904b15ea666c/matplotlib-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a994f29e968ca002b50982b27168addfd65f0105610b6be7fa515ca4b5307c95", size = 8448936 }, @@ -1825,9 +2156,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/2d/b5949fb2b76e9b47ab05e25a5f5f887c70de20d8b0cbc704a4e2ee71c786/matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d907fddb39f923d011875452ff1eca29a9e7f21722b873e90db32e5d8ddff12e", size = 8610334 }, { url = "https://files.pythonhosted.org/packages/d6/9a/6e3c799d5134d9af44b01c787e1360bee38cf51850506ea2e743a787700b/matplotlib-3.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3b427392354d10975c1d0f4ee18aa5844640b512d5311ef32efd4dd7db106ede", size = 9406777 }, { url = "https://files.pythonhosted.org/packages/0e/dd/e6ae97151e5ed648ab2ea48885bc33d39202b640eec7a2910e2c843f7ac0/matplotlib-3.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5fd41b0ec7ee45cd960a8e71aea7c946a28a0b8a4dcee47d2856b2af051f334c", size = 8109742 }, - { url = "https://files.pythonhosted.org/packages/32/5f/29def7ce4e815ab939b56280976ee35afffb3bbdb43f332caee74cb8c951/matplotlib-3.10.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81713dd0d103b379de4516b861d964b1d789a144103277769238c732229d7f03", size = 8155500 }, - { url = "https://files.pythonhosted.org/packages/de/6d/d570383c9f7ca799d0a54161446f9ce7b17d6c50f2994b653514bcaa108f/matplotlib-3.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:359f87baedb1f836ce307f0e850d12bb5f1936f70d035561f90d41d305fdacea", size = 8032398 }, - { url = "https://files.pythonhosted.org/packages/c9/b4/680aa700d99b48e8c4393fa08e9ab8c49c0555ee6f4c9c0a5e8ea8dfde5d/matplotlib-3.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae80dc3a4add4665cf2faa90138384a7ffe2a4e37c58d83e115b54287c4f06ef", size = 8587361 }, ] [[package]] @@ -1851,6 +2179,30 @@ 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 = "mistune" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/6e/96fc7cb3288666c5de2c396eb0e338dc95f7a8e4920e43e38783a22d0084/mistune-3.1.0.tar.gz", hash = "sha256:dbcac2f78292b9dc066cd03b7a3a26b62d85f8159f2ea5fd28e55df79908d667", size = 94401 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/b3/743ffc3f59da380da504d84ccd1faf9a857a1445991ff19bf2ec754163c2/mistune-3.1.0-py3-none-any.whl", hash = "sha256:b05198cf6d671b3deba6c87ec6cf0d4eb7b72c524636eddb6dbf13823b52cee1", size = 53694 }, +] + +[[package]] +name = "ml-dtypes" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/15/76f86faa0902836cc133939732f7611ace68cf54148487a99c539c272dc8/ml_dtypes-0.4.1.tar.gz", hash = "sha256:fad5f2de464fd09127e49b7fd1252b9006fb43d2edc1ff112d390c324af5ca7a", size = 692594 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/1a/99e924f12e4b62139fbac87419698c65f956d58de0dbfa7c028fa5b096aa/ml_dtypes-0.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:827d3ca2097085cf0355f8fdf092b888890bb1b1455f52801a2d7756f056f54b", size = 405077 }, + { url = "https://files.pythonhosted.org/packages/8f/8c/7b610bd500617854c8cc6ed7c8cfb9d48d6a5c21a1437a36a4b9bc8a3598/ml_dtypes-0.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:772426b08a6172a891274d581ce58ea2789cc8abc1c002a27223f314aaf894e7", size = 2181554 }, + { url = "https://files.pythonhosted.org/packages/c7/c6/f89620cecc0581dc1839e218c4315171312e46c62a62da6ace204bda91c0/ml_dtypes-0.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:126e7d679b8676d1a958f2651949fbfa182832c3cd08020d8facd94e4114f3e9", size = 2160488 }, + { url = "https://files.pythonhosted.org/packages/ae/11/a742d3c31b2cc8557a48efdde53427fd5f9caa2fa3c9c27d826e78a66f51/ml_dtypes-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:df0fb650d5c582a9e72bb5bd96cfebb2cdb889d89daff621c8fbc60295eba66c", size = 127462 }, +] + [[package]] name = "more-itertools" version = "10.5.0" @@ -1875,28 +2227,6 @@ version = "1.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/cb/d0/7555686ae7ff5731205df1012ede15dd9d927f6227ea151e901c7406af4f/msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e", size = 167260 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/f9/a892a6038c861fa849b11a2bb0502c07bc698ab6ea53359e5771397d883b/msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd", size = 150428 }, - { url = "https://files.pythonhosted.org/packages/df/7a/d174cc6a3b6bb85556e6a046d3193294a92f9a8e583cdbd46dc8a1d7e7f4/msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d", size = 84131 }, - { url = "https://files.pythonhosted.org/packages/08/52/bf4fbf72f897a23a56b822997a72c16de07d8d56d7bf273242f884055682/msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5", size = 81215 }, - { url = "https://files.pythonhosted.org/packages/02/95/dc0044b439b518236aaf012da4677c1b8183ce388411ad1b1e63c32d8979/msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5", size = 371229 }, - { url = "https://files.pythonhosted.org/packages/ff/75/09081792db60470bef19d9c2be89f024d366b1e1973c197bb59e6aabc647/msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e", size = 378034 }, - { url = "https://files.pythonhosted.org/packages/32/d3/c152e0c55fead87dd948d4b29879b0f14feeeec92ef1fd2ec21b107c3f49/msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b", size = 363070 }, - { url = "https://files.pythonhosted.org/packages/d9/2c/82e73506dd55f9e43ac8aa007c9dd088c6f0de2aa19e8f7330e6a65879fc/msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f", size = 359863 }, - { url = "https://files.pythonhosted.org/packages/cb/a0/3d093b248837094220e1edc9ec4337de3443b1cfeeb6e0896af8ccc4cc7a/msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68", size = 368166 }, - { url = "https://files.pythonhosted.org/packages/e4/13/7646f14f06838b406cf5a6ddbb7e8dc78b4996d891ab3b93c33d1ccc8678/msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b", size = 370105 }, - { url = "https://files.pythonhosted.org/packages/67/fa/dbbd2443e4578e165192dabbc6a22c0812cda2649261b1264ff515f19f15/msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044", size = 68513 }, - { url = "https://files.pythonhosted.org/packages/24/ce/c2c8fbf0ded750cb63cbcbb61bc1f2dfd69e16dca30a8af8ba80ec182dcd/msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f", size = 74687 }, - { url = "https://files.pythonhosted.org/packages/b7/5e/a4c7154ba65d93be91f2f1e55f90e76c5f91ccadc7efc4341e6f04c8647f/msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7", size = 150803 }, - { url = "https://files.pythonhosted.org/packages/60/c2/687684164698f1d51c41778c838d854965dd284a4b9d3a44beba9265c931/msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa", size = 84343 }, - { url = "https://files.pythonhosted.org/packages/42/ae/d3adea9bb4a1342763556078b5765e666f8fdf242e00f3f6657380920972/msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701", size = 81408 }, - { url = "https://files.pythonhosted.org/packages/dc/17/6313325a6ff40ce9c3207293aee3ba50104aed6c2c1559d20d09e5c1ff54/msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6", size = 396096 }, - { url = "https://files.pythonhosted.org/packages/a8/a1/ad7b84b91ab5a324e707f4c9761633e357820b011a01e34ce658c1dda7cc/msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59", size = 403671 }, - { url = "https://files.pythonhosted.org/packages/bb/0b/fd5b7c0b308bbf1831df0ca04ec76fe2f5bf6319833646b0a4bd5e9dc76d/msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0", size = 387414 }, - { url = "https://files.pythonhosted.org/packages/f0/03/ff8233b7c6e9929a1f5da3c7860eccd847e2523ca2de0d8ef4878d354cfa/msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e", size = 383759 }, - { url = "https://files.pythonhosted.org/packages/1f/1b/eb82e1fed5a16dddd9bc75f0854b6e2fe86c0259c4353666d7fab37d39f4/msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6", size = 394405 }, - { url = "https://files.pythonhosted.org/packages/90/2e/962c6004e373d54ecf33d695fb1402f99b51832631e37c49273cc564ffc5/msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5", size = 396041 }, - { url = "https://files.pythonhosted.org/packages/f8/20/6e03342f629474414860c48aeffcc2f7f50ddaf351d95f20c3f1c67399a8/msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88", size = 68538 }, - { url = "https://files.pythonhosted.org/packages/aa/c4/5a582fc9a87991a3e6f6800e9bb2f3c82972912235eb9539954f3e9997c7/msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788", size = 74871 }, { url = "https://files.pythonhosted.org/packages/e1/d6/716b7ca1dbde63290d2973d22bbef1b5032ca634c3ff4384a958ec3f093a/msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d", size = 152421 }, { url = "https://files.pythonhosted.org/packages/70/da/5312b067f6773429cec2f8f08b021c06af416bba340c912c2ec778539ed6/msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2", size = 85277 }, { url = "https://files.pythonhosted.org/packages/28/51/da7f3ae4462e8bb98af0d5bdf2707f1b8c65a0d4f496e46b6afb06cbc286/msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420", size = 82222 }, @@ -1934,45 +2264,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cd/22/590508afb85d5c27ebcb2837410413f4613eebdda6e4e02997fe08ba78e4/msgpack_numpy_opentensor-0.5.0-py2.py3-none-any.whl", hash = "sha256:8a61c597a976425a87094d8e89846aa9528eb1f037e97ff1428fe3cd61a238e7", size = 7209 }, ] +[[package]] +name = "mss" +version = "10.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/2c/6a50c69793918502b4391729bd5741964c9f8ccc98c8482d4a680384923b/mss-10.0.0.tar.gz", hash = "sha256:d903e0d51262bf0f8782841cf16eaa6d7e3e1f12eae35ab41c2e318837c6637f", size = 83127 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/aa/b897ae9e1c1616e4df9fb319637ef6a9d07cca6d46de6b59c80209f006a4/mss-10.0.0-py3-none-any.whl", hash = "sha256:82cf6460a53d09e79b7b6d871163c982e6c7e9649c426e7b7591b74956d5cb64", size = 24050 }, +] + [[package]] name = "multidict" version = "6.1.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/d6/be/504b89a5e9ca731cd47487e91c469064f8ae5af93b7259758dcfc2b9c848/multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", size = 64002 } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/68/259dee7fd14cf56a17c554125e534f6274c2860159692a414d0b402b9a6d/multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60", size = 48628 }, - { url = "https://files.pythonhosted.org/packages/50/79/53ba256069fe5386a4a9e80d4e12857ced9de295baf3e20c68cdda746e04/multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1", size = 29327 }, - { url = "https://files.pythonhosted.org/packages/ff/10/71f1379b05b196dae749b5ac062e87273e3f11634f447ebac12a571d90ae/multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53", size = 29689 }, - { url = "https://files.pythonhosted.org/packages/71/45/70bac4f87438ded36ad4793793c0095de6572d433d98575a5752629ef549/multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5", size = 126639 }, - { url = "https://files.pythonhosted.org/packages/80/cf/17f35b3b9509b4959303c05379c4bfb0d7dd05c3306039fc79cf035bbac0/multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581", size = 134315 }, - { url = "https://files.pythonhosted.org/packages/ef/1f/652d70ab5effb33c031510a3503d4d6efc5ec93153562f1ee0acdc895a57/multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56", size = 129471 }, - { url = "https://files.pythonhosted.org/packages/a6/64/2dd6c4c681688c0165dea3975a6a4eab4944ea30f35000f8b8af1df3148c/multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429", size = 124585 }, - { url = "https://files.pythonhosted.org/packages/87/56/e6ee5459894c7e554b57ba88f7257dc3c3d2d379cb15baaa1e265b8c6165/multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748", size = 116957 }, - { url = "https://files.pythonhosted.org/packages/36/9e/616ce5e8d375c24b84f14fc263c7ef1d8d5e8ef529dbc0f1df8ce71bb5b8/multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db", size = 128609 }, - { url = "https://files.pythonhosted.org/packages/8c/4f/4783e48a38495d000f2124020dc96bacc806a4340345211b1ab6175a6cb4/multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056", size = 123016 }, - { url = "https://files.pythonhosted.org/packages/3e/b3/4950551ab8fc39862ba5e9907dc821f896aa829b4524b4deefd3e12945ab/multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76", size = 133542 }, - { url = "https://files.pythonhosted.org/packages/96/4d/f0ce6ac9914168a2a71df117935bb1f1781916acdecbb43285e225b484b8/multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160", size = 130163 }, - { url = "https://files.pythonhosted.org/packages/be/72/17c9f67e7542a49dd252c5ae50248607dfb780bcc03035907dafefb067e3/multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7", size = 126832 }, - { url = "https://files.pythonhosted.org/packages/71/9f/72d719e248cbd755c8736c6d14780533a1606ffb3fbb0fbd77da9f0372da/multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0", size = 26402 }, - { url = "https://files.pythonhosted.org/packages/04/5a/d88cd5d00a184e1ddffc82aa2e6e915164a6d2641ed3606e766b5d2f275a/multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d", size = 28800 }, - { url = "https://files.pythonhosted.org/packages/93/13/df3505a46d0cd08428e4c8169a196131d1b0c4b515c3649829258843dde6/multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6", size = 48570 }, - { url = "https://files.pythonhosted.org/packages/f0/e1/a215908bfae1343cdb72f805366592bdd60487b4232d039c437fe8f5013d/multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156", size = 29316 }, - { url = "https://files.pythonhosted.org/packages/70/0f/6dc70ddf5d442702ed74f298d69977f904960b82368532c88e854b79f72b/multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb", size = 29640 }, - { url = "https://files.pythonhosted.org/packages/d8/6d/9c87b73a13d1cdea30b321ef4b3824449866bd7f7127eceed066ccb9b9ff/multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b", size = 131067 }, - { url = "https://files.pythonhosted.org/packages/cc/1e/1b34154fef373371fd6c65125b3d42ff5f56c7ccc6bfff91b9b3c60ae9e0/multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72", size = 138507 }, - { url = "https://files.pythonhosted.org/packages/fb/e0/0bc6b2bac6e461822b5f575eae85da6aae76d0e2a79b6665d6206b8e2e48/multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304", size = 133905 }, - { url = "https://files.pythonhosted.org/packages/ba/af/73d13b918071ff9b2205fcf773d316e0f8fefb4ec65354bbcf0b10908cc6/multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351", size = 129004 }, - { url = "https://files.pythonhosted.org/packages/74/21/23960627b00ed39643302d81bcda44c9444ebcdc04ee5bedd0757513f259/multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb", size = 121308 }, - { url = "https://files.pythonhosted.org/packages/8b/5c/cf282263ffce4a596ed0bb2aa1a1dddfe1996d6a62d08842a8d4b33dca13/multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3", size = 132608 }, - { url = "https://files.pythonhosted.org/packages/d7/3e/97e778c041c72063f42b290888daff008d3ab1427f5b09b714f5a8eff294/multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399", size = 127029 }, - { url = "https://files.pythonhosted.org/packages/47/ac/3efb7bfe2f3aefcf8d103e9a7162572f01936155ab2f7ebcc7c255a23212/multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423", size = 137594 }, - { url = "https://files.pythonhosted.org/packages/42/9b/6c6e9e8dc4f915fc90a9b7798c44a30773dea2995fdcb619870e705afe2b/multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3", size = 134556 }, - { url = "https://files.pythonhosted.org/packages/1d/10/8e881743b26aaf718379a14ac58572a240e8293a1c9d68e1418fb11c0f90/multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753", size = 130993 }, - { url = "https://files.pythonhosted.org/packages/45/84/3eb91b4b557442802d058a7579e864b329968c8d0ea57d907e7023c677f2/multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80", size = 26405 }, - { url = "https://files.pythonhosted.org/packages/9f/0b/ad879847ecbf6d27e90a6eabb7eff6b62c129eefe617ea45eae7c1f0aead/multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926", size = 28795 }, { url = "https://files.pythonhosted.org/packages/fd/16/92057c74ba3b96d5e211b553895cd6dc7cc4d1e43d9ab8fafc727681ef71/multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", size = 48713 }, { url = "https://files.pythonhosted.org/packages/94/3d/37d1b8893ae79716179540b89fc6a0ee56b4a65fcc0d63535c6f5d96f217/multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", size = 29516 }, { url = "https://files.pythonhosted.org/packages/a2/12/adb6b3200c363062f805275b4c1e656be2b3681aada66c80129932ff0bae/multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", size = 29557 }, @@ -2015,8 +2321,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/76/6e712a2623d146d314f17598df5de7224c85c0060ef63fd95cc15a25b3fa/multiprocess-0.70.16-pp310-pypy310_pp73-macosx_10_13_x86_64.whl", hash = "sha256:476887be10e2f59ff183c006af746cb6f1fd0eadcfd4ef49e605cbe2659920ee", size = 134980 }, - { url = "https://files.pythonhosted.org/packages/0f/ab/1e6e8009e380e22254ff539ebe117861e5bdb3bff1fc977920972237c6c7/multiprocess-0.70.16-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d951bed82c8f73929ac82c61f01a7b5ce8f3e5ef40f5b52553b4f547ce2b08ec", size = 134982 }, { url = "https://files.pythonhosted.org/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02", size = 134824 }, { url = "https://files.pythonhosted.org/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a", size = 143519 }, { url = "https://files.pythonhosted.org/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e", size = 146741 }, @@ -2036,6 +2340,79 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/ab/85d8da5c9a45e072301beb37ad7f833cd344e04c817d97e0cc75681d248f/munch-2.5.0-py2.py3-none-any.whl", hash = "sha256:6f44af89a2ce4ed04ff8de41f70b226b984db10a91dcc7b9ac2efc1c77022fdd", size = 10347 }, ] +[[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 = "namex" +version = "0.0.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/48/d275cdb6216c6bb4f9351675795a0b48974e138f16b1ffe0252c1f8faa28/namex-0.0.8.tar.gz", hash = "sha256:32a50f6c565c0bb10aa76298c959507abdc0e850efe085dc38f3440fcb3aa90b", size = 6623 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/59/7854fbfb59f8ae35483ce93493708be5942ebb6328cd85b3a609df629736/namex-0.0.8-py3-none-any.whl", hash = "sha256:7ddb6c2bb0e753a311b7590f84f6da659dd0c05e65cb89d519d54c0a250c0487", size = 5806 }, +] + +[[package]] +name = "nbclient" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434 }, +] + +[[package]] +name = "nbconvert" +version = "7.16.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach", extra = ["css"] }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/2c/d026c0367f2be2463d4c2f5b538e28add2bc67bc13730abb7f364ae4eb8b/nbconvert-7.16.5.tar.gz", hash = "sha256:c83467bb5777fdfaac5ebbb8e864f300b277f68692ecc04d6dab72f2d8442344", size = 856367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/9e/2dcc9fe00cf55d95a8deae69384e9cea61816126e345754f6c75494d32ec/nbconvert-7.16.5-py3-none-any.whl", hash = "sha256:e12eac052d6fd03040af4166c563d76e7aeead2e9aadf5356db552a1784bd547", size = 258061 }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, +] + [[package]] name = "nest-asyncio" version = "1.6.0" @@ -2063,6 +2440,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, ] +[[package]] +name = "ninja" +version = "1.11.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/8f/21a2701f95b7d0d5137736561b3427ece0c4a1e085d4a223b92d16ab7d8b/ninja-1.11.1.3.tar.gz", hash = "sha256:edfa0d2e9d7ead1635b03e40a32ad56cc8f56798b6e2e9848d8300b174897076", size = 129532 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/ba/0069cd4a83d68f7b0308be70e219b15d675e50c8ea28763a3f0373c45bfc/ninja-1.11.1.3-py3-none-macosx_10_9_universal2.whl", hash = "sha256:2b4879ea3f1169f3d855182c57dcc84d1b5048628c8b7be0d702b81882a37237", size = 279132 }, + { url = "https://files.pythonhosted.org/packages/72/6b/3805be87df8417a0c7b21078c8045f2a1e59b34f371bfe4cb4fb0d6df7f2/ninja-1.11.1.3-py3-none-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bc3ebc8b2e47716149f3541742b5cd8e0b08f51013b825c05baca3e34854370d", size = 472101 }, + { url = "https://files.pythonhosted.org/packages/6b/35/a8e38d54768e67324e365e2a41162be298f51ec93e6bd4b18d237d7250d8/ninja-1.11.1.3-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a27e78ca71316c8654965ee94b286a98c83877bfebe2607db96897bbfe458af0", size = 422884 }, + { url = "https://files.pythonhosted.org/packages/2f/99/7996457319e139c02697fb2aa28e42fe32bb0752cef492edc69d56a3552e/ninja-1.11.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2883ea46b3c5079074f56820f9989c6261fcc6fd873d914ee49010ecf283c3b2", size = 157046 }, + { url = "https://files.pythonhosted.org/packages/6d/8b/93f38e5cddf76ccfdab70946515b554f25d2b4c95ef9b2f9cfbc43fa7cc1/ninja-1.11.1.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c4bdb9fd2d0c06501ae15abfd23407660e95659e384acd36e013b6dd7d8a8e4", size = 180014 }, + { url = "https://files.pythonhosted.org/packages/7d/1d/713884d0fa3c972164f69d552e0701d30e2bf25eba9ef160bfb3dc69926a/ninja-1.11.1.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:114ed5c61c8474df6a69ab89097a20749b769e2c219a452cb2fadc49b0d581b0", size = 157098 }, + { url = "https://files.pythonhosted.org/packages/c7/22/ecb0f70e77c9e22ee250aa717a608a142756833a34d43943d7d658ee0e56/ninja-1.11.1.3-py3-none-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fa2247fce98f683bc712562d82b22b8a0a5c000738a13147ca2d1b68c122298", size = 130089 }, + { url = "https://files.pythonhosted.org/packages/ec/a6/3ee846c20ab6ad95b90c5c8703c76cb1f39cc8ce2d1ae468956e3b1b2581/ninja-1.11.1.3-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:a38c6c6c8032bed68b70c3b065d944c35e9f903342875d3a3218c1607987077c", size = 372508 }, + { url = "https://files.pythonhosted.org/packages/95/0d/aa44abe4141f29148ce671ac8c92045878906b18691c6f87a29711c2ff1c/ninja-1.11.1.3-py3-none-musllinux_1_1_i686.whl", hash = "sha256:56ada5d33b8741d298836644042faddebc83ee669782d661e21563034beb5aba", size = 419369 }, + { url = "https://files.pythonhosted.org/packages/f7/ec/48bf5105568ac9bd2016b701777bdd5000cc09a14ac837fef9f15e8d634e/ninja-1.11.1.3-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:53409151da081f3c198bb0bfc220a7f4e821e022c5b7d29719adda892ddb31bb", size = 420304 }, + { url = "https://files.pythonhosted.org/packages/18/e5/69df63976cf971a03379899f8520a036c9dbab26330b37197512aed5b3df/ninja-1.11.1.3-py3-none-musllinux_1_1_s390x.whl", hash = "sha256:1ad2112c2b0159ed7c4ae3731595191b1546ba62316fc40808edecd0306fefa3", size = 416056 }, + { url = "https://files.pythonhosted.org/packages/6f/4f/bdb401af7ed0e24a3fef058e13a149f2de1ce4b176699076993615d55610/ninja-1.11.1.3-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:28aea3c1c280cba95b8608d50797169f3a34280e3e9a6379b6e340f0c9eaeeb0", size = 379725 }, + { url = "https://files.pythonhosted.org/packages/bd/68/05e7863bf13128c61652eeb3ec7096c3d3a602f32f31752dbfb034e3fa07/ninja-1.11.1.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b6966f83064a88a51693073eea3decd47e08c3965241e09578ef7aa3a7738329", size = 434881 }, + { url = "https://files.pythonhosted.org/packages/bd/ad/edc0d1efe77f29f45bbca2e1dab07ef597f61a88de6e4bccffc0aec2256c/ninja-1.11.1.3-py3-none-win32.whl", hash = "sha256:a4a3b71490557e18c010cbb26bd1ea9a0c32ee67e8f105e9731515b6e0af792e", size = 255988 }, + { url = "https://files.pythonhosted.org/packages/03/93/09a9f7672b4f97438aca6217ac54212a63273f1cd3b46b731d0bb22c53e7/ninja-1.11.1.3-py3-none-win_amd64.whl", hash = "sha256:04d48d14ea7ba11951c156599ab526bdda575450797ff57c6fdf99b2554d09c7", size = 296502 }, + { url = "https://files.pythonhosted.org/packages/d9/9d/0cc1e82849070ff3cbee69f326cb48a839407bcd15d8844443c30a5e7509/ninja-1.11.1.3-py3-none-win_arm64.whl", hash = "sha256:17978ad611d8ead578d83637f5ae80c2261b033db0b493a7ce94f88623f29e1b", size = 270571 }, +] + [[package]] name = "nltk" version = "3.9.1" @@ -2078,32 +2479,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/66/7d9e26593edda06e8cb531874633f7c2372279c3b0f46235539fe546df8b/nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1", size = 1505442 }, ] +[[package]] +name = "notebook" +version = "7.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, + { name = "jupyterlab" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/04/ac488379d5afef43402b3fb4be2857db1a09804fecf98b9b714c741b225b/notebook-7.3.2.tar.gz", hash = "sha256:705e83a1785f45b383bf3ee13cb76680b92d24f56fb0c7d2136fe1d850cd3ca8", size = 12781804 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/9b/76e50ee18f183ea5fe1784a9eeaa50f2c71802e4740d6e959592b0993298/notebook-7.3.2-py3-none-any.whl", hash = "sha256:e5f85fc59b69d3618d73cf27544418193ff8e8058d5bf61d315ce4f473556288", size = 13163630 }, +] + +[[package]] +name = "notebook-shim" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307 }, +] + [[package]] name = "numpy" version = "2.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015 } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245 }, - { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540 }, - { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623 }, - { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774 }, - { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081 }, - { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451 }, - { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572 }, - { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722 }, - { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170 }, - { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558 }, - { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137 }, - { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552 }, - { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957 }, - { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573 }, - { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330 }, - { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895 }, - { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253 }, - { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074 }, - { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640 }, - { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230 }, { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803 }, { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835 }, { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499 }, @@ -2236,6 +2645,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 }, ] +[[package]] +name = "object-detection" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contextlib2" }, + { name = "cython" }, + { name = "jupyter" }, + { name = "lxml" }, + { name = "matplotlib" }, + { name = "pillow" }, + { name = "tensorflow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/40/00826e52abc78b1fac0c3fbabdbede418de13cd177fb1abdc5e9f2f6ff99/object_detection-0.0.3.tar.gz", hash = "sha256:8dae59bb3c7b4a6d7fcf8c2862971afdd9cd966deafba14f521fb11b3df3d069", size = 504105 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/37/24d57adb29a0242aca08b99cb2ad08c2b427c05c988f169622c401a6f9ed/object_detection-0.0.3-py3-none-any.whl", hash = "sha256:a487aa0eada8105a9bb026b0993f251f6e0b8b0ae8fb69c2de3381394aa49b9c", size = 1488520 }, +] + [[package]] name = "openai" version = "1.59.5" @@ -2272,6 +2699,82 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/6c/fab8113424af5049f85717e8e527ca3773299a3c6b02506e66436e19874f/opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe", size = 38842521 }, ] +[[package]] +name = "opencv-python-headless" +version = "4.10.0.84" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/7e/d20f68a5f1487adf19d74378d349932a386b1ece3be9be9915e5986db468/opencv-python-headless-4.10.0.84.tar.gz", hash = "sha256:f2017c6101d7c2ef8d7bc3b414c37ff7f54d64413a1847d89970b6b7069b4e1a", size = 95117755 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/9b/583c8d9259f6fc19413f83fd18dd8e6cbc8eefb0b4dc6da52dd151fe3272/opencv_python_headless-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a4f4bcb07d8f8a7704d9c8564c224c8b064c63f430e95b61ac0bffaa374d330e", size = 54835657 }, + { url = "https://files.pythonhosted.org/packages/c0/7b/b4c67f5dad7a9a61c47f7a39e4050e8a4628bd64b3c3daaeb755d759f928/opencv_python_headless-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:5ae454ebac0eb0a0b932e3406370aaf4212e6a3fdb5038cc86c7aea15a6851da", size = 56475470 }, + { url = "https://files.pythonhosted.org/packages/91/61/f838ce2046f3ec3591ea59ea3549085e399525d3b4558c4ed60b55ed88c0/opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46071015ff9ab40fccd8a163da0ee14ce9846349f06c6c8c0f2870856ffa45db", size = 29329705 }, + { url = "https://files.pythonhosted.org/packages/d1/09/248f86a404567303cdf120e4a301f389b68e3b18e5c0cc428de327da609c/opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:377d08a7e48a1405b5e84afcbe4798464ce7ee17081c1c23619c8b398ff18295", size = 49858781 }, + { url = "https://files.pythonhosted.org/packages/30/c0/66f88d58500e990a9a0a5c06f98862edf1d0a3a430781218a8c193948438/opencv_python_headless-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:9092404b65458ed87ce932f613ffbb1106ed2c843577501e5768912360fc50ec", size = 28675298 }, + { url = "https://files.pythonhosted.org/packages/26/d0/22f68eb23eea053a31655960f133c0be9726c6a881547e6e9e7e2a946c4f/opencv_python_headless-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:afcf28bd1209dd58810d33defb622b325d3cbe49dcd7a43a902982c33e5fad05", size = 38754031 }, +] + +[[package]] +name = "opt-einsum" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/b9/2ac072041e899a52f20cf9510850ff58295003aa75525e58343591b0cbfb/opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac", size = 63004 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd", size = 71932 }, +] + +[[package]] +name = "optree" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/f2/56afdaeaae36b076659be7db8e72be0924dd64ebd1c131675c77f7e704a6/optree-0.13.1.tar.gz", hash = "sha256:af67856aa8073d237fe67313d84f8aeafac32c1cef7239c628a2768d02679c43", size = 155738 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/e7/f605320e064ba54078f2966a9034fa2b3fc47db1e728e07a2a38b2e9075f/optree-0.13.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0914ba436d6c0781dc9b04e3b95e06fe5c4fc6a87e94893da971805a3790efe8", size = 600953 }, + { url = "https://files.pythonhosted.org/packages/fa/7c/b7bedf44dbc54c55b8a408a4f978d9bb1ffbfb376093c33fc8576b1848dd/optree-0.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:111172446e8a4f0d3be13a853fa28cb46b5679a1c7ca15b2e6db2b43dbbf9efb", size = 322341 }, + { url = "https://files.pythonhosted.org/packages/71/05/ea228c1677a53855572a0ebb0c4e2a3e5d8e792d59e2b536ef50a9a02495/optree-0.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28f083ede9be89503357a6b9e5d304826701596abe13d33e8f6fa2cd85b407fc", size = 352675 }, + { url = "https://files.pythonhosted.org/packages/6f/22/c65ef2b6b191119a90223226b4a02100a9c9dd3a38e8410e473bd1653eff/optree-0.13.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aec6da79a6130b4c76073241c0f31c11b96a38e70c7a00f9ed918d7464394ab", size = 399295 }, + { url = "https://files.pythonhosted.org/packages/01/be/56f946d3af013561d46c95f75880302cab03f1490ef939569852af6331c0/optree-0.13.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a408a43f16840475612c7058eb80b53791bf8b8266c5b3cd07f69697958fd97d", size = 392916 }, + { url = "https://files.pythonhosted.org/packages/e3/ec/6041c3ffe04af5890af7ab2b5f0ca48253032dce32aa5cddf8188ad4cc4b/optree-0.13.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da76fc43dcc22fe58d11634a04672ca7cc270aed469ac35fd5c78b7b9bc9125", size = 365179 }, + { url = "https://files.pythonhosted.org/packages/98/10/087a684c7b5029e3be1f335d9df422b406cbfd842c77abfa7b17085adce5/optree-0.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d866f707b9f3a9f0e670a73fe8feee4993b2dbdbf9eef598e1cf2e5cb2876413", size = 385480 }, + { url = "https://files.pythonhosted.org/packages/9d/58/f7430d613197260fc38fead8bc974a0069c4513ea3c04f11a771daf8b20f/optree-0.13.1-cp312-cp312-win32.whl", hash = "sha256:bc9c396f64f9aacdf852713bd75f1b9a83f118660fd82e87c937c081b7ddccd1", size = 261578 }, + { url = "https://files.pythonhosted.org/packages/e3/de/b114d999746f9a9fb64476c8520ad499c11651912cecffe77aee1d5bec18/optree-0.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:587fb8de8e75e80fe7c7240e269630876bec3ee2038724893370976207813e4b", size = 292036 }, + { url = "https://files.pythonhosted.org/packages/9f/d7/5dec5d97c0a0c7951f0c8f5d24b4c6c8529d41ee69d0705f06bfa8b4874f/optree-0.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:5da0fd26325a07354915cc4e3a9aee797cb75dff07c60d24b3f309457069abd3", size = 292044 }, + { url = "https://files.pythonhosted.org/packages/3f/53/f3727cad24f16a06666f328f1212476988cadac9b9e7919ddfb2c22eb662/optree-0.13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f788b2ad120deb73b4908a74473cd6de79cfb9f33bbe9dcb59cea2e2477d4e28", size = 608270 }, + { url = "https://files.pythonhosted.org/packages/64/f2/68beb9da2dd52baa50e7a589ed2bd8434fdd70cdba06754aa5910263da06/optree-0.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2909cb42add6bb1a5a2b0243bdd8c4b861bf072f3741e26239481907ac8ad4e6", size = 325703 }, + { url = "https://files.pythonhosted.org/packages/45/db/08921e56f3425bf649eb593eb28775263c935d029985d35572dc5690cc1a/optree-0.13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc5fa2ff5090389f3a906567446f01d692bd6fe5cfcc5ae2d5861f24e8e0e4d", size = 355813 }, + { url = "https://files.pythonhosted.org/packages/e5/e3/587e0d28dc2cee064902adfebca97db124e12b275dbe9c2b05a70a22345f/optree-0.13.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4711f5cac5a2a49c3d6c9f0eca7b77c22b452170bb33ea01c3214ebb17931db9", size = 402566 }, + { url = "https://files.pythonhosted.org/packages/8a/1d/0d5bbab8c99580b732b89ef2c5fcdd6ef410478295949fdf2984fa1bfc28/optree-0.13.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c4ab1d391b89cb88eb3c63383d5eb0930bc21141de9d5acd277feed9e38eb65", size = 397005 }, + { url = "https://files.pythonhosted.org/packages/16/fa/fc2a8183e14f0d195d25824bf65095ff32b34bd469614a6c30d0a596a30f/optree-0.13.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5e5f09c85ae558a6bdaea57e63168082e728e777391393e9e2792f0d15b7b59", size = 369400 }, + { url = "https://files.pythonhosted.org/packages/9f/42/8c08ce4ebb3d9a6e4415f1a97830c84879e2d1a43710a7c8a18b2c3e169d/optree-0.13.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c8ee1e988c634a451146b87d9ebdbf650a75dc1f52a9cffcd89fabb7289321c", size = 390179 }, + { url = "https://files.pythonhosted.org/packages/06/02/3a701d6307fdfefe4fcecbac644803e2a4314ab2406ff465e03129cc85f6/optree-0.13.1-cp313-cp313-win32.whl", hash = "sha256:5b6531cd4eb23fadbbf77faf834e1119da06d7af3154f55786b59953cd87bb8a", size = 264264 }, + { url = "https://files.pythonhosted.org/packages/ef/f9/8a1421181c5eb0c0f81d1423a900baeb3faba68a48747bbdffb7581239ac/optree-0.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:27d81dc43b522ba47ba7d2e7d91dbb486940348b1bf85caeb0afc2815c0aa492", size = 293682 }, + { url = "https://files.pythonhosted.org/packages/80/34/d1b1849a6240385c4a3af5da9425b11912204d0b1cf142d802815319b73a/optree-0.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:f39c7174a3f3cdc3f5fe6fb4b832f608c40ac174d7567ed6734b2ee952094631", size = 293670 }, + { url = "https://files.pythonhosted.org/packages/0d/d6/f81e6748bcc3f35a2f570a814014e3418b0ed425d7cbc2b42d88d12863d5/optree-0.13.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3010ae24e994f6e00071098d34e98e78eb995b7454a2ef629a0bf7df17441b24", size = 702861 }, + { url = "https://files.pythonhosted.org/packages/08/7f/70a2d02110ccb245bc57bd9ad57668acfea0ff364c27d7dfe1735ede79ed/optree-0.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5b5626c38d4a18a144063db5c1dbb558431d83ca10682324f74665a12214801f", size = 370740 }, + { url = "https://files.pythonhosted.org/packages/63/37/4ddf05267467809236203e2007e9443519c4d55e0744ce7eea1aa74dffee/optree-0.13.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1935639dd498a42367633e3877797e1330e39d44d48bbca1a136bb4dbe4c1bc9", size = 374695 }, + { url = "https://files.pythonhosted.org/packages/19/f2/51a63a799f6dce31813d7e02a7547394aebcb39f407e62038ecbd999d490/optree-0.13.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01819c3df950696f32c91faf8d376ae6b695ffdba18f330f1cab6b8e314e4612", size = 418671 }, + { url = "https://files.pythonhosted.org/packages/f0/7c/a08191e0c9202f2be9c415057eea3cf3a5af18e9a6d81f4c7b0e6faf0a1f/optree-0.13.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48c29d9c6c64c8dc48c8ee97f7c1d5cdb83e37320f0be0857c06ce4b97994aea", size = 414966 }, + { url = "https://files.pythonhosted.org/packages/8f/37/7bf815f4da7234e387863228b17246b42b8c02553882581a4013a64a88d0/optree-0.13.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:025d23400b8b579462a251420f0a9ae77d3d3593f84276f3465985731d79d722", size = 389219 }, + { url = "https://files.pythonhosted.org/packages/3d/84/bb521a66d3a84fe2f1500ef67d245c2cc1a26277fcaaf4bc70b22c06e99b/optree-0.13.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55e82426bef151149cfa41d68ac957730fcd420996c0db8324fca81aa6a810ba", size = 405377 }, + { url = "https://files.pythonhosted.org/packages/06/99/3eb53829c4c0b6dc20115d957d2d8e945630ddf40c656dc4e39c5a6e51f2/optree-0.13.1-cp313-cp313t-win32.whl", hash = "sha256:e40f018f522fcfd244688d1b3a360518e636ba7f636385aae0566eae3e7d29bc", size = 292734 }, + { url = "https://files.pythonhosted.org/packages/2f/59/d7601959ad0b90d309794c0975a256304488b4c5671f24e3e12101ade7ef/optree-0.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d580f1bf23bb352c4db6b3544f282f1ac08dcb0d9ab537d25e56220353438cf7", size = 331457 }, + { url = "https://files.pythonhosted.org/packages/8b/36/c01a5bc34660d46c6a3b1fe090bbdc8c76af7b5c1a6613cc671aa6df8349/optree-0.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:c4d13f55dbd509d27be3af54d53b4ca0751bc518244ced6d0567e518e51452a2", size = 331470 }, +] + +[[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" @@ -2293,20 +2796,6 @@ dependencies = [ ] 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 }, @@ -2329,6 +2818,24 @@ 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 = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663 }, +] + +[[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 = "password-strength" version = "0.0.3.post2" @@ -2341,6 +2848,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1c/d6/08fd888c980589e4e27c2a4177e972481e8881600138e63afb785fe52630/password_strength-0.0.3.post2-py2.py3-none-any.whl", hash = "sha256:6739357c2863d707b7c7f247ff7c6882a70904a18d12c9aaf98f8b95da176fb9", size = 12167 }, ] +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + [[package]] name = "peft" version = "0.14.0" @@ -2362,34 +2878,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/05/e58e3aaa36544d30a917814e336fc65a746f708e5874945e92999bc22fa3/peft-0.14.0-py3-none-any.whl", hash = "sha256:2f04f3a870c3baf30f15e7dcaa5dd70d3e54cfdd146d3c6c187735d3ae0a0700", size = 374831 }, ] +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "sys_platform != 'win32'" }, +] +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 = "pillow" version = "11.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/1c/2dcea34ac3d7bc96a1fd1bd0a6e06a57c67167fec2cff8d95d88229a8817/pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8", size = 3229983 }, - { url = "https://files.pythonhosted.org/packages/14/ca/6bec3df25e4c88432681de94a3531cc738bd85dea6c7aa6ab6f81ad8bd11/pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192", size = 3101831 }, - { url = "https://files.pythonhosted.org/packages/d4/2c/668e18e5521e46eb9667b09e501d8e07049eb5bfe39d56be0724a43117e6/pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2", size = 4314074 }, - { url = "https://files.pythonhosted.org/packages/02/80/79f99b714f0fc25f6a8499ecfd1f810df12aec170ea1e32a4f75746051ce/pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26", size = 4394933 }, - { url = "https://files.pythonhosted.org/packages/81/aa/8d4ad25dc11fd10a2001d5b8a80fdc0e564ac33b293bdfe04ed387e0fd95/pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07", size = 4353349 }, - { url = "https://files.pythonhosted.org/packages/84/7a/cd0c3eaf4a28cb2a74bdd19129f7726277a7f30c4f8424cd27a62987d864/pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482", size = 4476532 }, - { url = "https://files.pythonhosted.org/packages/8f/8b/a907fdd3ae8f01c7670dfb1499c53c28e217c338b47a813af8d815e7ce97/pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e", size = 4279789 }, - { url = "https://files.pythonhosted.org/packages/6f/9a/9f139d9e8cccd661c3efbf6898967a9a337eb2e9be2b454ba0a09533100d/pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269", size = 4413131 }, - { url = "https://files.pythonhosted.org/packages/a8/68/0d8d461f42a3f37432203c8e6df94da10ac8081b6d35af1c203bf3111088/pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49", size = 2291213 }, - { url = "https://files.pythonhosted.org/packages/14/81/d0dff759a74ba87715509af9f6cb21fa21d93b02b3316ed43bda83664db9/pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a", size = 2625725 }, - { url = "https://files.pythonhosted.org/packages/ce/1f/8d50c096a1d58ef0584ddc37e6f602828515219e9d2428e14ce50f5ecad1/pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65", size = 2375213 }, - { url = "https://files.pythonhosted.org/packages/dd/d6/2000bfd8d5414fb70cbbe52c8332f2283ff30ed66a9cde42716c8ecbe22c/pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457", size = 3229968 }, - { url = "https://files.pythonhosted.org/packages/d9/45/3fe487010dd9ce0a06adf9b8ff4f273cc0a44536e234b0fad3532a42c15b/pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35", size = 3101806 }, - { url = "https://files.pythonhosted.org/packages/e3/72/776b3629c47d9d5f1c160113158a7a7ad177688d3a1159cd3b62ded5a33a/pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2", size = 4322283 }, - { url = "https://files.pythonhosted.org/packages/e4/c2/e25199e7e4e71d64eeb869f5b72c7ddec70e0a87926398785ab944d92375/pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070", size = 4402945 }, - { url = "https://files.pythonhosted.org/packages/c1/ed/51d6136c9d5911f78632b1b86c45241c712c5a80ed7fa7f9120a5dff1eba/pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6", size = 4361228 }, - { url = "https://files.pythonhosted.org/packages/48/a4/fbfe9d5581d7b111b28f1d8c2762dee92e9821bb209af9fa83c940e507a0/pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1", size = 4484021 }, - { url = "https://files.pythonhosted.org/packages/39/db/0b3c1a5018117f3c1d4df671fb8e47d08937f27519e8614bbe86153b65a5/pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2", size = 4287449 }, - { url = "https://files.pythonhosted.org/packages/d9/58/bc128da7fea8c89fc85e09f773c4901e95b5936000e6f303222490c052f3/pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96", size = 4419972 }, - { url = "https://files.pythonhosted.org/packages/5f/bb/58f34379bde9fe197f51841c5bbe8830c28bbb6d3801f16a83b8f2ad37df/pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f", size = 2291201 }, - { url = "https://files.pythonhosted.org/packages/3a/c6/fce9255272bcf0c39e15abd2f8fd8429a954cf344469eaceb9d0d1366913/pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761", size = 2625686 }, - { url = "https://files.pythonhosted.org/packages/c8/52/8ba066d569d932365509054859f74f2a9abee273edcef5cd75e4bc3e831e/pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71", size = 2375194 }, { url = "https://files.pythonhosted.org/packages/95/20/9ce6ed62c91c073fcaa23d216e68289e19d95fb8188b9fb7a63d36771db8/pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a", size = 3226818 }, { url = "https://files.pythonhosted.org/packages/b9/d8/f6004d98579a2596c098d1e30d10b248798cceff82d2b77aa914875bfea1/pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b", size = 3101662 }, { url = "https://files.pythonhosted.org/packages/08/d9/892e705f90051c7a2574d9f24579c9e100c828700d78a63239676f960b74/pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3", size = 4329317 }, @@ -2420,13 +2926,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494 }, { url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595 }, { url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651 }, - { url = "https://files.pythonhosted.org/packages/fa/c5/389961578fb677b8b3244fcd934f720ed25a148b9a5cc81c91bdf59d8588/pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90", size = 3198345 }, - { url = "https://files.pythonhosted.org/packages/c4/fa/803c0e50ffee74d4b965229e816af55276eac1d5806712de86f9371858fd/pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb", size = 3072938 }, - { url = "https://files.pythonhosted.org/packages/dc/67/2a3a5f8012b5d8c63fe53958ba906c1b1d0482ebed5618057ef4d22f8076/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442", size = 3400049 }, - { url = "https://files.pythonhosted.org/packages/e5/a0/514f0d317446c98c478d1872497eb92e7cde67003fed74f696441e647446/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83", size = 3422431 }, - { url = "https://files.pythonhosted.org/packages/cd/00/20f40a935514037b7d3f87adfc87d2c538430ea625b63b3af8c3f5578e72/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f", size = 3446208 }, - { url = "https://files.pythonhosted.org/packages/28/3c/7de681727963043e093c72e6c3348411b0185eab3263100d4490234ba2f6/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73", size = 3509746 }, - { url = "https://files.pythonhosted.org/packages/41/67/936f9814bdd74b2dfd4822f1f7725ab5d8ff4103919a1664eb4874c58b2f/pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0", size = 2626353 }, ] [[package]] @@ -2490,44 +2989,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/cc/8dd693b9e2577690e86fb589478e8788df2920859e04743800eb7d02213c/primp-0.10.0-cp38-abi3-win_amd64.whl", hash = "sha256:7fe94c3164c2efffff08f7f54c018ac445112961b3ce4f4f499315ba0a9d1ef3", size = 3116121 }, ] +[[package]] +name = "prometheus-client" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/62/14/7d0f567991f3a9af8d1cd4f619040c93b68f09a02b6d0b6ab1b2d1ded5fe/prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb", size = 78551 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/c2/ab7d37426c179ceb9aeb109a85cda8948bb269b7561a0be870cc656eefe4/prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301", size = 54682 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/4f/feb5e137aff82f7c7f3248267b97451da3644f6cdc218edfe549fb354127/prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90", size = 424684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/6a/fd08d94654f7e67c52ca30523a178b3f8ccc4237fce4be90d39c938a831a/prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e", size = 386595 }, +] + [[package]] name = "propcache" version = "0.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/c8/2a13f78d82211490855b2fb303b6721348d0787fdd9a12ac46d99d3acde1/propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64", size = 41735 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/a5/0ea64c9426959ef145a938e38c832fc551843481d356713ececa9a8a64e8/propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6", size = 79296 }, - { url = "https://files.pythonhosted.org/packages/76/5a/916db1aba735f55e5eca4733eea4d1973845cf77dfe67c2381a2ca3ce52d/propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2", size = 45622 }, - { url = "https://files.pythonhosted.org/packages/2d/62/685d3cf268b8401ec12b250b925b21d152b9d193b7bffa5fdc4815c392c2/propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea", size = 45133 }, - { url = "https://files.pythonhosted.org/packages/4d/3d/31c9c29ee7192defc05aa4d01624fd85a41cf98e5922aaed206017329944/propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212", size = 204809 }, - { url = "https://files.pythonhosted.org/packages/10/a1/e4050776f4797fc86140ac9a480d5dc069fbfa9d499fe5c5d2fa1ae71f07/propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3", size = 219109 }, - { url = "https://files.pythonhosted.org/packages/c9/c0/e7ae0df76343d5e107d81e59acc085cea5fd36a48aa53ef09add7503e888/propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d", size = 217368 }, - { url = "https://files.pythonhosted.org/packages/fc/e1/e0a2ed6394b5772508868a977d3238f4afb2eebaf9976f0b44a8d347ad63/propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634", size = 205124 }, - { url = "https://files.pythonhosted.org/packages/50/c1/e388c232d15ca10f233c778bbdc1034ba53ede14c207a72008de45b2db2e/propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2", size = 195463 }, - { url = "https://files.pythonhosted.org/packages/0a/fd/71b349b9def426cc73813dbd0f33e266de77305e337c8c12bfb0a2a82bfb/propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958", size = 198358 }, - { url = "https://files.pythonhosted.org/packages/02/f2/d7c497cd148ebfc5b0ae32808e6c1af5922215fe38c7a06e4e722fe937c8/propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c", size = 195560 }, - { url = "https://files.pythonhosted.org/packages/bb/57/f37041bbe5e0dfed80a3f6be2612a3a75b9cfe2652abf2c99bef3455bbad/propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583", size = 196895 }, - { url = "https://files.pythonhosted.org/packages/83/36/ae3cc3e4f310bff2f064e3d2ed5558935cc7778d6f827dce74dcfa125304/propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf", size = 207124 }, - { url = "https://files.pythonhosted.org/packages/8c/c4/811b9f311f10ce9d31a32ff14ce58500458443627e4df4ae9c264defba7f/propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034", size = 210442 }, - { url = "https://files.pythonhosted.org/packages/18/dd/a1670d483a61ecac0d7fc4305d91caaac7a8fc1b200ea3965a01cf03bced/propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b", size = 203219 }, - { url = "https://files.pythonhosted.org/packages/f9/2d/30ced5afde41b099b2dc0c6573b66b45d16d73090e85655f1a30c5a24e07/propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4", size = 40313 }, - { url = "https://files.pythonhosted.org/packages/23/84/bd9b207ac80da237af77aa6e153b08ffa83264b1c7882495984fcbfcf85c/propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba", size = 44428 }, - { url = "https://files.pythonhosted.org/packages/bc/0f/2913b6791ebefb2b25b4efd4bb2299c985e09786b9f5b19184a88e5778dd/propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16", size = 79297 }, - { url = "https://files.pythonhosted.org/packages/cf/73/af2053aeccd40b05d6e19058419ac77674daecdd32478088b79375b9ab54/propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717", size = 45611 }, - { url = "https://files.pythonhosted.org/packages/3c/09/8386115ba7775ea3b9537730e8cf718d83bbf95bffe30757ccf37ec4e5da/propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3", size = 45146 }, - { url = "https://files.pythonhosted.org/packages/03/7a/793aa12f0537b2e520bf09f4c6833706b63170a211ad042ca71cbf79d9cb/propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9", size = 232136 }, - { url = "https://files.pythonhosted.org/packages/f1/38/b921b3168d72111769f648314100558c2ea1d52eb3d1ba7ea5c4aa6f9848/propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787", size = 239706 }, - { url = "https://files.pythonhosted.org/packages/14/29/4636f500c69b5edea7786db3c34eb6166f3384b905665ce312a6e42c720c/propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465", size = 238531 }, - { url = "https://files.pythonhosted.org/packages/85/14/01fe53580a8e1734ebb704a3482b7829a0ef4ea68d356141cf0994d9659b/propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af", size = 231063 }, - { url = "https://files.pythonhosted.org/packages/33/5c/1d961299f3c3b8438301ccfbff0143b69afcc30c05fa28673cface692305/propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7", size = 220134 }, - { url = "https://files.pythonhosted.org/packages/00/d0/ed735e76db279ba67a7d3b45ba4c654e7b02bc2f8050671ec365d8665e21/propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f", size = 220009 }, - { url = "https://files.pythonhosted.org/packages/75/90/ee8fab7304ad6533872fee982cfff5a53b63d095d78140827d93de22e2d4/propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54", size = 212199 }, - { url = "https://files.pythonhosted.org/packages/eb/ec/977ffaf1664f82e90737275873461695d4c9407d52abc2f3c3e24716da13/propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505", size = 214827 }, - { url = "https://files.pythonhosted.org/packages/57/48/031fb87ab6081764054821a71b71942161619549396224cbb242922525e8/propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82", size = 228009 }, - { url = "https://files.pythonhosted.org/packages/1a/06/ef1390f2524850838f2390421b23a8b298f6ce3396a7cc6d39dedd4047b0/propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca", size = 231638 }, - { url = "https://files.pythonhosted.org/packages/38/2a/101e6386d5a93358395da1d41642b79c1ee0f3b12e31727932b069282b1d/propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e", size = 222788 }, - { url = "https://files.pythonhosted.org/packages/db/81/786f687951d0979007e05ad9346cd357e50e3d0b0f1a1d6074df334b1bbb/propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034", size = 40170 }, - { url = "https://files.pythonhosted.org/packages/cf/59/7cc7037b295d5772eceb426358bb1b86e6cab4616d971bd74275395d100d/propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3", size = 44404 }, { url = "https://files.pythonhosted.org/packages/4c/28/1d205fe49be8b1b4df4c50024e62480a442b1a7b818e734308bb0d17e7fb/propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a", size = 79588 }, { url = "https://files.pythonhosted.org/packages/21/ee/fc4d893f8d81cd4971affef2a6cb542b36617cd1d8ce56b406112cb80bf7/propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0", size = 45825 }, { url = "https://files.pythonhosted.org/packages/4a/de/bbe712f94d088da1d237c35d735f675e494a816fd6f54e9db2f61ef4d03f/propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d", size = 45357 }, @@ -2563,6 +3051,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/b6/c5319caea262f4821995dca2107483b94a3345d4607ad797c76cb9c36bcc/propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54", size = 11818 }, ] +[[package]] +name = "proto-plus" +version = "1.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/05/74417b2061e1bf1b82776037cad97094228fa1c1b6e82d08a78d3fb6ddb6/proto_plus-1.25.0.tar.gz", hash = "sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91", size = 56124 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/25/0b7cc838ae3d76d46539020ec39fc92bfc9acc29367e58fe912702c2a79e/proto_plus-1.25.0-py3-none-any.whl", hash = "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961", size = 50126 }, +] + [[package]] name = "protobuf" version = "5.29.3" @@ -2592,6 +3092,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/d7/7831438e6c3ebbfa6e01a927127a6cb42ad3ab844247f3c5b96bea25d73d/psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649", size = 254444 }, ] +[[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 = "py" version = "1.11.0" @@ -2607,26 +3125,6 @@ version = "0.1.11" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f0/8a/5e22cbd00b799b33ce0a45ae3715c9ea3fcd263f877544819e7d03753c49/py_bip39_bindings-0.1.11.tar.gz", hash = "sha256:ebc128ccf3a0750d758557e094802f0975c3760a939f8a8b76392d7dbe6b52a1", size = 18103 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/0c/ab1bb098eaca1954c02ff1ef625817e7580907273d0f10de29eb4bac4f31/py_bip39_bindings-0.1.11-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:324a7363f8b49201ebe1cc72d970017ec5139f8a5ddf605fa2774904eb7f08a1", size = 399429 }, - { url = "https://files.pythonhosted.org/packages/c0/87/3d9903a85f9b5b3d57eb04ec484bf148e12a8dcb255160d3071cabb3861c/py_bip39_bindings-0.1.11-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:77173b83c7ade4ca3c91fae0da9c9b1bc5f4c6819baa2276feacd5abec6005fa", size = 792362 }, - { url = "https://files.pythonhosted.org/packages/09/c1/2bda881db1c8fedc009bdd70db5ec6ccc840331f03726946907b417ce9fe/py_bip39_bindings-0.1.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84e5177fb3d3b9607f5d7d526a89f91b35687fcc34b643fc96cd168a0ae025cb", size = 413986 }, - { url = "https://files.pythonhosted.org/packages/7f/df/32db1f9e09757f292c9d88d487b58cdae741ab4bd1567571725c27d4cbf4/py_bip39_bindings-0.1.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ecd1cfb17f0b1bb56f0b1de5c533ff9830a60b5d657846b8cf500ff9fca8b3", size = 1225281 }, - { url = "https://files.pythonhosted.org/packages/c1/0b/b83a9d6d60a5941b1c01ff4df6e4a7994c665f3167038dc26338814d57f1/py_bip39_bindings-0.1.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3408dc0809fca5691f9c02c8292d62590d90de4f02a4b2dcab35817fa857a71", size = 1227053 }, - { url = "https://files.pythonhosted.org/packages/50/45/180a09137e318ff1b41e5a7641e383a7a1605876256b915e2761a3b6c8f8/py_bip39_bindings-0.1.11-cp310-cp310-manylinux_2_28_armv7l.whl", hash = "sha256:d6f0eda277c6d0ef28cc83fd3f59a0f745394ea1e2807f2fea49186084b3d47d", size = 1180932 }, - { url = "https://files.pythonhosted.org/packages/8d/a9/b63d8cddbcbd31bbfa85475bd286779f56c5c313c84b7cd47f0f60a7c204/py_bip39_bindings-0.1.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:963357db40dc7a816d55097a85929cae18c6174c5bedf0410f6e72181270b2b1", size = 1264074 }, - { url = "https://files.pythonhosted.org/packages/69/86/4702ca3849aeb2730311632937219e6fb6d492ff5aef1bad4c1ef23efb33/py_bip39_bindings-0.1.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:be06dc751be86cbd72cd71e318979d3ab27cee12fd84d1e5e4e84575c5c9355d", size = 1252036 }, - { url = "https://files.pythonhosted.org/packages/66/f5/e39751148880e2d422a609608c228734d2f56f36635ad4958fe2f36417d6/py_bip39_bindings-0.1.11-cp310-none-win32.whl", hash = "sha256:b4e05b06831874fa8715bdb128ea776674ad708858a4b3b1a27e5710859b086d", size = 296768 }, - { url = "https://files.pythonhosted.org/packages/d5/67/fc950648f9ea9c3db6bbd55c3384dd6a5a1814b59948ba5f80b8bee7de96/py_bip39_bindings-0.1.11-cp310-none-win_amd64.whl", hash = "sha256:e01a03e858a648d294bcf063368bf09027efa282f5192abddaf7af69c5e2a574", size = 283551 }, - { url = "https://files.pythonhosted.org/packages/44/b6/0bd5bf1c4cb00e000a9c909280aa7f8654208ee136e2cd1f3650a8de59ed/py_bip39_bindings-0.1.11-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:27cce22727e28705a660464689ade6d2cdad4e622bead5bde2ffa53c4f605ee5", size = 399429 }, - { url = "https://files.pythonhosted.org/packages/22/44/b6ffdc17cc499b72821a1d777fb465af842d5b7373f1825a45dce551fedc/py_bip39_bindings-0.1.11-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:cdf35d031587296dcbdb22dbc67f2eaf5b5df9d5036b77fbeb93affbb9eec8d3", size = 792364 }, - { url = "https://files.pythonhosted.org/packages/15/8d/0883d814a26f922b331218c777ecaec61919aebf9c54d4991f919b21ab8a/py_bip39_bindings-0.1.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2fd5b926686207752d5f2e2ff164a9489b3613239d0967362f10c2fbd64eb018", size = 413967 }, - { url = "https://files.pythonhosted.org/packages/72/30/e3c76035b83c9552bbeee90645411a3d52983067badbd8a5854a823701f9/py_bip39_bindings-0.1.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba84c38962bffdaea0e499245731d669cc21d1280f81ace8ff60ed3550024570", size = 1225281 }, - { url = "https://files.pythonhosted.org/packages/38/c9/3b73fe8ffd285387c4fe7b60ccd0072ee16d5153409619c472852ec88acc/py_bip39_bindings-0.1.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9024ec3c4a3db005b355f9a00602cede290dec5e9c7cf7dd06a26f620b0cf99", size = 1227054 }, - { url = "https://files.pythonhosted.org/packages/7e/2f/d096e6e08439e5b3c1f41e95c5828700012c130611a64fe9e82a43b0ca45/py_bip39_bindings-0.1.11-cp311-cp311-manylinux_2_28_armv7l.whl", hash = "sha256:ce028c8aef51dec2a85f298461b2988cca28740bf3cc23472c3469d3f853714e", size = 1180933 }, - { url = "https://files.pythonhosted.org/packages/0b/8f/00c2239452f26e180229d74acd63ac0c027f8eb9a5fb90b879c6c1192102/py_bip39_bindings-0.1.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:51882cd0fa7529173b3543c089c24c775f1876ddf48f10e60f2ed07ad2af5cae", size = 1264074 }, - { url = "https://files.pythonhosted.org/packages/1a/7a/524e38494a0ffb7ca211225acde0324cf216f081dffae7fd55446b009889/py_bip39_bindings-0.1.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ee776f3b33b2d71fee48679951f117e3d1f052449ec2fcb184f3c64a4c77e4f", size = 1252039 }, - { url = "https://files.pythonhosted.org/packages/e9/a0/68bbb9e9326266a9acca2558d6556e22df31bcf4d2235ee1cdaf362add82/py_bip39_bindings-0.1.11-cp311-none-win32.whl", hash = "sha256:d8b722e49562810f94eb61c9efa172f327537c74c37da3e86b161f7f444c51bf", size = 296767 }, - { url = "https://files.pythonhosted.org/packages/0a/47/4c5d0ff9949b725696b1b10b5b87f6c6d3c333d8458b354b7c8536272eef/py_bip39_bindings-0.1.11-cp311-none-win_amd64.whl", hash = "sha256:be934052497f07605768e2c7184e4f4269b3e2e77930131dfc9bdbb791e6fdf4", size = 283550 }, { url = "https://files.pythonhosted.org/packages/cb/a5/0d29c79ee79475ceca80ca19b5975917827af6ce4dd2711ed197822a12ea/py_bip39_bindings-0.1.11-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:afa9c5762cfaec01141f478a9c3132de01ec3890ff2e5a4013c79d3ba3aff8bb", size = 798236 }, { url = "https://files.pythonhosted.org/packages/47/fd/a4baff5368ef8be569064e5aef1319c4e75b24a80c70c0f3a871727c6a38/py_bip39_bindings-0.1.11-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a3af7c1f955c6bbd613c6b38d022f7c73896acaf0ecc972ac0dee4b952e14568", size = 406227 }, { url = "https://files.pythonhosted.org/packages/78/44/fe4a107204690d18691a2db7cacfd6043331f6982dc59962d9e220d46711/py_bip39_bindings-0.1.11-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6aed3e86f105a36676e8dd0c8bc3f611a81b7ba4309b22a77fdc0f63b260e094", size = 1215916 }, @@ -2644,30 +3142,6 @@ version = "1.2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/22/51/c5f00db791472d4f6c14b383dca0da621db6b68b8c73bef46bf136cb1c93/py_ed25519_zebra_bindings-1.2.0.tar.gz", hash = "sha256:d9ec63d54b1801d5b5bdef0b3096ed94e2e1a7c870c937682afc7b8b25ffc2fc", size = 11851 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/2b/e79646f36bfe281186a6fa88282e32fa124057a14195918bab15e0e01ed5/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d488bf0ac70424514fddb3cf9cca6166ad149b7655970719e9bbef398054e6ad", size = 297590 }, - { url = "https://files.pythonhosted.org/packages/3c/fe/44cd25020aeea4a67b56d580e0ae64ff99dae67623a06afa68edba519470/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c3f97af9b0db7fe2bba1b1ac8d684711fc33e6383c067e1a1fc642e1595282a", size = 324153 }, - { url = "https://files.pythonhosted.org/packages/e7/1c/2fbf3356f9df6865a30fce4802161441a4490ebe8557513f0d5d5e6a80de/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9798a82efe73cfff02eb4c09576af0dc0ca3b41cc3e17cf469179add708c8b40", size = 337730 }, - { url = "https://files.pythonhosted.org/packages/46/7b/25dfb9335256e9e8df2307558da22516e226eeb741d6db2b36ca5b839c29/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0837d10e19e72bb4665c584c89f207bad8b3d29cf2410c0f9ea310c6698f4b26", size = 318874 }, - { url = "https://files.pythonhosted.org/packages/cf/3d/cd917bb2063ff45b2bf084d6ef3a68a5ba905e4fe8c502c04a47c30956b1/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bb278da1728db5259d5c29dcc95717336a69fc6e6159cb7400ac262ee8a96ca", size = 337623 }, - { url = "https://files.pythonhosted.org/packages/0f/18/6578f5418aaa6699d965e802b8953dbb03c56fe58d942f078723e72ecc1b/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5a739e82c82a1f62de54cc0482e9d007b961c84220849ffd86924e34f8db5c9e", size = 474570 }, - { url = "https://files.pythonhosted.org/packages/91/ff/ad829d892424c4bb1135f555ea5f768f98b174eefa506b83c848d762c5be/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d311a44ae162da4b391eb4d47675709b5044b925bef20e4e2209cdfa28ccc1ee", size = 586941 }, - { url = "https://files.pythonhosted.org/packages/75/23/6bffc12b205c21e47dfbcbea76fffb638bf759425ff6da0bec528023538e/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c4a30a6a22f28173de66634294824455ae683163be32565f36fbfa27b8a76495", size = 516467 }, - { url = "https://files.pythonhosted.org/packages/66/10/1e2a675bfc4af6f5396060fb7e57850eb7739a8a4ff92c9b550c34def43a/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:325eb5d0c7a406fd6abbd5b2daeb6d16e4c161a86909bf11a34a3a2c351e7fa0", size = 489615 }, - { url = "https://files.pythonhosted.org/packages/31/ba/53ff308b8ab008606e4de8c24a3511e7c61e14c24b9df90b8aee0ca01ed6/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-win32.whl", hash = "sha256:dcd8f8ecbc3593c54fb3fcc1d0d847d2fdf86c8d2e6840d319d152f4efdef498", size = 186382 }, - { url = "https://files.pythonhosted.org/packages/f1/31/483de0dcc26e8762be7922b924bc889b142591121beb1e827d644f632797/py_ed25519_zebra_bindings-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:b762e13f1e2cedfac4be954a70a75330a5368e2c0ecd64db7ce1e2e9672ed4da", size = 187218 }, - { url = "https://files.pythonhosted.org/packages/5f/41/d98363e8d78919b2340a318f52d6d90f30d67af9e472fdafd30b7003dea5/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dbfe655442b73d49c1ac740f87a480cfee4c013fcb0ba2b639290b20f8dc9bb5", size = 293119 }, - { url = "https://files.pythonhosted.org/packages/86/fa/be0fc2a0340325fd1f8b82f56bd9304d30e694639748a19ef749c8c5e9cf/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b03308c3eb2311b5d308c3df22dbf244073e4c014cda5da2609a562adb4121fc", size = 267743 }, - { url = "https://files.pythonhosted.org/packages/a2/8c/9dfd6b0dec395edb8c8a5475552443320a3085db97f9c7d9332055ba8195/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1828031f38f246d35c7c7b427c17a3525fc311c0402d3b32572510977b9d0f67", size = 297320 }, - { url = "https://files.pythonhosted.org/packages/48/e7/9469f84d868227344240182df25cba274ec3f9d812fc243d27ed2c2ad356/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f88238cf167ba5681e74a556b1e6ce825cb157825ce40c7f757b7d02a7c47dfb", size = 324110 }, - { url = "https://files.pythonhosted.org/packages/91/df/a010af828cb9b3c6864ec74a7d0c54130ef1f557ec0bdfab449881418e29/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b25ca1596ae3be7e6ce6e78252ce7efa570000f9ba5b39cfe8dd10e79f73d50", size = 337742 }, - { url = "https://files.pythonhosted.org/packages/82/73/78e57f453a88345d6481a3eeb96ef9f56b7a7c9ee68a7c577b4385c80405/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a33b1d8af961d28831caf2d481879bb1592f700da79aa5613d845ae6b8153a", size = 318630 }, - { url = "https://files.pythonhosted.org/packages/6a/e2/8b2df28a19e7f552429421c005ec7ae41bf1ed664915fed705f687442220/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:41ee171c18852f6db4a86e68c4fbd622f5415f15c0ab9b40ac1fe66a8ddc3844", size = 337481 }, - { url = "https://files.pythonhosted.org/packages/0d/f3/044b3f5a4c299b53d1beb9e33b3af223bbc6d43707e5acf7edf012b39ff7/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:58623ff56bf1da2581a7d52507d9757ec3b03d49879fc8611646faf666bd0120", size = 474458 }, - { url = "https://files.pythonhosted.org/packages/fe/7d/dff91ff74d992ba0d30e48fda51d715021249ba8f19ae0c5906e4870d6df/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3fdd9cc305dd88562b9fe4d27762070bfdaa1e88647a1509a22fe252e17148d7", size = 586915 }, - { url = "https://files.pythonhosted.org/packages/2e/b3/43d5466fab2025a4213f29242b42f1591cb1dea0ed25098f00809bb6bab2/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:911f068d15159798309dc1895ce156b1bca2f91e34446be3ac5f54f2d3418979", size = 516303 }, - { url = "https://files.pythonhosted.org/packages/5b/7c/8d8b4bffc70ca69fd38b6ec7c61bcad94f2ad1e215b14ecf160de6876563/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9d0fc9c1afbf4b5ff0bc03accf5f07bf53971839eb373d1139eb3bb5a02b3bd0", size = 489278 }, - { url = "https://files.pythonhosted.org/packages/96/33/7cc1f8d6528755c680fa441b67f5010f5f0b2a772dbe0e63cbde86c2d887/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-win32.whl", hash = "sha256:256b96fdf0e264a348bf4176c0fb180a0efc6627ac312cb5e71ec95b347d1ff5", size = 186346 }, - { url = "https://files.pythonhosted.org/packages/20/27/b14ff7fb43a456848492e795d4aeadc5823d1fb8fce7b1ff0d35c467d117/py_ed25519_zebra_bindings-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:efa06b8a0e11c62c10fdf576679ab3039aa6a7254e6cfa4d2d230941799fef5b", size = 187094 }, { url = "https://files.pythonhosted.org/packages/38/3f/1cbe6c29d5630ab8b29f6f1d52723f8123331d7a3b1a2a5f8070e2f5bc09/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8d63a447d3adac9b431fecd886cf711a6d44200d8b2497598a8ab44ac897f1fb", size = 290728 }, { url = "https://files.pythonhosted.org/packages/7f/88/fc2759f89c2d07e594455c2b2442bbf6a5ee223af3f87f452a6369e17fce/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5b1c32414a6da709e84d0614e1ed153a5e1dbcbf6d4d17baa31c493fdbd4da4", size = 266106 }, { url = "https://files.pythonhosted.org/packages/2d/f6/bba44de332b01b048fd739c242829cef0aac776730df2b96d5da0643cb51/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:780073555571390c4b355b5646c0b59c2a90d3393e354d58c4ad904121a2aee2", size = 296312 }, @@ -2699,15 +3173,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/7a/0d5073188f94fd3b22a836e867e32fae0e26f3b39f734314e3eff5b530f6/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:4fd00c8686b17e31ec29d8e4e7ce97f465fe26227f12c9e111e012b9d0dff4b9", size = 585681 }, { url = "https://files.pythonhosted.org/packages/ab/99/add86df518d799a17c91763eebf756de68b1a858a5c7977de1b335e886cc/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e4e55fc5be4ba0c723d424cefdbb8d863e74d2ff25fbeadca9539ca60d78cc0f", size = 514835 }, { url = "https://files.pythonhosted.org/packages/e7/fc/bf32dc80a597501fc7ef8b18638f78e5ee672b0b43cc02373075f9b1f8d4/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:91816ed4cef90d4d08fa9f55fa0c5687c5eba601dc1a44f211adcf1c20d96cc3", size = 488524 }, - { url = "https://files.pythonhosted.org/packages/6a/99/90bcd1e9c7aa0e7ffcab2ca359139d0e2adf47509666c6ac2f7dba69682f/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f76228db22d018a66e858b27d4c074a0111438919a45276ac1a00d397d6daca", size = 297980 }, - { url = "https://files.pythonhosted.org/packages/b1/18/2877f37a621b89447aff5f3ae0f66ccc2bea31f5a681c0bc70eeac813c4e/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f7c0875eda221bfdc1029207d7807c2ae5446bf4aaf5d34def94b8fa2abeace", size = 324793 }, - { url = "https://files.pythonhosted.org/packages/a2/6f/f7d3a6533e4fb3e76b22c9936149e7f622a2789fdd886204a87668fc45c5/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c713b7dba676380e2a1c3208667a71bf4bcc02a67b487894cda35c6103079e9", size = 338015 }, - { url = "https://files.pythonhosted.org/packages/b9/ef/2b9ccc88c971398f430570793eae77bf3531ac001caf40e699511b3ca9ed/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fe2882a1377199cdb656e42adf5e97869d1b04af1f66a7300179f95692603c2", size = 319683 }, - { url = "https://files.pythonhosted.org/packages/cd/ed/505573c349c0a7f45205e31902dc2027cfc277ff39c5980703458fdb1718/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c6afd09a1b831444a5107ca8e48f14db837a2351cac25e70e71f80f976c76ca2", size = 338271 }, - { url = "https://files.pythonhosted.org/packages/cf/88/5e7e30c38f7b4a83a59d45e7a8084d8a1ba17c8c88b0951301c610cc333b/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:91c0627efe7048ce552be5db08c11a99d532b2e115316daed3b53e52ba9f383b", size = 475282 }, - { url = "https://files.pythonhosted.org/packages/ce/2a/481a28104d0f9a287eee558bf8240d571147d894141f825df4377e831c23/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:d6efc48c7c26838044c7f58ba2e7944776ef6eaef21c962a528ddffd3943e1b4", size = 587506 }, - { url = "https://files.pythonhosted.org/packages/dc/59/990991e44c1d91f7b4e70da848573faf23231ba720939342cce1d1f8d9ea/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:7cb8befc4c52c681c4e2f5994adeff28f529f767c979921faaa1fbb84a52afae", size = 516902 }, - { url = "https://files.pythonhosted.org/packages/21/f1/13947be60e9c411fdc21680275b5117becc425bb21e24194be61210c8493/py_ed25519_zebra_bindings-1.2.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3b976f2c6053011c08dcde2f5805e285a8ff53eec5a42be0cc24ce93bc5729ac", size = 490091 }, ] [[package]] @@ -2716,32 +3181,6 @@ version = "0.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/65/55/e5c27d1387f6cb3a6bf7714e1e0c4a62edc3b006710e2d081e8bdfa4123f/py_sr25519_bindings-0.2.1.tar.gz", hash = "sha256:1b96d3dde43adcf86ab427a9fd72b2c6291dca36eb40747df631588c16f01c1a", size = 18439 } wheels = [ - { url = "https://files.pythonhosted.org/packages/89/bc/d9c0e997def0884b81ef2fdea5f3f65fae0974dd60ca0639ff0eb04d5ae2/py_sr25519_bindings-0.2.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10489c399768dc4ac91c90a6c8da60aeb77a48b21a81944244d41b0d4c4be2f", size = 331167 }, - { url = "https://files.pythonhosted.org/packages/cd/33/1d9d0345a7c71b6296927f29fb6500e460187c381d7d4fffa57e6ab1f77b/py_sr25519_bindings-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8358a7b3048765008a79733447dfdcafdce3f66859c98634055fee6868252e12", size = 306032 }, - { url = "https://files.pythonhosted.org/packages/68/61/66811bb11031a48c54b7cdfaf75731e86cf136449a10ca3287e9963092bd/py_sr25519_bindings-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:202af5a516614907ddaef073104ae6d0a98ec96743d11cb87faa09d2b235a6b4", size = 340111 }, - { url = "https://files.pythonhosted.org/packages/4c/f0/a0cf86f6b424303c1ab51e55470006da83950b39c35716e9206f2260882e/py_sr25519_bindings-0.2.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0b0d977c9ba6063d7807dda84264f10b1951736ba528b4d4078e5c9989051b1", size = 367989 }, - { url = "https://files.pythonhosted.org/packages/f5/8d/0053fb7afef406ba3aca08ff51d0c0afc31b3c7a3874c824a92c0a5366e8/py_sr25519_bindings-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e6c46cbbb87eb9db3c7deebd71c296d67c0725d9379ee737255e22c15c64bae", size = 384040 }, - { url = "https://files.pythonhosted.org/packages/a6/c6/59ba7e7edb8e496ad8ae2777d102cacb41aae7d5fb05c2ed6972fdd0d55f/py_sr25519_bindings-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9368e9ca0bc1c967db0dd5cfc401f23d364064e99a48d21ea12a068612ccce7e", size = 365611 }, - { url = "https://files.pythonhosted.org/packages/8f/ca/30ab71b4357e9380b4bd8ed65338cf9ec36970798fe8b8e0f3273c3aed71/py_sr25519_bindings-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f1ade92569b0281ff24476bd93333865370d86746b2d7949545f1ca70ac4e14", size = 385332 }, - { url = "https://files.pythonhosted.org/packages/15/bc/1846c1600eb2004e49bf7bd07fbfcb73c61b8f17d5ab6478874b1abd0d94/py_sr25519_bindings-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7286da1662afc300038441620092a0ae527430f7c50b0768e826d46893dd5095", size = 523820 }, - { url = "https://files.pythonhosted.org/packages/b7/8b/bfd727048597181a83cc43282a61f9d7eebc1aed317e4999c58763cdf1dd/py_sr25519_bindings-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1afbf451ecb78d5a1fa3be0f1cafb914aa2d4464ce15374bbff495cc384b1947", size = 627704 }, - { url = "https://files.pythonhosted.org/packages/23/87/fd02d1a53d9287d31ecef4842cfe7df84377a797c297d8497052fa9f5068/py_sr25519_bindings-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:873c0ec12fed805f4086e36ebbb673c95af09e4007ea66d5a9bbd2cc29dfa076", size = 551535 }, - { url = "https://files.pythonhosted.org/packages/14/86/7054e137d105de5408c2510c0d457b69756cdd4d4fbdb77aa2ba71b164b8/py_sr25519_bindings-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5917f8584cf6a81e32f03547d9fbd8c783db2372d49bd9ff8c5c57d969ea1039", size = 529596 }, - { url = "https://files.pythonhosted.org/packages/b0/ef/6701c33c8f243dfc143e55ead788727b1d99865915fe82e72019dfd16109/py_sr25519_bindings-0.2.1-cp310-none-win32.whl", hash = "sha256:09f184393e01d0d2b62d3782a6d18dd0824a225444e0171c08e03f8cf3920e7b", size = 217893 }, - { url = "https://files.pythonhosted.org/packages/70/9c/bca6f55663f573eee619a2100af35797449362e906316e01b8e5ddb612f2/py_sr25519_bindings-0.2.1-cp310-none-win_amd64.whl", hash = "sha256:2d548a8ea057c6f150572059475761101ba8ef15e3b349d2d0cb108652f6aaf8", size = 224183 }, - { url = "https://files.pythonhosted.org/packages/b7/e5/62067ff055a940bcbb02467f7fb63fd85a89cc12153f8c78199ce5c71fb9/py_sr25519_bindings-0.2.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4941e6e0e180f7e72565043ed3ba7190455c9feaa2ab9ee6038904f2b4bb6c5b", size = 331203 }, - { url = "https://files.pythonhosted.org/packages/0a/6c/48a6e1289012b4ab704ccec5315a7c1f1694909b5cc332a36ec87ab03608/py_sr25519_bindings-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b63d7cf5bb4d9b986d7f7012c80b92be70311dc9b75862f7880e03b71a29543d", size = 306083 }, - { url = "https://files.pythonhosted.org/packages/e6/da/b7ab72a15e950779edf376b344b6de43aacc7250e319ff23996ef96cda5b/py_sr25519_bindings-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6752bf3b109446d99f3a368e3ba805812fc5bc09e52ef1c82f5a47e43b19973", size = 340172 }, - { url = "https://files.pythonhosted.org/packages/15/7f/4defee54893a3947936f3b5b8b1fe8cb10bb6d01cf87240345f511636e8d/py_sr25519_bindings-0.2.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0368dcdf5ec8d2bb9c13273c78c3c5b033211d37a70a2f1d2080f29a7d118340", size = 368044 }, - { url = "https://files.pythonhosted.org/packages/44/a9/b6ddb161bb28f7da1b261d8e6d59d9669a15bdbfe8bfff0ff15f9a28f0a6/py_sr25519_bindings-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2618b02b4a3babac07b8bb61fe9550f911f038bb079665682ca76b2e664e5258", size = 384053 }, - { url = "https://files.pythonhosted.org/packages/7a/66/5d4c78ad9766cd46e5439e9fb84cb10bc47b9c4929c8ea99ee880f405f50/py_sr25519_bindings-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab1bc4dc524efefaecf3a85f4a0ff05c1ca9509d4d64056199984550f3c98b3", size = 365700 }, - { url = "https://files.pythonhosted.org/packages/07/ef/f96d4e2472af62768ffd81df2170f643de87b0ab831e405a4572b9959379/py_sr25519_bindings-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7ccdc89d5e3ae0dd163c8150ec76b6bb3291c1cec9746eb79e9544b3423f35f9", size = 385360 }, - { url = "https://files.pythonhosted.org/packages/9e/91/ea5e750e5f2896412fcbbe32da3be8ffab50f4221df7fe3ab367c51a99ac/py_sr25519_bindings-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ae6545c414cfa5d7207c9c77aaa576bb374982fb2105a7a9c2764afa5621f6d4", size = 523867 }, - { url = "https://files.pythonhosted.org/packages/7c/d0/e56f6753b264dd4c3f40364879429af7127c8b235c7a2f6d5fbb69137004/py_sr25519_bindings-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7046774e39e0166d3c12632969c9d1713e6ad9ca8206bbe82923ba6935b0a01f", size = 627828 }, - { url = "https://files.pythonhosted.org/packages/63/19/7a8d5cca0a498da55b0457be98f03e428e4981b563e5d1c8c92dfc7d136e/py_sr25519_bindings-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cba9a8821176895b080ea761e5ab9cd8727660bf401478a6532a30ae3429573d", size = 551658 }, - { url = "https://files.pythonhosted.org/packages/58/4e/083694bded9ce2d8d598f086aa4ca67f2b9c5d9bfd79ca46f04c95e9322b/py_sr25519_bindings-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c31aba05819e5b6b26746dc1b078cf680bd471f135c55e376e95c7774e22e936", size = 529627 }, - { url = "https://files.pythonhosted.org/packages/3d/cc/837b57c938d2b1d0e6f296dc09a3e65b0d762b2387301f8a51452d679391/py_sr25519_bindings-0.2.1-cp311-none-win32.whl", hash = "sha256:d4bfb9c9a5c46563ccf12e74862ee95d2961556ba7aca62c9e4d6e4f7c37b4e0", size = 217894 }, - { url = "https://files.pythonhosted.org/packages/5e/43/3f91ccad4b8d96ddf9a26b00be11de6ad0d260ab26e17ad8f98088512c3a/py_sr25519_bindings-0.2.1-cp311-none-win_amd64.whl", hash = "sha256:4f0d5c065d5e6122e53e771035aa335534363b451358b408d211df1c46773617", size = 224191 }, { url = "https://files.pythonhosted.org/packages/fa/6f/5dca831fe2617075237d49868d1bd4f025d0dbd23676d7dec3aaf39642cd/py_sr25519_bindings-0.2.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:01ef73c0b3d3f703b54ee69c0f5ff4aa54b4233212c466fd497c7a84d170963a", size = 330633 }, { url = "https://files.pythonhosted.org/packages/3e/86/569b69e01a962e0c3cd63465e5faad589e54f0c27bfaed5436fef283d56c/py_sr25519_bindings-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7ce8ac85e5ea82825a863f3f6f071e5ead610d7675820eb8ffe772267445ec0b", size = 306030 }, { url = "https://files.pythonhosted.org/packages/a1/ae/ad0d1fff92966b4ca020abc3ea12e3e1f34c3a937bab28fa0e6bf893d587/py_sr25519_bindings-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f59ac8c03c8ef819db063627f4a8247aab0db11d88b21562abbe371612cf66ab", size = 340266 }, @@ -2755,15 +3194,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/e2/bb29457851816c1637bdd7176ac419073faeecf452dcfae54b50ddb81bc1/py_sr25519_bindings-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c24bc55699d12948571969c26e65138a942bdaca062171288c40c44b9a4f266", size = 530013 }, { url = "https://files.pythonhosted.org/packages/4b/70/21d32090ca207738a3979620865e2a48ccbed64871cffafb24c6febe234d/py_sr25519_bindings-0.2.1-cp312-none-win32.whl", hash = "sha256:d4799c9a8f280abdfe564d397bad45da380275c8d22604e059bd7b3d5af404b5", size = 218181 }, { url = "https://files.pythonhosted.org/packages/bb/df/06a61ef52a6889d6879bfa8a5877688f62854c8eab491ad7af60e797a3ef/py_sr25519_bindings-0.2.1-cp312-none-win_amd64.whl", hash = "sha256:0746befd71d1766d8747910cfeb2cec2be2c859c3b3618eda1dc3cb4a1b85175", size = 224095 }, - { url = "https://files.pythonhosted.org/packages/bf/21/d5a2b0d2b518fa530cd7b08d8906390c9b3eb2476b8d04f3b9eeae848207/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50f8b34fed2c98814dcd414379ef43bf63cd4c05d7d90b83c590cca60fe804d6", size = 340467 }, - { url = "https://files.pythonhosted.org/packages/1e/92/38b9b6ae88eee7ffbe99fd0a577b82ff0ea2b030c92e85696355f1e1f0f4/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:141b0f8fb99cb249984f7c9ec67dd1768aae4d137d47ea0eca027d669503e132", size = 368253 }, - { url = "https://files.pythonhosted.org/packages/d5/c5/90eb85986e929e4e004a5c1f4f1f158bf792426d7ace5a2d59ed3557dc8e/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45cfef18bdfde67d445650a388bfafecbd1844a64c19087e9e4267548998c100", size = 384131 }, - { url = "https://files.pythonhosted.org/packages/31/04/a4575abaaf793836bd43f976288ab637edce45a0e523472d44681a3fe98c/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:639410c0258a543bb84b0518616af724716737054ac5c78daa4d956d17841b17", size = 365591 }, - { url = "https://files.pythonhosted.org/packages/b8/fb/1d32c088d5c297c4c9f6d6887cec43a5f4bb677b058291555cd1ede6d870/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a98e5a395445046f37fc4e365556ce06fa344e3b711de0564ac3fd2b351a1b3e", size = 385688 }, - { url = "https://files.pythonhosted.org/packages/9e/84/f2aa8a575d4dfc9d066dda65f571b814ac662cb81fbacaefd5244bb3df7b/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:7935b79a91aa72db42b5015117018554980c320256e63bc930b8bd148a0765a4", size = 524790 }, - { url = "https://files.pythonhosted.org/packages/33/eb/5f2cba82b804cda52625570875aaea108b9a1c7c01b3ea8a33f34b656420/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:cedf5d0669c23ddab8804982f665c7e99b13e8452db78128f231217b8528c31a", size = 628076 }, - { url = "https://files.pythonhosted.org/packages/c8/87/32b480bc5dfabf8e4ba43ced626d5ce40c0132fbb512ea22fbea63cf6447/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:9ea24db07992f756409729adad1e3ec9aa0a9d4fece5da90768a56ac1563f0f4", size = 552147 }, - { url = "https://files.pythonhosted.org/packages/0b/7f/88d3a788c85727076f512fae958d464f49046cdd9a88958cf39b2b47ceb4/py_sr25519_bindings-0.2.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e8b7e42cd4a5177dd83bbcdef77591fd72d3da02616545011ebcdd872f8cc39d", size = 530561 }, ] [[package]] @@ -2772,20 +3202,6 @@ version = "18.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7f/7b/640785a9062bb00314caa8a387abce547d2a420cf09bd6c715fe659ccffb/pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73", size = 1118671 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/bb/8d4a1573f66e0684f190dd2b55fd0b97a7214de8882d58a3867e777bf640/pyarrow-18.1.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e21488d5cfd3d8b500b3238a6c4b075efabc18f0f6d80b29239737ebd69caa6c", size = 29531620 }, - { url = "https://files.pythonhosted.org/packages/30/90/893acfad917533b624a97b9e498c0e8393908508a0a72d624fe935e632bf/pyarrow-18.1.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:b516dad76f258a702f7ca0250885fc93d1fa5ac13ad51258e39d402bd9e2e1e4", size = 30836521 }, - { url = "https://files.pythonhosted.org/packages/a3/2a/526545a7464b5fb2fa6e2c4bad16ca90e59e1843025c534fd907b7f73e5a/pyarrow-18.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f443122c8e31f4c9199cb23dca29ab9427cef990f283f80fe15b8e124bcc49b", size = 39213905 }, - { url = "https://files.pythonhosted.org/packages/8a/77/4b3fab91a30e19e233e738d0c5eca5a8f6dd05758bc349a2ca262c65de79/pyarrow-18.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a03da7f2758645d17b7b4f83c8bffeae5bbb7f974523fe901f36288d2eab71", size = 40128881 }, - { url = "https://files.pythonhosted.org/packages/aa/e2/a88e16c5e45e562449c52305bd3bc2f9d704295322d3434656e7ccac1444/pyarrow-18.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ba17845efe3aa358ec266cf9cc2800fa73038211fb27968bfa88acd09261a470", size = 38627517 }, - { url = "https://files.pythonhosted.org/packages/6d/84/8037c20005ccc7b869726465be0957bd9c29cfc88612962030f08292ad06/pyarrow-18.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:3c35813c11a059056a22a3bef520461310f2f7eea5c8a11ef9de7062a23f8d56", size = 40060187 }, - { url = "https://files.pythonhosted.org/packages/2a/38/d6435c723ff73df8ae74626ea778262fbcc2b9b0d1a4f3db915b61711b05/pyarrow-18.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9736ba3c85129d72aefa21b4f3bd715bc4190fe4426715abfff90481e7d00812", size = 25118314 }, - { url = "https://files.pythonhosted.org/packages/9e/4d/a4988e7d82f4fbc797715db4185939a658eeffb07a25bab7262bed1ea076/pyarrow-18.1.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:eaeabf638408de2772ce3d7793b2668d4bb93807deed1725413b70e3156a7854", size = 29554860 }, - { url = "https://files.pythonhosted.org/packages/59/03/3a42c5c1e4bd4c900ab62aa1ff6b472bdb159ba8f1c3e5deadab7222244f/pyarrow-18.1.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:3b2e2239339c538f3464308fd345113f886ad031ef8266c6f004d49769bb074c", size = 30867076 }, - { url = "https://files.pythonhosted.org/packages/75/7e/332055ac913373e89256dce9d14b7708f55f7bd5be631456c897f0237738/pyarrow-18.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f39a2e0ed32a0970e4e46c262753417a60c43a3246972cfc2d3eb85aedd01b21", size = 39212135 }, - { url = "https://files.pythonhosted.org/packages/8c/64/5099cdb325828722ef7ffeba9a4696f238eb0cdeae227f831c2d77fcf1bd/pyarrow-18.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31e9417ba9c42627574bdbfeada7217ad8a4cbbe45b9d6bdd4b62abbca4c6f6", size = 40125195 }, - { url = "https://files.pythonhosted.org/packages/83/88/1938d783727db1b178ff71bc6a6143d7939e406db83a9ec23cad3dad325c/pyarrow-18.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:01c034b576ce0eef554f7c3d8c341714954be9b3f5d5bc7117006b85fcf302fe", size = 38641884 }, - { url = "https://files.pythonhosted.org/packages/5e/b5/9e14e9f7590e0eaa435ecea84dabb137284a4dbba7b3c337b58b65b76d95/pyarrow-18.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f266a2c0fc31995a06ebd30bcfdb7f615d7278035ec5b1cd71c48d56daaf30b0", size = 40076877 }, - { url = "https://files.pythonhosted.org/packages/4d/a3/817ac7fe0891a2d66e247e223080f3a6a262d8aefd77e11e8c27e6acf4e1/pyarrow-18.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d4f13eee18433f99adefaeb7e01d83b59f73360c231d4782d9ddfaf1c3fbde0a", size = 25119811 }, { url = "https://files.pythonhosted.org/packages/6a/50/12829e7111b932581e51dda51d5cb39207a056c30fe31ef43f14c63c4d7e/pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d", size = 29514620 }, { url = "https://files.pythonhosted.org/packages/d1/41/468c944eab157702e96abab3d07b48b8424927d4933541ab43788bb6964d/pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee", size = 30856494 }, { url = "https://files.pythonhosted.org/packages/68/f9/29fb659b390312a7345aeb858a9d9c157552a8852522f2c8bad437c29c0a/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992", size = 39203624 }, @@ -2808,6 +3224,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c8/11/fabf6ecabb1fe5b7d96889228ca2a9158c4c3bb732e3b8ee3f7f6d40b703/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba", size = 40043567 }, ] +[[package]] +name = "pybboxes" +version = "0.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/93/5556dbd5a2f1c83e5c912b2a38ba81dd0b3961a0618b974ecfdb36b65701/pybboxes-0.1.6.tar.gz", hash = "sha256:558bfd2a7ca37def07ac95108f3b6504d728332b0c5b871df1017de5c7c1f68d", size = 19019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/3f/46f6613b41a3c2b4f7af3b526035771ca5bb12d6fdf3b23145899f785e36/pybboxes-0.1.6-py3-none-any.whl", hash = "sha256:e6f7ca43a38bfe2c6ec5b67c6f6e95790e5d9c8c41d84ef11ba896fe181816d5", size = 24858 }, +] + +[[package]] +name = "pyclipper" +version = "1.3.0.post6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/b2/550fe500e49c464d73fabcb8cb04d47e4885d6ca4cfc1f5b0a125a95b19a/pyclipper-1.3.0.post6.tar.gz", hash = "sha256:42bff0102fa7a7f2abdd795a2594654d62b786d0c6cd67b72d469114fdeb608c", size = 165909 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/c8/197d9a1d8354922d24d11d22fb2e0cc1ebc182f8a30496b7ddbe89467ce1/pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6363b9d79ba1b5d8f32d1623e797c1e9f994600943402e68d5266067bdde173e", size = 270487 }, + { url = "https://files.pythonhosted.org/packages/8e/8e/eb14eadf054494ad81446e21c4ea163b941747610b0eb9051644395f567e/pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32cd7fb9c1c893eb87f82a072dbb5e26224ea7cebbad9dc306d67e1ac62dd229", size = 143469 }, + { url = "https://files.pythonhosted.org/packages/cf/e5/6c4a8df6e904c133bb4c5309d211d31c751db60cbd36a7250c02b05494a1/pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3aab10e3c10ed8fa60c608fb87c040089b83325c937f98f06450cf9fcfdaf1d", size = 944206 }, + { url = "https://files.pythonhosted.org/packages/76/65/cb014acc41cd5bf6bbfa4671c7faffffb9cee01706642c2dec70c5209ac8/pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58eae2ff92a8cae1331568df076c4c5775bf946afab0068b217f0cf8e188eb3c", size = 963797 }, + { url = "https://files.pythonhosted.org/packages/80/ec/b40cd81ab7598984167508a5369a2fa31a09fe3b3e3d0b73aa50e06d4b3f/pyclipper-1.3.0.post6-cp312-cp312-win32.whl", hash = "sha256:793b0aa54b914257aa7dc76b793dd4dcfb3c84011d48df7e41ba02b571616eaf", size = 99456 }, + { url = "https://files.pythonhosted.org/packages/24/3a/7d6292e3c94fb6b872d8d7e80d909dc527ee6b0af73b753c63fdde65a7da/pyclipper-1.3.0.post6-cp312-cp312-win_amd64.whl", hash = "sha256:d3f9da96f83b8892504923beb21a481cd4516c19be1d39eb57a92ef1c9a29548", size = 110278 }, + { url = "https://files.pythonhosted.org/packages/8c/b3/75232906bd13f869600d23bdb8fe6903cc899fa7e96981ae4c9b7d9c409e/pyclipper-1.3.0.post6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f129284d2c7bcd213d11c0f35e1ae506a1144ce4954e9d1734d63b120b0a1b58", size = 268254 }, + { url = "https://files.pythonhosted.org/packages/0b/db/35843050a3dd7586781497a21ca6c8d48111afb66061cb40c3d3c288596d/pyclipper-1.3.0.post6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:188fbfd1d30d02247f92c25ce856f5f3c75d841251f43367dbcf10935bc48f38", size = 142204 }, + { url = "https://files.pythonhosted.org/packages/7c/d7/1faa0ff35caa02cb32cb0583688cded3f38788f33e02bfe6461fbcc1bee1/pyclipper-1.3.0.post6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d129d0c2587f2f5904d201a4021f859afbb45fada4261c9fdedb2205b09d23", size = 943835 }, + { url = "https://files.pythonhosted.org/packages/31/10/c0bf140bee2844e2c0617fdcc8a4e8daf98e71710046b06034e6f1963404/pyclipper-1.3.0.post6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c9c80b5c46eef38ba3f12dd818dc87f5f2a0853ba914b6f91b133232315f526", size = 962510 }, + { url = "https://files.pythonhosted.org/packages/85/6f/8c6afc49b51b1bf16d5903ecd5aee657cf88f52c83cb5fabf771deeba728/pyclipper-1.3.0.post6-cp313-cp313-win32.whl", hash = "sha256:b15113ec4fc423b58e9ae80aa95cf5a0802f02d8f02a98a46af3d7d66ff0cc0e", size = 98836 }, + { url = "https://files.pythonhosted.org/packages/d5/19/9ff4551b42f2068686c50c0d199072fa67aee57fc5cf86770cacf71efda3/pyclipper-1.3.0.post6-cp313-cp313-win_amd64.whl", hash = "sha256:e5ff68fa770ac654c7974fc78792978796f068bd274e95930c0691c31e192889", size = 109672 }, +] + [[package]] name = "pycparser" version = "2.22" @@ -2835,10 +3283,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/1d/81d59d228381576b92ecede5cd7239762c14001a828bdba30d64896e9778/pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53", size = 1812863 }, { url = "https://files.pythonhosted.org/packages/25/b3/09ff7072e6d96c9939c24cf51d3c389d7c345bf675420355c22402f71b68/pycryptodome-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca", size = 1691593 }, { url = "https://files.pythonhosted.org/packages/a8/91/38e43628148f68ba9b68dedbc323cf409e537fd11264031961fd7c744034/pycryptodome-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd", size = 1765997 }, - { url = "https://files.pythonhosted.org/packages/08/16/ae464d4ac338c1dd41f89c41f9488e54f7d2a3acf93bb920bb193b99f8e3/pycryptodome-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d5ebe0763c982f069d3877832254f64974139f4f9655058452603ff559c482e8", size = 1615855 }, - { url = "https://files.pythonhosted.org/packages/1e/8c/b0cee957eee1950ce7655006b26a8894cee1dc4b8747ae913684352786eb/pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee86cbde706be13f2dec5a42b52b1c1d1cbb90c8e405c68d0755134735c8dc6", size = 1650018 }, - { url = "https://files.pythonhosted.org/packages/93/4d/d7138068089b99f6b0368622e60f97a577c936d75f533552a82613060c58/pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fd54003ec3ce4e0f16c484a10bc5d8b9bd77fa662a12b85779a2d2d85d67ee0", size = 1687977 }, - { url = "https://files.pythonhosted.org/packages/96/02/90ae1ac9f28be4df0ed88c127bf4acc1b102b40053e172759d4d1c54d937/pycryptodome-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5dfafca172933506773482b0e18f0cd766fd3920bd03ec85a283df90d8a17bc6", size = 1788273 }, ] [[package]] @@ -2864,31 +3308,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/a0/a7/61d013c73773bb03d02de9de8e4e5b2ed2c100dc98ae7046d54485ecf5d4/pydantic_core-2.16.1.tar.gz", hash = "sha256:daff04257b49ab7f4b3f73f98283d3dbb1a65bf3500d55c7beac3c66c310fe34", size = 368201 } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/7c/72819fce116e969f45497206cb84966d99d0b2a8be0173cc3d57b29ee0ff/pydantic_core-2.16.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:300616102fb71241ff477a2cbbc847321dbec49428434a2f17f37528721c4948", size = 1903838 }, - { url = "https://files.pythonhosted.org/packages/a2/a3/142ef9bb5417a67dab782ca5add911fa76a3bef21a5b77ad343683e8c584/pydantic_core-2.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5511f962dd1b9b553e9534c3b9c6a4b0c9ded3d8c2be96e61d56f933feef9e1f", size = 1745896 }, - { url = "https://files.pythonhosted.org/packages/57/08/a6c8392e7d7d08dca77d7cd8531349eeba779b10a0648520ebb23f48fcbf/pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98f0edee7ee9cc7f9221af2e1b95bd02810e1c7a6d115cfd82698803d385b28f", size = 1900593 }, - { url = "https://files.pythonhosted.org/packages/14/03/3f67a97640dd7b252dfecc7f9ba0e4c84cb520885af2f219c40b54e4a297/pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9795f56aa6b2296f05ac79d8a424e94056730c0b860a62b0fdcfe6340b658cc8", size = 1915761 }, - { url = "https://files.pythonhosted.org/packages/f2/49/0f7e0aad58a2620306e190064d316430d455bda1bc40ce35be96252c1346/pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c45f62e4107ebd05166717ac58f6feb44471ed450d07fecd90e5f69d9bf03c48", size = 2065743 }, - { url = "https://files.pythonhosted.org/packages/3f/b6/eb8f5511810d8d7d2c18228ee2a414fe200678a897e938186189dc128b05/pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:462d599299c5971f03c676e2b63aa80fec5ebc572d89ce766cd11ca8bcb56f3f", size = 3259998 }, - { url = "https://files.pythonhosted.org/packages/a0/7e/96bd9a40f6d5edc3b4566951acac7a8f26e423c77001a7dc065d6200052a/pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ebaa4bf6386a3b22eec518da7d679c8363fb7fb70cf6972161e5542f470798", size = 2180287 }, - { url = "https://files.pythonhosted.org/packages/1e/2a/2fd602af15a12dddf0d5b931639ce1a190ca5256e93e68fae209bcb05118/pydantic_core-2.16.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:99f9a50b56713a598d33bc23a9912224fc5d7f9f292444e6664236ae471ddf17", size = 1973184 }, - { url = "https://files.pythonhosted.org/packages/8b/84/8f2ec2a6e961236c202a9ef36f9f498d19ae9305cfbbd4135dab4a8273ed/pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8ec364e280db4235389b5e1e6ee924723c693cbc98e9d28dc1767041ff9bc388", size = 2071546 }, - { url = "https://files.pythonhosted.org/packages/8e/d3/abdf35c0fad643b82cba7cec1d5875b8ffdaf2d775ee12f916c301edcbad/pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:653a5dfd00f601a0ed6654a8b877b18d65ac32c9d9997456e0ab240807be6cf7", size = 2212439 }, - { url = "https://files.pythonhosted.org/packages/85/60/d78c76282beec6260c449331f428e17a228836895a24a8648cbe95aead59/pydantic_core-2.16.1-cp310-none-win32.whl", hash = "sha256:1661c668c1bb67b7cec96914329d9ab66755911d093bb9063c4c8914188af6d4", size = 1761095 }, - { url = "https://files.pythonhosted.org/packages/71/1a/e6a75d2768b8e53eebdc6146a839c0d5629b1f985b3a44d39e4829c8d39e/pydantic_core-2.16.1-cp310-none-win_amd64.whl", hash = "sha256:561be4e3e952c2f9056fba5267b99be4ec2afadc27261505d4992c50b33c513c", size = 1927450 }, - { url = "https://files.pythonhosted.org/packages/25/95/1357b15051f458a15eeaf03d35d0f7466ec8979eb69061dacc6d8f7924d9/pydantic_core-2.16.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:102569d371fadc40d8f8598a59379c37ec60164315884467052830b28cc4e9da", size = 1900014 }, - { url = "https://files.pythonhosted.org/packages/3d/98/11b5400a80f527f5d6c5bdb71a77f4cf3754bef5d512d94471ecbc32e2ff/pydantic_core-2.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:735dceec50fa907a3c314b84ed609dec54b76a814aa14eb90da31d1d36873a5e", size = 1745364 }, - { url = "https://files.pythonhosted.org/packages/20/25/55f41724bd64954fba6e661d6afa3d5a1b3561c0611f5c4e523fee3da640/pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e83ebbf020be727d6e0991c1b192a5c2e7113eb66e3def0cd0c62f9f266247e4", size = 1899615 }, - { url = "https://files.pythonhosted.org/packages/54/5e/6fcf81f868f634f8a163867df89303033cc18985daf693f34475f40117aa/pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:30a8259569fbeec49cfac7fda3ec8123486ef1b729225222f0d41d5f840b476f", size = 1914379 }, - { url = "https://files.pythonhosted.org/packages/2c/8b/930c415a0c1f24ceb602f208b698c21ab80b869a416803ac2a45fdecc006/pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920c4897e55e2881db6a6da151198e5001552c3777cd42b8a4c2f72eedc2ee91", size = 2064696 }, - { url = "https://files.pythonhosted.org/packages/ac/ef/4a84325e7734d8f0b29295b99498ef4c78c8b7ba4b2db51a2b27b6627a17/pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5247a3d74355f8b1d780d0f3b32a23dd9f6d3ff43ef2037c6dcd249f35ecf4c", size = 3258963 }, - { url = "https://files.pythonhosted.org/packages/98/19/955b83b6e33b7ac27914860069a918fe49b29c13bc149dc7bb7c60954812/pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5bea8012df5bb6dda1e67d0563ac50b7f64a5d5858348b5c8cb5043811c19d", size = 2179513 }, - { url = "https://files.pythonhosted.org/packages/a2/14/70e69bcca582a02b939c4eb2ba6df5f474aa0e9c41103986952d3a05da27/pydantic_core-2.16.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ed3025a8a7e5a59817b7494686d449ebfbe301f3e757b852c8d0d1961d6be864", size = 1972273 }, - { url = "https://files.pythonhosted.org/packages/fc/35/994e7001bacfc117d22c64b87426b2fd7539ee9963f7159e7ec60f100720/pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06f0d5a1d9e1b7932477c172cc720b3b23c18762ed7a8efa8398298a59d177c7", size = 2070589 }, - { url = "https://files.pythonhosted.org/packages/e3/e1/7a47532a6046636af04c58710517bc7dc9a76bface7a74edb7913a98950f/pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:150ba5c86f502c040b822777e2e519b5625b47813bd05f9273a8ed169c97d9ae", size = 2211305 }, - { url = "https://files.pythonhosted.org/packages/77/e9/d05dc5c8e7c5698026f01de6affe04015eb719e216a4c4b70cd7946c2785/pydantic_core-2.16.1-cp311-none-win32.whl", hash = "sha256:d6cbdf12ef967a6aa401cf5cdf47850559e59eedad10e781471c960583f25aa1", size = 1760415 }, - { url = "https://files.pythonhosted.org/packages/b2/47/14bf2397a5daa0cc1b99306499a39525966b73aba4d31b17e373e229f07e/pydantic_core-2.16.1-cp311-none-win_amd64.whl", hash = "sha256:afa01d25769af33a8dac0d905d5c7bb2d73c7c3d5161b2dd6f8b5b5eea6a3c4c", size = 1926977 }, - { url = "https://files.pythonhosted.org/packages/df/e0/0b108193cb405c21bdb3a6d95fdf4d0ae65accd187d9df5b8011c484f275/pydantic_core-2.16.1-cp311-none-win_arm64.whl", hash = "sha256:1a2fe7b00a49b51047334d84aafd7e39f80b7675cad0083678c58983662da89b", size = 1844114 }, { url = "https://files.pythonhosted.org/packages/82/8f/d83953f652b0048fd8be62b6eabed7e3397008b6d050bd080ab78d3e6d14/pydantic_core-2.16.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f478ec204772a5c8218e30eb813ca43e34005dff2eafa03931b3d8caef87d51", size = 1868275 }, { url = "https://files.pythonhosted.org/packages/c1/33/f627f1d31f7986ade7396237a8b5904c629837878978e9eeb400f85b3e29/pydantic_core-2.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1936ef138bed2165dd8573aa65e3095ef7c2b6247faccd0e15186aabdda7f66", size = 1718240 }, { url = "https://files.pythonhosted.org/packages/f5/7e/1bcd8ce164868c40d841528f92e5f1f5a1a6cb705a063c425cd00f8b1eef/pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d3a433ef5dc3021c9534a58a3686c88363c591974c16c54a01af7efd741f13", size = 1873433 }, @@ -2902,14 +3321,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/81/fce7fcd7f877ab56c2e677366da41abdd3071dcc6592354bc96f0135c43a/pydantic_core-2.16.1-cp312-none-win32.whl", hash = "sha256:b9f3e0bffad6e238f7acc20c393c1ed8fab4371e3b3bc311020dfa6020d99212", size = 1764252 }, { url = "https://files.pythonhosted.org/packages/ab/de/32c35c9d84652da17673357295d19016cc4768fea0dd071d8c09ca4cacbb/pydantic_core-2.16.1-cp312-none-win_amd64.whl", hash = "sha256:a0b4cfe408cd84c53bab7d83e4209458de676a6ec5e9c623ae914ce1cb79b96f", size = 1881165 }, { url = "https://files.pythonhosted.org/packages/61/db/78cafc630e4b3193c5a702ae20916349f5f0ef5bdaa918565549f3160059/pydantic_core-2.16.1-cp312-none-win_arm64.whl", hash = "sha256:d195add190abccefc70ad0f9a0141ad7da53e16183048380e688b466702195dd", size = 1853531 }, - { url = "https://files.pythonhosted.org/packages/5f/06/f92cb6971b2163488f2d908095a35bdc38a8b30970674abb1473d407e07e/pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d25ef0c33f22649b7a088035fd65ac1ce6464fa2876578df1adad9472f918a76", size = 1906426 }, - { url = "https://files.pythonhosted.org/packages/18/f1/f4d89d38dc82c2f973e2487504bb3cd30d62340a249e64871f1fe6293049/pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99c095457eea8550c9fa9a7a992e842aeae1429dab6b6b378710f62bfb70b394", size = 1766939 }, - { url = "https://files.pythonhosted.org/packages/4c/10/e714042114547b25a2c792ad72251447407eef9449501573b3fc20b8d02c/pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b49c604ace7a7aa8af31196abbf8f2193be605db6739ed905ecaf62af31ccae0", size = 1911451 }, - { url = "https://files.pythonhosted.org/packages/30/e3/802b56e1207ae8c581bdfb838340c4bd404a7a924a5c26ea3fe615db116e/pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56da23034fe66221f2208c813d8aa509eea34d97328ce2add56e219c3a9f41c", size = 2036878 }, - { url = "https://files.pythonhosted.org/packages/6c/dd/572df4ced42416411c19d427b37e247cbeddbc8c05f5586aea62cce6b753/pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cebf8d56fee3b08ad40d332a807ecccd4153d3f1ba8231e111d9759f02edfd05", size = 1968197 }, - { url = "https://files.pythonhosted.org/packages/87/53/af8141c6ca8b8e2a8e73314bcd313554fc9d01e063f02e13386dff3000b8/pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:1ae8048cba95f382dba56766525abca438328455e35c283bb202964f41a780b0", size = 2083060 }, - { url = "https://files.pythonhosted.org/packages/c8/71/6459ce68d47a30a932b97b49f1c6f66743821c4a410dd10c41b0e347d264/pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:780daad9e35b18d10d7219d24bfb30148ca2afc309928e1d4d53de86822593dc", size = 2216609 }, - { url = "https://files.pythonhosted.org/packages/ec/20/b76edaec257bf631929dcbefaaa7a7c621ede570a99140f404b6278a433b/pydantic_core-2.16.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c94b5537bf6ce66e4d7830c6993152940a188600f6ae044435287753044a8fe2", size = 2027647 }, ] [[package]] @@ -2968,17 +3379,51 @@ version = "8.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } wheels = [ { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, ] +[[package]] +name = "python-bidi" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/b5/c1684fb8120ad868b43e4b037ac83aafaa70724cdb078d066bae836c31f6/python_bidi-0.6.3.tar.gz", hash = "sha256:e12114969001a328aea859f79efc30ab9c15241befb86e07029d8961d97fae36", size = 45054 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/15/4f316e1b463ea71b4533e904548f47000f9e20ad394b3e8c83ac65638534/python_bidi-0.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4bdc9dc1143c558ca6931d6712339a30470959f2b7eecb3d0687db7075c20a87", size = 257039 }, + { url = "https://files.pythonhosted.org/packages/ad/2a/9b54acafa641c360ba3a632726046a8da38da1f3ed7b816ae6eec748ca95/python_bidi-0.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0775499b8037103278f05b2bf92d25bf04f40a9f77884ec3d42b01a1e52a40fe", size = 252791 }, + { url = "https://files.pythonhosted.org/packages/ca/b2/55f4d18760dac53e542817b3a2e11e3d5ae631678f2c2248c6f1e67336ea/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb3091aa5efbfc4da6fd52a2fccbf7853c6dc253ddaf9a189bcf3c4345865aa9", size = 294297 }, + { url = "https://files.pythonhosted.org/packages/63/7b/d1ca351cdab44300296a4b758a370bf923666deaa4d19819a7fc4478dc52/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75a9b68b3f5a8da9a33fe37607d9b267a8a3c5806d283a4a47365256773dd1e", size = 297650 }, + { url = "https://files.pythonhosted.org/packages/e4/9e/bcb93f0e30abbd9fbbda736402e3ee86566b1253ea57d1e9b465eb4b62a0/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:208e09819ee0485c2ed4dc1932c39fc073dac3f2cb70b6d2ae0b7296e86831e6", size = 323959 }, + { url = "https://files.pythonhosted.org/packages/26/dd/e795e1186ba6b9e3ebb9bc316c6572036e59d0c2841a5e182403d483eb57/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e17b67d86cd38f2bebc4a46090f83cabb0d1da3a3c920c68efe8093ae1a8d0d1", size = 327207 }, + { url = "https://files.pythonhosted.org/packages/c9/27/fc5777273bcb3de7b82524852995edd67dc9948ed916a3f9236714628ae3/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933a17938f767fa64a8365732eba787a81c26214d89e1b3abe87912325ba26a9", size = 286371 }, + { url = "https://files.pythonhosted.org/packages/e9/11/90b4072461759074564a29d05a83128f02214fea1012ca0984d3a5a7fdbb/python_bidi-0.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:772efb3e0ef17396bfd9d47da4805c74ed6c04f27cac08d7757f76602837fb9d", size = 300797 }, + { url = "https://files.pythonhosted.org/packages/61/2c/d50a50adf51b6c53022b9ad247d65cad50acde94f877fbab49a8854ccaae/python_bidi-0.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9a99114f33f8c0273a61b4afe7d4d715e098318ee4e5ce8f6bb5da8dcd3f95c7", size = 468349 }, + { url = "https://files.pythonhosted.org/packages/68/46/a9d285552961670aedc0bbac403defa7c9c4b17ab6957ef0b7db05d12eec/python_bidi-0.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b30e620d39e85a30bb42f460fd8b5274caf261517edeb853b975d9ea1939b6bd", size = 549521 }, + { url = "https://files.pythonhosted.org/packages/36/3a/5b6b6b73a210cc99b1d1ea697741fc4fc1853aa788b3db1737bfdcaabc75/python_bidi-0.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bee94e3152a6c9ba731e086c9cc6203904290506ba52c505a2e59abab481eb13", size = 471509 }, + { url = "https://files.pythonhosted.org/packages/dd/5a/d3d0588caeb041bbf85b421b30a7a9893057c72fcddcaaf2d54289123487/python_bidi-0.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:926164ec594e9ea9a64faf54273c711d5e3233bcc6ef8966c6eeaddfb3b3075f", size = 451688 }, + { url = "https://files.pythonhosted.org/packages/1d/1f/0539881d381dda2a281056ccfb55905439bad86cfb2787792065a2d8d08b/python_bidi-0.6.3-cp312-none-win32.whl", hash = "sha256:cea395a7daee14c7d50a7e20890d12b9ff1938d81b23eb564f1707a175c37202", size = 151600 }, + { url = "https://files.pythonhosted.org/packages/aa/e7/b4f6a71a7e7aec0e17c36bfca2ff5d962b86b0e04e278de6fc6c0cd6d643/python_bidi-0.6.3-cp312-none-win_amd64.whl", hash = "sha256:350e6c76f942465871f2b473a2076f5002f1df06e4c7abee3029ccca5f006786", size = 156933 }, + { url = "https://files.pythonhosted.org/packages/61/77/957be8fa4f681c31af11323376ecd2dcac6cfbadb12e8555e2b826cf6653/python_bidi-0.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:617d4391b19969de725922a256599e8218fc9c1ef0ff85884f1698fff482a977", size = 256754 }, + { url = "https://files.pythonhosted.org/packages/39/7e/b66b86a1aca9725366b6f4fd549ff36162de7acd53ba13de7704cbc56968/python_bidi-0.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:81f418d54948542b21c03cd8ce622a480ead85fc53175a124c4562bdf55cec49", size = 252526 }, + { url = "https://files.pythonhosted.org/packages/cb/c9/149957c1d75156bf617fd9824b92b7468926a4ba90e74cf1254acffb31de/python_bidi-0.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0999b77af58396cfd789c8d068bac78d2d51363265aaf1369622099be9e0eb32", size = 294071 }, + { url = "https://files.pythonhosted.org/packages/63/50/29ba7d99cb23138da8ce070a677cc1eaaa1545f18cb54350d702366e94f3/python_bidi-0.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f5a0e852e8451147d96876f8233a9db6ed28c914d9767a6696cbc899e7df00c2", size = 297250 }, + { url = "https://files.pythonhosted.org/packages/c5/4b/793e5fe37dc59ec9c48349bfa6373aa771a02dc2270b14a90fa6d14a7920/python_bidi-0.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905e212b12c9edfaa3a916a3acd11426b89507ed0f31641257ad586467602e8d", size = 323242 }, + { url = "https://files.pythonhosted.org/packages/b8/ae/f0be940ac876ef120e26a40b3614996e2ae55c8abdd0e98e7e102207309a/python_bidi-0.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:144adab8dc3a8560e294461114ce6dafec1a986cde6297994c1d31b3252f3298", size = 326738 }, + { url = "https://files.pythonhosted.org/packages/9a/1f/7757dacc33e95e38fd47f02dcb6d4f67ffce0ea432ae7299568129aa55a8/python_bidi-0.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abdbd5c265d64251798243d97228bb78441a1320fe3cf51c9a31191c56407839", size = 286038 }, + { url = "https://files.pythonhosted.org/packages/7a/b0/be55c786d54e8d37e38ca5199727619d616312daeefa8a0be21ef1366412/python_bidi-0.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f824a878a593121570ce3da847d3b9ac50521782c433996d7f81f770d3ed00", size = 300370 }, + { url = "https://files.pythonhosted.org/packages/e1/c8/3d89cef5bd8ebe6fa262f5035763ef99878a7634455a6b0eeb2abf0f1c0d/python_bidi-0.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c7dcbc7eb70a0c7c66ed5219213ee2afcc815988cb9e4b134631579c4ae46980", size = 467798 }, + { url = "https://files.pythonhosted.org/packages/44/52/9b7ee589eae15637722e17b7fcfa7f1ad7ead4b623c46fed23eaa687144c/python_bidi-0.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ccbf53bc71a0a1b7f77524d1c2e51b245ae23a4f16afb80728071e21c187a768", size = 549247 }, + { url = "https://files.pythonhosted.org/packages/c8/08/6cf732e0945e1aa03e189c61288f1d67acd051ffbaae84cb85e329d22d33/python_bidi-0.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:702527506ca97bf549710ce03d89a2577ebe35e34c42eaecfbacb0862ba06dc6", size = 471227 }, + { url = "https://files.pythonhosted.org/packages/06/b6/929538d89494ae493bd67ed671e9a01c88b9054a5dc85dd4f400641cbbe7/python_bidi-0.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1563a8d9cfaeeeb5b4fc806f52a500b19893c63652bbd497dd6ed9def7b9ee8e", size = 451042 }, + { url = "https://files.pythonhosted.org/packages/af/a4/d5e062e07fbf070c8982468280bae6418c513628df7881fdf46a5a02fba4/python_bidi-0.6.3-cp313-none-win32.whl", hash = "sha256:f9b8e024eeaddecb4ca189e3199181985fab20c224db9a1f08db48b905c9905a", size = 151261 }, + { url = "https://files.pythonhosted.org/packages/b3/54/0b698ac336192ef5ae5c055ff60fa7d15f2411ce107129d3703323196ef2/python_bidi-0.6.3-cp313-none-win_amd64.whl", hash = "sha256:36b3fb05ef990613a81a23822246eaf6eef29af5182f8d8cdd174be13c92d1cc", size = 156666 }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -3000,6 +3445,15 @@ 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 = "python-json-logger" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/c4/358cd13daa1d912ef795010897a483ab2f0b41c9ea1b35235a8b2f7d15a7/python_json_logger-3.2.1.tar.gz", hash = "sha256:8eb0554ea17cb75b05d2848bc14fb02fbdbd9d6972120781b974380bfa162008", size = 16287 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/72/2f30cf26664fcfa0bd8ec5ee62ec90c03bd485e4a294d92aabc76c5203a5/python_json_logger-3.2.1-py3-none-any.whl", hash = "sha256:cdc17047eb5374bd311e748b42f99d71223f3b0e186f4206cc5d52aefe85b090", size = 14924 }, +] + [[package]] name = "python-levenshtein" version = "0.26.1" @@ -3030,30 +3484,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, ] +[[package]] +name = "pywin32" +version = "308" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, + { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, + { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, + { url = "https://files.pythonhosted.org/packages/a9/a4/aa562d8935e3df5e49c161b427a3a2efad2ed4e9cf81c3de636f1fdddfd0/pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", size = 5938579 }, + { url = "https://files.pythonhosted.org/packages/c7/50/b0efb8bb66210da67a53ab95fd7a98826a97ee21f1d22949863e6d588b22/pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4", size = 6542056 }, + { url = "https://files.pythonhosted.org/packages/26/df/2b63e3e4f2df0224f8aaf6d131f54fe4e8c96400eb9df563e2aae2e1a1f9/pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", size = 7974986 }, +] + +[[package]] +name = "pywinpty" +version = "2.0.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/82/90f8750423cba4b9b6c842df227609fb60704482d7abf6dd47e2babc055a/pywinpty-2.0.14.tar.gz", hash = "sha256:18bd9529e4a5daf2d9719aa17788ba6013e594ae94c5a0c27e83df3278b0660e", size = 27769 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/79/759ae767a3b78d340446efd54dd1fe4f7dafa4fc7be96ed757e44bcdba54/pywinpty-2.0.14-cp312-none-win_amd64.whl", hash = "sha256:55dad362ef3e9408ade68fd173e4f9032b3ce08f68cfe7eacb2c263ea1179737", size = 1397207 }, + { url = "https://files.pythonhosted.org/packages/7d/34/b77b3c209bf2eaa6455390c8d5449241637f5957f41636a2204065d52bfa/pywinpty-2.0.14-cp313-none-win_amd64.whl", hash = "sha256:074fb988a56ec79ca90ed03a896d40707131897cefb8f76f926e3834227f2819", size = 1396698 }, +] + [[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 }, @@ -3074,42 +3533,56 @@ wheels = [ { 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 = "pyzmq" +version = "26.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/05/bed626b9f7bb2322cdbbf7b4bd8f54b1b617b0d2ab2d3547d6e39428a48e/pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f", size = 271975 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/2f/78a766c8913ad62b28581777ac4ede50c6d9f249d39c2963e279524a1bbe/pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9", size = 1343105 }, + { url = "https://files.pythonhosted.org/packages/b7/9c/4b1e2d3d4065be715e007fe063ec7885978fad285f87eae1436e6c3201f4/pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52", size = 1008365 }, + { url = "https://files.pythonhosted.org/packages/4f/ef/5a23ec689ff36d7625b38d121ef15abfc3631a9aecb417baf7a4245e4124/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08", size = 665923 }, + { url = "https://files.pythonhosted.org/packages/ae/61/d436461a47437d63c6302c90724cf0981883ec57ceb6073873f32172d676/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5", size = 903400 }, + { url = "https://files.pythonhosted.org/packages/47/42/fc6d35ecefe1739a819afaf6f8e686f7f02a4dd241c78972d316f403474c/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae", size = 860034 }, + { url = "https://files.pythonhosted.org/packages/07/3b/44ea6266a6761e9eefaa37d98fabefa112328808ac41aa87b4bbb668af30/pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711", size = 860579 }, + { url = "https://files.pythonhosted.org/packages/38/6f/4df2014ab553a6052b0e551b37da55166991510f9e1002c89cab7ce3b3f2/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6", size = 1196246 }, + { url = "https://files.pythonhosted.org/packages/38/9d/ee240fc0c9fe9817f0c9127a43238a3e28048795483c403cc10720ddef22/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3", size = 1507441 }, + { url = "https://files.pythonhosted.org/packages/85/4f/01711edaa58d535eac4a26c294c617c9a01f09857c0ce191fd574d06f359/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b", size = 1406498 }, + { url = "https://files.pythonhosted.org/packages/07/18/907134c85c7152f679ed744e73e645b365f3ad571f38bdb62e36f347699a/pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7", size = 575533 }, + { url = "https://files.pythonhosted.org/packages/ce/2c/a6f4a20202a4d3c582ad93f95ee78d79bbdc26803495aec2912b17dbbb6c/pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a", size = 637768 }, + { url = "https://files.pythonhosted.org/packages/5f/0e/eb16ff731632d30554bf5af4dbba3ffcd04518219d82028aea4ae1b02ca5/pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b", size = 540675 }, + { url = "https://files.pythonhosted.org/packages/04/a7/0f7e2f6c126fe6e62dbae0bc93b1bd3f1099cf7fea47a5468defebe3f39d/pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726", size = 1006564 }, + { url = "https://files.pythonhosted.org/packages/31/b6/a187165c852c5d49f826a690857684333a6a4a065af0a6015572d2284f6a/pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3", size = 1340447 }, + { url = "https://files.pythonhosted.org/packages/68/ba/f4280c58ff71f321602a6e24fd19879b7e79793fb8ab14027027c0fb58ef/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50", size = 665485 }, + { url = "https://files.pythonhosted.org/packages/77/b5/c987a5c53c7d8704216f29fc3d810b32f156bcea488a940e330e1bcbb88d/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb", size = 903484 }, + { url = "https://files.pythonhosted.org/packages/29/c9/07da157d2db18c72a7eccef8e684cefc155b712a88e3d479d930aa9eceba/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187", size = 859981 }, + { url = "https://files.pythonhosted.org/packages/43/09/e12501bd0b8394b7d02c41efd35c537a1988da67fc9c745cae9c6c776d31/pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b", size = 860334 }, + { url = "https://files.pythonhosted.org/packages/eb/ff/f5ec1d455f8f7385cc0a8b2acd8c807d7fade875c14c44b85c1bddabae21/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18", size = 1196179 }, + { url = "https://files.pythonhosted.org/packages/ec/8a/bb2ac43295b1950fe436a81fc5b298be0b96ac76fb029b514d3ed58f7b27/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115", size = 1507668 }, + { url = "https://files.pythonhosted.org/packages/a9/49/dbc284ebcfd2dca23f6349227ff1616a7ee2c4a35fe0a5d6c3deff2b4fed/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e", size = 1406539 }, + { url = "https://files.pythonhosted.org/packages/00/68/093cdce3fe31e30a341d8e52a1ad86392e13c57970d722c1f62a1d1a54b6/pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5", size = 575567 }, + { url = "https://files.pythonhosted.org/packages/92/ae/6cc4657148143412b5819b05e362ae7dd09fb9fe76e2a539dcff3d0386bc/pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad", size = 637551 }, + { url = "https://files.pythonhosted.org/packages/6c/67/fbff102e201688f97c8092e4c3445d1c1068c2f27bbd45a578df97ed5f94/pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797", size = 540378 }, + { url = "https://files.pythonhosted.org/packages/3f/fe/2d998380b6e0122c6c4bdf9b6caf490831e5f5e2d08a203b5adff060c226/pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a", size = 1007378 }, + { url = "https://files.pythonhosted.org/packages/4a/f4/30d6e7157f12b3a0390bde94d6a8567cdb88846ed068a6e17238a4ccf600/pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc", size = 1329532 }, + { url = "https://files.pythonhosted.org/packages/82/86/3fe917870e15ee1c3ad48229a2a64458e36036e64b4afa9659045d82bfa8/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5", size = 653242 }, + { url = "https://files.pythonhosted.org/packages/50/2d/242e7e6ef6c8c19e6cb52d095834508cd581ffb925699fd3c640cdc758f1/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672", size = 888404 }, + { url = "https://files.pythonhosted.org/packages/ac/11/7270566e1f31e4ea73c81ec821a4b1688fd551009a3d2bab11ec66cb1e8f/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797", size = 845858 }, + { url = "https://files.pythonhosted.org/packages/91/d5/72b38fbc69867795c8711bdd735312f9fef1e3d9204e2f63ab57085434b9/pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386", size = 847375 }, + { url = "https://files.pythonhosted.org/packages/dd/9a/10ed3c7f72b4c24e719c59359fbadd1a27556a28b36cdf1cd9e4fb7845d5/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306", size = 1183489 }, + { url = "https://files.pythonhosted.org/packages/72/2d/8660892543fabf1fe41861efa222455811adac9f3c0818d6c3170a1153e3/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6", size = 1492932 }, + { url = "https://files.pythonhosted.org/packages/7b/d6/32fd69744afb53995619bc5effa2a405ae0d343cd3e747d0fbc43fe894ee/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0", size = 1392485 }, +] + [[package]] name = "rapidfuzz" version = "3.11.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a4/aa/25e5a20131369d82c7b8288c99c2c3011ec47a3f5953ccc9cb8145720be5/rapidfuzz-3.11.0.tar.gz", hash = "sha256:a53ca4d3f52f00b393fab9b5913c5bafb9afc27d030c8a1db1283da6917a860f", size = 57983000 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/70/820ebf9eb22ad97b9e0bb9fd1ad8c6be4c8db5a0974d12ce27b5c9a30db0/rapidfuzz-3.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb8a54543d16ab1b69e2c5ed96cabbff16db044a50eddfc028000138ca9ddf33", size = 1954240 }, - { url = "https://files.pythonhosted.org/packages/41/bc/e39abdc28160d8147ccab0aa922a29be50529dcf149615a68a324ff6f9b1/rapidfuzz-3.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:231c8b2efbd7f8d2ecd1ae900363ba168b8870644bb8f2b5aa96e4a7573bde19", size = 1427139 }, - { url = "https://files.pythonhosted.org/packages/b6/2d/19b8e5d80257b13d73ba994552b78a69ac2ed70f1de716f1b02fcb84d09c/rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54e7f442fb9cca81e9df32333fb075ef729052bcabe05b0afc0441f462299114", size = 1419602 }, - { url = "https://files.pythonhosted.org/packages/8c/82/1fc80cc531ec712872025c19118d78eb23aff09c7144b380c2c4b544b0d1/rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:906f1f2a1b91c06599b3dd1be207449c5d4fc7bd1e1fa2f6aef161ea6223f165", size = 5635370 }, - { url = "https://files.pythonhosted.org/packages/3c/5c/007b90af25f98e301b5f7a095059b09f602701443d555724c9226a45514c/rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed59044aea9eb6c663112170f2399b040d5d7b162828b141f2673e822093fa8", size = 1680848 }, - { url = "https://files.pythonhosted.org/packages/01/04/e481530eff5d1cf337b86a3095dd4de0b758c37291e51eb0d8c4f7d49719/rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cb1965a28b0fa64abdee130c788a0bc0bb3cf9ef7e3a70bf055c086c14a3d7e", size = 1682131 }, - { url = "https://files.pythonhosted.org/packages/10/15/b0ec18edfe6146d8915679644ab7584cd0165724d6a53bcc43bd59f8edb5/rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b488b244931d0291412917e6e46ee9f6a14376625e150056fe7c4426ef28225", size = 3134097 }, - { url = "https://files.pythonhosted.org/packages/8b/0e/cf0a5d62977381bca981fc171fd6c85dc52ca1239eaacf9c1d38978c5866/rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f0ba13557fec9d5ffc0a22826754a7457cc77f1b25145be10b7bb1d143ce84c6", size = 2332928 }, - { url = "https://files.pythonhosted.org/packages/dc/71/568d383eb36586c9e7e13f1327203e2be0938e5ff070c1fa2a99b418808e/rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3871fa7dfcef00bad3c7e8ae8d8fd58089bad6fb21f608d2bf42832267ca9663", size = 6940409 }, - { url = "https://files.pythonhosted.org/packages/ba/23/02972657d69e6d3aae2cdbd67debad080410ff9ef8849d8eab5e580a48a5/rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b2669eafee38c5884a6e7cc9769d25c19428549dcdf57de8541cf9e82822e7db", size = 2715928 }, - { url = "https://files.pythonhosted.org/packages/17/17/d964d770faa4e25e125618c00e31607cf8ce639d518fc29d200edf06cfda/rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ffa1bb0e26297b0f22881b219ffc82a33a3c84ce6174a9d69406239b14575bd5", size = 3265078 }, - { url = "https://files.pythonhosted.org/packages/bc/13/a117412b1e4ed0bb23b9891a45a59812d96fde8c076b8b8b828aa7ca3710/rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:45b15b8a118856ac9caac6877f70f38b8a0d310475d50bc814698659eabc1cdb", size = 4169215 }, - { url = "https://files.pythonhosted.org/packages/9f/0d/89ef496aedf885db4bfe7f46ac6727666afe0d9d8ca5b4f9c7cc8eef0378/rapidfuzz-3.11.0-cp310-cp310-win32.whl", hash = "sha256:22033677982b9c4c49676f215b794b0404073f8974f98739cb7234e4a9ade9ad", size = 1841736 }, - { url = "https://files.pythonhosted.org/packages/47/9a/69019f4e9c8a42e4aca0169dbae71602aba4e0fa4c5e84515f3ed682e59a/rapidfuzz-3.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:be15496e7244361ff0efcd86e52559bacda9cd975eccf19426a0025f9547c792", size = 1614955 }, - { url = "https://files.pythonhosted.org/packages/37/65/6fb036e39d175299ce44e5186ee2d08b9ea02d732ed6dbd70280f63b4eba/rapidfuzz-3.11.0-cp310-cp310-win_arm64.whl", hash = "sha256:714a7ba31ba46b64d30fccfe95f8013ea41a2e6237ba11a805a27cdd3bce2573", size = 863543 }, - { url = "https://files.pythonhosted.org/packages/40/ac/9ca008834104ad138fbfe2d7ae4443ada55e00c4eb4272d288897e8763b8/rapidfuzz-3.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8724a978f8af7059c5323d523870bf272a097478e1471295511cf58b2642ff83", size = 1955019 }, - { url = "https://files.pythonhosted.org/packages/4c/55/d026c01c9312c9c2a413679052a9bb884743fc5655e59339116d83a2125b/rapidfuzz-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b63cb1f2eb371ef20fb155e95efd96e060147bdd4ab9fc400c97325dfee9fe1", size = 1427753 }, - { url = "https://files.pythonhosted.org/packages/d1/a0/5f3fae81dd1efdf47da19641e321ae84b4f49a5a7b2ab3ff78bd04a0ae7f/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82497f244aac10b20710448645f347d862364cc4f7d8b9ba14bd66b5ce4dec18", size = 1411472 }, - { url = "https://files.pythonhosted.org/packages/3c/3f/770b0fca00faf42983fe21fbd379f429dc2600c58d7015f969fb1f73c1db/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:339607394941801e6e3f6c1ecd413a36e18454e7136ed1161388de674f47f9d9", size = 5614973 }, - { url = "https://files.pythonhosted.org/packages/08/6f/e3df1c41adf27f4b8a95c9de947ed49e7311a676cd05bdd62a17bb1f21ec/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84819390a36d6166cec706b9d8f0941f115f700b7faecab5a7e22fc367408bc3", size = 1665667 }, - { url = "https://files.pythonhosted.org/packages/1a/9b/6c91b98dc70270c35913f359c17e30d4185c83663c4721363540f4c03016/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eea8d9e20632d68f653455265b18c35f90965e26f30d4d92f831899d6682149b", size = 1676166 }, - { url = "https://files.pythonhosted.org/packages/59/9d/eec7a1bfd3566fb17617b41bfb19556c483241d6864eea3c01b88efe5459/rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b659e1e2ea2784a9a397075a7fc395bfa4fe66424042161c4bcaf6e4f637b38", size = 3130890 }, - { url = "https://files.pythonhosted.org/packages/26/7c/0a4bb5fbb03a362ea3e1409515d3ae641d9bc869c1375d97d8c47e369cc0/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1315cd2a351144572e31fe3df68340d4b83ddec0af8b2e207cd32930c6acd037", size = 2339850 }, - { url = "https://files.pythonhosted.org/packages/f8/c1/6b839db83caaa47721398b76390a3145202beb108fa433e842879b497439/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a7743cca45b4684c54407e8638f6d07b910d8d811347b9d42ff21262c7c23245", size = 6941921 }, - { url = "https://files.pythonhosted.org/packages/cc/c9/eaac43bb5e44f3594afddbbdb1a28d7bc0bcb69f93ed9a2ef0c949a48fb2/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5bb636b0150daa6d3331b738f7c0f8b25eadc47f04a40e5c23c4bfb4c4e20ae3", size = 2717551 }, - { url = "https://files.pythonhosted.org/packages/ef/d3/06ca5ee6b7f030f6527ea1e80fe9a4ab3597e86bc783574e3fc2b05a5265/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:42f4dd264ada7a9aa0805ea0da776dc063533917773cf2df5217f14eb4429eae", size = 3259550 }, - { url = "https://files.pythonhosted.org/packages/74/d8/094e75ee0424cce329901a0ff98c1821fd5d9dbc11bcdc9a3fddd2a09c4c/rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51f24cb39e64256221e6952f22545b8ce21cacd59c0d3e367225da8fc4b868d8", size = 4173546 }, - { url = "https://files.pythonhosted.org/packages/d7/81/f263059e3d9f11b076751ac7ef4eba303fa7f11e32155658953f1697c274/rapidfuzz-3.11.0-cp311-cp311-win32.whl", hash = "sha256:aaf391fb6715866bc14681c76dc0308f46877f7c06f61d62cc993b79fc3c4a2a", size = 1842172 }, - { url = "https://files.pythonhosted.org/packages/33/04/dc42c787f02505a4ca0a961172e8353ceee74ea378b795f3e49686e944b7/rapidfuzz-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:ebadd5b8624d8ad503e505a99b8eb26fe3ea9f8e9c2234e805a27b269e585842", size = 1621122 }, - { url = "https://files.pythonhosted.org/packages/4e/0f/461e709bd641922a32bc034976963acbb11d8cf0af28b526f3f35ae07975/rapidfuzz-3.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:d895998fec712544c13cfe833890e0226585cf0391dd3948412441d5d68a2b8c", size = 864792 }, { url = "https://files.pythonhosted.org/packages/c5/54/954ae2dc7dcb53f5f0953379a4a175d9c2f5e393656ab042843e53780d32/rapidfuzz-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f382fec4a7891d66fb7163c90754454030bb9200a13f82ee7860b6359f3f2fa8", size = 1938694 }, { url = "https://files.pythonhosted.org/packages/f9/74/4682d3370821db5374c0f192d1e4123598190cb53d88936016187f80f154/rapidfuzz-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dfaefe08af2a928e72344c800dcbaf6508e86a4ed481e28355e8d4b6a6a5230e", size = 1423836 }, { url = "https://files.pythonhosted.org/packages/e7/78/ce3d72767e186a9deca30dccb5096cfb03ec49e8e3abf2836ab10d1b4f74/rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92ebb7c12f682b5906ed98429f48a3dd80dd0f9721de30c97a01473d1a346576", size = 1393199 }, @@ -3140,12 +3613,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/d8/4677e36e958b4d95d039d254d597db9c020896c8130911dc36b136373b87/rapidfuzz-3.11.0-cp313-cp313-win32.whl", hash = "sha256:6ad02bab756751c90fa27f3069d7b12146613061341459abf55f8190d899649f", size = 1822624 }, { url = "https://files.pythonhosted.org/packages/e8/97/1c782140e688ea2c3337d94516c635c575aa39fe62782fd53ad5d2119df4/rapidfuzz-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:b1472986fd9c5d318399a01a0881f4a0bf4950264131bb8e2deba9df6d8c362b", size = 1604273 }, { url = "https://files.pythonhosted.org/packages/a6/83/8b713d50bec947e945a79be47f772484307fc876c426fb26c6f369098389/rapidfuzz-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c408f09649cbff8da76f8d3ad878b64ba7f7abdad1471efb293d2c075e80c822", size = 857385 }, - { url = "https://files.pythonhosted.org/packages/30/5a/8ac67667663d24cc4d4b76f63783e58ef03e4d4843d02dab6b2f8470ea5e/rapidfuzz-3.11.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f06e3c4c0a8badfc4910b9fd15beb1ad8f3b8fafa8ea82c023e5e607b66a78e4", size = 1853100 }, - { url = "https://files.pythonhosted.org/packages/dc/72/b043c26e93fb1bc5dfab1e5dd0f8d2f6135c2aa48e6db0660d4ecc5b157a/rapidfuzz-3.11.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fe7aaf5a54821d340d21412f7f6e6272a9b17a0cbafc1d68f77f2fc11009dcd5", size = 1361961 }, - { url = "https://files.pythonhosted.org/packages/5c/4a/29916c0dd853d22ef7b988af43f4e34d327581e16f60b4c9b0f229fa306c/rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25398d9ac7294e99876a3027ffc52c6bebeb2d702b1895af6ae9c541ee676702", size = 1354313 }, - { url = "https://files.pythonhosted.org/packages/41/39/f352af4ede7faeeea20bae2537f1fa60c3bbbf2696f0f2f3dda696745239/rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a52eea839e4bdc72c5e60a444d26004da00bb5bc6301e99b3dde18212e41465", size = 5478019 }, - { url = "https://files.pythonhosted.org/packages/99/8e/86f8a11ac0edda63ff5314d992aa1576fff5d8233f4310d46a6bb0551122/rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c87319b0ab9d269ab84f6453601fd49b35d9e4a601bbaef43743f26fabf496c", size = 3056881 }, - { url = "https://files.pythonhosted.org/packages/98/53/222dceb24a83c7d7d76086b6d5bfd3d6aa9988ea73d356d287b5c437c0d5/rapidfuzz-3.11.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3048c6ed29d693fba7d2a7caf165f5e0bb2b9743a0989012a98a47b975355cca", size = 1543944 }, +] + +[[package]] +name = "referencing" +version = "0.35.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/73ca1f8e72fff6fa52119dbd185f73a907b1989428917b24cff660129b6d/referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", size = 62991 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/59/2056f61236782a2c86b33906c025d4f4a0b17be0161b63b70fd9e8775d36/referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de", size = 26684 }, ] [[package]] @@ -3154,37 +3634,6 @@ version = "2024.11.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/3c/4651f6b130c6842a8f3df82461a8950f923925db8b6961063e82744bddcc/regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91", size = 482674 }, - { url = "https://files.pythonhosted.org/packages/15/51/9f35d12da8434b489c7b7bffc205c474a0a9432a889457026e9bc06a297a/regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0", size = 287684 }, - { url = "https://files.pythonhosted.org/packages/bd/18/b731f5510d1b8fb63c6b6d3484bfa9a59b84cc578ac8b5172970e05ae07c/regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e", size = 284589 }, - { url = "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde", size = 782511 }, - { url = "https://files.pythonhosted.org/packages/1b/2b/323e72d5d2fd8de0d9baa443e1ed70363ed7e7b2fb526f5950c5cb99c364/regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e", size = 821149 }, - { url = "https://files.pythonhosted.org/packages/90/30/63373b9ea468fbef8a907fd273e5c329b8c9535fee36fc8dba5fecac475d/regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2", size = 809707 }, - { url = "https://files.pythonhosted.org/packages/f2/98/26d3830875b53071f1f0ae6d547f1d98e964dd29ad35cbf94439120bb67a/regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf", size = 781702 }, - { url = "https://files.pythonhosted.org/packages/87/55/eb2a068334274db86208ab9d5599ffa63631b9f0f67ed70ea7c82a69bbc8/regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c", size = 771976 }, - { url = "https://files.pythonhosted.org/packages/74/c0/be707bcfe98254d8f9d2cff55d216e946f4ea48ad2fd8cf1428f8c5332ba/regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86", size = 697397 }, - { url = "https://files.pythonhosted.org/packages/49/dc/bb45572ceb49e0f6509f7596e4ba7031f6819ecb26bc7610979af5a77f45/regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67", size = 768726 }, - { url = "https://files.pythonhosted.org/packages/5a/db/f43fd75dc4c0c2d96d0881967897926942e935d700863666f3c844a72ce6/regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d", size = 775098 }, - { url = "https://files.pythonhosted.org/packages/99/d7/f94154db29ab5a89d69ff893159b19ada89e76b915c1293e98603d39838c/regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2", size = 839325 }, - { url = "https://files.pythonhosted.org/packages/f7/17/3cbfab1f23356fbbf07708220ab438a7efa1e0f34195bf857433f79f1788/regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008", size = 843277 }, - { url = "https://files.pythonhosted.org/packages/7e/f2/48b393b51900456155de3ad001900f94298965e1cad1c772b87f9cfea011/regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62", size = 773197 }, - { url = "https://files.pythonhosted.org/packages/45/3f/ef9589aba93e084cd3f8471fded352826dcae8489b650d0b9b27bc5bba8a/regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e", size = 261714 }, - { url = "https://files.pythonhosted.org/packages/42/7e/5f1b92c8468290c465fd50c5318da64319133231415a8aa6ea5ab995a815/regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519", size = 274042 }, - { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669 }, - { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684 }, - { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589 }, - { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121 }, - { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275 }, - { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257 }, - { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727 }, - { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667 }, - { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963 }, - { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700 }, - { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592 }, - { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929 }, - { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213 }, - { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734 }, - { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052 }, { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, @@ -3254,6 +3703,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4b/0d/53aea75710af4528a25ed6837d71d117602b01946b307a3912cb3cfcbcba/retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606", size = 7986 }, ] +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, +] + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242 }, +] + [[package]] name = "rich" version = "13.9.4" @@ -3261,13 +3731,59 @@ 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 = "rpds-py" +version = "0.22.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/80/cce854d0921ff2f0a9fa831ba3ad3c65cee3a46711addf39a2af52df2cfd/rpds_py-0.22.3.tar.gz", hash = "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d", size = 26771 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/47/3383ee3bd787a2a5e65a9b9edc37ccf8505c0a00170e3a5e6ea5fbcd97f7/rpds_py-0.22.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e", size = 352334 }, + { url = "https://files.pythonhosted.org/packages/40/14/aa6400fa8158b90a5a250a77f2077c0d0cd8a76fce31d9f2b289f04c6dec/rpds_py-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56", size = 342111 }, + { url = "https://files.pythonhosted.org/packages/7d/06/395a13bfaa8a28b302fb433fb285a67ce0ea2004959a027aea8f9c52bad4/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45", size = 384286 }, + { url = "https://files.pythonhosted.org/packages/43/52/d8eeaffab047e6b7b7ef7f00d5ead074a07973968ffa2d5820fa131d7852/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e", size = 391739 }, + { url = "https://files.pythonhosted.org/packages/83/31/52dc4bde85c60b63719610ed6f6d61877effdb5113a72007679b786377b8/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d", size = 427306 }, + { url = "https://files.pythonhosted.org/packages/70/d5/1bab8e389c2261dba1764e9e793ed6830a63f830fdbec581a242c7c46bda/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38", size = 442717 }, + { url = "https://files.pythonhosted.org/packages/82/a1/a45f3e30835b553379b3a56ea6c4eb622cf11e72008229af840e4596a8ea/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15", size = 385721 }, + { url = "https://files.pythonhosted.org/packages/a6/27/780c942de3120bdd4d0e69583f9c96e179dfff082f6ecbb46b8d6488841f/rpds_py-0.22.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059", size = 415824 }, + { url = "https://files.pythonhosted.org/packages/94/0b/aa0542ca88ad20ea719b06520f925bae348ea5c1fdf201b7e7202d20871d/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e", size = 561227 }, + { url = "https://files.pythonhosted.org/packages/0d/92/3ed77d215f82c8f844d7f98929d56cc321bb0bcfaf8f166559b8ec56e5f1/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61", size = 587424 }, + { url = "https://files.pythonhosted.org/packages/09/42/cacaeb047a22cab6241f107644f230e2935d4efecf6488859a7dd82fc47d/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7", size = 555953 }, + { url = "https://files.pythonhosted.org/packages/e6/52/c921dc6d5f5d45b212a456c1f5b17df1a471127e8037eb0972379e39dff4/rpds_py-0.22.3-cp312-cp312-win32.whl", hash = "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627", size = 221339 }, + { url = "https://files.pythonhosted.org/packages/f2/c7/f82b5be1e8456600395366f86104d1bd8d0faed3802ad511ef6d60c30d98/rpds_py-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4", size = 235786 }, + { url = "https://files.pythonhosted.org/packages/d0/bf/36d5cc1f2c609ae6e8bf0fc35949355ca9d8790eceb66e6385680c951e60/rpds_py-0.22.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84", size = 351657 }, + { url = "https://files.pythonhosted.org/packages/24/2a/f1e0fa124e300c26ea9382e59b2d582cba71cedd340f32d1447f4f29fa4e/rpds_py-0.22.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25", size = 341829 }, + { url = "https://files.pythonhosted.org/packages/cf/c2/0da1231dd16953845bed60d1a586fcd6b15ceaeb965f4d35cdc71f70f606/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4", size = 384220 }, + { url = "https://files.pythonhosted.org/packages/c7/73/a4407f4e3a00a9d4b68c532bf2d873d6b562854a8eaff8faa6133b3588ec/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5", size = 391009 }, + { url = "https://files.pythonhosted.org/packages/a9/c3/04b7353477ab360fe2563f5f0b176d2105982f97cd9ae80a9c5a18f1ae0f/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc", size = 426989 }, + { url = "https://files.pythonhosted.org/packages/8d/e6/e4b85b722bcf11398e17d59c0f6049d19cd606d35363221951e6d625fcb0/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b", size = 441544 }, + { url = "https://files.pythonhosted.org/packages/27/fc/403e65e56f65fff25f2973216974976d3f0a5c3f30e53758589b6dc9b79b/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518", size = 385179 }, + { url = "https://files.pythonhosted.org/packages/57/9b/2be9ff9700d664d51fd96b33d6595791c496d2778cb0b2a634f048437a55/rpds_py-0.22.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd", size = 415103 }, + { url = "https://files.pythonhosted.org/packages/bb/a5/03c2ad8ca10994fcf22dd2150dd1d653bc974fa82d9a590494c84c10c641/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2", size = 560916 }, + { url = "https://files.pythonhosted.org/packages/ba/2e/be4fdfc8b5b576e588782b56978c5b702c5a2307024120d8aeec1ab818f0/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16", size = 587062 }, + { url = "https://files.pythonhosted.org/packages/67/e0/2034c221937709bf9c542603d25ad43a68b4b0a9a0c0b06a742f2756eb66/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f", size = 555734 }, + { url = "https://files.pythonhosted.org/packages/ea/ce/240bae07b5401a22482b58e18cfbabaa392409b2797da60223cca10d7367/rpds_py-0.22.3-cp313-cp313-win32.whl", hash = "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de", size = 220663 }, + { url = "https://files.pythonhosted.org/packages/cb/f0/d330d08f51126330467edae2fa4efa5cec8923c87551a79299380fdea30d/rpds_py-0.22.3-cp313-cp313-win_amd64.whl", hash = "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9", size = 235503 }, + { url = "https://files.pythonhosted.org/packages/f7/c4/dbe1cc03df013bf2feb5ad00615038050e7859f381e96fb5b7b4572cd814/rpds_py-0.22.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b", size = 347698 }, + { url = "https://files.pythonhosted.org/packages/a4/3a/684f66dd6b0f37499cad24cd1c0e523541fd768576fa5ce2d0a8799c3cba/rpds_py-0.22.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b", size = 337330 }, + { url = "https://files.pythonhosted.org/packages/82/eb/e022c08c2ce2e8f7683baa313476492c0e2c1ca97227fe8a75d9f0181e95/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1", size = 380022 }, + { url = "https://files.pythonhosted.org/packages/e4/21/5a80e653e4c86aeb28eb4fea4add1f72e1787a3299687a9187105c3ee966/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83", size = 390754 }, + { url = "https://files.pythonhosted.org/packages/37/a4/d320a04ae90f72d080b3d74597074e62be0a8ecad7d7321312dfe2dc5a6a/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd", size = 423840 }, + { url = "https://files.pythonhosted.org/packages/87/70/674dc47d93db30a6624279284e5631be4c3a12a0340e8e4f349153546728/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1", size = 438970 }, + { url = "https://files.pythonhosted.org/packages/3f/64/9500f4d66601d55cadd21e90784cfd5d5f4560e129d72e4339823129171c/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3", size = 383146 }, + { url = "https://files.pythonhosted.org/packages/4d/45/630327addb1d17173adcf4af01336fd0ee030c04798027dfcb50106001e0/rpds_py-0.22.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130", size = 408294 }, + { url = "https://files.pythonhosted.org/packages/5f/ef/8efb3373cee54ea9d9980b772e5690a0c9e9214045a4e7fa35046e399fee/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c", size = 556345 }, + { url = "https://files.pythonhosted.org/packages/54/01/151d3b9ef4925fc8f15bfb131086c12ec3c3d6dd4a4f7589c335bf8e85ba/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b", size = 582292 }, + { url = "https://files.pythonhosted.org/packages/30/89/35fc7a6cdf3477d441c7aca5e9bbf5a14e0f25152aed7f63f4e0b141045d/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333", size = 553855 }, + { url = "https://files.pythonhosted.org/packages/8f/e0/830c02b2457c4bd20a8c5bb394d31d81f57fbefce2dbdd2e31feff4f7003/rpds_py-0.22.3-cp313-cp313t-win32.whl", hash = "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730", size = 219100 }, + { url = "https://files.pythonhosted.org/packages/f8/30/7ac943f69855c2db77407ae363484b915d861702dbba1aa82d68d57f42be/rpds_py-0.22.3-cp313-cp313t-win_amd64.whl", hash = "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf", size = 233794 }, +] + [[package]] name = "safetensors" version = "0.5.2" @@ -3290,6 +3806,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/86/ca/aa489392ec6fb59223ffce825461e1f811a3affd417121a2088be7a5758b/safetensors-0.5.2-cp38-abi3-win_amd64.whl", hash = "sha256:78abdddd03a406646107f973c7843276e7b64e5e32623529dc17f3d94a20f589", size = 303756 }, ] +[[package]] +name = "sahi" +version = "0.11.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "fire" }, + { name = "opencv-python" }, + { name = "pillow" }, + { name = "pybboxes" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "shapely" }, + { name = "terminaltables" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/59/336aceacd7ac0d50ba37b289c4844914aa79c69d01b51f48ae39fa9fc96f/sahi-0.11.14.tar.gz", hash = "sha256:52ca98a4f691e661a41d063142d8adc0849b2999a100e241fd584dfee26a9397", size = 84684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/2b/026f0861137499edca43263679f9e4ce21314b67c34c8d74c12f2833b8c3/sahi-0.11.14-py3-none-any.whl", hash = "sha256:961ba328ebbac2302f1475149624d4b7eaff729eff698befa34e21b22b19ad2f", size = 104009 }, +] + [[package]] name = "scalecodec" version = "1.2.11" @@ -3304,6 +3841,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bb/60/2a903fa9ed3dfc842240da22969a25b16ea213ed3ee25b7ba8ae1cba20c7/scalecodec-1.2.11-py3-none-any.whl", hash = "sha256:d15c94965f617caa25096f83a45f5f73031d05e6ee08d6039969f0a64fc35de1", size = 99164 }, ] +[[package]] +name = "scikit-image" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "imageio" }, + { name = "lazy-loader" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "scipy" }, + { name = "tifffile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/8d/383e5438c807804b66d68ed2c09202d185ea781b6022aa8b9fac3851137f/scikit_image-0.25.0.tar.gz", hash = "sha256:58d94fea11b6b3306b3770417dc1cbca7fa9bcbd6a13945d7910399c88c2018c", size = 22696477 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/6a/a8df6953a85042a8a219c97e1758486b997c9dd319e1c474362229406e57/scikit_image-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7e63f18b10f9b74590d2ca62cbc4147e696a5e72cfcbcd4af52395fd94fcdc6e", size = 13981411 }, + { url = "https://files.pythonhosted.org/packages/dd/4c/e40a77c57a6b90dda710bc64ed761c93e7b3dd1cef3815675a2bc6807755/scikit_image-0.25.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bad4af5edf58775607c153af5bc3f193c2b67261ea9817b62362c746e439d094", size = 13230600 }, + { url = "https://files.pythonhosted.org/packages/63/3f/fac8e1eefbe4a885fa1c9a384db8e11e47c19ab5558b25f370ade3901868/scikit_image-0.25.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44f7681ff99eed2c33d993bc4bfc17b62e6cadbca1081c7fdbb3607ce89b15e6", size = 14173033 }, + { url = "https://files.pythonhosted.org/packages/47/fe/f09efbf54782996a7f1d3db0e33cb9097f3cc6033392fb53459d7254fa7c/scikit_image-0.25.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:758f55d858aa796114a4275051ca4bb41d8b40c53eb78cb60f0b1ed235d4144b", size = 15002211 }, + { url = "https://files.pythonhosted.org/packages/89/30/4f95a7462411def5563c01d56674bd122bd6db55ae1e8c31ad68586e2d27/scikit_image-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f7178c6fb6163710571522847326ad936a603646255b22d3d76b6ba58153890", size = 12894520 }, + { url = "https://files.pythonhosted.org/packages/bc/e4/066d0ed167eb146877c50109e94ec254e266391f385c72d545f34cf51755/scikit_image-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d3b08a8894190bc49038dc1a61f6ef0991ff520e5268604abd7ad217f693a0cc", size = 13917192 }, + { url = "https://files.pythonhosted.org/packages/3f/7c/ada573675ad528caff75c8b175c2e28e62c65c7192cf2292a25c3d9774fa/scikit_image-0.25.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:8438eac699c8b2820e5956960191d0c3b302bf9c4d42dbf194a229db04abacc3", size = 13191642 }, + { url = "https://files.pythonhosted.org/packages/cf/c4/16dbe7f7ef7b675c7a11dd51280f09001abca9f3cd4f455f342765b81b43/scikit_image-0.25.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9920673ef08ea44026c80deb14cf84d5c0cc1a68efad914c126b76110ed017a8", size = 14113112 }, + { url = "https://files.pythonhosted.org/packages/8c/d2/84d658db2abecac5f7225213a69d211d95157e8fa155b4e017903549a922/scikit_image-0.25.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fe2f05cda852a5f90872054dd3709e9c4e670fc7332aef169867944e1b37431", size = 14974308 }, + { url = "https://files.pythonhosted.org/packages/b0/0d/4f017d5b85bf742624f8ccd6a03fb9cbf90704b52dbaefa7ffdb28e34775/scikit_image-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ede552097ee281d01b25dc4ce121fdc17b6a43c36bbc3c13e39f0e3d8fb5239", size = 12880013 }, +] + [[package]] name = "scikit-learn" version = "1.6.0" @@ -3316,16 +3881,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/fa/19/5aa2002044afc297ecaf1e3517ed07bba4aece3b5613b5160c1212995fc8/scikit_learn-1.6.0.tar.gz", hash = "sha256:9d58481f9f7499dff4196927aedd4285a0baec8caa3790efbe205f13de37dd6e", size = 7074944 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/97/55060f91a5e7c4df945e5a69b16148b5f2256e6e1ea3f17da8e27edf9953/scikit_learn-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:366fb3fa47dce90afed3d6106183f4978d6f24cfd595c2373424171b915ee718", size = 12060299 }, - { url = "https://files.pythonhosted.org/packages/36/7b/8c5dfc64a8344ebf2ae493d59af4b3650588051f654e164ff4f9952877b3/scikit_learn-1.6.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:59cd96a8d9f8dfd546f5d6e9787e1b989e981388d7803abbc9efdcde61e47460", size = 11105443 }, - { url = "https://files.pythonhosted.org/packages/25/9f/61544f2a5cae1bc27c97f0ec9ffcc9837e469f215817608840a4ccbb277a/scikit_learn-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efa7a579606c73a0b3d210e33ea410ea9e1af7933fe324cb7e6fbafae4ea5948", size = 12637137 }, - { url = "https://files.pythonhosted.org/packages/50/79/d21599fc44d2d497ced440480670b6314ebc00308e3bae0d0ebca44cd481/scikit_learn-1.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a46d3ca0f11a540b8eaddaf5e38172d8cd65a86cb3e3632161ec96c0cffb774c", size = 13490128 }, - { url = "https://files.pythonhosted.org/packages/ff/87/788da20cfefcd261123d4bb015b2de076e49cdd3b811b55e6811acd3cb21/scikit_learn-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:5be4577769c5dde6e1b53de8e6520f9b664ab5861dd57acee47ad119fd7405d6", size = 11118524 }, - { url = "https://files.pythonhosted.org/packages/07/95/070d6e70f735d13f1c10afebb65ba3526125b7d6c6fc7022651a4a061148/scikit_learn-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1f50b4f24cf12a81c3c09958ae3b864d7534934ca66ded3822de4996d25d7285", size = 12095168 }, - { url = "https://files.pythonhosted.org/packages/72/3d/0381e3a59ebd4154e6a61b0ceaf299c3c141035033dd3b868776cd9af02d/scikit_learn-1.6.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:eb9ae21f387826da14b0b9cb1034f5048ddb9182da429c689f5f4a87dc96930b", size = 11108880 }, - { url = "https://files.pythonhosted.org/packages/fe/2d/0999ae3eed2ac67b1b3cd7fc33370bd5ca59a7514ffe43ae2b6f3cd85b9b/scikit_learn-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0baa91eeb8c32632628874a5c91885eaedd23b71504d24227925080da075837a", size = 12585449 }, - { url = "https://files.pythonhosted.org/packages/0e/ec/1b15b59c6cc7a993320a52234369e787f50345a4753e50d5a015a91e1a20/scikit_learn-1.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c716d13ba0a2f8762d96ff78d3e0cde90bc9c9b5c13d6ab6bb9b2d6ca6705fd", size = 13489728 }, - { url = "https://files.pythonhosted.org/packages/96/a2/cbfb5743de748d574ffdfd557e9cb29ba4f8b8a3e07836c6c176f713de2f/scikit_learn-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:9aafd94bafc841b626681e626be27bf1233d5a0f20f0a6fdb4bee1a1963c6643", size = 11132946 }, { url = "https://files.pythonhosted.org/packages/18/0c/a5de627aa57b028aea7026cb3bbeaf63be3158adc118212d6cc7843d939a/scikit_learn-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:04a5ba45c12a5ff81518aa4f1604e826a45d20e53da47b15871526cda4ff5174", size = 12096999 }, { url = "https://files.pythonhosted.org/packages/a3/7d/02a96e6fb28ddb213e84b1b4a44148d26ec96fc9db9c74e050277e009892/scikit_learn-1.6.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:21fadfc2ad7a1ce8bd1d90f23d17875b84ec765eecbbfc924ff11fb73db582ce", size = 11160579 }, { url = "https://files.pythonhosted.org/packages/70/28/77b071f541d75247e6c3403f19aaa634371e972691f6aa1838ca9fd4cc52/scikit_learn-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30f34bb5fde90e020653bb84dcb38b6c83f90c70680dbd8c38bd9becbad7a127", size = 12246543 }, @@ -3351,22 +3906,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/d9/7b/2b8ac283cf32465ed08bc20a83d559fe7b174a484781702ba8accea001d6/scipy-1.15.0.tar.gz", hash = "sha256:300742e2cc94e36a2880ebe464a1c8b4352a7b0f3e36ec3d2ac006cdbe0219ac", size = 59407226 } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/6a/14ce8d4452acdced1b69ea32b0d304b04b00376deb4f1eb65f946aee41af/scipy-1.15.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:aeac60d3562a7bf2f35549bdfdb6b1751c50590f55ce7322b4b2fc821dc27fca", size = 41413763 }, - { url = "https://files.pythonhosted.org/packages/45/12/570ba186d0ae1d528f8f0524b88fb9a263653ce575ac085edd9c1ef29e9c/scipy-1.15.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5abbdc6ede5c5fed7910cf406a948e2c0869231c0db091593a6b2fa78be77e5d", size = 32518980 }, - { url = "https://files.pythonhosted.org/packages/51/5a/b6ac5aa213cfa196d15db5ee159010aa9b94d0bc2bfa917fb99297701628/scipy-1.15.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:eb1533c59f0ec6c55871206f15a5c72d1fae7ad3c0a8ca33ca88f7c309bbbf8c", size = 24792491 }, - { url = "https://files.pythonhosted.org/packages/35/1f/6af575b77b2ee057551643de75a30252ce32098b2d9fd45bcf969a6fa35b/scipy-1.15.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:de112c2dae53107cfeaf65101419662ac0a54e9a088c17958b51c95dac5de56d", size = 27886039 }, - { url = "https://files.pythonhosted.org/packages/6a/7b/0c261d4857f459de6dffe11b3818583944f8d87716ce0b3b5f058aa34ff3/scipy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2240e1fd0782e62e1aacdc7234212ee271d810f67e9cd3b8d521003a82603ef8", size = 38374628 }, - { url = "https://files.pythonhosted.org/packages/99/17/ca390fbbfea5b34e3a00fc819fcb7c22e8b889360882820030b533d26c01/scipy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d35aef233b098e4de88b1eac29f0df378278e7e250a915766786b773309137c4", size = 40599127 }, - { url = "https://files.pythonhosted.org/packages/1d/65/95d93b1360f5defc1b6bf0963ac4e0d3413c95d8e8d6a1624a256506dfd3/scipy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b29e4fc02e155a5fd1165f1e6a73edfdd110470736b0f48bcbe48083f0eee37", size = 42937900 }, - { url = "https://files.pythonhosted.org/packages/51/8c/c2d371111961f737ae08881f654cf54eca796c42ec0429add2a06df97049/scipy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:0e5b34f8894f9904cc578008d1a9467829c1817e9f9cb45e6d6eeb61d2ab7731", size = 43907603 }, - { url = "https://files.pythonhosted.org/packages/b8/53/7f627c180cdaa211fa537650ca05912f58cb68fc33bb2f9af3d29169913e/scipy-1.15.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:46e91b5b16909ff79224b56e19cbad65ca500b3afda69225820aa3afbf9ec020", size = 41423594 }, - { url = "https://files.pythonhosted.org/packages/c9/ab/f848933c6f656f2c7af2d56d0be44511b730498538fe04db70eb03a6ad86/scipy-1.15.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:82bff2eb01ccf7cea8b6ee5274c2dbeadfdac97919da308ee6d8e5bcbe846443", size = 32535797 }, - { url = "https://files.pythonhosted.org/packages/41/93/266693c471ec1e2e7748c1ee5e867299f3d0ac42e0e63f52649430ec1976/scipy-1.15.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:9c8254fe21dd2c6c8f7757035ec0c31daecf3bb3cffd93bc1ca661b731d28136", size = 24809325 }, - { url = "https://files.pythonhosted.org/packages/f3/55/1acc49a48bc11fb95cf625c0763f2749f8710265d2fecbf6ed6dd618fc54/scipy-1.15.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:c9624eeae79b18cab1a31944b5ef87aa14b125d6ab69b71db22f0dbd962caf1e", size = 27917711 }, - { url = "https://files.pythonhosted.org/packages/e2/f5/15f62812b36f2f94b9d1ca31d3d2bbabfb6979e48a0866041bee7031c461/scipy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d13bbc0658c11f3d19df4138336e4bce2c4fbd78c2755be4bf7b8e235481557f", size = 38331850 }, - { url = "https://files.pythonhosted.org/packages/ad/21/6dc57f6f6c8014dc6d07111e4976422580789fa96c4d7ddf63614939cb6c/scipy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdca4c7bb8dc41307e5f39e9e5d19c707d8e20a29845e7533b3bb20a9d4ccba0", size = 40587953 }, - { url = "https://files.pythonhosted.org/packages/da/dd/26db78c2054f8d81b28ae4688da7930ea3c33e5d1885928aadefeec979f9/scipy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f376d7c767731477bac25a85d0118efdc94a572c6b60decb1ee48bf2391a73b", size = 42963920 }, - { url = "https://files.pythonhosted.org/packages/82/89/eb4aaf929be0e2c03bb5e40ed61427aab9c8ba6c0764aebf82d7302bb3d3/scipy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:61513b989ee8d5218fbeb178b2d51534ecaddba050db949ae99eeb3d12f6825d", size = 43894857 }, { url = "https://files.pythonhosted.org/packages/35/70/fffb90a725dec6056c9059073856fd99de22a253459a874a63b8b8a012db/scipy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5beb0a2200372b7416ec73fdae94fe81a6e85e44eb49c35a11ac356d2b8eccc6", size = 41475240 }, { url = "https://files.pythonhosted.org/packages/63/ca/6b838a2e5e6718d879e8522d1155a068c2a769be04f7da8c5179ead32a7b/scipy-1.15.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fde0f3104dfa1dfbc1f230f65506532d0558d43188789eaf68f97e106249a913", size = 32595923 }, { url = "https://files.pythonhosted.org/packages/b1/07/4e69f6f7185915d77719bf226c1d554a4bb99f27cb92161fdd57b1434343/scipy-1.15.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:35c68f7044b4e7ad73a3e68e513dda946989e523df9b062bd3cf401a1a882192", size = 24869617 }, @@ -3392,6 +3931,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/df/989b2fd3f8ead6bcf89fc683fde94741eb3b291e41a3ce70cec08c80aa36/scipy-1.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:129f899ed275c0515d553b8d31696924e2ca87d1972421e46c376b9eb87de3d2", size = 43188844 }, ] +[[package]] +name = "seaborn" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914 }, +] + +[[package]] +name = "send2trash" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072 }, +] + [[package]] name = "sentence-transformers" version = "3.3.1" @@ -3429,30 +3991,6 @@ version = "1.3.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ae/4e/b09341b19b9ceb8b4c67298ab4a08ef7a4abdd3016c7bb152e9b6379031d/setproctitle-1.3.4.tar.gz", hash = "sha256:3b40d32a3e1f04e94231ed6dfee0da9e43b4f9c6b5450d53e6dd7754c34e0c50", size = 26456 } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/f4/95937eb5c5370324a942ba90174c6d0fc7c5ad2f7f8ea989ccdbd6e1be5e/setproctitle-1.3.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0f6661a69c68349172ba7b4d5dd65fec2b0917abc99002425ad78c3e58cf7595", size = 16855 }, - { url = "https://files.pythonhosted.org/packages/32/a6/d49dbb0d75d02d11db49151469e1fee740efa45de7288bffcc4d88d0c290/setproctitle-1.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:754bac5e470adac7f7ec2239c485cd0b75f8197ca8a5b86ffb20eb3a3676cc42", size = 11627 }, - { url = "https://files.pythonhosted.org/packages/2e/cd/73a0fc913db50c3b736750ce67824f1108c2173e5d043a16ef9874b4f988/setproctitle-1.3.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7bc7088c15150745baf66db62a4ced4507d44419eb66207b609f91b64a682af", size = 31187 }, - { url = "https://files.pythonhosted.org/packages/63/0f/74f9112f7f506acc01f085811c6d135751b6fa42d30207f53b25579d043a/setproctitle-1.3.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a46ef3ecf61e4840fbc1145fdd38acf158d0da7543eda7b773ed2b30f75c2830", size = 32534 }, - { url = "https://files.pythonhosted.org/packages/3b/88/53eec2373745069d4c8a59d41ee2ef4a48949b77cccd0077c270261b238a/setproctitle-1.3.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcb09d5c0ffa043254ec9a734a73f3791fec8bf6333592f906bb2e91ed2af1a", size = 29657 }, - { url = "https://files.pythonhosted.org/packages/50/1c/a4d3d8c20bf3bbafd8c5038e7da09043a9d21450b6a73694ada11c01b58a/setproctitle-1.3.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06c16b7a91cdc5d700271899e4383384a61aae83a3d53d0e2e5a266376083342", size = 30695 }, - { url = "https://files.pythonhosted.org/packages/a2/2a/9f290f0d10ea87a266d63025078eabfa040ad29ea10d815e167a5104de00/setproctitle-1.3.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9f9732e59863eaeedd3feef94b2b216cb86d40dda4fad2d0f0aaec3b31592716", size = 30340 }, - { url = "https://files.pythonhosted.org/packages/38/c4/5bfe02d4cdd16338973d452c7c6042abdd2827d90f7ce4e21bc003f2edb1/setproctitle-1.3.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e152f4ab9ea1632b5fecdd87cee354f2b2eb6e2dfc3aceb0eb36a01c1e12f94c", size = 29352 }, - { url = "https://files.pythonhosted.org/packages/b3/41/0dd85cef0e5a5a332bdda7b55738e950c2edecea3ae45c658990553d50f8/setproctitle-1.3.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:020ea47a79b2bbd7bd7b94b85ca956ba7cb026e82f41b20d2e1dac4008cead25", size = 31819 }, - { url = "https://files.pythonhosted.org/packages/d7/23/fbfacfed8805983a83324099484e47b9028d0d3c07a0fe017123eee3f580/setproctitle-1.3.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8c52b12b10e4057fc302bd09cb3e3f28bb382c30c044eb3396e805179a8260e4", size = 29745 }, - { url = "https://files.pythonhosted.org/packages/68/37/e18c5a00bfd1c4c2c815536d5c63a470e4364b571bd5096d38d0fe277bf5/setproctitle-1.3.4-cp310-cp310-win32.whl", hash = "sha256:a65a147f545f3fac86f11acb2d0b316d3e78139a9372317b7eb50561b2817ba0", size = 11358 }, - { url = "https://files.pythonhosted.org/packages/52/fd/1fae8c4c13af22d8d17816c44421085509a08dfa77f573d31447d6cd540c/setproctitle-1.3.4-cp310-cp310-win_amd64.whl", hash = "sha256:66821fada6426998762a3650a37fba77e814a249a95b1183011070744aff47f6", size = 12072 }, - { url = "https://files.pythonhosted.org/packages/5d/1a/1fb7d622195bcb3ce7b04366a833e51cfa5ad632c5dafe32e0763cd3fdc9/setproctitle-1.3.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0f749f07002c2d6fecf37cedc43207a88e6c651926a470a5f229070cf791879", size = 16851 }, - { url = "https://files.pythonhosted.org/packages/46/54/e3aa4f46eddf795f10452ea878ff85c3496d36409636530f9a37e2de3cbe/setproctitle-1.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:90ea8d302a5d30b948451d146e94674a3c5b020cc0ced9a1c28f8ddb0f203a5d", size = 11620 }, - { url = "https://files.pythonhosted.org/packages/61/47/80988221679dfd93c464248abb71c2a96338f2ca3f8e3288d0ecb7422f4d/setproctitle-1.3.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f859c88193ed466bee4eb9d45fbc29d2253e6aa3ccd9119c9a1d8d95f409a60d", size = 31519 }, - { url = "https://files.pythonhosted.org/packages/2c/72/14984c127f708597e412f1a8cf7cac809b9bca50a267a6b01b221b094330/setproctitle-1.3.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3afa5a0ed08a477ded239c05db14c19af585975194a00adf594d48533b23701", size = 32860 }, - { url = "https://files.pythonhosted.org/packages/16/9d/34ea09295620fddae65cf7caeac81bbfc386a3ae6ce26a4dcadbb54c134d/setproctitle-1.3.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a78fce9018cc3e9a772b6537bbe3fe92380acf656c9f86db2f45e685af376e", size = 30029 }, - { url = "https://files.pythonhosted.org/packages/44/bf/a447a51054ceed23f69d4f7370289044b4508569f11da6db2eec087bc174/setproctitle-1.3.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d758e2eed2643afac5f2881542fbb5aa97640b54be20d0a5ed0691d02f0867d", size = 31017 }, - { url = "https://files.pythonhosted.org/packages/ec/46/adcffde6fb8d95458da0a568afdf0dabbbff6470299d94014676e1ab43c0/setproctitle-1.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ef133a1a2ee378d549048a12d56f4ef0e2b9113b0b25b6b77821e9af94d50634", size = 30762 }, - { url = "https://files.pythonhosted.org/packages/a3/cd/747a67ce1f6ef8fd1fa46b0b13ba0e007b80914bd549318830b8691ab9f6/setproctitle-1.3.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1d2a154b79d5fb42d1eff06e05e22f0e8091261d877dd47b37d31352b74ecc37", size = 29753 }, - { url = "https://files.pythonhosted.org/packages/3d/86/5939546e57238462a7839ae78399a635d1cfc5d125c7a12a28face111730/setproctitle-1.3.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:202eae632815571297833876a0f407d0d9c7ad9d843b38adbe687fe68c5192ee", size = 32161 }, - { url = "https://files.pythonhosted.org/packages/62/83/9194a4baed06e0e90a69e2e4a77a75e5a3ff008046870c79bc36a5c45e1c/setproctitle-1.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2b0080819859e80a7776ac47cf6accb4b7ad313baf55fabac89c000480dcd103", size = 30104 }, - { url = "https://files.pythonhosted.org/packages/ac/cd/08928fec23cbf4dae2a7b245b72d86e6458d64f4e7e6956cd80a9fda8c80/setproctitle-1.3.4-cp311-cp311-win32.whl", hash = "sha256:9c9d7d1267dee8c6627963d9376efa068858cfc8f573c083b1b6a2d297a8710f", size = 11349 }, - { url = "https://files.pythonhosted.org/packages/aa/19/240c4b99d57e045d3b2e2effa5924e810eabb18c56ef9c2336a7746dffe4/setproctitle-1.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:475986ddf6df65d619acd52188336a20f616589403f5a5ceb3fc70cdc137037a", size = 12071 }, { url = "https://files.pythonhosted.org/packages/94/1f/02fb3c6038c819d86765316d2a911281fc56c7dd3a9355dceb3f26a5bf7b/setproctitle-1.3.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d06990dcfcd41bb3543c18dd25c8476fbfe1f236757f42fef560f6aa03ac8dfc", size = 16842 }, { url = "https://files.pythonhosted.org/packages/b8/0c/d69e1f91c8f3d3aa74394e9e6ebb818f7d323e2d138ce1127e9462d09ebc/setproctitle-1.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:317218c9d8b17a010ab2d2f0851e8ef584077a38b1ba2b7c55c9e44e79a61e73", size = 11614 }, { url = "https://files.pythonhosted.org/packages/86/ed/8031871d275302054b2f1b94b7cf5e850212cc412fe968f0979e64c1b838/setproctitle-1.3.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb5fefb53b9d9f334a5d9ec518a36b92a10b936011ac8a6b6dffd60135f16459", size = 31840 }, @@ -3477,10 +4015,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/53/01746ed8fb75239a001ee89d5eb8ad5a3022df510572d1cf60dd04567e13/setproctitle-1.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6dc3d656702791565994e64035a208be56b065675a5bc87b644c657d6d9e2232", size = 30812 }, { url = "https://files.pythonhosted.org/packages/5f/ea/3ce61e70a6b898e95c0a1e393964c829103dc4ad4b0732cd70c8fc13e54c/setproctitle-1.3.4-cp313-cp313-win32.whl", hash = "sha256:091f682809a4d12291cf0205517619d2e7014986b7b00ebecfde3d76f8ae5a8f", size = 11349 }, { url = "https://files.pythonhosted.org/packages/e7/1a/8149da1c19db6bd57164d62b1d91c188e7d77e695947cf1ac327c8aea513/setproctitle-1.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:adcd6ba863a315702184d92d3d3bbff290514f24a14695d310f02ae5e28bd1f7", size = 12062 }, - { url = "https://files.pythonhosted.org/packages/2f/d0/775418662081d44b91da236ed4503e10e7008c9c5fd30193e13db388fbef/setproctitle-1.3.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:939d364a187b2adfbf6ae488664277e717d56c7951a4ddeb4f23b281bc50bfe5", size = 11153 }, - { url = "https://files.pythonhosted.org/packages/fd/1f/b3b82633336cd9908bf74cbc06dd533025b3d3c202437c4e3d0bc871ca13/setproctitle-1.3.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb8a6a19be0cbf6da6fcbf3698b76c8af03fe83e4bd77c96c3922be3b88bf7da", size = 13310 }, - { url = "https://files.pythonhosted.org/packages/f5/89/887c6872ceed5ca344d25c8cc8a3f9b99bbcb25613c4b680476b29aabe23/setproctitle-1.3.4-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:779006f9e1aade9522a40e8d9635115ab15dd82b7af8e655967162e9c01e2573", size = 12911 }, - { url = "https://files.pythonhosted.org/packages/b0/8d/9e4a4651b1c5845a9aec0d2c08c65768ba5ca2ec76598124b45d384a5f46/setproctitle-1.3.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5519f2a7b8c535b0f1f77b30441476571373add72008230c81211ee17b423b57", size = 12105 }, ] [[package]] @@ -3492,6 +4026,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/88/70c5767a0e43eb4451c2200f07d042a4bcd7639276003a9c54a68cfcc1f8/setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4", size = 863432 }, ] +[[package]] +name = "shapely" +version = "2.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/89/0d20bac88016be35ff7d3c0c2ae64b477908f1b1dfa540c5d69ac7af07fe/shapely-2.0.6.tar.gz", hash = "sha256:997f6159b1484059ec239cacaa53467fd8b5564dabe186cd84ac2944663b0bf6", size = 282361 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/77/efd9f9d4b6a762f976f8b082f54c9be16f63050389500fb52e4f6cc07c1a/shapely-2.0.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cec9193519940e9d1b86a3b4f5af9eb6910197d24af02f247afbfb47bcb3fab0", size = 1450326 }, + { url = "https://files.pythonhosted.org/packages/68/53/5efa6e7a4036a94fe6276cf7bbb298afded51ca3396b03981ad680c8cc7d/shapely-2.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83b94a44ab04a90e88be69e7ddcc6f332da7c0a0ebb1156e1c4f568bbec983c3", size = 1298480 }, + { url = "https://files.pythonhosted.org/packages/88/a2/1be1db4fc262e536465a52d4f19d85834724fedf2299a1b9836bc82fe8fa/shapely-2.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:537c4b2716d22c92036d00b34aac9d3775e3691f80c7aa517c2c290351f42cd8", size = 2439311 }, + { url = "https://files.pythonhosted.org/packages/d5/7d/9a57e187cbf2fbbbdfd4044a4f9ce141c8d221f9963750d3b001f0ec080d/shapely-2.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fea108334be345c283ce74bf064fa00cfdd718048a8af7343c59eb40f59726", size = 2524835 }, + { url = "https://files.pythonhosted.org/packages/6d/0a/f407509ab56825f39bf8cfce1fb410238da96cf096809c3e404e5bc71ea1/shapely-2.0.6-cp312-cp312-win32.whl", hash = "sha256:42fd4cd4834747e4990227e4cbafb02242c0cffe9ce7ef9971f53ac52d80d55f", size = 1295613 }, + { url = "https://files.pythonhosted.org/packages/7b/b3/857afd9dfbfc554f10d683ac412eac6fa260d1f4cd2967ecb655c57e831a/shapely-2.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:665990c84aece05efb68a21b3523a6b2057e84a1afbef426ad287f0796ef8a48", size = 1442539 }, + { url = "https://files.pythonhosted.org/packages/34/e8/d164ef5b0eab86088cde06dee8415519ffd5bb0dd1bd9d021e640e64237c/shapely-2.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:42805ef90783ce689a4dde2b6b2f261e2c52609226a0438d882e3ced40bb3013", size = 1445344 }, + { url = "https://files.pythonhosted.org/packages/ce/e2/9fba7ac142f7831757a10852bfa465683724eadbc93d2d46f74a16f9af04/shapely-2.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6d2cb146191a47bd0cee8ff5f90b47547b82b6345c0d02dd8b25b88b68af62d7", size = 1296182 }, + { url = "https://files.pythonhosted.org/packages/cf/dc/790d4bda27d196cd56ec66975eaae3351c65614cafd0e16ddde39ec9fb92/shapely-2.0.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3fdef0a1794a8fe70dc1f514440aa34426cc0ae98d9a1027fb299d45741c381", size = 2423426 }, + { url = "https://files.pythonhosted.org/packages/af/b0/f8169f77eac7392d41e231911e0095eb1148b4d40c50ea9e34d999c89a7e/shapely-2.0.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c665a0301c645615a107ff7f52adafa2153beab51daf34587170d85e8ba6805", size = 2513249 }, + { url = "https://files.pythonhosted.org/packages/f6/1d/a8c0e9ab49ff2f8e4dedd71b0122eafb22a18ad7e9d256025e1f10c84704/shapely-2.0.6-cp313-cp313-win32.whl", hash = "sha256:0334bd51828f68cd54b87d80b3e7cee93f249d82ae55a0faf3ea21c9be7b323a", size = 1294848 }, + { url = "https://files.pythonhosted.org/packages/23/38/2bc32dd1e7e67a471d4c60971e66df0bdace88656c47a9a728ace0091075/shapely-2.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:d37d070da9e0e0f0a530a621e17c0b8c3c9d04105655132a87cfff8bd77cc4c2", size = 1441371 }, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -3556,22 +4113,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/50/65/9cbc9c4c3287bed2499e05033e207473504dc4df999ce49385fb1f8b058a/sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5", size = 9574485 } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/72/14ab694b8b3f0e35ef5beb74a8fea2811aa791ba1611c44dc90cdf46af17/SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72", size = 2092604 }, - { url = "https://files.pythonhosted.org/packages/1e/59/333fcbca58b79f5b8b61853d6137530198823392151fa8fd9425f367519e/SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908", size = 2083796 }, - { url = "https://files.pythonhosted.org/packages/6c/a0/ec3c188d2b0c1bc742262e76408d44104598d7247c23f5b06bb97ee21bfa/SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08", size = 3066165 }, - { url = "https://files.pythonhosted.org/packages/07/15/68ef91de5b8b7f80fb2d2b3b31ed42180c6227fe0a701aed9d01d34f98ec/SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07", size = 3074428 }, - { url = "https://files.pythonhosted.org/packages/e2/4c/9dfea5e63b87325eef6d9cdaac913459aa6a157a05a05ea6ff20004aee8e/SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5", size = 3030477 }, - { url = "https://files.pythonhosted.org/packages/16/a5/fcfde8e74ea5f683b24add22463bfc21e431d4a5531c8a5b55bc6fbea164/SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44", size = 3055942 }, - { url = "https://files.pythonhosted.org/packages/3c/ee/c22c415a771d791ae99146d72ffdb20e43625acd24835ea7fc157436d59f/SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa", size = 2064960 }, - { url = "https://files.pythonhosted.org/packages/aa/af/ad9c25cadc79bd851bdb9d82b68af9bdb91ff05f56d0da2f8a654825974f/SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5", size = 2089078 }, - { url = "https://files.pythonhosted.org/packages/00/4e/5a67963fd7cbc1beb8bd2152e907419f4c940ef04600b10151a751fe9e06/SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c", size = 2093782 }, - { url = "https://files.pythonhosted.org/packages/b3/24/30e33b6389ebb5a17df2a4243b091bc709fb3dfc9a48c8d72f8e037c943d/SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71", size = 2084180 }, - { url = "https://files.pythonhosted.org/packages/10/1e/70e9ed2143a27065246be40f78637ad5160ea0f5fd32f8cab819a31ff54d/SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff", size = 3202469 }, - { url = "https://files.pythonhosted.org/packages/b4/5f/95e0ed74093ac3c0db6acfa944d4d8ac6284ef5e1136b878a327ea1f975a/SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d", size = 3202464 }, - { url = "https://files.pythonhosted.org/packages/91/95/2cf9b85a6bc2ee660e40594dffe04e777e7b8617fd0c6d77a0f782ea96c9/SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb", size = 3139508 }, - { url = "https://files.pythonhosted.org/packages/92/ea/f0c01bc646456e4345c0fb5a3ddef457326285c2dc60435b0eb96b61bf31/SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8", size = 3159837 }, - { url = "https://files.pythonhosted.org/packages/a6/93/c8edbf153ee38fe529773240877bf1332ed95328aceef6254288f446994e/SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f", size = 2064529 }, - { url = "https://files.pythonhosted.org/packages/b1/03/d12b7c1d36fd80150c1d52e121614cf9377dac99e5497af8d8f5b2a8db64/SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959", size = 2089874 }, { url = "https://files.pythonhosted.org/packages/b8/bf/005dc47f0e57556e14512d5542f3f183b94fde46e15ff1588ec58ca89555/SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4", size = 2092378 }, { url = "https://files.pythonhosted.org/packages/94/65/f109d5720779a08e6e324ec89a744f5f92c48bd8005edc814bf72fbb24e5/SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855", size = 2082778 }, { url = "https://files.pythonhosted.org/packages/60/f6/d9aa8c49c44f9b8c9b9dada1f12fa78df3d4c42aa2de437164b83ee1123c/SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53", size = 3232191 }, @@ -3591,6 +4132,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/49/21633706dd6feb14cd3f7935fc00b60870ea057686035e1a99ae6d9d9d53/SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e", size = 1883787 }, ] +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + [[package]] name = "starlette" version = "0.37.2" @@ -3641,6 +4196,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/fe/81695a1aa331a842b582453b605175f419fe8540355886031328089d840a/sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8", size = 6189177 }, ] +[[package]] +name = "tensorboard" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "grpcio" }, + { name = "markdown" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "setuptools" }, + { name = "six" }, + { name = "tensorboard-data-server" }, + { name = "werkzeug" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/de/021c1d407befb505791764ad2cbd56ceaaa53a746baed01d2e2143f05f18/tensorboard-2.18.0-py3-none-any.whl", hash = "sha256:107ca4821745f73e2aefa02c50ff70a9b694f39f790b11e6f682f7d326745eab", size = 5503036 }, +] + +[[package]] +name = "tensorboard-data-server" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356 }, + { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598 }, + { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363 }, +] + +[[package]] +name = "tensorflow" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "astunparse" }, + { name = "flatbuffers" }, + { name = "gast" }, + { name = "google-pasta" }, + { name = "grpcio" }, + { name = "h5py" }, + { name = "keras" }, + { name = "libclang" }, + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "opt-einsum" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "requests" }, + { name = "setuptools" }, + { name = "six" }, + { name = "tensorboard" }, + { name = "termcolor" }, + { name = "typing-extensions" }, + { name = "wrapt" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/bf/4cc283db323fd723f630e2454b2857054d2c81ff5012c1857659e72470f1/tensorflow-2.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ec4133a215c59314e929e7cbe914579d3afbc7874d9fa924873ee633fe4f71d0", size = 239565465 }, + { url = "https://files.pythonhosted.org/packages/56/e4/55aaac2b15af4dad079e5af329a79d961e5206589d0e02b1e8da221472ed/tensorflow-2.18.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4822904b3559d8a9c25f0fe5fef191cfc1352ceca42ca64f2a7bc7ae0ff4a1f5", size = 231898760 }, + { url = "https://files.pythonhosted.org/packages/50/29/61ce80da0bfea3948326697dd1d832d28c863c9dacf90a27ee80fd4c1d31/tensorflow-2.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfdd65ea7e064064283dd78d529dd621257ee617218f63681935fd15817c6286", size = 615520727 }, + { url = "https://files.pythonhosted.org/packages/eb/f1/828bbccc84a72db960a7d116f55f3f6aec9f5658f5d32ce9db20142d5742/tensorflow-2.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:a701c2d3dca5f2efcab315b2c217f140ebd3da80410744e87d77016b3aaf53cb", size = 7520 }, +] + [[package]] name = "termcolor" version = "2.5.0" @@ -3650,6 +4269,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/be/df630c387a0a054815d60be6a97eb4e8f17385d5d6fe660e1c02750062b4/termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8", size = 7755 }, ] +[[package]] +name = "terminado" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "os_name != 'nt'" }, + { name = "pywinpty", marker = "os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux'" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154 }, +] + +[[package]] +name = "terminaltables" +version = "3.1.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/fc/0b73d782f5ab7feba8d007573a3773c58255f223c5940a7b7085f02153c3/terminaltables-3.1.10.tar.gz", hash = "sha256:ba6eca5cb5ba02bba4c9f4f985af80c54ec3dccf94cfcd190154386255e47543", size = 12264 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/fb/ea621e0a19733e01fe4005d46087d383693c0f4a8f824b47d8d4122c87e0/terminaltables-3.1.10-py2.py3-none-any.whl", hash = "sha256:e4fdc4179c9e4aab5f674d80f09d76fa436b96fdc698a8505e0a36bf0804a874", size = 15155 }, +] + +[[package]] +name = "thop" +version = "0.1.1.post2209072238" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/0f/72beeab4ff5221dc47127c80f8834b4bcd0cb36f6ba91c0b1d04a1233403/thop-0.1.1.post2209072238-py3-none-any.whl", hash = "sha256:01473c225231927d2ad718351f78ebf7cffe6af3bed464c4f1ba1ef0f7cdda27", size = 15443 }, +] + [[package]] name = "threadpoolctl" version = "3.5.0" @@ -3659,6 +4312,18 @@ 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 = "tifffile" +version = "2025.1.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/fc/697d8dac6936a81eda88e7d4653d567fcb0d504efad3fd28f5272f96fcf9/tifffile-2025.1.10.tar.gz", hash = "sha256:baaf0a3b87bf7ec375fa1537503353f70497eabe1bdde590f2e41cc0346e612f", size = 365585 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/50/7bef6a1259a2c4b81823653a69d2d51074f7b8095db2abae5abee962ab87/tifffile-2025.1.10-py3-none-any.whl", hash = "sha256:ed24cf4c99fb13b4f5fb29f8a0d5605e60558c950bccbdca2a6470732a27cfb3", size = 227551 }, +] + [[package]] name = "tinycss2" version = "1.4.0" @@ -3705,45 +4370,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/12/ced7105d2de62fa7c8fb5fce92cc4ce66b57c95fb875e9318dba7f8c5db0/toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", size = 25796 }, ] -[[package]] -name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, -] - [[package]] name = "toolz" version = "1.0.0" @@ -3774,20 +4400,12 @@ dependencies = [ { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "setuptools" }, { name = "sympy" }, { name = "triton", marker = "python_full_version < '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/ef/834af4a885b31a0b32fff2d80e1e40f771e1566ea8ded55347502440786a/torch-2.5.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:71328e1bbe39d213b8721678f9dcac30dfc452a46d586f1d514a6aa0a99d4744", size = 906446312 }, - { url = "https://files.pythonhosted.org/packages/69/f0/46e74e0d145f43fa506cb336eaefb2d240547e4ce1f496e442711093ab25/torch-2.5.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:34bfa1a852e5714cbfa17f27c49d8ce35e1b7af5608c4bc6e81392c352dbc601", size = 91919522 }, - { url = "https://files.pythonhosted.org/packages/a5/13/1eb674c8efbd04d71e4a157ceba991904f633e009a584dd65dccbafbb648/torch-2.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:32a037bd98a241df6c93e4c789b683335da76a2ac142c0973675b715102dc5fa", size = 203088048 }, - { url = "https://files.pythonhosted.org/packages/a9/9d/e0860474ee0ff8f6ef2c50ec8f71a250f38d78a9b9df9fd241ad3397a65b/torch-2.5.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:23d062bf70776a3d04dbe74db950db2a5245e1ba4f27208a87f0d743b0d06e86", size = 63877046 }, - { url = "https://files.pythonhosted.org/packages/d1/35/e8b2daf02ce933e4518e6f5682c72fd0ed66c15910ea1fb4168f442b71c4/torch-2.5.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:de5b7d6740c4b636ef4db92be922f0edc425b65ed78c5076c43c42d362a45457", size = 906474467 }, - { url = "https://files.pythonhosted.org/packages/40/04/bd91593a4ca178ece93ca55f27e2783aa524aaccbfda66831d59a054c31e/torch-2.5.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:340ce0432cad0d37f5a31be666896e16788f1adf8ad7be481196b503dad675b9", size = 91919450 }, - { url = "https://files.pythonhosted.org/packages/0d/4a/e51420d46cfc90562e85af2fee912237c662ab31140ab179e49bd69401d6/torch-2.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:603c52d2fe06433c18b747d25f5c333f9c1d58615620578c326d66f258686f9a", size = 203098237 }, - { url = "https://files.pythonhosted.org/packages/d0/db/5d9cbfbc7968d79c5c09a0bc0bc3735da079f2fd07cc10498a62b320a480/torch-2.5.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:31f8c39660962f9ae4eeec995e3049b5492eb7360dd4f07377658ef4d728fa4c", size = 63884466 }, { url = "https://files.pythonhosted.org/packages/8b/5c/36c114d120bfe10f9323ed35061bc5878cc74f3f594003854b0ea298942f/torch-2.5.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:ed231a4b3a5952177fafb661213d690a72caaad97d5824dd4fc17ab9e15cec03", size = 906389343 }, { url = "https://files.pythonhosted.org/packages/6d/69/d8ada8b6e0a4257556d5b4ddeb4345ea8eeaaef3c98b60d1cca197c7ad8e/torch-2.5.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:3f4b7f10a247e0dcd7ea97dc2d3bfbfc90302ed36d7f3952b0008d0df264e697", size = 91811673 }, { url = "https://files.pythonhosted.org/packages/5f/ba/607d013b55b9fd805db2a5c2662ec7551f1910b4eef39653eeaba182c5b2/torch-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:73e58e78f7d220917c5dbfad1a40e09df9929d3b95d25e57d9f8558f84c9a11c", size = 203046841 }, @@ -3805,20 +4423,30 @@ dependencies = [ { name = "torch" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/59/aea68d755da1451e1a0d894528a7edc9b58eb30d33e274bf21bef28dad1a/torchvision-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4878fefb96ef293d06c27210918adc83c399d9faaf34cda5a63e129f772328f1", size = 1787552 }, - { url = "https://files.pythonhosted.org/packages/a2/f6/7ff89a9f8703f623f5664afd66c8600e3f09fe188e1e0b7e6f9a8617f865/torchvision-0.20.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:8ffbdf8bf5b30eade22d459f5a313329eeadb20dc75efa142987b53c007098c3", size = 7238975 }, - { url = "https://files.pythonhosted.org/packages/f7/ce/4c31e9b96cc4f9fec746b258d2aa35f8d1247f4f58d63f9c505ea5eb254d/torchvision-0.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:75f8a4d51a593c4bab6c9bf7d75bdd88691b00a53b07656678bc55a3a753dd73", size = 14265343 }, - { url = "https://files.pythonhosted.org/packages/17/11/b5ce67715bbbec8798fb48c4a20ac28828aec1710ac01091a3eddcb8e075/torchvision-0.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:22c2fa44e20eb404b85e42b22b453863a14b0927d25e550fd4f84eea97fa5b39", size = 1562413 }, - { url = "https://files.pythonhosted.org/packages/28/57/4d7ad90be612f5ac6c4bdafcb0ff13e818e14a340a88c8ca00d9ed8c2dad/torchvision-0.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:344b339e15e6bbb59ee0700772616d0afefd209920c762b1604368d8c3458322", size = 1787548 }, - { url = "https://files.pythonhosted.org/packages/de/e9/e190ecec448d5a2abad8348cf085fcb39962a491e3f40dcb023721e04feb/torchvision-0.20.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:86f6523dee420000fe14c3527f6c8e0175139fda7d995b187f54a0b0ebec7eb6", size = 7241222 }, - { url = "https://files.pythonhosted.org/packages/b1/a3/cbb8177e5e379f0c040b00c6f80f14d323a97e30495d7115d169b101b2f7/torchvision-0.20.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:a40d766345927639da322c693934e5f91b1ba2218846c7104b868dea2314ce8e", size = 14267510 }, - { url = "https://files.pythonhosted.org/packages/69/55/ce836703ff77bb21582c3098d5311f8ddde7eadc7eab04be9561961f4725/torchvision-0.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:5b501d5c04b034d2ecda96a31ed050e383cf8201352e4c9276ca249cbecfded0", size = 1562402 }, { url = "https://files.pythonhosted.org/packages/c5/eb/4ba19616378f2bc085999432fded2b7dfdbdccc6dd0fc293203452508100/torchvision-0.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1a31256ff945d64f006bb306813a7c95a531fe16bfb2535c837dd4c104533d7a", size = 1787553 }, { url = "https://files.pythonhosted.org/packages/d4/75/00a852275ade58d3dc474530f7a7b6bc999a817148f0eb59d4fde12eb955/torchvision-0.20.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:17cd78adddf81dac57d7dccc9277a4d686425b1c55715f308769770cb26cad5c", size = 7240323 }, { url = "https://files.pythonhosted.org/packages/af/f0/ca1445406eb12cbeb7a41fc833a1941ede78e7c55621198b83ecd7bcfd0f/torchvision-0.20.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:9f853ba4497ac4691815ad41b523ee23cf5ba4f87b1ce869d704052e233ca8b7", size = 14266936 }, { url = "https://files.pythonhosted.org/packages/c3/18/00993d420b1d6e88582e51d4bc82c824c99a2e9c045d50eaf9b34fff729a/torchvision-0.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:4a330422c36dbfc946d3a6c1caec3489db07ecdf3675d83369adb2e5a0ca17c4", size = 1562392 }, ] +[[package]] +name = "tornado" +version = "6.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/45/a0daf161f7d6f36c3ea5fc0c2de619746cc3dd4c76402e9db545bd920f63/tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b", size = 501135 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/7e/71f604d8cea1b58f82ba3590290b66da1e72d840aeb37e0d5f7291bd30db/tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1", size = 436299 }, + { url = "https://files.pythonhosted.org/packages/96/44/87543a3b99016d0bf54fdaab30d24bf0af2e848f1d13d34a3a5380aabe16/tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803", size = 434253 }, + { url = "https://files.pythonhosted.org/packages/cb/fb/fdf679b4ce51bcb7210801ef4f11fdac96e9885daa402861751353beea6e/tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec", size = 437602 }, + { url = "https://files.pythonhosted.org/packages/4f/3b/e31aeffffc22b475a64dbeb273026a21b5b566f74dee48742817626c47dc/tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946", size = 436972 }, + { url = "https://files.pythonhosted.org/packages/22/55/b78a464de78051a30599ceb6983b01d8f732e6f69bf37b4ed07f642ac0fc/tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf", size = 437173 }, + { url = "https://files.pythonhosted.org/packages/79/5e/be4fb0d1684eb822c9a62fb18a3e44a06188f78aa466b2ad991d2ee31104/tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634", size = 437892 }, + { url = "https://files.pythonhosted.org/packages/f5/33/4f91fdd94ea36e1d796147003b490fe60a0215ac5737b6f9c65e160d4fe0/tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73", size = 437334 }, + { url = "https://files.pythonhosted.org/packages/2b/ae/c1b22d4524b0e10da2f29a176fb2890386f7bd1f63aacf186444873a88a0/tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c", size = 437261 }, + { url = "https://files.pythonhosted.org/packages/b5/25/36dbd49ab6d179bcfc4c6c093a51795a4f3bed380543a8242ac3517a1751/tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482", size = 438463 }, + { url = "https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", size = 438907 }, +] + [[package]] name = "tqdm" version = "4.67.1" @@ -3869,8 +4497,6 @@ dependencies = [ { name = "filelock", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/98/29/69aa56dc0b2eb2602b553881e34243475ea2afd9699be042316842788ff5/triton-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b0dd10a925263abbe9fa37dcde67a5e9b2383fc269fdf59f5657cac38c5d1d8", size = 209460013 }, - { url = "https://files.pythonhosted.org/packages/86/17/d9a5cf4fcf46291856d1e90762e36cbabd2a56c7265da0d1d9508c8e3943/triton-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f34f6e7885d1bf0eaaf7ba875a5f0ce6f3c13ba98f9503651c1e6dc6757ed5c", size = 209506424 }, { url = "https://files.pythonhosted.org/packages/78/eb/65f5ba83c2a123f6498a3097746607e5b2f16add29e36765305e4ac7fdd8/triton-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8182f42fd8080a7d39d666814fa36c5e30cc00ea7eeeb1a2983dbb4c99a0fdc", size = 209551444 }, ] @@ -3889,6 +4515,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/cc/0a838ba5ca64dc832aa43f727bd586309846b0ffb2ce52422543e6075e8a/typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847", size = 44908 }, ] +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20241206" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/60/47d92293d9bc521cd2301e423a358abfac0ad409b3a1606d8fbae1321961/types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb", size = 13802 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/b3/ca41df24db5eb99b00d97f89d7674a90cb6b3134c52fb8121b6d8d30f15c/types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53", size = 14384 }, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -3907,6 +4542,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, ] +[[package]] +name = "ultralytics" +version = "8.0.43" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "opencv-python" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "scipy" }, + { name = "seaborn" }, + { name = "sentry-sdk" }, + { name = "tensorboard" }, + { name = "thop" }, + { name = "torch" }, + { name = "torchvision" }, + { name = "tqdm" }, + { name = "wheel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/e6/9e4797a242500e39b691789eafcd27ff9b3b109533643357b5a0e8f21ac0/ultralytics-8.0.43.tar.gz", hash = "sha256:028dd87f57b2b3669b1ae89872836fed4adc20d3abd5bcf6e489bd25d7f8a59e", size = 252731 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/48/c7a54a863f11142a21bc5af2a568859cb4f6359bb2de9bbef877ff69583b/ultralytics-8.0.43-py3-none-any.whl", hash = "sha256:c4fe980c1ed25d2769b6638d45c46c91f73b6d27b6329f90cf80844c8cef1636", size = 299603 }, +] + +[[package]] +name = "ultralyticsplus" +version = "0.0.28" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fire" }, + { name = "huggingface-hub" }, + { name = "pandas" }, + { name = "sahi" }, + { name = "ultralytics" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/f3/0b3a17b812566af0d681968959d44353edcfe6aae6dbb3218c3f83e99d36/ultralyticsplus-0.0.28.tar.gz", hash = "sha256:45221b592d1c2836b7b6d33321a71322cbc6a902a87e56a8e71499eb76be2b98", size = 10707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/89/cff07d99887d30d87eba2400b1f4f93a61e997fcc7173ad43c46b6d52415/ultralyticsplus-0.0.28-py3-none-any.whl", hash = "sha256:f7f3eb87d64748b2499b807c665fbd843cb5b74071c2a30848d5964539fad87d", size = 11591 }, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140 }, +] + [[package]] name = "urllib3" version = "2.3.0" @@ -3923,13 +4611,21 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } wheels = [ { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, ] +[[package]] +name = "validators" +version = "0.34.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/64/07/91582d69320f6f6daaf2d8072608a4ad8884683d4840e7e4f3a9dbdcc639/validators-0.34.0.tar.gz", hash = "sha256:647fe407b45af9a74d245b943b18e6a816acf4926974278f6dd617778e1e781f", size = 70955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/78/36828a4d857b25896f9774c875714ba4e9b3bc8a92d2debe3f4df3a83d4f/validators-0.34.0-py3-none-any.whl", hash = "sha256:c804b476e3e6d3786fa07a30073a4ef694e617805eb1946ceee3fe5a9b8b1321", size = 43536 }, +] + [[package]] name = "wandb" version = "0.19.0" @@ -3947,7 +4643,6 @@ dependencies = [ { name = "sentry-sdk" }, { name = "setproctitle" }, { name = "setuptools" }, - { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/cc/3322f2c4d85b84a18cb93e97ecad216fe6a59ec39118a82bdfed7872f660/wandb-0.19.0.tar.gz", hash = "sha256:cfacf2cc323561909e7572e772a4a5f849f28248a4529247b199466171cd84f8", size = 11821728 } wheels = [ @@ -3981,27 +4676,38 @@ dependencies = [ { name = "bert-score" }, { name = "bittensor" }, { name = "bittensor-cli" }, + { name = "black" }, { name = "clip" }, { name = "colormath" }, { name = "datasets" }, { name = "ddt" }, { name = "duckduckgo-search" }, + { name = "easyocr" }, { name = "einops" }, + { name = "keras-ocr" }, { name = "lxml" }, { name = "matplotlib-inline" }, + { name = "mss" }, { name = "nltk" }, + { name = "numpy" }, + { name = "object-detection" }, { name = "openai" }, { name = "opencv-python" }, { name = "peft" }, { name = "pip-chill" }, { name = "playwright" }, + { name = "proto-plus" }, + { name = "protobuf" }, { name = "pydantic" }, { name = "python-dotenv" }, + { name = "scikit-image" }, { name = "scikit-learn" }, { name = "sentence-transformers" }, { name = "shtab" }, { name = "sqlalchemy" }, + { name = "tensorflow" }, { name = "tinycss2" }, + { name = "ultralyticsplus" }, { name = "wandb" }, ] @@ -4012,31 +4718,51 @@ requires-dist = [ { name = "bert-score", specifier = "==0.3.13" }, { name = "bittensor", specifier = "==8.5.1rc5" }, { name = "bittensor-cli", specifier = "==8.2.0rc8" }, + { name = "black", specifier = ">=24.10.0" }, { name = "clip", git = "https://github.com/openai/CLIP.git" }, - { name = "colormath", specifier = "==3.0.0" }, + { name = "colormath", specifier = ">=3.0.0" }, { name = "datasets" }, { name = "datasets", specifier = "==3.2.0" }, { name = "ddt", specifier = "==1.6.0" }, { name = "duckduckgo-search" }, + { name = "easyocr", specifier = ">=1.7.2" }, { name = "einops" }, + { name = "keras-ocr", specifier = ">=0.9.3" }, { name = "lxml", specifier = "==5.3.0" }, { name = "matplotlib-inline", specifier = "==0.1.7" }, + { name = "mss", specifier = ">=10.0.0" }, { name = "nltk" }, + { name = "numpy", specifier = ">=2.0.2" }, + { name = "object-detection", specifier = ">=0.0.3" }, { name = "openai" }, { name = "opencv-python", specifier = "==4.10.0.84" }, { name = "peft" }, { name = "pip-chill", specifier = "==1.0.3" }, { name = "playwright", specifier = "==1.49.1" }, + { name = "proto-plus", specifier = ">=1.25.0" }, + { name = "protobuf", specifier = ">=5.29.3" }, { name = "pydantic", specifier = "==2.6" }, { name = "python-dotenv", specifier = "==1.0.1" }, + { name = "scikit-image", specifier = ">=0.25.0" }, { name = "scikit-learn", specifier = "==1.6.0" }, { name = "sentence-transformers" }, { name = "shtab", specifier = "==1.6.5" }, { name = "sqlalchemy" }, + { name = "tensorflow", specifier = ">=2.18.0" }, { name = "tinycss2", specifier = "==1.4.0" }, + { name = "ultralyticsplus", specifier = ">=0.0.28" }, { name = "wandb", specifier = "==0.19.0" }, ] +[[package]] +name = "webcolors" +version = "24.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934 }, +] + [[package]] name = "webencodings" version = "0.5.1" @@ -4061,28 +4787,6 @@ version = "14.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f4/1b/380b883ce05bb5f45a905b61790319a28958a9ab1e4b6b95ff5464b60ca1/websockets-14.1.tar.gz", hash = "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8", size = 162840 } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/91/b1b375dbd856fd5fff3f117de0e520542343ecaf4e8fc60f1ac1e9f5822c/websockets-14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a0adf84bc2e7c86e8a202537b4fd50e6f7f0e4a6b6bf64d7ccb96c4cd3330b29", size = 161950 }, - { url = "https://files.pythonhosted.org/packages/61/8f/4d52f272d3ebcd35e1325c646e98936099a348374d4a6b83b524bded8116/websockets-14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90b5d9dfbb6d07a84ed3e696012610b6da074d97453bd01e0e30744b472c8179", size = 159601 }, - { url = "https://files.pythonhosted.org/packages/c4/b1/29e87b53eb1937992cdee094a0988aadc94f25cf0b37e90c75eed7123d75/websockets-14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2177ee3901075167f01c5e335a6685e71b162a54a89a56001f1c3e9e3d2ad250", size = 159854 }, - { url = "https://files.pythonhosted.org/packages/3f/e6/752a2f5e8321ae2a613062676c08ff2fccfb37dc837a2ee919178a372e8a/websockets-14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f14a96a0034a27f9d47fd9788913924c89612225878f8078bb9d55f859272b0", size = 168835 }, - { url = "https://files.pythonhosted.org/packages/60/27/ca62de7877596926321b99071639275e94bb2401397130b7cf33dbf2106a/websockets-14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f874ba705deea77bcf64a9da42c1f5fc2466d8f14daf410bc7d4ceae0a9fcb0", size = 167844 }, - { url = "https://files.pythonhosted.org/packages/7e/db/f556a1d06635c680ef376be626c632e3f2bbdb1a0189d1d1bffb061c3b70/websockets-14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199", size = 168157 }, - { url = "https://files.pythonhosted.org/packages/b3/bc/99e5f511838c365ac6ecae19674eb5e94201aa4235bd1af3e6fa92c12905/websockets-14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bea45f19b7ca000380fbd4e02552be86343080120d074b87f25593ce1700ad58", size = 168561 }, - { url = "https://files.pythonhosted.org/packages/c6/e7/251491585bad61c79e525ac60927d96e4e17b18447cc9c3cfab47b2eb1b8/websockets-14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:219c8187b3ceeadbf2afcf0f25a4918d02da7b944d703b97d12fb01510869078", size = 167979 }, - { url = "https://files.pythonhosted.org/packages/ac/98/7ac2e4eeada19bdbc7a3a66a58e3ebdf33648b9e1c5b3f08c3224df168cf/websockets-14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad2ab2547761d79926effe63de21479dfaf29834c50f98c4bf5b5480b5838434", size = 167925 }, - { url = "https://files.pythonhosted.org/packages/ab/3d/09e65c47ee2396b7482968068f6e9b516221e1032b12dcf843b9412a5dfb/websockets-14.1-cp310-cp310-win32.whl", hash = "sha256:1288369a6a84e81b90da5dbed48610cd7e5d60af62df9851ed1d1d23a9069f10", size = 162831 }, - { url = "https://files.pythonhosted.org/packages/8a/67/59828a3d09740e6a485acccfbb66600632f2178b6ed1b61388ee96f17d5a/websockets-14.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0744623852f1497d825a49a99bfbec9bea4f3f946df6eb9d8a2f0c37a2fec2e", size = 163266 }, - { url = "https://files.pythonhosted.org/packages/97/ed/c0d03cb607b7fe1f7ff45e2cd4bb5cd0f9e3299ced79c2c303a6fff44524/websockets-14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:449d77d636f8d9c17952628cc7e3b8faf6e92a17ec581ec0c0256300717e1512", size = 161949 }, - { url = "https://files.pythonhosted.org/packages/06/91/bf0a44e238660d37a2dda1b4896235d20c29a2d0450f3a46cd688f43b239/websockets-14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a35f704be14768cea9790d921c2c1cc4fc52700410b1c10948511039be824aac", size = 159606 }, - { url = "https://files.pythonhosted.org/packages/ff/b8/7185212adad274c2b42b6a24e1ee6b916b7809ed611cbebc33b227e5c215/websockets-14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b1f3628a0510bd58968c0f60447e7a692933589b791a6b572fcef374053ca280", size = 159854 }, - { url = "https://files.pythonhosted.org/packages/5a/8a/0849968d83474be89c183d8ae8dcb7f7ada1a3c24f4d2a0d7333c231a2c3/websockets-14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c3deac3748ec73ef24fc7be0b68220d14d47d6647d2f85b2771cb35ea847aa1", size = 169402 }, - { url = "https://files.pythonhosted.org/packages/bd/4f/ef886e37245ff6b4a736a09b8468dae05d5d5c99de1357f840d54c6f297d/websockets-14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7048eb4415d46368ef29d32133134c513f507fff7d953c18c91104738a68c3b3", size = 168406 }, - { url = "https://files.pythonhosted.org/packages/11/43/e2dbd4401a63e409cebddedc1b63b9834de42f51b3c84db885469e9bdcef/websockets-14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cf0ad281c979306a6a34242b371e90e891bce504509fb6bb5246bbbf31e7b6", size = 168776 }, - { url = "https://files.pythonhosted.org/packages/6d/d6/7063e3f5c1b612e9f70faae20ebaeb2e684ffa36cb959eb0862ee2809b32/websockets-14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cc1fc87428c1d18b643479caa7b15db7d544652e5bf610513d4a3478dbe823d0", size = 169083 }, - { url = "https://files.pythonhosted.org/packages/49/69/e6f3d953f2fa0f8a723cf18cd011d52733bd7f6e045122b24e0e7f49f9b0/websockets-14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f95ba34d71e2fa0c5d225bde3b3bdb152e957150100e75c86bc7f3964c450d89", size = 168529 }, - { url = "https://files.pythonhosted.org/packages/70/ff/f31fa14561fc1d7b8663b0ed719996cf1f581abee32c8fb2f295a472f268/websockets-14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9481a6de29105d73cf4515f2bef8eb71e17ac184c19d0b9918a3701c6c9c4f23", size = 168475 }, - { url = "https://files.pythonhosted.org/packages/f1/15/b72be0e4bf32ff373aa5baef46a4c7521b8ea93ad8b49ca8c6e8e764c083/websockets-14.1-cp311-cp311-win32.whl", hash = "sha256:368a05465f49c5949e27afd6fbe0a77ce53082185bbb2ac096a3a8afaf4de52e", size = 162833 }, - { url = "https://files.pythonhosted.org/packages/bc/ef/2d81679acbe7057ffe2308d422f744497b52009ea8bab34b6d74a2657d1d/websockets-14.1-cp311-cp311-win_amd64.whl", hash = "sha256:6d24fc337fc055c9e83414c94e1ee0dee902a486d19d2a7f0929e49d7d604b09", size = 163263 }, { url = "https://files.pythonhosted.org/packages/55/64/55698544ce29e877c9188f1aee9093712411a8fc9732cca14985e49a8e9c/websockets-14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed907449fe5e021933e46a3e65d651f641975a768d0649fee59f10c2985529ed", size = 161957 }, { url = "https://files.pythonhosted.org/packages/a2/b1/b088f67c2b365f2c86c7b48edb8848ac27e508caf910a9d9d831b2f343cb/websockets-14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:87e31011b5c14a33b29f17eb48932e63e1dcd3fa31d72209848652310d3d1f0d", size = 159620 }, { url = "https://files.pythonhosted.org/packages/c1/89/2a09db1bbb40ba967a1b8225b07b7df89fea44f06de9365f17f684d0f7e6/websockets-14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc6ccf7d54c02ae47a48ddf9414c54d48af9c01076a2e1023e3b486b6e72c707", size = 159852 }, @@ -4105,15 +4809,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/1b/e808685530185915299740d82b3a4af3f2b44e56ccf4389397c7a5d95d39/websockets-14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:836bef7ae338a072e9d1863502026f01b14027250a4545672673057997d5c05a", size = 168757 }, { url = "https://files.pythonhosted.org/packages/b6/19/6ab716d02a3b068fbbeb6face8a7423156e12c446975312f1c7c0f4badab/websockets-14.1-cp313-cp313-win32.whl", hash = "sha256:0d4290d559d68288da9f444089fd82490c8d2744309113fc26e2da6e48b65da6", size = 162834 }, { url = "https://files.pythonhosted.org/packages/6c/fd/ab6b7676ba712f2fc89d1347a4b5bdc6aa130de10404071f2b2606450209/websockets-14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8621a07991add373c3c5c2cf89e1d277e49dc82ed72c75e3afc74bd0acc446f0", size = 163277 }, - { url = "https://files.pythonhosted.org/packages/fb/cd/382a05a1ba2a93bd9fb807716a660751295df72e77204fb130a102fcdd36/websockets-14.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5dc25a9dbd1a7f61eca4b7cb04e74ae4b963d658f9e4f9aad9cd00b688692c8", size = 159633 }, - { url = "https://files.pythonhosted.org/packages/b7/a0/fa7c62e2952ef028b422fbf420f9353d9dd4dfaa425de3deae36e98c0784/websockets-14.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:04a97aca96ca2acedf0d1f332c861c5a4486fdcba7bcef35873820f940c4231e", size = 159867 }, - { url = "https://files.pythonhosted.org/packages/c1/94/954b4924f868db31d5f0935893c7a8446515ee4b36bb8ad75a929469e453/websockets-14.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df174ece723b228d3e8734a6f2a6febbd413ddec39b3dc592f5a4aa0aff28098", size = 161121 }, - { url = "https://files.pythonhosted.org/packages/7a/2e/f12bbb41a8f2abb76428ba4fdcd9e67b5b364a3e7fa97c88f4d6950aa2d4/websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:034feb9f4286476f273b9a245fb15f02c34d9586a5bc936aff108c3ba1b21beb", size = 160731 }, - { url = "https://files.pythonhosted.org/packages/13/97/b76979401f2373af1fe3e08f960b265cecab112e7dac803446fb98351a52/websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c308dabd2b380807ab64b62985eaccf923a78ebc572bd485375b9ca2b7dc7", size = 160681 }, - { url = "https://files.pythonhosted.org/packages/39/9c/16916d9a436c109a1d7ba78817e8fee357b78968be3f6e6f517f43afa43d/websockets-14.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a42d3ecbb2db5080fc578314439b1d79eef71d323dc661aa616fb492436af5d", size = 163316 }, { url = "https://files.pythonhosted.org/packages/b0/0b/c7e5d11020242984d9d37990310520ed663b942333b83a033c2f20191113/websockets-14.1-py3-none-any.whl", hash = "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e", size = 156277 }, ] +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 }, +] + [[package]] name = "wheel" version = "0.45.1" @@ -4123,42 +4833,60 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494 }, ] +[[package]] +name = "widgetsnbextension" +version = "4.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/fc/238c424fd7f4ebb25f8b1da9a934a3ad7c848286732ae04263661eb0fc03/widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6", size = 1164730 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/02/88b65cc394961a60c43c70517066b6b679738caf78506a5da7b88ffcb643/widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71", size = 2335872 }, +] + +[[package]] +name = "wrapt" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/dd/35c573cc2b4b8d65ea96bba0247d05710f284857d30e2266d1874f1c727d/wrapt-1.17.1.tar.gz", hash = "sha256:16b2fdfa09a74a3930175b6d9d7d008022aa72a4f02de2b3eecafcc1adfd3cfe", size = 55552 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/40/7fb607aa889b107ab7417f633f1893f48be4fd8bd12ec89c6355d26560a8/wrapt-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b1a4c8edd038fee0ce67bf119b16eaa45d22a52bbaf7d0a17d2312eb0003b1bb", size = 38820 }, + { url = "https://files.pythonhosted.org/packages/ce/24/9e8b8b670c5ebab2c05e51ad7403c5317985c53071d0ce4bb85684b9dce1/wrapt-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:181a844005c9818792212a32e004cb4c6bd8e35cae8e97b1a39a1918d95cef58", size = 38921 }, + { url = "https://files.pythonhosted.org/packages/d7/00/c07c9893e6761ee60d59ec319b33b2d3c5b68da674cbbf8ebf6c54cba146/wrapt-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21ffcf16f5c243a626b0f8da637948e3d5984e3bc0c1bc500ad990e88e974e3b", size = 88720 }, + { url = "https://files.pythonhosted.org/packages/d6/09/d3962a902a6be1d5a66b04ec10189618796a5a9b3fb87d0873294661289d/wrapt-1.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0eb33799b7582bb73787b9903b70595f8eff67eecc9455f668ed01adf53f9eea", size = 80899 }, + { url = "https://files.pythonhosted.org/packages/e2/fc/92d37def794c3626fb3c3aa112aa629544ba21f6c565034dae0e587f03c0/wrapt-1.17.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57e932ad1908b53e9ad67a746432f02bc8473a9ee16e26a47645a2b224fba5fd", size = 89222 }, + { url = "https://files.pythonhosted.org/packages/cd/4f/e0921cb71ed320508cbcf0e450449642c4b892f64bc5b2696ca725427dea/wrapt-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b8bd35c15bc82c5cbe397e8196fa57a17ce5d3f30e925a6fd39e4c5bb02fdcff", size = 86707 }, + { url = "https://files.pythonhosted.org/packages/85/16/f61d6afe9c3c9932f8699a62e4e594bcac87fdffc7dbd8f603939c44cfa5/wrapt-1.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:93018dbb956e0ad99ea2fa2c3c22f033549dcb1f56ad9f4555dfe25e49688c5d", size = 79685 }, + { url = "https://files.pythonhosted.org/packages/95/1d/a1940ce270fa7793044e7131d48528b7d4a6ab2e038142a7c82d722aa5c1/wrapt-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5bd9186d52cf3d36bf1823be0e85297e4dbad909bc6dd495ce0d272806d84a7", size = 87568 }, + { url = "https://files.pythonhosted.org/packages/f0/ca/d1292891bfdda05a77b0bdc2ecdca4a9484b02d64a65e2afddfcb5ac17e1/wrapt-1.17.1-cp312-cp312-win32.whl", hash = "sha256:d609f0ab0603bbcbf2de906b366b9f9bec75c32b4493550a940de658cc2ce512", size = 36672 }, + { url = "https://files.pythonhosted.org/packages/63/0f/0d52bff5074392586eb754609bc0877cea5340a2152f946166002b70ed07/wrapt-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:2c160bb8815787646b27a0c8575a26a4d6bf6abd7c5eb250ad3f2d38b29cb2cb", size = 38866 }, + { url = "https://files.pythonhosted.org/packages/0e/16/82d25dd10e97eabb561d491487ff111ac272a4024f40df098bd61c96840b/wrapt-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:99e544e6ce26f89ad5acc6f407bc4daf7c1d42321e836f5c768f834100bdf35c", size = 38821 }, + { url = "https://files.pythonhosted.org/packages/08/e2/c79dd3c9712988156ea86cd507a81f2b3f045eb84af2d0f7aedb42c709f9/wrapt-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:78da796b74f2c8e0af021ee99feb3bff7cb46f8e658fe25c20e66be1080db4a2", size = 38920 }, + { url = "https://files.pythonhosted.org/packages/4d/d8/bc2bb9797543b31ef7311074583c83addbfc21f1bead66ca7c9d637de6fd/wrapt-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f1bc359f6c52e53565e7af24b423e7a1eea97d155f38ac9e90e95303514710b", size = 88691 }, + { url = "https://files.pythonhosted.org/packages/e7/d3/8d64b5ced10eb0ef856ae864c806292de4891c4945db3444188d45a17b43/wrapt-1.17.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cbead724daa13cae46e8ab3bb24938d8514d123f34345535b184f3eb1b7ad717", size = 80862 }, + { url = "https://files.pythonhosted.org/packages/65/22/ee8e9a7014f7c011edac4a9babea4d0aa73a363dd618afc9b31669e478a8/wrapt-1.17.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdf7b0e3d3713331c0bb9daac47cd10e5aa60d060e53696f50de4e560bd5617f", size = 89174 }, + { url = "https://files.pythonhosted.org/packages/fd/10/3d1610d0c220a9f09317d7c9c216889b9dd67329e23d2fcf1017f2d67fc9/wrapt-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f17e8d926f63aed65ff949682c922f96d00f65c2e852c24272232313fa7823d5", size = 86721 }, + { url = "https://files.pythonhosted.org/packages/3c/c1/2f4b20057afcfbfad4886138a702ae2ffd79abbb43884b31e2388895e367/wrapt-1.17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9e04f3bd30e0b23c0ca7e1d4084e7d28b6d7d2feb8b7bc69b496fe881280579b", size = 79761 }, + { url = "https://files.pythonhosted.org/packages/f2/c9/c6bde0a10a7108da0ffaa0a8337221e66636199b367e7d6f1d035e0b306a/wrapt-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5660e470edfa15ae7ef407272c642d29e9962777a6b30bfa8fc0da2173dc9afd", size = 87586 }, + { url = "https://files.pythonhosted.org/packages/2a/ad/956a2db1196bde82088f5576eb1d7a290c4ffc0dec00bfc9d29fca440fff/wrapt-1.17.1-cp313-cp313-win32.whl", hash = "sha256:a992f9e019145e84616048556546edeaba68e05e1c1ffbe8391067a63cdadb0c", size = 36678 }, + { url = "https://files.pythonhosted.org/packages/d7/3a/8bf805ab213f7830b5998027ada2a3fae8e93529df7b0c446946d7f8e9e9/wrapt-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:5c2e24ba455af4b0a237a890ea6ed9bafd01fac2c47095f87c53ea3344215d43", size = 38873 }, + { url = "https://files.pythonhosted.org/packages/d7/76/878e3891ea25875608c5075b81240a4060e48eec786ff354b2a5d3eb87f1/wrapt-1.17.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88623fd957ba500d8bb0f7427a76496d99313ca2f9e932481c0882e034cf1add", size = 40060 }, + { url = "https://files.pythonhosted.org/packages/76/4b/fdde9124f6f61a56e1982cd0f7f0bc8fe2ababb876a50da3308e9ea462a0/wrapt-1.17.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:162d5f15bdd3b8037e06540902227ef9e0f298496c0afaadd9e2875851446693", size = 40154 }, + { url = "https://files.pythonhosted.org/packages/17/f2/e3d909ded67bd7d15b7f02f9cb05e111d2fef9499c1dc0f43690391b8c53/wrapt-1.17.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bb82447ddae4e3d9b51f40c494f66e6cbd8fb0e8e8b993678416535c67f9a0d", size = 113469 }, + { url = "https://files.pythonhosted.org/packages/6f/96/2ba3bd9b2d81b139a5784bf997bffc54979b561c272a953af3a69c242e02/wrapt-1.17.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ce4cff3922707048d754e365c4ebf41a3bcbf29b329349bf85d51873c7c7e9e", size = 101207 }, + { url = "https://files.pythonhosted.org/packages/eb/9a/c8e0275eeef83f0b8bf685034244fb0bf21d2e759fd7a6d54005de6b887f/wrapt-1.17.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fdc4e73a3fa0c25eed4d836d9732226f0326957cb075044a7f252b465299433", size = 109341 }, + { url = "https://files.pythonhosted.org/packages/4b/e3/346259c335b04d342beddba6a97030932b53a8ae35d7ff8a319ab2204270/wrapt-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bca1c0824f824bcd97b4b179dd55dcad1dab419252be2b2faebbcacefa3b27b2", size = 110232 }, + { url = "https://files.pythonhosted.org/packages/c3/aa/9611db2f50359b0b091e501405bc2497b7369185b342cae7bb2218a986e8/wrapt-1.17.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6d44b14f3a2f6343a07c90344850b7af5515538ce3a5d01f9c87d8bae9bd8724", size = 100474 }, + { url = "https://files.pythonhosted.org/packages/33/c2/edbcad020deeb742bce83647a7d13e47c35fafcab4fba4a89ec006ad0385/wrapt-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:169033329022739c6f0d8cd3031a113953b0ba500f3d5978904bdd40baec4568", size = 106377 }, + { url = "https://files.pythonhosted.org/packages/4f/bf/e2aa032cea63737cbabd4069c86d6aa4ba075ee19c44a165e1362a5b403b/wrapt-1.17.1-cp313-cp313t-win32.whl", hash = "sha256:52f0907287d9104112dbebda46af4db0793fcc4c64c8a867099212d116b6db64", size = 37987 }, + { url = "https://files.pythonhosted.org/packages/77/fb/439f032c1b52a1750c304ff85253edfec3a50d4e39fa9a338ab0f837acb4/wrapt-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:7966f98fa36933333d8a1c3d8552aa3d0735001901a4aabcfbd5a502b4ef14fe", size = 40751 }, + { url = "https://files.pythonhosted.org/packages/94/47/299f204e352655c117b9dec03fc585866df7eea72660515208ec67c185c4/wrapt-1.17.1-py3-none-any.whl", hash = "sha256:f3117feb1fc479eaf84b549d3f229d5d2abdb823f003bc2a1c6dd70072912fa0", size = 23589 }, +] + [[package]] name = "xxhash" version = "3.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/8a/0e9feca390d512d293afd844d31670e25608c4a901e10202aa98785eab09/xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212", size = 31970 }, - { url = "https://files.pythonhosted.org/packages/16/e6/be5aa49580cd064a18200ab78e29b88b1127e1a8c7955eb8ecf81f2626eb/xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520", size = 30801 }, - { url = "https://files.pythonhosted.org/packages/20/ee/b8a99ebbc6d1113b3a3f09e747fa318c3cde5b04bd9c197688fadf0eeae8/xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680", size = 220927 }, - { url = "https://files.pythonhosted.org/packages/58/62/15d10582ef159283a5c2b47f6d799fc3303fe3911d5bb0bcc820e1ef7ff4/xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da", size = 200360 }, - { url = "https://files.pythonhosted.org/packages/23/41/61202663ea9b1bd8e53673b8ec9e2619989353dba8cfb68e59a9cbd9ffe3/xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23", size = 428528 }, - { url = "https://files.pythonhosted.org/packages/f2/07/d9a3059f702dec5b3b703737afb6dda32f304f6e9da181a229dafd052c29/xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196", size = 194149 }, - { url = "https://files.pythonhosted.org/packages/eb/58/27caadf78226ecf1d62dbd0c01d152ed381c14c1ee4ad01f0d460fc40eac/xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c", size = 207703 }, - { url = "https://files.pythonhosted.org/packages/b1/08/32d558ce23e1e068453c39aed7b3c1cdc690c177873ec0ca3a90d5808765/xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482", size = 216255 }, - { url = "https://files.pythonhosted.org/packages/3f/d4/2b971e2d2b0a61045f842b622ef11e94096cf1f12cd448b6fd426e80e0e2/xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296", size = 202744 }, - { url = "https://files.pythonhosted.org/packages/19/ae/6a6438864a8c4c39915d7b65effd85392ebe22710412902487e51769146d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415", size = 210115 }, - { url = "https://files.pythonhosted.org/packages/48/7d/b3c27c27d1fc868094d02fe4498ccce8cec9fcc591825c01d6bcb0b4fc49/xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198", size = 414247 }, - { url = "https://files.pythonhosted.org/packages/a1/05/918f9e7d2fbbd334b829997045d341d6239b563c44e683b9a7ef8fe50f5d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442", size = 191419 }, - { url = "https://files.pythonhosted.org/packages/08/29/dfe393805b2f86bfc47c290b275f0b7c189dc2f4e136fd4754f32eb18a8d/xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da", size = 30114 }, - { url = "https://files.pythonhosted.org/packages/7b/d7/aa0b22c4ebb7c3ccb993d4c565132abc641cd11164f8952d89eb6a501909/xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9", size = 30003 }, - { url = "https://files.pythonhosted.org/packages/69/12/f969b81541ee91b55f1ce469d7ab55079593c80d04fd01691b550e535000/xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6", size = 26773 }, - { url = "https://files.pythonhosted.org/packages/b8/c7/afed0f131fbda960ff15eee7f304fa0eeb2d58770fade99897984852ef23/xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1", size = 31969 }, - { url = "https://files.pythonhosted.org/packages/8c/0c/7c3bc6d87e5235672fcc2fb42fd5ad79fe1033925f71bf549ee068c7d1ca/xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8", size = 30800 }, - { url = "https://files.pythonhosted.org/packages/04/9e/01067981d98069eec1c20201f8c145367698e9056f8bc295346e4ea32dd1/xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166", size = 221566 }, - { url = "https://files.pythonhosted.org/packages/d4/09/d4996de4059c3ce5342b6e1e6a77c9d6c91acce31f6ed979891872dd162b/xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7", size = 201214 }, - { url = "https://files.pythonhosted.org/packages/62/f5/6d2dc9f8d55a7ce0f5e7bfef916e67536f01b85d32a9fbf137d4cadbee38/xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623", size = 429433 }, - { url = "https://files.pythonhosted.org/packages/d9/72/9256303f10e41ab004799a4aa74b80b3c5977d6383ae4550548b24bd1971/xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a", size = 194822 }, - { url = "https://files.pythonhosted.org/packages/34/92/1a3a29acd08248a34b0e6a94f4e0ed9b8379a4ff471f1668e4dce7bdbaa8/xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88", size = 208538 }, - { url = "https://files.pythonhosted.org/packages/53/ad/7fa1a109663366de42f724a1cdb8e796a260dbac45047bce153bc1e18abf/xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c", size = 216953 }, - { url = "https://files.pythonhosted.org/packages/35/02/137300e24203bf2b2a49b48ce898ecce6fd01789c0fcd9c686c0a002d129/xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2", size = 203594 }, - { url = "https://files.pythonhosted.org/packages/23/03/aeceb273933d7eee248c4322b98b8e971f06cc3880e5f7602c94e5578af5/xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084", size = 210971 }, - { url = "https://files.pythonhosted.org/packages/e3/64/ed82ec09489474cbb35c716b189ddc1521d8b3de12b1b5ab41ce7f70253c/xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d", size = 415050 }, - { url = "https://files.pythonhosted.org/packages/71/43/6db4c02dcb488ad4e03bc86d70506c3d40a384ee73c9b5c93338eb1f3c23/xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839", size = 192216 }, - { url = "https://files.pythonhosted.org/packages/22/6d/db4abec29e7a567455344433d095fdb39c97db6955bb4a2c432e486b4d28/xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da", size = 30120 }, - { url = "https://files.pythonhosted.org/packages/52/1c/fa3b61c0cf03e1da4767213672efe186b1dfa4fc901a4a694fb184a513d1/xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58", size = 30003 }, - { url = "https://files.pythonhosted.org/packages/6b/8e/9e6fc572acf6e1cc7ccb01973c213f895cb8668a9d4c2b58a99350da14b7/xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3", size = 26777 }, { url = "https://files.pythonhosted.org/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969 }, { url = "https://files.pythonhosted.org/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787 }, { url = "https://files.pythonhosted.org/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959 }, @@ -4189,11 +4917,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1f/6d/c61e0668943a034abc3a569cdc5aeae37d686d9da7e39cf2ed621d533e36/xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637", size = 30172 }, { url = "https://files.pythonhosted.org/packages/96/14/8416dce965f35e3d24722cdf79361ae154fa23e2ab730e5323aa98d7919e/xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43", size = 30041 }, { url = "https://files.pythonhosted.org/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801 }, - { url = "https://files.pythonhosted.org/packages/ab/9a/233606bada5bd6f50b2b72c45de3d9868ad551e83893d2ac86dc7bb8553a/xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c", size = 29732 }, - { url = "https://files.pythonhosted.org/packages/0c/67/f75276ca39e2c6604e3bee6c84e9db8a56a4973fde9bf35989787cf6e8aa/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986", size = 36214 }, - { url = "https://files.pythonhosted.org/packages/0f/f8/f6c61fd794229cc3848d144f73754a0c107854372d7261419dcbbd286299/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6", size = 32020 }, - { url = "https://files.pythonhosted.org/packages/79/d3/c029c99801526f859e6b38d34ab87c08993bf3dcea34b11275775001638a/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b", size = 40515 }, - { url = "https://files.pythonhosted.org/packages/62/e3/bef7b82c1997579c94de9ac5ea7626d01ae5858aa22bf4fcb38bf220cb3e/xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da", size = 30064 }, ] [[package]] @@ -4207,38 +4930,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/98/e005bc608765a8a5569f58e650961314873c8469c333616eb40bff19ae97/yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34", size = 141458 }, - { url = "https://files.pythonhosted.org/packages/df/5d/f8106b263b8ae8a866b46d9be869ac01f9b3fb7f2325f3ecb3df8003f796/yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7", size = 94365 }, - { url = "https://files.pythonhosted.org/packages/56/3e/d8637ddb9ba69bf851f765a3ee288676f7cf64fb3be13760c18cbc9d10bd/yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed", size = 92181 }, - { url = "https://files.pythonhosted.org/packages/76/f9/d616a5c2daae281171de10fba41e1c0e2d8207166fc3547252f7d469b4e1/yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde", size = 315349 }, - { url = "https://files.pythonhosted.org/packages/bb/b4/3ea5e7b6f08f698b3769a06054783e434f6d59857181b5c4e145de83f59b/yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b", size = 330494 }, - { url = "https://files.pythonhosted.org/packages/55/f1/e0fc810554877b1b67420568afff51b967baed5b53bcc983ab164eebf9c9/yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5", size = 326927 }, - { url = "https://files.pythonhosted.org/packages/a9/42/b1753949b327b36f210899f2dd0a0947c0c74e42a32de3f8eb5c7d93edca/yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc", size = 319703 }, - { url = "https://files.pythonhosted.org/packages/f0/6d/e87c62dc9635daefb064b56f5c97df55a2e9cc947a2b3afd4fd2f3b841c7/yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd", size = 310246 }, - { url = "https://files.pythonhosted.org/packages/e3/ef/e2e8d1785cdcbd986f7622d7f0098205f3644546da7919c24b95790ec65a/yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990", size = 319730 }, - { url = "https://files.pythonhosted.org/packages/fc/15/8723e22345bc160dfde68c4b3ae8b236e868f9963c74015f1bc8a614101c/yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db", size = 321681 }, - { url = "https://files.pythonhosted.org/packages/86/09/bf764e974f1516efa0ae2801494a5951e959f1610dd41edbfc07e5e0f978/yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62", size = 324812 }, - { url = "https://files.pythonhosted.org/packages/f6/4c/20a0187e3b903c97d857cf0272d687c1b08b03438968ae8ffc50fe78b0d6/yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760", size = 337011 }, - { url = "https://files.pythonhosted.org/packages/c9/71/6244599a6e1cc4c9f73254a627234e0dad3883ece40cc33dce6265977461/yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b", size = 338132 }, - { url = "https://files.pythonhosted.org/packages/af/f5/e0c3efaf74566c4b4a41cb76d27097df424052a064216beccae8d303c90f/yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690", size = 331849 }, - { url = "https://files.pythonhosted.org/packages/8a/b8/3d16209c2014c2f98a8f658850a57b716efb97930aebf1ca0d9325933731/yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6", size = 84309 }, - { url = "https://files.pythonhosted.org/packages/fd/b7/2e9a5b18eb0fe24c3a0e8bae994e812ed9852ab4fd067c0107fadde0d5f0/yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8", size = 90484 }, - { url = "https://files.pythonhosted.org/packages/40/93/282b5f4898d8e8efaf0790ba6d10e2245d2c9f30e199d1a85cae9356098c/yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069", size = 141555 }, - { url = "https://files.pythonhosted.org/packages/6d/9c/0a49af78df099c283ca3444560f10718fadb8a18dc8b3edf8c7bd9fd7d89/yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193", size = 94351 }, - { url = "https://files.pythonhosted.org/packages/5a/a1/205ab51e148fdcedad189ca8dd587794c6f119882437d04c33c01a75dece/yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889", size = 92286 }, - { url = "https://files.pythonhosted.org/packages/ed/fe/88b690b30f3f59275fb674f5f93ddd4a3ae796c2b62e5bb9ece8a4914b83/yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8", size = 340649 }, - { url = "https://files.pythonhosted.org/packages/07/eb/3b65499b568e01f36e847cebdc8d7ccb51fff716dbda1ae83c3cbb8ca1c9/yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca", size = 356623 }, - { url = "https://files.pythonhosted.org/packages/33/46/f559dc184280b745fc76ec6b1954de2c55595f0ec0a7614238b9ebf69618/yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8", size = 354007 }, - { url = "https://files.pythonhosted.org/packages/af/ba/1865d85212351ad160f19fb99808acf23aab9a0f8ff31c8c9f1b4d671fc9/yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae", size = 344145 }, - { url = "https://files.pythonhosted.org/packages/94/cb/5c3e975d77755d7b3d5193e92056b19d83752ea2da7ab394e22260a7b824/yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3", size = 336133 }, - { url = "https://files.pythonhosted.org/packages/19/89/b77d3fd249ab52a5c40859815765d35c91425b6bb82e7427ab2f78f5ff55/yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb", size = 347967 }, - { url = "https://files.pythonhosted.org/packages/35/bd/f6b7630ba2cc06c319c3235634c582a6ab014d52311e7d7c22f9518189b5/yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e", size = 346397 }, - { url = "https://files.pythonhosted.org/packages/18/1a/0b4e367d5a72d1f095318344848e93ea70da728118221f84f1bf6c1e39e7/yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59", size = 350206 }, - { url = "https://files.pythonhosted.org/packages/b5/cf/320fff4367341fb77809a2d8d7fe75b5d323a8e1b35710aafe41fdbf327b/yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d", size = 362089 }, - { url = "https://files.pythonhosted.org/packages/57/cf/aadba261d8b920253204085268bad5e8cdd86b50162fcb1b10c10834885a/yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e", size = 366267 }, - { url = "https://files.pythonhosted.org/packages/54/58/fb4cadd81acdee6dafe14abeb258f876e4dd410518099ae9a35c88d8097c/yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a", size = 359141 }, - { url = "https://files.pythonhosted.org/packages/9a/7a/4c571597589da4cd5c14ed2a0b17ac56ec9ee7ee615013f74653169e702d/yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1", size = 84402 }, - { url = "https://files.pythonhosted.org/packages/ae/7b/8600250b3d89b625f1121d897062f629883c2f45339623b69b1747ec65fa/yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5", size = 91030 }, { url = "https://files.pythonhosted.org/packages/33/85/bd2e2729752ff4c77338e0102914897512e92496375e079ce0150a6dc306/yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50", size = 142644 }, { url = "https://files.pythonhosted.org/packages/ff/74/1178322cc0f10288d7eefa6e4a85d8d2e28187ccab13d5b844e8b5d7c88d/yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576", size = 94962 }, { url = "https://files.pythonhosted.org/packages/be/75/79c6acc0261e2c2ae8a1c41cf12265e91628c8c58ae91f5ff59e29c0787f/yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640", size = 92795 }, diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 73d031e6..f795f0bb 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -34,10 +34,16 @@ def do_GET(self): self.end_headers() self.wfile.write(b'User-agent: *\nDisallow: /') # Example content elif self.path.startswith('/lighthouse_score'): - html_index = int(self.path.split('/')[-1]) - self.send_header('Content-type', 'text/html') - self.end_headers() - self.wfile.write(htmls[html_index].encode('utf-8')) + try: + html_index = int(self.path.split('/')[-1]) + self.send_header('Content-type', 'text/html') + self.end_headers() + self.wfile.write(htmls[html_index].encode('utf-8')) + except Exception as e: + bt.logging.error(f"Error getting lighthouse score: {e}") + self.send_response(404) + self.end_headers() + self.wfile.write(b"Not Found") else: self.send_response(404) self.end_headers() diff --git a/webgenie/rewards/visual_reward/metrics/ocr_free_utils.py b/webgenie/rewards/visual_reward/metrics/ocr_free_utils.py index 2e977fcc..4d0e17ea 100644 --- a/webgenie/rewards/visual_reward/metrics/ocr_free_utils.py +++ b/webgenie/rewards/visual_reward/metrics/ocr_free_utils.py @@ -135,6 +135,7 @@ def extract_text_recursive(element, parent_color='#000000'): return list(children_texts) with open(html_file, 'r', encoding='utf-8') as file: + soup = BeautifulSoup(file, 'html.parser') body = soup.body return extract_text_recursive(body) if body else [] diff --git a/webgenie/rewards/visual_reward/metrics/visual_score.py b/webgenie/rewards/visual_reward/metrics/visual_score.py index ac0df6b7..fb1346cb 100644 --- a/webgenie/rewards/visual_reward/metrics/visual_score.py +++ b/webgenie/rewards/visual_reward/metrics/visual_score.py @@ -15,9 +15,11 @@ def patch_asscalar(a): import matplotlib.pyplot as plt from scipy.optimize import linear_sum_assignment import random +import time from sklearn.metrics.pairwise import cosine_similarity from difflib import SequenceMatcher from tqdm import tqdm + from pathlib import Path from PIL import Image, ImageDraw import torch @@ -474,14 +476,18 @@ def visual_eval_v3_multi(input_list, debug=False): # final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) # return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) # continue - + print(len(predict_blocks)) if debug: print(predict_blocks) print(original_blocks) - + print("Merging blocks") + start_time = time.time() predict_blocks = merge_blocks_by_bbox(predict_blocks) predict_blocks_m, original_blocks_m, matching = find_possible_merge(predict_blocks, deepcopy(original_blocks), consecutive_bonus, window_size, debug=debug) + print(f"Merging blocks time: {time.time() - start_time:.2f} seconds") + print("Filtering matching") + start_time = time.time() filtered_matching = [] for i, j in matching: text_similarity = SequenceMatcher(None, predict_blocks_m[i]['text'], original_blocks_m[j]['text']).ratio() @@ -490,6 +496,7 @@ def visual_eval_v3_multi(input_list, debug=False): continue filtered_matching.append([i, j, text_similarity]) matching = filtered_matching + print(f"Filtering matching time: {time.time() - start_time:.2f} seconds") indices1 = [item[0] for item in matching] indices2 = [item[1] for item in matching] @@ -510,7 +517,9 @@ def visual_eval_v3_multi(input_list, debug=False): if j not in indices2: unmatched_area_2 += original_blocks_m[j]['bbox'][2] * original_blocks_m[j]['bbox'][3] sum_areas.append(unmatched_area_1 + unmatched_area_2) - + + print("Calculating scores") + start_time = time.time() for i, j, text_similarity in matching: sum_block_area = predict_blocks_m[i]['bbox'][2] * predict_blocks_m[i]['bbox'][3] + original_blocks_m[j]['bbox'][2] * original_blocks_m[j]['bbox'][3] @@ -520,6 +529,7 @@ def visual_eval_v3_multi(input_list, debug=False): original_blocks_m[j]['bbox'][0] + original_blocks_m[j]['bbox'][2] / 2, \ original_blocks_m[j]['bbox'][1] + original_blocks_m[j]['bbox'][3] / 2) # Normalized ciede2000 formula + text_color_similarity = color_similarity_ciede2000(predict_blocks_m[i]['color'], original_blocks_m[j]['color']) matched_list.append([predict_blocks_m[i]['bbox'], original_blocks_m[j]['bbox']]) @@ -542,6 +552,7 @@ def visual_eval_v3_multi(input_list, debug=False): print("color score", text_color_similarity) print("----------------------------------") pass + print(f"Calculating scores time: {time.time() - start_time:.2f} seconds") """ if debug: img1 = cv2.imread(predict_img_list[k]) From 0f115985795ea7239df6c41b9e0d6e79516a73d2 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 13 Jan 2025 23:58:37 -0600 Subject: [PATCH 191/554] chore: shorten the random website html --- webgenie/datasets/random_website_dataset.py | 31 ++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index 0a98c53a..a115e2ff 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -2,7 +2,7 @@ import nltk import random -from bs4 import BeautifulSoup +from bs4 import BeautifulSoup, Tag, NavigableString from collections import Counter from duckduckgo_search import DDGS from nltk.corpus import brown @@ -81,6 +81,34 @@ async def get_rendered_html(self, url): # Return the modified HTML as a string return str(soup) + + async def shorten_html(self, html: str, max_children: int = 20, max_text_length: int = 400)->str: + soup = BeautifulSoup(html, "html.parser") + def traverse(node): + # If it’s a tag, we might need to limit its children + if isinstance(node, Tag): + # If node has too many children, remove the extras + if len(node.contents) > max_children: + # Keep only the first max_children + node.contents = node.contents[:max_children] + + # Recurse on each child + for child in node.contents: + traverse(child) + + # If it’s a text node, shorten if needed + elif isinstance(node, NavigableString): + text_str = str(node) + if len(text_str) > max_text_length: + shortened = text_str[:max_text_length] + "..." + node.replace_with(shortened) + + # Start traversal from the root soup element (html, body, etc.) + traverse(soup) + + return str(soup) + + async def generate_context(self)->DatasetEntry: try: @@ -90,6 +118,7 @@ async def generate_context(self)->DatasetEntry: raise Exception("Failed to get a valid website URL") bt.logging.debug(f"Generated website URL: {website_url}") html = await self.get_rendered_html(website_url) + html = await self.shorten_html(html) return DatasetEntry( src="random_website", topic="random_website", From f03bea54ff990bbcb60f36e1d1c121a030efa912 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 14 Jan 2025 22:38:15 -0600 Subject: [PATCH 192/554] feat: update accuracy score --- pyproject.toml | 11 +- tests/data/image_techcompany.jpg | Bin 0 -> 204880 bytes tests/data/test.html | 119 ++ tests/data/test_p.html | 119 ++ tests/data/test_p_1.html | 119 ++ .../test_huggingface_dataset.py | 0 tests/{ => rewards}/test_lighthouse.py | 0 tests/rewards/test_visual_score.py | 38 + tests/test_clip.py | 8 - tests/test_reward.py | 82 + uv.lock | 1425 +---------------- webgenie.db | Bin 20480 -> 0 bytes .../competitions/image_task_competition.py | 2 +- webgenie/constants.py | 6 + webgenie/helpers/htmls.py | 32 +- .../rewards/visual_reward/common/browser.py | 21 + .../visual_reward/common/color_diff.py | 80 + .../common/extract_html_elements.py | 140 ++ .../visual_reward/common/inpaint_image.py | 36 + webgenie/rewards/visual_reward/common/sift.py | 41 + .../visual_reward/common/similarity.py | 54 + .../visual_reward/common/take_screenshot.py | 27 + .../high_level_matching_score/__init__.py | 1 + .../clip_matching_score.py | 71 + .../high_level_matching_score.py | 13 + .../high_level_matching_score/histogram.py | 46 + .../low_level_matching_score/__init__.py | 1 + .../element_matching_score.py | 46 + .../input_matching_score.py | 49 + .../low_level_matching_score.py | 36 + .../text_matching_score.py | 62 + .../rewards/visual_reward/metrics/__init__.py | 0 .../visual_reward/metrics/dedup_post_gen.py | 69 - .../visual_reward/metrics/ocr_free_utils.py | 261 --- .../metrics/screenshot_multiple.py | 29 - .../metrics/screenshot_single.py | 52 - .../visual_reward/metrics/visual_score.py | 596 ------- .../visual_reward/metrics_v2/__init__.py | 0 .../metrics_v2/dedup_post_gen.py | 69 - .../metrics_v2/ocr_free_utils.py | 268 ---- .../metrics_v2/screenshot_multiple.py | 29 - .../metrics_v2/screenshot_single.py | 52 - .../visual_reward/metrics_v2/visual_score.py | 599 ------- .../rewards/visual_reward/visual_reward.py | 17 +- 44 files changed, 1260 insertions(+), 3466 deletions(-) create mode 100644 tests/data/image_techcompany.jpg create mode 100644 tests/data/test.html create mode 100644 tests/data/test_p.html create mode 100644 tests/data/test_p_1.html rename tests/{ => datasets}/test_huggingface_dataset.py (100%) rename tests/{ => rewards}/test_lighthouse.py (100%) create mode 100644 tests/rewards/test_visual_score.py delete mode 100644 tests/test_clip.py create mode 100644 tests/test_reward.py delete mode 100644 webgenie.db create mode 100644 webgenie/rewards/visual_reward/common/browser.py create mode 100644 webgenie/rewards/visual_reward/common/color_diff.py create mode 100644 webgenie/rewards/visual_reward/common/extract_html_elements.py create mode 100644 webgenie/rewards/visual_reward/common/inpaint_image.py create mode 100644 webgenie/rewards/visual_reward/common/sift.py create mode 100644 webgenie/rewards/visual_reward/common/similarity.py create mode 100644 webgenie/rewards/visual_reward/common/take_screenshot.py create mode 100644 webgenie/rewards/visual_reward/high_level_matching_score/__init__.py create mode 100644 webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py create mode 100644 webgenie/rewards/visual_reward/high_level_matching_score/high_level_matching_score.py create mode 100644 webgenie/rewards/visual_reward/high_level_matching_score/histogram.py create mode 100644 webgenie/rewards/visual_reward/low_level_matching_score/__init__.py create mode 100644 webgenie/rewards/visual_reward/low_level_matching_score/element_matching_score.py create mode 100644 webgenie/rewards/visual_reward/low_level_matching_score/input_matching_score.py create mode 100644 webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py create mode 100644 webgenie/rewards/visual_reward/low_level_matching_score/text_matching_score.py delete mode 100644 webgenie/rewards/visual_reward/metrics/__init__.py delete mode 100644 webgenie/rewards/visual_reward/metrics/dedup_post_gen.py delete mode 100644 webgenie/rewards/visual_reward/metrics/ocr_free_utils.py delete mode 100644 webgenie/rewards/visual_reward/metrics/screenshot_multiple.py delete mode 100644 webgenie/rewards/visual_reward/metrics/screenshot_single.py delete mode 100644 webgenie/rewards/visual_reward/metrics/visual_score.py delete mode 100644 webgenie/rewards/visual_reward/metrics_v2/__init__.py delete mode 100644 webgenie/rewards/visual_reward/metrics_v2/dedup_post_gen.py delete mode 100644 webgenie/rewards/visual_reward/metrics_v2/ocr_free_utils.py delete mode 100644 webgenie/rewards/visual_reward/metrics_v2/screenshot_multiple.py delete mode 100644 webgenie/rewards/visual_reward/metrics_v2/screenshot_single.py delete mode 100644 webgenie/rewards/visual_reward/metrics_v2/visual_score.py diff --git a/pyproject.toml b/pyproject.toml index 29bc2341..804e2216 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,23 +27,16 @@ dependencies = [ "pydantic==2.6", "python-dotenv==1.0.1", "scikit-learn==1.6.0", - "sentence-transformers", "shtab==1.6.5", "sqlalchemy", "tinycss2==1.4.0", "wandb==0.19.0", "numpy>=2.0.2", - "easyocr>=1.7.2", "colormath>=3.0.0", "scikit-image>=0.25.0", - "object-detection>=0.0.3", + "sentence-transformers", "tensorflow>=2.18.0", - "protobuf>=5.29.3", - "proto-plus>=1.25.0", - "black>=24.10.0", - "ultralyticsplus>=0.0.28", - "mss>=10.0.0", - "keras-ocr>=0.9.3", + "tf-keras", ] [project.urls] diff --git a/tests/data/image_techcompany.jpg b/tests/data/image_techcompany.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d977b66b91078543d9d1123f86922288bc1fb9e0 GIT binary patch literal 204880 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≠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

PD@HyFoF@4 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 yCmJ}>z=;M + + + + + +Tech Company + + + + +

+
+Hero Image +
+
+

Welcome to Tech Company

+

At Tech Company, we are dedicated to providing the best technology solutions for your needs. Our team of experts is always ready to help you with any questions or problems you may have.

+
+ + + \ No newline at end of file diff --git a/tests/data/test_p.html b/tests/data/test_p.html new file mode 100644 index 00000000..249d64e6 --- /dev/null +++ b/tests/data/test_p.html @@ -0,0 +1,119 @@ + + + + + + +Tech Company + + + + +
+
+ + +
+
+
+Hero Image +
+
+

Welcome to Tech Company

+

At Tech Company, we are dedicated to providing the best technology solutions for your needs. Our team of experts is always ready to help you with any questions or problems you may have.

+
+ + + \ No newline at end of file diff --git a/tests/data/test_p_1.html b/tests/data/test_p_1.html new file mode 100644 index 00000000..71aa1445 --- /dev/null +++ b/tests/data/test_p_1.html @@ -0,0 +1,119 @@ + + + + + + +Tech Company + + + + +
+
+ + +
+
+
+Hero Image +
+
+

Welcome to Tech Company

+

At Tech Company, we are dedicated to providing the best technology solutions for your needs. Our team of experts is always ready to help you with any questions or problems you may have.

+
+ + + \ No newline at end of file diff --git a/tests/test_huggingface_dataset.py b/tests/datasets/test_huggingface_dataset.py similarity index 100% rename from tests/test_huggingface_dataset.py rename to tests/datasets/test_huggingface_dataset.py diff --git a/tests/test_lighthouse.py b/tests/rewards/test_lighthouse.py similarity index 100% rename from tests/test_lighthouse.py rename to tests/rewards/test_lighthouse.py diff --git a/tests/rewards/test_visual_score.py b/tests/rewards/test_visual_score.py new file mode 100644 index 00000000..a79d7038 --- /dev/null +++ b/tests/rewards/test_visual_score.py @@ -0,0 +1,38 @@ +import asyncio +import sys +import os +import cv2 +import numpy as np +from dotenv import load_dotenv, find_dotenv +def init_test(): + parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + sys.path.append(parent_dir) + load_dotenv(find_dotenv(filename=".env.validator")) + +init_test() + + +from webgenie.rewards.visual_reward.common.browser import start_browser, stop_browser +from webgenie.rewards.visual_reward.common.extract_html_elements import extract_html_elements +from webgenie.rewards.visual_reward.low_level_matching_score.text_matching_score import calculate_text_matching_similarity +from webgenie.rewards.visual_reward.low_level_matching_score.input_matching_score import calculate_input_matching_similarity +from webgenie.rewards.visual_reward.low_level_matching_score.element_matching_score import calculate_element_matching_similarity +from webgenie.rewards.visual_reward.high_level_matching_score.clip_matching_score import calculate_clip_score +from webgenie.rewards.visual_reward.high_level_matching_score.histogram import histogram_matching_score +from webgenie.rewards.visual_reward.high_level_matching_score.high_level_matching_score import high_level_matching_score + +async def test_text_matching_score(): + await start_browser() + import time + start_time = time.time() + url = "test1.html" + url_predict = "miner.html" + scores = await high_level_matching_score([url_predict], url) + print(scores) + print(time.time() - start_time) + await stop_browser() + +if __name__ == "__main__": + asyncio.run(test_text_matching_score()) + + diff --git a/tests/test_clip.py b/tests/test_clip.py deleted file mode 100644 index 99437214..00000000 --- a/tests/test_clip.py +++ /dev/null @@ -1,8 +0,0 @@ -from init_test import init_test - -init_test() - -from metrics.text_mask import get_text_contour_image - -mask = get_text_contour_image("tests/data/test.png") -mask.save("tests/data/mask.png") diff --git a/tests/test_reward.py b/tests/test_reward.py new file mode 100644 index 00000000..d27d7276 --- /dev/null +++ b/tests/test_reward.py @@ -0,0 +1,82 @@ +from init_test import init_test +init_test() + +import asyncio +import numpy as np +from typing import List +import time +from webgenie.tasks import Task, Solution, ImageTask +from webgenie.rewards import ( + LighthouseReward, + QualityReward, + VisualReward, +) + +from webgenie.protocol import WebgenieImageSynapse +from webgenie.competitions.competition import ( + ACCURACY_METRIC_NAME, + SEO_METRIC_NAME, + QUALITY_METRIC_NAME, +) + +from webgenie.helpers.htmls import html_to_screenshot +from neurons.miners.openai_miner import OpenaiMiner + +metrics = { + ACCURACY_METRIC_NAME: VisualReward(), + SEO_METRIC_NAME: LighthouseReward(), + QUALITY_METRIC_NAME: QualityReward(), +} + +async def calculate_scores(task: Task, solutions: List[Solution]) -> dict[str, np.ndarray]: + scores: dict[str, np.ndarray] = {} + + for metric_name, reward_model in metrics.items(): + print(metric_name) + start_time = time.time() + reward_scores = await reward_model.reward(task, solutions) + execution_time = time.time() - start_time + print(f"Execution time: {execution_time:.2f} seconds") + scores[metric_name] = reward_scores + print(scores[metric_name]) + return scores + + +async def main(): + ground_truth_html_path = "work/original_f7e98ea8-cc04-44ea-8f40-432914b0f0ea.html" + with open(ground_truth_html_path, "r") as f: + ground_truth_html = f.read() + + print("HTML to screenshot") + start_time = time.time() + base64_image = await html_to_screenshot(ground_truth_html) + execution_time = time.time() - start_time + print(f"Execution time: {execution_time:.2f} seconds") + + task = ImageTask( + ground_truth_html=ground_truth_html, + ) + + miner = OpenaiMiner(neuron=None) + synapse = WebgenieImageSynapse( + base64_image = base64_image, + ) + + print("Miner forward image") + start_time = time.time() + synapse = await miner.forward_image(synapse) + print(synapse.html) + execution_time = time.time() - start_time + print(f"Execution time: {execution_time:.2f} seconds") + solutions = [Solution(html=synapse.html) for _ in range(1)] + + print("Calculate scores") + start_time = time.time() + scores = await calculate_scores(task, solutions) + execution_time = time.time() - start_time + print(f"Execution time: {execution_time:.2f} seconds") + + print(scores) + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/uv.lock b/uv.lock index 57151925..bb565851 100644 --- a/uv.lock +++ b/uv.lock @@ -163,70 +163,6 @@ 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 = "appnope" -version = "0.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, -] - -[[package]] -name = "argon2-cffi" -version = "23.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "argon2-cffi-bindings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124 }, -] - -[[package]] -name = "argon2-cffi-bindings" -version = "21.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658 }, - { url = "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583 }, - { url = "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168 }, - { url = "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709 }, - { url = "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613 }, - { url = "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583 }, - { url = "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475 }, - { url = "https://files.pythonhosted.org/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698 }, - { url = "https://files.pythonhosted.org/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817 }, - { url = "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104 }, -] - -[[package]] -name = "arrow" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-dateutil" }, - { name = "types-python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419 }, -] - -[[package]] -name = "asttokens" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 }, -] - [[package]] name = "astunparse" version = "1.6.3" @@ -240,15 +176,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/03/13dde6512ad7b4557eb792fbcf0c653af6076b81e5941d36ec61f7ce6028/astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8", size = 12732 }, ] -[[package]] -name = "async-lru" -version = "2.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/80/e2/2b4651eff771f6fd900d233e175ddc5e2be502c7eb62c0c42f975c6d36cd/async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627", size = 10019 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/9f/3c3503693386c4b0f245eaf5ca6198e3b28879ca0a40bde6b0e319793453/async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224", size = 6111 }, -] - [[package]] name = "async-property" version = "0.2.2" @@ -267,15 +194,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 }, ] -[[package]] -name = "babel" -version = "2.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/74/f1bc80f23eeba13393b7222b11d95ca3af2c1e28edca18af487137eefed9/babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316", size = 9348104 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 }, -] - [[package]] name = "backoff" version = "2.2.1" @@ -437,47 +355,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bc/3f/e973420941b0d0b23d944fd60cd95c3bbbca38f5c582d83409f6243880fa/bittensor_wallet-2.1.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e35adc5303b2186df889e07c79bf0bc074df382df49e6c216a8feb27f00453a4", size = 2953541 }, ] -[[package]] -name = "black" -version = "24.10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d8/0d/cc2fb42b8c50d80143221515dd7e4766995bd07c56c9a3ed30baf080b6dc/black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", size = 645813 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/04/bf74c71f592bcd761610bbf67e23e6a3cff824780761f536512437f1e655/black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3", size = 1644256 }, - { url = "https://files.pythonhosted.org/packages/4c/ea/a77bab4cf1887f4b2e0bce5516ea0b3ff7d04ba96af21d65024629afedb6/black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65", size = 1448534 }, - { url = "https://files.pythonhosted.org/packages/4e/3e/443ef8bc1fbda78e61f79157f303893f3fddf19ca3c8989b163eb3469a12/black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f", size = 1761892 }, - { url = "https://files.pythonhosted.org/packages/52/93/eac95ff229049a6901bc84fec6908a5124b8a0b7c26ea766b3b8a5debd22/black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8", size = 1434796 }, - { url = "https://files.pythonhosted.org/packages/d0/a0/a993f58d4ecfba035e61fca4e9f64a2ecae838fc9f33ab798c62173ed75c/black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981", size = 1643986 }, - { url = "https://files.pythonhosted.org/packages/37/d5/602d0ef5dfcace3fb4f79c436762f130abd9ee8d950fa2abdbf8bbc555e0/black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b", size = 1448085 }, - { url = "https://files.pythonhosted.org/packages/47/6d/a3a239e938960df1a662b93d6230d4f3e9b4a22982d060fc38c42f45a56b/black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2", size = 1760928 }, - { url = "https://files.pythonhosted.org/packages/dd/cf/af018e13b0eddfb434df4d9cd1b2b7892bab119f7a20123e93f6910982e8/black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b", size = 1436875 }, - { url = "https://files.pythonhosted.org/packages/8d/a7/4b27c50537ebca8bec139b872861f9d2bf501c5ec51fcf897cb924d9e264/black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", size = 206898 }, -] - -[[package]] -name = "bleach" -version = "6.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "webencodings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406 }, -] - -[package.optional-dependencies] -css = [ - { name = "tinycss2" }, -] - [[package]] name = "bt-decode" version = "0.4.0" @@ -644,27 +521,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/ce/cf/70ea34103a76cc6fb1892289bda321cd0cc73b1a5500ee7fe9ef9f64acef/colormath-3.0.0.tar.gz", hash = "sha256:3d4605af344527da0e4f9f504fad7ddbebda35322c566a6c72e28edb1ff31217", size = 39761 } -[[package]] -name = "comm" -version = "0.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, -] - -[[package]] -name = "contextlib2" -version = "21.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/13/37ea7805ae3057992e96ecb1cffa2fa35c2ef4498543b846f90dd2348d8f/contextlib2-21.6.0.tar.gz", hash = "sha256:ab1e2bfe1d01d968e1b7e8d9023bc51ef3509bba217bb730cee3827e1ee82869", size = 43795 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/56/6d6872f79d14c0cb02f1646cbb4592eef935857c0951a105874b7b62a0c3/contextlib2-21.6.0-py2.py3-none-any.whl", hash = "sha256:3fbdb64466afd23abaf6c977627b75b6139a5a3e8ce38405c5b413aed7a0471f", size = 13277 }, -] - [[package]] name = "contourpy" version = "1.3.1" @@ -744,31 +600,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, ] -[[package]] -name = "cython" -version = "3.0.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/84/4d/b720d6000f4ca77f030bd70f12550820f0766b568e43f11af7f7ad9061aa/cython-3.0.11.tar.gz", hash = "sha256:7146dd2af8682b4ca61331851e6aebce9fe5158e75300343f80c07ca80b1faff", size = 2755544 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/50/fbb23239efe2183e4eaf76689270d6f5b3bbcf9be9ad1eb97cc34349e6fc/Cython-3.0.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:11996c40c32abf843ba652a6d53cb15944c88d91f91fc4e6f0028f5df8a8f8a1", size = 3141274 }, - { url = "https://files.pythonhosted.org/packages/87/e5/76379edb21fd5bb9e2aaa1d305492bc35bba96dfb51f5d96867d9863b6df/Cython-3.0.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63f2c892e9f9c1698ecfee78205541623eb31cd3a1b682668be7ac12de94aa8e", size = 3340904 }, - { url = "https://files.pythonhosted.org/packages/9a/ef/44af6aded89444dc45f4466ff207a05d3376c641cf1146c03fd14c55ae64/Cython-3.0.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b14c24f1dc4c4c9d997cca8d1b7fb01187a218aab932328247dcf5694a10102", size = 3514052 }, - { url = "https://files.pythonhosted.org/packages/e0/d5/ef8c7b6aa7a83c508f5c3bf0dfb9eb0a2a9be910c0b1f205f842128269c3/Cython-3.0.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8eed5c015685106db15dd103fd040948ddca9197b1dd02222711815ea782a27", size = 3573721 }, - { url = "https://files.pythonhosted.org/packages/e5/4a/58d6c208563504a35febff94904bb291b368a8b0f28a5e0593c770967caa/Cython-3.0.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780f89c95b8aec1e403005b3bf2f0a2afa060b3eba168c86830f079339adad89", size = 3393594 }, - { url = "https://files.pythonhosted.org/packages/a0/92/a60a400be286dc661609da9db903680bba1423362000b689cf8ef0aec811/Cython-3.0.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a690f2ff460682ea985e8d38ec541be97e0977fa0544aadc21efc116ff8d7579", size = 3601319 }, - { url = "https://files.pythonhosted.org/packages/ac/11/f02fc24d1a071b93e1d07497b0a528687b1f93bb4945c635119480fab3c0/Cython-3.0.11-cp312-cp312-win32.whl", hash = "sha256:2252b5aa57621848e310fe7fa6f7dce5f73aa452884a183d201a8bcebfa05a00", size = 2608335 }, - { url = "https://files.pythonhosted.org/packages/35/00/78ffea3a0ab176267a25ff049518b2582db7ac265bbf27944243d1a81ce2/Cython-3.0.11-cp312-cp312-win_amd64.whl", hash = "sha256:da394654c6da15c1d37f0b7ec5afd325c69a15ceafee2afba14b67a5df8a82c8", size = 2792586 }, - { url = "https://files.pythonhosted.org/packages/eb/19/1d7164b724f62b67c59aa3531a2be8ed1a0c7e4e80afcc6502d8409c4ee3/Cython-3.0.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4341d6a64d47112884e0bcf31e6c075268220ee4cd02223047182d4dda94d637", size = 3134881 }, - { url = "https://files.pythonhosted.org/packages/0a/d7/8d834d7ec4b6e55db857f44e328246d40cb527917040fabf3c48d27609b3/Cython-3.0.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351955559b37e6c98b48aecb178894c311be9d731b297782f2b78d111f0c9015", size = 3330582 }, - { url = "https://files.pythonhosted.org/packages/1c/ae/d520f3cd94a8926bc47275a968e51bbc669a28f27a058cdfc5c3081fbbf7/Cython-3.0.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c02361af9bfa10ff1ccf967fc75159e56b1c8093caf565739ed77a559c1f29f", size = 3503750 }, - { url = "https://files.pythonhosted.org/packages/6e/e4/45c556f4a6d40b6938368d420d3c985bbef9088b7d4a8d8c6648d50e4a94/Cython-3.0.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6823aef13669a32caf18bbb036de56065c485d9f558551a9b55061acf9c4c27f", size = 3566498 }, - { url = "https://files.pythonhosted.org/packages/ce/2d/544f6aa3cab31b99ddb07e7eaaaca6a43db52fe0dc59090195c48fc0b033/Cython-3.0.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6fb68cef33684f8cc97987bee6ae919eee7e18ee6a3ad7ed9516b8386ef95ae6", size = 3389063 }, - { url = "https://files.pythonhosted.org/packages/55/1a/9d871cc1514df273cd2ccfe3efe5ff1df509ce11768c02a834052709f152/Cython-3.0.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:790263b74432cb997740d73665f4d8d00b9cd1cecbdd981d93591ddf993d4f12", size = 3596353 }, - { url = "https://files.pythonhosted.org/packages/47/4e/4db412f595de4b2224a81ea5332ce107ce3e93bf87275c78648f2e3e37b8/Cython-3.0.11-cp313-cp313-win32.whl", hash = "sha256:e6dd395d1a704e34a9fac00b25f0036dce6654c6b898be6f872ac2bb4f2eda48", size = 2602768 }, - { url = "https://files.pythonhosted.org/packages/e7/91/8a29e1bce2f8a893a4c24874943b64e8ede14fac9990bd4a3f13a46c2720/Cython-3.0.11-cp313-cp313-win_amd64.whl", hash = "sha256:52186101d51497519e99b60d955fd5cb3bf747c67f00d742e70ab913f1e42d31", size = 2784414 }, - { url = "https://files.pythonhosted.org/packages/43/39/bdbec9142bc46605b54d674bf158a78b191c2b75be527c6dcf3e6dfe90b8/Cython-3.0.11-py2.py3-none-any.whl", hash = "sha256:0e25f6425ad4a700d7f77cd468da9161e63658837d1bc34861a9861a4ef6346d", size = 1171267 }, -] - [[package]] name = "cytoolz" version = "1.0.1" @@ -842,23 +673,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/05/310e94d212fa6654261e40887de1d155afb72e3dadf7b625550dd5c71678/ddt-1.6.0-py2.py3-none-any.whl", hash = "sha256:e3c93b961a108b4f4d5a6c7f2263513d928baf3bb5b32af8e1c804bfb041141d", size = 7095 }, ] -[[package]] -name = "debugpy" -version = "1.8.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/e7/666f4c9b0e24796af50aadc28d36d21c2e01e831a934535f956e09b3650c/debugpy-1.8.11.tar.gz", hash = "sha256:6ad2688b69235c43b020e04fecccdf6a96c8943ca9c2fb340b8adc103c655e57", size = 1640124 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/ae/2cf26f3111e9d94384d9c01e9d6170188b0aeda15b60a4ac6457f7c8a26f/debugpy-1.8.11-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:84e511a7545d11683d32cdb8f809ef63fc17ea2a00455cc62d0a4dbb4ed1c308", size = 2498756 }, - { url = "https://files.pythonhosted.org/packages/b0/16/ec551789d547541a46831a19aa15c147741133da188e7e6acf77510545a7/debugpy-1.8.11-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce291a5aca4985d82875d6779f61375e959208cdf09fcec40001e65fb0a54768", size = 4219136 }, - { url = "https://files.pythonhosted.org/packages/72/6f/b2b3ce673c55f882d27a6eb04a5f0c68bcad6b742ac08a86d8392ae58030/debugpy-1.8.11-cp312-cp312-win32.whl", hash = "sha256:28e45b3f827d3bf2592f3cf7ae63282e859f3259db44ed2b129093ca0ac7940b", size = 5224440 }, - { url = "https://files.pythonhosted.org/packages/77/09/b1f05be802c1caef5b3efc042fc6a7cadd13d8118b072afd04a9b9e91e06/debugpy-1.8.11-cp312-cp312-win_amd64.whl", hash = "sha256:44b1b8e6253bceada11f714acf4309ffb98bfa9ac55e4fce14f9e5d4484287a1", size = 5264578 }, - { url = "https://files.pythonhosted.org/packages/2e/66/931dc2479aa8fbf362dc6dcee707d895a84b0b2d7b64020135f20b8db1ed/debugpy-1.8.11-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:8988f7163e4381b0da7696f37eec7aca19deb02e500245df68a7159739bbd0d3", size = 2483651 }, - { url = "https://files.pythonhosted.org/packages/10/07/6c171d0fe6b8d237e35598b742f20ba062511b3a4631938cc78eefbbf847/debugpy-1.8.11-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c1f6a173d1140e557347419767d2b14ac1c9cd847e0b4c5444c7f3144697e4e", size = 4213770 }, - { url = "https://files.pythonhosted.org/packages/89/f1/0711da6ac250d4fe3bf7b3e9b14b4a86e82a98b7825075c07e19bab8da3d/debugpy-1.8.11-cp313-cp313-win32.whl", hash = "sha256:bb3b15e25891f38da3ca0740271e63ab9db61f41d4d8541745cfc1824252cb28", size = 5223911 }, - { url = "https://files.pythonhosted.org/packages/56/98/5e27fa39050749ed460025bcd0034a0a5e78a580a14079b164cc3abdeb98/debugpy-1.8.11-cp313-cp313-win_amd64.whl", hash = "sha256:d8768edcbeb34da9e11bcb8b5c2e0958d25218df7a6e56adf415ef262cd7b6d1", size = 5264166 }, - { url = "https://files.pythonhosted.org/packages/77/0a/d29a5aacf47b4383ed569b8478c02d59ee3a01ad91224d2cff8562410e43/debugpy-1.8.11-py2.py3-none-any.whl", hash = "sha256:0e22f846f4211383e6a416d04b4c13ed174d24cc5d43f5fd52e7821d0ebc8920", size = 5226874 }, -] - [[package]] name = "decorator" version = "5.1.1" @@ -868,15 +682,6 @@ 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 = "defusedxml" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, -] - [[package]] name = "dill" version = "0.3.8" @@ -921,28 +726,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/8f/ee72af555cd58feb928ff0fd3977913f4ecd0ce8ad92cf4031c36de91776/duckduckgo_search-7.2.1-py3-none-any.whl", hash = "sha256:72ebbf6ad8759e3c3c79521cd66256e7a4ac741c522fd9342db94de91745ef87", size = 19720 }, ] -[[package]] -name = "easyocr" -version = "1.7.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ninja" }, - { name = "numpy" }, - { name = "opencv-python-headless" }, - { name = "pillow" }, - { name = "pyclipper" }, - { name = "python-bidi" }, - { name = "pyyaml" }, - { name = "scikit-image" }, - { name = "scipy" }, - { name = "shapely" }, - { name = "torch" }, - { name = "torchvision" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/84/4a2cab0e6adde6a85e7ba543862e5fc0250c51f3ac721a078a55cdcff250/easyocr-1.7.2-py3-none-any.whl", hash = "sha256:5be12f9b0e595d443c9c3d10b0542074b50f0ec2d98b141a109cd961fd1c177c", size = 2870178 }, -] - [[package]] name = "ecdsa" version = "0.19.0" @@ -955,38 +738,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/e7/ed3243b30d1bec41675b6394a1daae46349dc2b855cb83be846a5a918238/ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a", size = 149266 }, ] -[[package]] -name = "editdistance" -version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d5/18/9f4f975ca87a390832b1c22478f3702fcdf739f83211e24d054b7551270d/editdistance-0.8.1.tar.gz", hash = "sha256:d1cdf80a5d5014b0c9126a69a42ce55a457b457f6986ff69ca98e4fe4d2d8fed", size = 50006 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/4c/7f195588949b4e72436dc7fc902632381f96e586af829685b56daebb38b8/editdistance-0.8.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04af61b3fcdd287a07c15b6ae3b02af01c5e3e9c3aca76b8c1d13bd266b6f57", size = 106723 }, - { url = "https://files.pythonhosted.org/packages/8d/82/31dc1640d830cd7d36865098329f34e4dad3b77f31cfb9404b347e700196/editdistance-0.8.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:18fc8b6eaae01bfd9cf999af726c1e8dcf667d120e81aa7dbd515bea7427f62f", size = 80998 }, - { url = "https://files.pythonhosted.org/packages/ea/2a/6b823e71cef694d6f070a1d82be2842706fa193541aab8856a8f42044cd0/editdistance-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a87839450a5987028738d061ffa5ef6a68bac2ddc68c9147a8aae9806629c7f", size = 79248 }, - { url = "https://files.pythonhosted.org/packages/e1/31/bfb8e590f922089dc3471ed7828a6da2fc9453eba38c332efa9ee8749fd7/editdistance-0.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24b5f9c9673c823d91b5973d0af8b39f883f414a55ade2b9d097138acd10f31e", size = 415262 }, - { url = "https://files.pythonhosted.org/packages/a9/c7/57423942b2f847cdbbb46494568d00cd8a45500904ea026f0aad6ca01bc7/editdistance-0.8.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c59248eabfad603f0fba47b0c263d5dc728fb01c2b6b50fb6ca187cec547fdb3", size = 418905 }, - { url = "https://files.pythonhosted.org/packages/1b/05/dfa4cdcce063596cbf0d7a32c46cd0f4fa70980311b7da64d35f33ad02a0/editdistance-0.8.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e239d88ff52821cf64023fabd06a1d9a07654f364b64bf1284577fd3a79d0e", size = 412511 }, - { url = "https://files.pythonhosted.org/packages/0e/14/39608ff724a9523f187c4e28926d78bc68f2798f74777ac6757981108345/editdistance-0.8.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2f7f71698f83e8c83839ac0d876a0f4ef996c86c5460aebd26d85568d4afd0db", size = 917293 }, - { url = "https://files.pythonhosted.org/packages/df/92/4a1c61d72da40dedfd0ff950fdc71ae83f478330c58a8bccfd776518bd67/editdistance-0.8.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:04e229d6f4ce0c12abc9f4cd4023a5b5fa9620226e0207b119c3c2778b036250", size = 975580 }, - { url = "https://files.pythonhosted.org/packages/47/3d/9877566e724c8a37f2228a84ec5cbf66dbfd0673515baf68a0fe07caff40/editdistance-0.8.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e16721636da6d6b68a2c09eaced35a94f4a4a704ec09f45756d4fd5e128ed18d", size = 929121 }, - { url = "https://files.pythonhosted.org/packages/d2/f5/8c50757d198b8ca30ddb91e8b8f0247a8dca04ff2ec30755245f0ab1ff0c/editdistance-0.8.1-cp312-cp312-win32.whl", hash = "sha256:87533cf2ebc3777088d991947274cd7e1014b9c861a8aa65257bcdc0ee492526", size = 81039 }, - { url = "https://files.pythonhosted.org/packages/28/f0/65101e51dc7c850e7b7581a5d8fa8721a1d7479a0dca6c08386328e19882/editdistance-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:09f01ed51746d90178af7dd7ea4ebb41497ef19f53c7f327e864421743dffb0a", size = 79853 }, -] - -[[package]] -name = "efficientnet" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "keras-applications" }, - { name = "scikit-image" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/94/0af58e74b6e3f1f217a84289e9fd5a11e75276ac675d5c24d512f6a56720/efficientnet-1.0.0.tar.gz", hash = "sha256:868715f6f5467186c0fa67ee8c9d50260b22f3a1bfb5919acd9911358be54df9", size = 15153 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/82/f3ae07316f0461417dc54affab6e86ab188a5a22f33176d35271628b96e0/efficientnet-1.0.0-py3-none-any.whl", hash = "sha256:c6fd035d856c9f1c409f3ac19e7655e54a13851046c821e9d4417fd12bcbaae4", size = 17685 }, -] - [[package]] name = "einops" version = "0.8.0" @@ -996,15 +747,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/5a/f0b9ad6c0a9017e62d4735daaeb11ba3b6c009d69a26141b258cd37b5588/einops-0.8.0-py3-none-any.whl", hash = "sha256:9572fb63046264a862693b0a87088af3bdc8c068fde03de63453cbbde245465f", size = 43223 }, ] -[[package]] -name = "essential-generators" -version = "1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/ed/8f1b16654c55b9ba09a4a5058d85debd0acb89f8eb000071522c6c071952/essential_generators-1.0.tar.gz", hash = "sha256:a4e015b01ea45cf32b9afe7fdfd239e9ad13654d1bfe2e4d9008204e62a1743d", size = 9231075 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/f4/20863402b45c3475c8ace4274c5ec870ba6df7259e9a4e3e1436c964f176/essential_generators-1.0-py3-none-any.whl", hash = "sha256:6a0f4f589cf4feb0431485e7c5abf58088e1d28ce2981b4c5a7142723646b172", size = 9452110 }, -] - [[package]] name = "eth-hash" version = "0.7.0" @@ -1054,15 +796,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/3d/f01836312cd8b4a8768546e78b48feb52375123e2f4343119b27e78db9b9/eth_utils-2.2.2-py3-none-any.whl", hash = "sha256:2580a8065273f62ca1ec4c175228c52e626a5f1007e965d2117e5eca1a93cae8", size = 23893 }, ] -[[package]] -name = "executing" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/e3/7d45f492c2c4a0e8e0fad57d081a7c8a0286cdd86372b070cca1ec0caa1e/executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab", size = 977485 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/fd/afcd0496feca3276f509df3dbd5dae726fcc756f1a08d9e25abe1733f962/executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf", size = 25805 }, -] - [[package]] name = "fastapi" version = "0.110.3" @@ -1077,15 +810,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/d1/5958526c3bdbed74f88bf69b86506db5b25a600207f0f688473667690de6/fastapi-0.110.3-py3-none-any.whl", hash = "sha256:fd7600612f755e4050beb74001310b5a7e1796d149c2ee363124abdfa0289d32", size = 91834 }, ] -[[package]] -name = "fastjsonschema" -version = "2.21.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8b/50/4b769ce1ac4071a1ef6d86b1a3fb56cdc3a37615e8c5519e1af96cdac366/fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4", size = 373939 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/2b/0817a2b257fe88725c25589d89aec060581aabf668707a8d03b2e9e0cb2a/fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667", size = 23924 }, -] - [[package]] name = "filelock" version = "3.16.1" @@ -1095,15 +819,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, ] -[[package]] -name = "fire" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "termcolor" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6b/b6/82c7e601d6d3c3278c40b7bd35e17e82aa227f050aa9f66cb7b7fce29471/fire-0.7.0.tar.gz", hash = "sha256:961550f07936eaf65ad1dc8360f2b2bf8408fad46abbfa4d2a3794f8d2a95cdf", size = 87189 } - [[package]] name = "flatbuffers" version = "24.12.23" @@ -1138,15 +853,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/3b/406d17b1f63e04a82aa621936e6e1c53a8c05458abd66300ac85ea7f9ae9/fonttools-4.55.3-py3-none-any.whl", hash = "sha256:f412604ccbeee81b091b420272841e5ec5ef68967a9790e80bffd0e30b8e2977", size = 1111638 }, ] -[[package]] -name = "fqdn" -version = "1.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121 }, -] - [[package]] name = "frozenlist" version = "1.5.0" @@ -1423,26 +1129,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/f9/f78e7f5ac8077c481bf6b43b8bc736605363034b3d5eb3ce8eb79f53f5f1/imageio-2.36.1-py3-none-any.whl", hash = "sha256:20abd2cae58e55ca1af8a8dcf43293336a59adf0391f1917bf8518633cfc2cdf", size = 315435 }, ] -[[package]] -name = "imgaug" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "imageio" }, - { name = "matplotlib" }, - { name = "numpy" }, - { name = "opencv-python" }, - { name = "pillow" }, - { name = "scikit-image" }, - { name = "scipy" }, - { name = "shapely" }, - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/25/7d/820295b8fdaf06dce9688ef2fdeb5a317896d3276db7723e5a94e85e1253/imgaug-0.4.0.tar.gz", hash = "sha256:46bab63ed38f8980630ff721a09ca2281b7dbd4d8c11258818b6ebcc69ea46c7", size = 937254 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/66/b1/af3142c4a85cba6da9f4ebb5ff4e21e2616309552caca5e8acefe9840622/imgaug-0.4.0-py2.py3-none-any.whl", hash = "sha256:ce61e65b4eb7405fc62c1b0a79d2fa92fd47f763aaecb65152d29243592111f9", size = 948018 }, -] - [[package]] name = "iniconfig" version = "2.0.0" @@ -1452,90 +1138,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, ] -[[package]] -name = "ipykernel" -version = "6.29.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "appnope", marker = "sys_platform == 'darwin'" }, - { name = "comm" }, - { name = "debugpy" }, - { name = "ipython" }, - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "matplotlib-inline" }, - { name = "nest-asyncio" }, - { name = "packaging" }, - { name = "psutil" }, - { name = "pyzmq" }, - { name = "tornado" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/67594cb0c7055dc50814b21731c22a601101ea3b1b50a9a1b090e11f5d0f/ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215", size = 163367 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173 }, -] - -[[package]] -name = "ipython" -version = "8.31.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "decorator" }, - { 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" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/01/35/6f90fdddff7a08b7b715fccbd2427b5212c9525cd043d26fdc45bee0708d/ipython-8.31.0.tar.gz", hash = "sha256:b6a2274606bec6166405ff05e54932ed6e5cfecaca1fc05f2cacde7bb074d70b", size = 5501011 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/60/d0feb6b6d9fe4ab89fe8fe5b47cbf6cd936bfd9f1e7ffa9d0015425aeed6/ipython-8.31.0-py3-none-any.whl", hash = "sha256:46ec58f8d3d076a61d128fe517a51eb730e3aaf0c184ea8c17d16e366660c6a6", size = 821583 }, -] - -[[package]] -name = "ipywidgets" -version = "8.1.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "comm" }, - { name = "ipython" }, - { name = "jupyterlab-widgets" }, - { name = "traitlets" }, - { name = "widgetsnbextension" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c7/4c/dab2a281b07596a5fc220d49827fe6c794c66f1493d7a74f1df0640f2cc5/ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17", size = 116723 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/2d/9c0b76f2f9cc0ebede1b9371b6f317243028ed60b90705863d493bae622e/ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245", size = 139767 }, -] - -[[package]] -name = "isoduration" -version = "20.11.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "arrow" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321 }, -] - -[[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 = "jinja2" version = "3.1.5" @@ -1592,262 +1194,6 @@ 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 = "json5" -version = "0.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/3d/bbe62f3d0c05a689c711cff57b2e3ac3d3e526380adb7c781989f075115c/json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559", size = 48202 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/42/797895b952b682c3dafe23b1834507ee7f02f4d6299b65aaa61425763278/json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa", size = 34049 }, -] - -[[package]] -name = "jsonpointer" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, -] - -[[package]] -name = "jsonschema" -version = "4.23.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, -] - -[package.optional-dependencies] -format-nongpl = [ - { name = "fqdn" }, - { name = "idna" }, - { name = "isoduration" }, - { name = "jsonpointer" }, - { name = "rfc3339-validator" }, - { name = "rfc3986-validator" }, - { name = "uri-template" }, - { name = "webcolors" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2024.10.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, -] - -[[package]] -name = "jupyter" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ipykernel" }, - { name = "ipywidgets" }, - { name = "jupyter-console" }, - { name = "jupyterlab" }, - { name = "nbconvert" }, - { name = "notebook" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/f3/af28ea964ab8bc1e472dba2e82627d36d470c51f5cd38c37502eeffaa25e/jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a", size = 5714959 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83", size = 2657 }, -] - -[[package]] -name = "jupyter-client" -version = "8.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-core" }, - { name = "python-dateutil" }, - { name = "pyzmq" }, - { name = "tornado" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105 }, -] - -[[package]] -name = "jupyter-console" -version = "6.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ipykernel" }, - { name = "ipython" }, - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "pyzmq" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bd/2d/e2fd31e2fc41c14e2bcb6c976ab732597e907523f6b2420305f9fc7fdbdb/jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539", size = 34363 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/77/71d78d58f15c22db16328a476426f7ac4a60d3a5a7ba3b9627ee2f7903d4/jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485", size = 24510 }, -] - -[[package]] -name = "jupyter-core" -version = "5.7.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "platformdirs" }, - { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/00/11/b56381fa6c3f4cc5d2cf54a7dbf98ad9aa0b339ef7a601d6053538b079a7/jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9", size = 87629 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/fb/108ecd1fe961941959ad0ee4e12ee7b8b1477247f30b1fdfd83ceaf017f0/jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", size = 28965 }, -] - -[[package]] -name = "jupyter-events" -version = "0.11.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jsonschema", extra = ["format-nongpl"] }, - { name = "python-json-logger" }, - { name = "pyyaml" }, - { name = "referencing" }, - { name = "rfc3339-validator" }, - { name = "rfc3986-validator" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f4/65/5791c8a979b5646ca29ea50e42b6708908b789f7ff389d1a03c1b93a1c54/jupyter_events-0.11.0.tar.gz", hash = "sha256:c0bc56a37aac29c1fbc3bcfbddb8c8c49533f9cf11f1c4e6adadba936574ab90", size = 62039 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/8c/9b65cb2cd4ea32d885993d5542244641590530836802a2e8c7449a4c61c9/jupyter_events-0.11.0-py3-none-any.whl", hash = "sha256:36399b41ce1ca45fe8b8271067d6a140ffa54cec4028e95491c93b78a855cacf", size = 19423 }, -] - -[[package]] -name = "jupyter-lsp" -version = "2.2.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-server" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/85/b4/3200b0b09c12bc3b72d943d923323c398eff382d1dcc7c0dbc8b74630e40/jupyter-lsp-2.2.5.tar.gz", hash = "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001", size = 48741 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/e0/7bd7cff65594fd9936e2f9385701e44574fc7d721331ff676ce440b14100/jupyter_lsp-2.2.5-py3-none-any.whl", hash = "sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da", size = 69146 }, -] - -[[package]] -name = "jupyter-server" -version = "2.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "argon2-cffi" }, - { name = "jinja2" }, - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "jupyter-events" }, - { name = "jupyter-server-terminals" }, - { name = "nbconvert" }, - { name = "nbformat" }, - { name = "overrides" }, - { name = "packaging" }, - { name = "prometheus-client" }, - { name = "pywinpty", marker = "os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux'" }, - { name = "pyzmq" }, - { name = "send2trash" }, - { name = "terminado" }, - { name = "tornado" }, - { name = "traitlets" }, - { name = "websocket-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/61/8c/df09d4ab646141f130f9977b32b206ba8615d1969b2eba6a2e84b7f89137/jupyter_server-2.15.0.tar.gz", hash = "sha256:9d446b8697b4f7337a1b7cdcac40778babdd93ba614b6d68ab1c0c918f1c4084", size = 725227 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/a2/89eeaf0bb954a123a909859fa507fa86f96eb61b62dc30667b60dbd5fdaf/jupyter_server-2.15.0-py3-none-any.whl", hash = "sha256:872d989becf83517012ee669f09604aa4a28097c0bd90b2f424310156c2cdae3", size = 385826 }, -] - -[[package]] -name = "jupyter-server-terminals" -version = "0.5.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pywinpty", marker = "os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux'" }, - { name = "terminado" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656 }, -] - -[[package]] -name = "jupyterlab" -version = "4.3.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "async-lru" }, - { name = "httpx" }, - { name = "ipykernel" }, - { name = "jinja2" }, - { name = "jupyter-core" }, - { name = "jupyter-lsp" }, - { name = "jupyter-server" }, - { name = "jupyterlab-server" }, - { name = "notebook-shim" }, - { name = "packaging" }, - { name = "setuptools" }, - { name = "tornado" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a7/45/1052f842e066902b1d78126df7e2269b1b9408991e1344e167b2e429f9e1/jupyterlab-4.3.4.tar.gz", hash = "sha256:f0bb9b09a04766e3423cccc2fc23169aa2ffedcdf8713e9e0fb33cac0b6859d0", size = 21797583 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/48/af57263e53cfc220e522de047aa0993f53bab734fe812af1e03e33ac6d7c/jupyterlab-4.3.4-py3-none-any.whl", hash = "sha256:b754c2601c5be6adf87cb5a1d8495d653ffb945f021939f77776acaa94dae952", size = 11665373 }, -] - -[[package]] -name = "jupyterlab-pygments" -version = "0.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884 }, -] - -[[package]] -name = "jupyterlab-server" -version = "2.27.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "babel" }, - { name = "jinja2" }, - { name = "json5" }, - { name = "jsonschema" }, - { name = "jupyter-server" }, - { name = "packaging" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0a/c9/a883ce65eb27905ce77ace410d83587c82ea64dc85a48d1f7ed52bcfa68d/jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4", size = 76173 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4", size = 59700 }, -] - -[[package]] -name = "jupyterlab-widgets" -version = "3.0.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/59/73/fa26bbb747a9ea4fca6b01453aa22990d52ab62dd61384f1ac0dc9d4e7ba/jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed", size = 203556 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/93/858e87edc634d628e5d752ba944c2833133a28fa87bb093e6832ced36a3e/jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54", size = 214392 }, -] - [[package]] name = "keras" version = "3.8.0" @@ -1867,38 +1213,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/cf/aea9087c4d7fafe956a0cc0ff6c3327d10fb8442cda50f992a2186921fa0/keras-3.8.0-py3-none-any.whl", hash = "sha256:b65d125976b0f8bf8ad1e93311a98e7dfb334ff6023627a59a52b35499165ec3", size = 1301880 }, ] -[[package]] -name = "keras-applications" -version = "1.0.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "h5py" }, - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/21/56/4bcec5a8d9503a87e58e814c4e32ac2b32c37c685672c30bc8c54c6e478a/Keras_Applications-1.0.8.tar.gz", hash = "sha256:5579f9a12bcde9748f4a12233925a59b93b73ae6947409ff34aa2ba258189fe5", size = 289095 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/e3/19762fdfc62877ae9102edf6342d71b28fbfd9dea3d2f96a882ce099b03f/Keras_Applications-1.0.8-py3-none-any.whl", hash = "sha256:df4323692b8c1174af821bf906f1e442e63fa7589bf0f1230a0b6bdc5a810c95", size = 50704 }, -] - -[[package]] -name = "keras-ocr" -version = "0.9.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "editdistance" }, - { name = "efficientnet" }, - { name = "essential-generators" }, - { name = "fonttools" }, - { name = "imgaug" }, - { name = "pyclipper" }, - { name = "shapely" }, - { name = "tqdm" }, - { name = "validators" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/f3/7ad1edb975c6c485d73146438dfff188f1c22b798d3a076e4f644e7bdce1/keras_ocr-0.9.3-py3-none-any.whl", hash = "sha256:8fdbb9044a814910e86d3853f5e7f13085c98ac210f27204fe75f4f8c4ac6262", size = 42313 }, -] - [[package]] name = "kiwisolver" version = "1.4.8" @@ -2179,15 +1493,6 @@ 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 = "mistune" -version = "3.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/6e/96fc7cb3288666c5de2c396eb0e338dc95f7a8e4920e43e38783a22d0084/mistune-3.1.0.tar.gz", hash = "sha256:dbcac2f78292b9dc066cd03b7a3a26b62d85f8159f2ea5fd28e55df79908d667", size = 94401 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/b3/743ffc3f59da380da504d84ccd1faf9a857a1445991ff19bf2ec754163c2/mistune-3.1.0-py3-none-any.whl", hash = "sha256:b05198cf6d671b3deba6c87ec6cf0d4eb7b72c524636eddb6dbf13823b52cee1", size = 53694 }, -] - [[package]] name = "ml-dtypes" version = "0.4.1" @@ -2264,15 +1569,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cd/22/590508afb85d5c27ebcb2837410413f4613eebdda6e4e02997fe08ba78e4/msgpack_numpy_opentensor-0.5.0-py2.py3-none-any.whl", hash = "sha256:8a61c597a976425a87094d8e89846aa9528eb1f037e97ff1428fe3cd61a238e7", size = 7209 }, ] -[[package]] -name = "mss" -version = "10.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/2c/6a50c69793918502b4391729bd5741964c9f8ccc98c8482d4a680384923b/mss-10.0.0.tar.gz", hash = "sha256:d903e0d51262bf0f8782841cf16eaa6d7e3e1f12eae35ab41c2e318837c6637f", size = 83127 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/29/aa/b897ae9e1c1616e4df9fb319637ef6a9d07cca6d46de6b59c80209f006a4/mss-10.0.0-py3-none-any.whl", hash = "sha256:82cf6460a53d09e79b7b6d871163c982e6c7e9649c426e7b7591b74956d5cb64", size = 24050 }, -] - [[package]] name = "multidict" version = "6.1.0" @@ -2340,15 +1636,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/ab/85d8da5c9a45e072301beb37ad7f833cd344e04c817d97e0cc75681d248f/munch-2.5.0-py2.py3-none-any.whl", hash = "sha256:6f44af89a2ce4ed04ff8de41f70b226b984db10a91dcc7b9ac2efc1c77022fdd", size = 10347 }, ] -[[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 = "namex" version = "0.0.8" @@ -2358,61 +1645,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/59/7854fbfb59f8ae35483ce93493708be5942ebb6328cd85b3a609df629736/namex-0.0.8-py3-none-any.whl", hash = "sha256:7ddb6c2bb0e753a311b7590f84f6da659dd0c05e65cb89d519d54c0a250c0487", size = 5806 }, ] -[[package]] -name = "nbclient" -version = "0.10.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "nbformat" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434 }, -] - -[[package]] -name = "nbconvert" -version = "7.16.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beautifulsoup4" }, - { name = "bleach", extra = ["css"] }, - { name = "defusedxml" }, - { name = "jinja2" }, - { name = "jupyter-core" }, - { name = "jupyterlab-pygments" }, - { name = "markupsafe" }, - { name = "mistune" }, - { name = "nbclient" }, - { name = "nbformat" }, - { name = "packaging" }, - { name = "pandocfilters" }, - { name = "pygments" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/46/2c/d026c0367f2be2463d4c2f5b538e28add2bc67bc13730abb7f364ae4eb8b/nbconvert-7.16.5.tar.gz", hash = "sha256:c83467bb5777fdfaac5ebbb8e864f300b277f68692ecc04d6dab72f2d8442344", size = 856367 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/9e/2dcc9fe00cf55d95a8deae69384e9cea61816126e345754f6c75494d32ec/nbconvert-7.16.5-py3-none-any.whl", hash = "sha256:e12eac052d6fd03040af4166c563d76e7aeead2e9aadf5356db552a1784bd547", size = 258061 }, -] - -[[package]] -name = "nbformat" -version = "5.10.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fastjsonschema" }, - { name = "jsonschema" }, - { name = "jupyter-core" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, -] - [[package]] name = "nest-asyncio" version = "1.6.0" @@ -2440,30 +1672,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, ] -[[package]] -name = "ninja" -version = "1.11.1.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/8f/21a2701f95b7d0d5137736561b3427ece0c4a1e085d4a223b92d16ab7d8b/ninja-1.11.1.3.tar.gz", hash = "sha256:edfa0d2e9d7ead1635b03e40a32ad56cc8f56798b6e2e9848d8300b174897076", size = 129532 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/ba/0069cd4a83d68f7b0308be70e219b15d675e50c8ea28763a3f0373c45bfc/ninja-1.11.1.3-py3-none-macosx_10_9_universal2.whl", hash = "sha256:2b4879ea3f1169f3d855182c57dcc84d1b5048628c8b7be0d702b81882a37237", size = 279132 }, - { url = "https://files.pythonhosted.org/packages/72/6b/3805be87df8417a0c7b21078c8045f2a1e59b34f371bfe4cb4fb0d6df7f2/ninja-1.11.1.3-py3-none-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bc3ebc8b2e47716149f3541742b5cd8e0b08f51013b825c05baca3e34854370d", size = 472101 }, - { url = "https://files.pythonhosted.org/packages/6b/35/a8e38d54768e67324e365e2a41162be298f51ec93e6bd4b18d237d7250d8/ninja-1.11.1.3-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a27e78ca71316c8654965ee94b286a98c83877bfebe2607db96897bbfe458af0", size = 422884 }, - { url = "https://files.pythonhosted.org/packages/2f/99/7996457319e139c02697fb2aa28e42fe32bb0752cef492edc69d56a3552e/ninja-1.11.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2883ea46b3c5079074f56820f9989c6261fcc6fd873d914ee49010ecf283c3b2", size = 157046 }, - { url = "https://files.pythonhosted.org/packages/6d/8b/93f38e5cddf76ccfdab70946515b554f25d2b4c95ef9b2f9cfbc43fa7cc1/ninja-1.11.1.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c4bdb9fd2d0c06501ae15abfd23407660e95659e384acd36e013b6dd7d8a8e4", size = 180014 }, - { url = "https://files.pythonhosted.org/packages/7d/1d/713884d0fa3c972164f69d552e0701d30e2bf25eba9ef160bfb3dc69926a/ninja-1.11.1.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:114ed5c61c8474df6a69ab89097a20749b769e2c219a452cb2fadc49b0d581b0", size = 157098 }, - { url = "https://files.pythonhosted.org/packages/c7/22/ecb0f70e77c9e22ee250aa717a608a142756833a34d43943d7d658ee0e56/ninja-1.11.1.3-py3-none-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fa2247fce98f683bc712562d82b22b8a0a5c000738a13147ca2d1b68c122298", size = 130089 }, - { url = "https://files.pythonhosted.org/packages/ec/a6/3ee846c20ab6ad95b90c5c8703c76cb1f39cc8ce2d1ae468956e3b1b2581/ninja-1.11.1.3-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:a38c6c6c8032bed68b70c3b065d944c35e9f903342875d3a3218c1607987077c", size = 372508 }, - { url = "https://files.pythonhosted.org/packages/95/0d/aa44abe4141f29148ce671ac8c92045878906b18691c6f87a29711c2ff1c/ninja-1.11.1.3-py3-none-musllinux_1_1_i686.whl", hash = "sha256:56ada5d33b8741d298836644042faddebc83ee669782d661e21563034beb5aba", size = 419369 }, - { url = "https://files.pythonhosted.org/packages/f7/ec/48bf5105568ac9bd2016b701777bdd5000cc09a14ac837fef9f15e8d634e/ninja-1.11.1.3-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:53409151da081f3c198bb0bfc220a7f4e821e022c5b7d29719adda892ddb31bb", size = 420304 }, - { url = "https://files.pythonhosted.org/packages/18/e5/69df63976cf971a03379899f8520a036c9dbab26330b37197512aed5b3df/ninja-1.11.1.3-py3-none-musllinux_1_1_s390x.whl", hash = "sha256:1ad2112c2b0159ed7c4ae3731595191b1546ba62316fc40808edecd0306fefa3", size = 416056 }, - { url = "https://files.pythonhosted.org/packages/6f/4f/bdb401af7ed0e24a3fef058e13a149f2de1ce4b176699076993615d55610/ninja-1.11.1.3-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:28aea3c1c280cba95b8608d50797169f3a34280e3e9a6379b6e340f0c9eaeeb0", size = 379725 }, - { url = "https://files.pythonhosted.org/packages/bd/68/05e7863bf13128c61652eeb3ec7096c3d3a602f32f31752dbfb034e3fa07/ninja-1.11.1.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b6966f83064a88a51693073eea3decd47e08c3965241e09578ef7aa3a7738329", size = 434881 }, - { url = "https://files.pythonhosted.org/packages/bd/ad/edc0d1efe77f29f45bbca2e1dab07ef597f61a88de6e4bccffc0aec2256c/ninja-1.11.1.3-py3-none-win32.whl", hash = "sha256:a4a3b71490557e18c010cbb26bd1ea9a0c32ee67e8f105e9731515b6e0af792e", size = 255988 }, - { url = "https://files.pythonhosted.org/packages/03/93/09a9f7672b4f97438aca6217ac54212a63273f1cd3b46b731d0bb22c53e7/ninja-1.11.1.3-py3-none-win_amd64.whl", hash = "sha256:04d48d14ea7ba11951c156599ab526bdda575450797ff57c6fdf99b2554d09c7", size = 296502 }, - { url = "https://files.pythonhosted.org/packages/d9/9d/0cc1e82849070ff3cbee69f326cb48a839407bcd15d8844443c30a5e7509/ninja-1.11.1.3-py3-none-win_arm64.whl", hash = "sha256:17978ad611d8ead578d83637f5ae80c2261b033db0b493a7ce94f88623f29e1b", size = 270571 }, -] - [[package]] name = "nltk" version = "3.9.1" @@ -2479,34 +1687,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/66/7d9e26593edda06e8cb531874633f7c2372279c3b0f46235539fe546df8b/nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1", size = 1505442 }, ] -[[package]] -name = "notebook" -version = "7.3.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-server" }, - { name = "jupyterlab" }, - { name = "jupyterlab-server" }, - { name = "notebook-shim" }, - { name = "tornado" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ea/04/ac488379d5afef43402b3fb4be2857db1a09804fecf98b9b714c741b225b/notebook-7.3.2.tar.gz", hash = "sha256:705e83a1785f45b383bf3ee13cb76680b92d24f56fb0c7d2136fe1d850cd3ca8", size = 12781804 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/9b/76e50ee18f183ea5fe1784a9eeaa50f2c71802e4740d6e959592b0993298/notebook-7.3.2-py3-none-any.whl", hash = "sha256:e5f85fc59b69d3618d73cf27544418193ff8e8058d5bf61d315ce4f473556288", size = 13163630 }, -] - -[[package]] -name = "notebook-shim" -version = "0.2.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-server" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307 }, -] - [[package]] name = "numpy" version = "2.0.2" @@ -2638,29 +1818,11 @@ wheels = [ [[package]] name = "nvidia-nvtx-cu12" -version = "12.4.127" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/39/471f581edbb7804b39e8063d92fc8305bdc7a80ae5c07dbe6ea5c50d14a5/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3", size = 100417 }, - { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 }, -] - -[[package]] -name = "object-detection" -version = "0.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "contextlib2" }, - { name = "cython" }, - { name = "jupyter" }, - { name = "lxml" }, - { name = "matplotlib" }, - { name = "pillow" }, - { name = "tensorflow" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/61/40/00826e52abc78b1fac0c3fbabdbede418de13cd177fb1abdc5e9f2f6ff99/object_detection-0.0.3.tar.gz", hash = "sha256:8dae59bb3c7b4a6d7fcf8c2862971afdd9cd966deafba14f521fb11b3df3d069", size = 504105 } +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/37/24d57adb29a0242aca08b99cb2ad08c2b427c05c988f169622c401a6f9ed/object_detection-0.0.3-py3-none-any.whl", hash = "sha256:a487aa0eada8105a9bb026b0993f251f6e0b8b0ae8fb69c2de3381394aa49b9c", size = 1488520 }, + { url = "https://files.pythonhosted.org/packages/06/39/471f581edbb7804b39e8063d92fc8305bdc7a80ae5c07dbe6ea5c50d14a5/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3", size = 100417 }, + { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 }, ] [[package]] @@ -2699,23 +1861,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/6c/fab8113424af5049f85717e8e527ca3773299a3c6b02506e66436e19874f/opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe", size = 38842521 }, ] -[[package]] -name = "opencv-python-headless" -version = "4.10.0.84" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2f/7e/d20f68a5f1487adf19d74378d349932a386b1ece3be9be9915e5986db468/opencv-python-headless-4.10.0.84.tar.gz", hash = "sha256:f2017c6101d7c2ef8d7bc3b414c37ff7f54d64413a1847d89970b6b7069b4e1a", size = 95117755 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/9b/583c8d9259f6fc19413f83fd18dd8e6cbc8eefb0b4dc6da52dd151fe3272/opencv_python_headless-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a4f4bcb07d8f8a7704d9c8564c224c8b064c63f430e95b61ac0bffaa374d330e", size = 54835657 }, - { url = "https://files.pythonhosted.org/packages/c0/7b/b4c67f5dad7a9a61c47f7a39e4050e8a4628bd64b3c3daaeb755d759f928/opencv_python_headless-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:5ae454ebac0eb0a0b932e3406370aaf4212e6a3fdb5038cc86c7aea15a6851da", size = 56475470 }, - { url = "https://files.pythonhosted.org/packages/91/61/f838ce2046f3ec3591ea59ea3549085e399525d3b4558c4ed60b55ed88c0/opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46071015ff9ab40fccd8a163da0ee14ce9846349f06c6c8c0f2870856ffa45db", size = 29329705 }, - { url = "https://files.pythonhosted.org/packages/d1/09/248f86a404567303cdf120e4a301f389b68e3b18e5c0cc428de327da609c/opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:377d08a7e48a1405b5e84afcbe4798464ce7ee17081c1c23619c8b398ff18295", size = 49858781 }, - { url = "https://files.pythonhosted.org/packages/30/c0/66f88d58500e990a9a0a5c06f98862edf1d0a3a430781218a8c193948438/opencv_python_headless-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:9092404b65458ed87ce932f613ffbb1106ed2c843577501e5768912360fc50ec", size = 28675298 }, - { url = "https://files.pythonhosted.org/packages/26/d0/22f68eb23eea053a31655960f133c0be9726c6a881547e6e9e7e2a946c4f/opencv_python_headless-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:afcf28bd1209dd58810d33defb622b325d3cbe49dcd7a43a902982c33e5fad05", size = 38754031 }, -] - [[package]] name = "opt-einsum" version = "3.4.0" @@ -2766,15 +1911,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8b/36/c01a5bc34660d46c6a3b1fe090bbdc8c76af7b5c1a6613cc671aa6df8349/optree-0.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:c4d13f55dbd509d27be3af54d53b4ca0751bc518244ced6d0567e518e51452a2", size = 331470 }, ] -[[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" @@ -2818,24 +1954,6 @@ 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 = "pandocfilters" -version = "1.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663 }, -] - -[[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 = "password-strength" version = "0.0.3.post2" @@ -2848,15 +1966,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1c/d6/08fd888c980589e4e27c2a4177e972481e8881600138e63afb785fe52630/password_strength-0.0.3.post2-py2.py3-none-any.whl", hash = "sha256:6739357c2863d707b7c7f247ff7c6882a70904a18d12c9aaf98f8b95da176fb9", size = 12167 }, ] -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, -] - [[package]] name = "peft" version = "0.14.0" @@ -2878,18 +1987,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/05/e58e3aaa36544d30a917814e336fc65a746f708e5874945e92999bc22fa3/peft-0.14.0-py3-none-any.whl", hash = "sha256:2f04f3a870c3baf30f15e7dcaa5dd70d3e54cfdd146d3c6c187735d3ae0a0700", size = 374831 }, ] -[[package]] -name = "pexpect" -version = "4.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ptyprocess", marker = "sys_platform != 'win32'" }, -] -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 = "pillow" version = "11.1.0" @@ -2989,27 +2086,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/cc/8dd693b9e2577690e86fb589478e8788df2920859e04743800eb7d02213c/primp-0.10.0-cp38-abi3-win_amd64.whl", hash = "sha256:7fe94c3164c2efffff08f7f54c018ac445112961b3ce4f4f499315ba0a9d1ef3", size = 3116121 }, ] -[[package]] -name = "prometheus-client" -version = "0.21.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/62/14/7d0f567991f3a9af8d1cd4f619040c93b68f09a02b6d0b6ab1b2d1ded5fe/prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb", size = 78551 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/c2/ab7d37426c179ceb9aeb109a85cda8948bb269b7561a0be870cc656eefe4/prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301", size = 54682 }, -] - -[[package]] -name = "prompt-toolkit" -version = "3.0.48" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2d/4f/feb5e137aff82f7c7f3248267b97451da3644f6cdc218edfe549fb354127/prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90", size = 424684 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/6a/fd08d94654f7e67c52ca30523a178b3f8ccc4237fce4be90d39c938a831a/prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e", size = 386595 }, -] - [[package]] name = "propcache" version = "0.2.1" @@ -3051,18 +2127,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/b6/c5319caea262f4821995dca2107483b94a3345d4607ad797c76cb9c36bcc/propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54", size = 11818 }, ] -[[package]] -name = "proto-plus" -version = "1.25.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7e/05/74417b2061e1bf1b82776037cad97094228fa1c1b6e82d08a78d3fb6ddb6/proto_plus-1.25.0.tar.gz", hash = "sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91", size = 56124 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/25/0b7cc838ae3d76d46539020ec39fc92bfc9acc29367e58fe912702c2a79e/proto_plus-1.25.0-py3-none-any.whl", hash = "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961", size = 50126 }, -] - [[package]] name = "protobuf" version = "5.29.3" @@ -3092,24 +2156,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/d7/7831438e6c3ebbfa6e01a927127a6cb42ad3ab844247f3c5b96bea25d73d/psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649", size = 254444 }, ] -[[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 = "py" version = "1.11.0" @@ -3224,38 +2270,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c8/11/fabf6ecabb1fe5b7d96889228ca2a9158c4c3bb732e3b8ee3f7f6d40b703/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba", size = 40043567 }, ] -[[package]] -name = "pybboxes" -version = "0.1.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ab/93/5556dbd5a2f1c83e5c912b2a38ba81dd0b3961a0618b974ecfdb36b65701/pybboxes-0.1.6.tar.gz", hash = "sha256:558bfd2a7ca37def07ac95108f3b6504d728332b0c5b871df1017de5c7c1f68d", size = 19019 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/3f/46f6613b41a3c2b4f7af3b526035771ca5bb12d6fdf3b23145899f785e36/pybboxes-0.1.6-py3-none-any.whl", hash = "sha256:e6f7ca43a38bfe2c6ec5b67c6f6e95790e5d9c8c41d84ef11ba896fe181816d5", size = 24858 }, -] - -[[package]] -name = "pyclipper" -version = "1.3.0.post6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/b2/550fe500e49c464d73fabcb8cb04d47e4885d6ca4cfc1f5b0a125a95b19a/pyclipper-1.3.0.post6.tar.gz", hash = "sha256:42bff0102fa7a7f2abdd795a2594654d62b786d0c6cd67b72d469114fdeb608c", size = 165909 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/c8/197d9a1d8354922d24d11d22fb2e0cc1ebc182f8a30496b7ddbe89467ce1/pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6363b9d79ba1b5d8f32d1623e797c1e9f994600943402e68d5266067bdde173e", size = 270487 }, - { url = "https://files.pythonhosted.org/packages/8e/8e/eb14eadf054494ad81446e21c4ea163b941747610b0eb9051644395f567e/pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32cd7fb9c1c893eb87f82a072dbb5e26224ea7cebbad9dc306d67e1ac62dd229", size = 143469 }, - { url = "https://files.pythonhosted.org/packages/cf/e5/6c4a8df6e904c133bb4c5309d211d31c751db60cbd36a7250c02b05494a1/pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3aab10e3c10ed8fa60c608fb87c040089b83325c937f98f06450cf9fcfdaf1d", size = 944206 }, - { url = "https://files.pythonhosted.org/packages/76/65/cb014acc41cd5bf6bbfa4671c7faffffb9cee01706642c2dec70c5209ac8/pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58eae2ff92a8cae1331568df076c4c5775bf946afab0068b217f0cf8e188eb3c", size = 963797 }, - { url = "https://files.pythonhosted.org/packages/80/ec/b40cd81ab7598984167508a5369a2fa31a09fe3b3e3d0b73aa50e06d4b3f/pyclipper-1.3.0.post6-cp312-cp312-win32.whl", hash = "sha256:793b0aa54b914257aa7dc76b793dd4dcfb3c84011d48df7e41ba02b571616eaf", size = 99456 }, - { url = "https://files.pythonhosted.org/packages/24/3a/7d6292e3c94fb6b872d8d7e80d909dc527ee6b0af73b753c63fdde65a7da/pyclipper-1.3.0.post6-cp312-cp312-win_amd64.whl", hash = "sha256:d3f9da96f83b8892504923beb21a481cd4516c19be1d39eb57a92ef1c9a29548", size = 110278 }, - { url = "https://files.pythonhosted.org/packages/8c/b3/75232906bd13f869600d23bdb8fe6903cc899fa7e96981ae4c9b7d9c409e/pyclipper-1.3.0.post6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f129284d2c7bcd213d11c0f35e1ae506a1144ce4954e9d1734d63b120b0a1b58", size = 268254 }, - { url = "https://files.pythonhosted.org/packages/0b/db/35843050a3dd7586781497a21ca6c8d48111afb66061cb40c3d3c288596d/pyclipper-1.3.0.post6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:188fbfd1d30d02247f92c25ce856f5f3c75d841251f43367dbcf10935bc48f38", size = 142204 }, - { url = "https://files.pythonhosted.org/packages/7c/d7/1faa0ff35caa02cb32cb0583688cded3f38788f33e02bfe6461fbcc1bee1/pyclipper-1.3.0.post6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d129d0c2587f2f5904d201a4021f859afbb45fada4261c9fdedb2205b09d23", size = 943835 }, - { url = "https://files.pythonhosted.org/packages/31/10/c0bf140bee2844e2c0617fdcc8a4e8daf98e71710046b06034e6f1963404/pyclipper-1.3.0.post6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c9c80b5c46eef38ba3f12dd818dc87f5f2a0853ba914b6f91b133232315f526", size = 962510 }, - { url = "https://files.pythonhosted.org/packages/85/6f/8c6afc49b51b1bf16d5903ecd5aee657cf88f52c83cb5fabf771deeba728/pyclipper-1.3.0.post6-cp313-cp313-win32.whl", hash = "sha256:b15113ec4fc423b58e9ae80aa95cf5a0802f02d8f02a98a46af3d7d66ff0cc0e", size = 98836 }, - { url = "https://files.pythonhosted.org/packages/d5/19/9ff4551b42f2068686c50c0d199072fa67aee57fc5cf86770cacf71efda3/pyclipper-1.3.0.post6-cp313-cp313-win_amd64.whl", hash = "sha256:e5ff68fa770ac654c7974fc78792978796f068bd274e95930c0691c31e192889", size = 109672 }, -] - [[package]] name = "pycparser" version = "2.22" @@ -3388,42 +2402,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, ] -[[package]] -name = "python-bidi" -version = "0.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/74/b5/c1684fb8120ad868b43e4b037ac83aafaa70724cdb078d066bae836c31f6/python_bidi-0.6.3.tar.gz", hash = "sha256:e12114969001a328aea859f79efc30ab9c15241befb86e07029d8961d97fae36", size = 45054 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/15/4f316e1b463ea71b4533e904548f47000f9e20ad394b3e8c83ac65638534/python_bidi-0.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4bdc9dc1143c558ca6931d6712339a30470959f2b7eecb3d0687db7075c20a87", size = 257039 }, - { url = "https://files.pythonhosted.org/packages/ad/2a/9b54acafa641c360ba3a632726046a8da38da1f3ed7b816ae6eec748ca95/python_bidi-0.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0775499b8037103278f05b2bf92d25bf04f40a9f77884ec3d42b01a1e52a40fe", size = 252791 }, - { url = "https://files.pythonhosted.org/packages/ca/b2/55f4d18760dac53e542817b3a2e11e3d5ae631678f2c2248c6f1e67336ea/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb3091aa5efbfc4da6fd52a2fccbf7853c6dc253ddaf9a189bcf3c4345865aa9", size = 294297 }, - { url = "https://files.pythonhosted.org/packages/63/7b/d1ca351cdab44300296a4b758a370bf923666deaa4d19819a7fc4478dc52/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75a9b68b3f5a8da9a33fe37607d9b267a8a3c5806d283a4a47365256773dd1e", size = 297650 }, - { url = "https://files.pythonhosted.org/packages/e4/9e/bcb93f0e30abbd9fbbda736402e3ee86566b1253ea57d1e9b465eb4b62a0/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:208e09819ee0485c2ed4dc1932c39fc073dac3f2cb70b6d2ae0b7296e86831e6", size = 323959 }, - { url = "https://files.pythonhosted.org/packages/26/dd/e795e1186ba6b9e3ebb9bc316c6572036e59d0c2841a5e182403d483eb57/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e17b67d86cd38f2bebc4a46090f83cabb0d1da3a3c920c68efe8093ae1a8d0d1", size = 327207 }, - { url = "https://files.pythonhosted.org/packages/c9/27/fc5777273bcb3de7b82524852995edd67dc9948ed916a3f9236714628ae3/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933a17938f767fa64a8365732eba787a81c26214d89e1b3abe87912325ba26a9", size = 286371 }, - { url = "https://files.pythonhosted.org/packages/e9/11/90b4072461759074564a29d05a83128f02214fea1012ca0984d3a5a7fdbb/python_bidi-0.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:772efb3e0ef17396bfd9d47da4805c74ed6c04f27cac08d7757f76602837fb9d", size = 300797 }, - { url = "https://files.pythonhosted.org/packages/61/2c/d50a50adf51b6c53022b9ad247d65cad50acde94f877fbab49a8854ccaae/python_bidi-0.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9a99114f33f8c0273a61b4afe7d4d715e098318ee4e5ce8f6bb5da8dcd3f95c7", size = 468349 }, - { url = "https://files.pythonhosted.org/packages/68/46/a9d285552961670aedc0bbac403defa7c9c4b17ab6957ef0b7db05d12eec/python_bidi-0.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b30e620d39e85a30bb42f460fd8b5274caf261517edeb853b975d9ea1939b6bd", size = 549521 }, - { url = "https://files.pythonhosted.org/packages/36/3a/5b6b6b73a210cc99b1d1ea697741fc4fc1853aa788b3db1737bfdcaabc75/python_bidi-0.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bee94e3152a6c9ba731e086c9cc6203904290506ba52c505a2e59abab481eb13", size = 471509 }, - { url = "https://files.pythonhosted.org/packages/dd/5a/d3d0588caeb041bbf85b421b30a7a9893057c72fcddcaaf2d54289123487/python_bidi-0.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:926164ec594e9ea9a64faf54273c711d5e3233bcc6ef8966c6eeaddfb3b3075f", size = 451688 }, - { url = "https://files.pythonhosted.org/packages/1d/1f/0539881d381dda2a281056ccfb55905439bad86cfb2787792065a2d8d08b/python_bidi-0.6.3-cp312-none-win32.whl", hash = "sha256:cea395a7daee14c7d50a7e20890d12b9ff1938d81b23eb564f1707a175c37202", size = 151600 }, - { url = "https://files.pythonhosted.org/packages/aa/e7/b4f6a71a7e7aec0e17c36bfca2ff5d962b86b0e04e278de6fc6c0cd6d643/python_bidi-0.6.3-cp312-none-win_amd64.whl", hash = "sha256:350e6c76f942465871f2b473a2076f5002f1df06e4c7abee3029ccca5f006786", size = 156933 }, - { url = "https://files.pythonhosted.org/packages/61/77/957be8fa4f681c31af11323376ecd2dcac6cfbadb12e8555e2b826cf6653/python_bidi-0.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:617d4391b19969de725922a256599e8218fc9c1ef0ff85884f1698fff482a977", size = 256754 }, - { url = "https://files.pythonhosted.org/packages/39/7e/b66b86a1aca9725366b6f4fd549ff36162de7acd53ba13de7704cbc56968/python_bidi-0.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:81f418d54948542b21c03cd8ce622a480ead85fc53175a124c4562bdf55cec49", size = 252526 }, - { url = "https://files.pythonhosted.org/packages/cb/c9/149957c1d75156bf617fd9824b92b7468926a4ba90e74cf1254acffb31de/python_bidi-0.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0999b77af58396cfd789c8d068bac78d2d51363265aaf1369622099be9e0eb32", size = 294071 }, - { url = "https://files.pythonhosted.org/packages/63/50/29ba7d99cb23138da8ce070a677cc1eaaa1545f18cb54350d702366e94f3/python_bidi-0.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f5a0e852e8451147d96876f8233a9db6ed28c914d9767a6696cbc899e7df00c2", size = 297250 }, - { url = "https://files.pythonhosted.org/packages/c5/4b/793e5fe37dc59ec9c48349bfa6373aa771a02dc2270b14a90fa6d14a7920/python_bidi-0.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905e212b12c9edfaa3a916a3acd11426b89507ed0f31641257ad586467602e8d", size = 323242 }, - { url = "https://files.pythonhosted.org/packages/b8/ae/f0be940ac876ef120e26a40b3614996e2ae55c8abdd0e98e7e102207309a/python_bidi-0.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:144adab8dc3a8560e294461114ce6dafec1a986cde6297994c1d31b3252f3298", size = 326738 }, - { url = "https://files.pythonhosted.org/packages/9a/1f/7757dacc33e95e38fd47f02dcb6d4f67ffce0ea432ae7299568129aa55a8/python_bidi-0.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abdbd5c265d64251798243d97228bb78441a1320fe3cf51c9a31191c56407839", size = 286038 }, - { url = "https://files.pythonhosted.org/packages/7a/b0/be55c786d54e8d37e38ca5199727619d616312daeefa8a0be21ef1366412/python_bidi-0.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f824a878a593121570ce3da847d3b9ac50521782c433996d7f81f770d3ed00", size = 300370 }, - { url = "https://files.pythonhosted.org/packages/e1/c8/3d89cef5bd8ebe6fa262f5035763ef99878a7634455a6b0eeb2abf0f1c0d/python_bidi-0.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c7dcbc7eb70a0c7c66ed5219213ee2afcc815988cb9e4b134631579c4ae46980", size = 467798 }, - { url = "https://files.pythonhosted.org/packages/44/52/9b7ee589eae15637722e17b7fcfa7f1ad7ead4b623c46fed23eaa687144c/python_bidi-0.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ccbf53bc71a0a1b7f77524d1c2e51b245ae23a4f16afb80728071e21c187a768", size = 549247 }, - { url = "https://files.pythonhosted.org/packages/c8/08/6cf732e0945e1aa03e189c61288f1d67acd051ffbaae84cb85e329d22d33/python_bidi-0.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:702527506ca97bf549710ce03d89a2577ebe35e34c42eaecfbacb0862ba06dc6", size = 471227 }, - { url = "https://files.pythonhosted.org/packages/06/b6/929538d89494ae493bd67ed671e9a01c88b9054a5dc85dd4f400641cbbe7/python_bidi-0.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1563a8d9cfaeeeb5b4fc806f52a500b19893c63652bbd497dd6ed9def7b9ee8e", size = 451042 }, - { url = "https://files.pythonhosted.org/packages/af/a4/d5e062e07fbf070c8982468280bae6418c513628df7881fdf46a5a02fba4/python_bidi-0.6.3-cp313-none-win32.whl", hash = "sha256:f9b8e024eeaddecb4ca189e3199181985fab20c224db9a1f08db48b905c9905a", size = 151261 }, - { url = "https://files.pythonhosted.org/packages/b3/54/0b698ac336192ef5ae5c055ff60fa7d15f2411ce107129d3703323196ef2/python_bidi-0.6.3-cp313-none-win_amd64.whl", hash = "sha256:36b3fb05ef990613a81a23822246eaf6eef29af5182f8d8cdd174be13c92d1cc", size = 156666 }, -] - [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -3445,15 +2423,6 @@ 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 = "python-json-logger" -version = "3.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/c4/358cd13daa1d912ef795010897a483ab2f0b41c9ea1b35235a8b2f7d15a7/python_json_logger-3.2.1.tar.gz", hash = "sha256:8eb0554ea17cb75b05d2848bc14fb02fbdbd9d6972120781b974380bfa162008", size = 16287 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/72/2f30cf26664fcfa0bd8ec5ee62ec90c03bd485e4a294d92aabc76c5203a5/python_json_logger-3.2.1-py3-none-any.whl", hash = "sha256:cdc17047eb5374bd311e748b42f99d71223f3b0e186f4206cc5d52aefe85b090", size = 14924 }, -] - [[package]] name = "python-levenshtein" version = "0.26.1" @@ -3484,29 +2453,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, ] -[[package]] -name = "pywin32" -version = "308" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, - { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, - { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, - { url = "https://files.pythonhosted.org/packages/a9/a4/aa562d8935e3df5e49c161b427a3a2efad2ed4e9cf81c3de636f1fdddfd0/pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", size = 5938579 }, - { url = "https://files.pythonhosted.org/packages/c7/50/b0efb8bb66210da67a53ab95fd7a98826a97ee21f1d22949863e6d588b22/pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4", size = 6542056 }, - { url = "https://files.pythonhosted.org/packages/26/df/2b63e3e4f2df0224f8aaf6d131f54fe4e8c96400eb9df563e2aae2e1a1f9/pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", size = 7974986 }, -] - -[[package]] -name = "pywinpty" -version = "2.0.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/82/90f8750423cba4b9b6c842df227609fb60704482d7abf6dd47e2babc055a/pywinpty-2.0.14.tar.gz", hash = "sha256:18bd9529e4a5daf2d9719aa17788ba6013e594ae94c5a0c27e83df3278b0660e", size = 27769 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/79/759ae767a3b78d340446efd54dd1fe4f7dafa4fc7be96ed757e44bcdba54/pywinpty-2.0.14-cp312-none-win_amd64.whl", hash = "sha256:55dad362ef3e9408ade68fd173e4f9032b3ce08f68cfe7eacb2c263ea1179737", size = 1397207 }, - { url = "https://files.pythonhosted.org/packages/7d/34/b77b3c209bf2eaa6455390c8d5449241637f5957f41636a2204065d52bfa/pywinpty-2.0.14-cp313-none-win_amd64.whl", hash = "sha256:074fb988a56ec79ca90ed03a896d40707131897cefb8f76f926e3834227f2819", size = 1396698 }, -] - [[package]] name = "pyyaml" version = "6.0.2" @@ -3533,50 +2479,6 @@ wheels = [ { 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 = "pyzmq" -version = "26.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "implementation_name == 'pypy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fd/05/bed626b9f7bb2322cdbbf7b4bd8f54b1b617b0d2ab2d3547d6e39428a48e/pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f", size = 271975 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/28/2f/78a766c8913ad62b28581777ac4ede50c6d9f249d39c2963e279524a1bbe/pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9", size = 1343105 }, - { url = "https://files.pythonhosted.org/packages/b7/9c/4b1e2d3d4065be715e007fe063ec7885978fad285f87eae1436e6c3201f4/pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52", size = 1008365 }, - { url = "https://files.pythonhosted.org/packages/4f/ef/5a23ec689ff36d7625b38d121ef15abfc3631a9aecb417baf7a4245e4124/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08", size = 665923 }, - { url = "https://files.pythonhosted.org/packages/ae/61/d436461a47437d63c6302c90724cf0981883ec57ceb6073873f32172d676/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5", size = 903400 }, - { url = "https://files.pythonhosted.org/packages/47/42/fc6d35ecefe1739a819afaf6f8e686f7f02a4dd241c78972d316f403474c/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae", size = 860034 }, - { url = "https://files.pythonhosted.org/packages/07/3b/44ea6266a6761e9eefaa37d98fabefa112328808ac41aa87b4bbb668af30/pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711", size = 860579 }, - { url = "https://files.pythonhosted.org/packages/38/6f/4df2014ab553a6052b0e551b37da55166991510f9e1002c89cab7ce3b3f2/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6", size = 1196246 }, - { url = "https://files.pythonhosted.org/packages/38/9d/ee240fc0c9fe9817f0c9127a43238a3e28048795483c403cc10720ddef22/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3", size = 1507441 }, - { url = "https://files.pythonhosted.org/packages/85/4f/01711edaa58d535eac4a26c294c617c9a01f09857c0ce191fd574d06f359/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b", size = 1406498 }, - { url = "https://files.pythonhosted.org/packages/07/18/907134c85c7152f679ed744e73e645b365f3ad571f38bdb62e36f347699a/pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7", size = 575533 }, - { url = "https://files.pythonhosted.org/packages/ce/2c/a6f4a20202a4d3c582ad93f95ee78d79bbdc26803495aec2912b17dbbb6c/pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a", size = 637768 }, - { url = "https://files.pythonhosted.org/packages/5f/0e/eb16ff731632d30554bf5af4dbba3ffcd04518219d82028aea4ae1b02ca5/pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b", size = 540675 }, - { url = "https://files.pythonhosted.org/packages/04/a7/0f7e2f6c126fe6e62dbae0bc93b1bd3f1099cf7fea47a5468defebe3f39d/pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726", size = 1006564 }, - { url = "https://files.pythonhosted.org/packages/31/b6/a187165c852c5d49f826a690857684333a6a4a065af0a6015572d2284f6a/pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3", size = 1340447 }, - { url = "https://files.pythonhosted.org/packages/68/ba/f4280c58ff71f321602a6e24fd19879b7e79793fb8ab14027027c0fb58ef/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50", size = 665485 }, - { url = "https://files.pythonhosted.org/packages/77/b5/c987a5c53c7d8704216f29fc3d810b32f156bcea488a940e330e1bcbb88d/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb", size = 903484 }, - { url = "https://files.pythonhosted.org/packages/29/c9/07da157d2db18c72a7eccef8e684cefc155b712a88e3d479d930aa9eceba/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187", size = 859981 }, - { url = "https://files.pythonhosted.org/packages/43/09/e12501bd0b8394b7d02c41efd35c537a1988da67fc9c745cae9c6c776d31/pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b", size = 860334 }, - { url = "https://files.pythonhosted.org/packages/eb/ff/f5ec1d455f8f7385cc0a8b2acd8c807d7fade875c14c44b85c1bddabae21/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18", size = 1196179 }, - { url = "https://files.pythonhosted.org/packages/ec/8a/bb2ac43295b1950fe436a81fc5b298be0b96ac76fb029b514d3ed58f7b27/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115", size = 1507668 }, - { url = "https://files.pythonhosted.org/packages/a9/49/dbc284ebcfd2dca23f6349227ff1616a7ee2c4a35fe0a5d6c3deff2b4fed/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e", size = 1406539 }, - { url = "https://files.pythonhosted.org/packages/00/68/093cdce3fe31e30a341d8e52a1ad86392e13c57970d722c1f62a1d1a54b6/pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5", size = 575567 }, - { url = "https://files.pythonhosted.org/packages/92/ae/6cc4657148143412b5819b05e362ae7dd09fb9fe76e2a539dcff3d0386bc/pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad", size = 637551 }, - { url = "https://files.pythonhosted.org/packages/6c/67/fbff102e201688f97c8092e4c3445d1c1068c2f27bbd45a578df97ed5f94/pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797", size = 540378 }, - { url = "https://files.pythonhosted.org/packages/3f/fe/2d998380b6e0122c6c4bdf9b6caf490831e5f5e2d08a203b5adff060c226/pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a", size = 1007378 }, - { url = "https://files.pythonhosted.org/packages/4a/f4/30d6e7157f12b3a0390bde94d6a8567cdb88846ed068a6e17238a4ccf600/pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc", size = 1329532 }, - { url = "https://files.pythonhosted.org/packages/82/86/3fe917870e15ee1c3ad48229a2a64458e36036e64b4afa9659045d82bfa8/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5", size = 653242 }, - { url = "https://files.pythonhosted.org/packages/50/2d/242e7e6ef6c8c19e6cb52d095834508cd581ffb925699fd3c640cdc758f1/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672", size = 888404 }, - { url = "https://files.pythonhosted.org/packages/ac/11/7270566e1f31e4ea73c81ec821a4b1688fd551009a3d2bab11ec66cb1e8f/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797", size = 845858 }, - { url = "https://files.pythonhosted.org/packages/91/d5/72b38fbc69867795c8711bdd735312f9fef1e3d9204e2f63ab57085434b9/pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386", size = 847375 }, - { url = "https://files.pythonhosted.org/packages/dd/9a/10ed3c7f72b4c24e719c59359fbadd1a27556a28b36cdf1cd9e4fb7845d5/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306", size = 1183489 }, - { url = "https://files.pythonhosted.org/packages/72/2d/8660892543fabf1fe41861efa222455811adac9f3c0818d6c3170a1153e3/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6", size = 1492932 }, - { url = "https://files.pythonhosted.org/packages/7b/d6/32fd69744afb53995619bc5effa2a405ae0d343cd3e747d0fbc43fe894ee/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0", size = 1392485 }, -] - [[package]] name = "rapidfuzz" version = "3.11.0" @@ -3615,19 +2517,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a6/83/8b713d50bec947e945a79be47f772484307fc876c426fb26c6f369098389/rapidfuzz-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c408f09649cbff8da76f8d3ad878b64ba7f7abdad1471efb293d2c075e80c822", size = 857385 }, ] -[[package]] -name = "referencing" -version = "0.35.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/99/5b/73ca1f8e72fff6fa52119dbd185f73a907b1989428917b24cff660129b6d/referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", size = 62991 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/59/2056f61236782a2c86b33906c025d4f4a0b17be0161b63b70fd9e8775d36/referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de", size = 26684 }, -] - [[package]] name = "regex" version = "2024.11.6" @@ -3703,27 +2592,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4b/0d/53aea75710af4528a25ed6837d71d117602b01946b307a3912cb3cfcbcba/retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606", size = 7986 }, ] -[[package]] -name = "rfc3339-validator" -version = "0.1.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, -] - -[[package]] -name = "rfc3986-validator" -version = "0.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242 }, -] - [[package]] name = "rich" version = "13.9.4" @@ -3737,53 +2605,6 @@ 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 = "rpds-py" -version = "0.22.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/80/cce854d0921ff2f0a9fa831ba3ad3c65cee3a46711addf39a2af52df2cfd/rpds_py-0.22.3.tar.gz", hash = "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d", size = 26771 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/75/47/3383ee3bd787a2a5e65a9b9edc37ccf8505c0a00170e3a5e6ea5fbcd97f7/rpds_py-0.22.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e", size = 352334 }, - { url = "https://files.pythonhosted.org/packages/40/14/aa6400fa8158b90a5a250a77f2077c0d0cd8a76fce31d9f2b289f04c6dec/rpds_py-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56", size = 342111 }, - { url = "https://files.pythonhosted.org/packages/7d/06/395a13bfaa8a28b302fb433fb285a67ce0ea2004959a027aea8f9c52bad4/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45", size = 384286 }, - { url = "https://files.pythonhosted.org/packages/43/52/d8eeaffab047e6b7b7ef7f00d5ead074a07973968ffa2d5820fa131d7852/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e", size = 391739 }, - { url = "https://files.pythonhosted.org/packages/83/31/52dc4bde85c60b63719610ed6f6d61877effdb5113a72007679b786377b8/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d", size = 427306 }, - { url = "https://files.pythonhosted.org/packages/70/d5/1bab8e389c2261dba1764e9e793ed6830a63f830fdbec581a242c7c46bda/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38", size = 442717 }, - { url = "https://files.pythonhosted.org/packages/82/a1/a45f3e30835b553379b3a56ea6c4eb622cf11e72008229af840e4596a8ea/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15", size = 385721 }, - { url = "https://files.pythonhosted.org/packages/a6/27/780c942de3120bdd4d0e69583f9c96e179dfff082f6ecbb46b8d6488841f/rpds_py-0.22.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059", size = 415824 }, - { url = "https://files.pythonhosted.org/packages/94/0b/aa0542ca88ad20ea719b06520f925bae348ea5c1fdf201b7e7202d20871d/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e", size = 561227 }, - { url = "https://files.pythonhosted.org/packages/0d/92/3ed77d215f82c8f844d7f98929d56cc321bb0bcfaf8f166559b8ec56e5f1/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61", size = 587424 }, - { url = "https://files.pythonhosted.org/packages/09/42/cacaeb047a22cab6241f107644f230e2935d4efecf6488859a7dd82fc47d/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7", size = 555953 }, - { url = "https://files.pythonhosted.org/packages/e6/52/c921dc6d5f5d45b212a456c1f5b17df1a471127e8037eb0972379e39dff4/rpds_py-0.22.3-cp312-cp312-win32.whl", hash = "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627", size = 221339 }, - { url = "https://files.pythonhosted.org/packages/f2/c7/f82b5be1e8456600395366f86104d1bd8d0faed3802ad511ef6d60c30d98/rpds_py-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4", size = 235786 }, - { url = "https://files.pythonhosted.org/packages/d0/bf/36d5cc1f2c609ae6e8bf0fc35949355ca9d8790eceb66e6385680c951e60/rpds_py-0.22.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84", size = 351657 }, - { url = "https://files.pythonhosted.org/packages/24/2a/f1e0fa124e300c26ea9382e59b2d582cba71cedd340f32d1447f4f29fa4e/rpds_py-0.22.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25", size = 341829 }, - { url = "https://files.pythonhosted.org/packages/cf/c2/0da1231dd16953845bed60d1a586fcd6b15ceaeb965f4d35cdc71f70f606/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4", size = 384220 }, - { url = "https://files.pythonhosted.org/packages/c7/73/a4407f4e3a00a9d4b68c532bf2d873d6b562854a8eaff8faa6133b3588ec/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5", size = 391009 }, - { url = "https://files.pythonhosted.org/packages/a9/c3/04b7353477ab360fe2563f5f0b176d2105982f97cd9ae80a9c5a18f1ae0f/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc", size = 426989 }, - { url = "https://files.pythonhosted.org/packages/8d/e6/e4b85b722bcf11398e17d59c0f6049d19cd606d35363221951e6d625fcb0/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b", size = 441544 }, - { url = "https://files.pythonhosted.org/packages/27/fc/403e65e56f65fff25f2973216974976d3f0a5c3f30e53758589b6dc9b79b/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518", size = 385179 }, - { url = "https://files.pythonhosted.org/packages/57/9b/2be9ff9700d664d51fd96b33d6595791c496d2778cb0b2a634f048437a55/rpds_py-0.22.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd", size = 415103 }, - { url = "https://files.pythonhosted.org/packages/bb/a5/03c2ad8ca10994fcf22dd2150dd1d653bc974fa82d9a590494c84c10c641/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2", size = 560916 }, - { url = "https://files.pythonhosted.org/packages/ba/2e/be4fdfc8b5b576e588782b56978c5b702c5a2307024120d8aeec1ab818f0/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16", size = 587062 }, - { url = "https://files.pythonhosted.org/packages/67/e0/2034c221937709bf9c542603d25ad43a68b4b0a9a0c0b06a742f2756eb66/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f", size = 555734 }, - { url = "https://files.pythonhosted.org/packages/ea/ce/240bae07b5401a22482b58e18cfbabaa392409b2797da60223cca10d7367/rpds_py-0.22.3-cp313-cp313-win32.whl", hash = "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de", size = 220663 }, - { url = "https://files.pythonhosted.org/packages/cb/f0/d330d08f51126330467edae2fa4efa5cec8923c87551a79299380fdea30d/rpds_py-0.22.3-cp313-cp313-win_amd64.whl", hash = "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9", size = 235503 }, - { url = "https://files.pythonhosted.org/packages/f7/c4/dbe1cc03df013bf2feb5ad00615038050e7859f381e96fb5b7b4572cd814/rpds_py-0.22.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b", size = 347698 }, - { url = "https://files.pythonhosted.org/packages/a4/3a/684f66dd6b0f37499cad24cd1c0e523541fd768576fa5ce2d0a8799c3cba/rpds_py-0.22.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b", size = 337330 }, - { url = "https://files.pythonhosted.org/packages/82/eb/e022c08c2ce2e8f7683baa313476492c0e2c1ca97227fe8a75d9f0181e95/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1", size = 380022 }, - { url = "https://files.pythonhosted.org/packages/e4/21/5a80e653e4c86aeb28eb4fea4add1f72e1787a3299687a9187105c3ee966/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83", size = 390754 }, - { url = "https://files.pythonhosted.org/packages/37/a4/d320a04ae90f72d080b3d74597074e62be0a8ecad7d7321312dfe2dc5a6a/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd", size = 423840 }, - { url = "https://files.pythonhosted.org/packages/87/70/674dc47d93db30a6624279284e5631be4c3a12a0340e8e4f349153546728/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1", size = 438970 }, - { url = "https://files.pythonhosted.org/packages/3f/64/9500f4d66601d55cadd21e90784cfd5d5f4560e129d72e4339823129171c/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3", size = 383146 }, - { url = "https://files.pythonhosted.org/packages/4d/45/630327addb1d17173adcf4af01336fd0ee030c04798027dfcb50106001e0/rpds_py-0.22.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130", size = 408294 }, - { url = "https://files.pythonhosted.org/packages/5f/ef/8efb3373cee54ea9d9980b772e5690a0c9e9214045a4e7fa35046e399fee/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c", size = 556345 }, - { url = "https://files.pythonhosted.org/packages/54/01/151d3b9ef4925fc8f15bfb131086c12ec3c3d6dd4a4f7589c335bf8e85ba/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b", size = 582292 }, - { url = "https://files.pythonhosted.org/packages/30/89/35fc7a6cdf3477d441c7aca5e9bbf5a14e0f25152aed7f63f4e0b141045d/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333", size = 553855 }, - { url = "https://files.pythonhosted.org/packages/8f/e0/830c02b2457c4bd20a8c5bb394d31d81f57fbefce2dbdd2e31feff4f7003/rpds_py-0.22.3-cp313-cp313t-win32.whl", hash = "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730", size = 219100 }, - { url = "https://files.pythonhosted.org/packages/f8/30/7ac943f69855c2db77407ae363484b915d861702dbba1aa82d68d57f42be/rpds_py-0.22.3-cp313-cp313t-win_amd64.whl", hash = "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf", size = 233794 }, -] - [[package]] name = "safetensors" version = "0.5.2" @@ -3806,27 +2627,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/86/ca/aa489392ec6fb59223ffce825461e1f811a3affd417121a2088be7a5758b/safetensors-0.5.2-cp38-abi3-win_amd64.whl", hash = "sha256:78abdddd03a406646107f973c7843276e7b64e5e32623529dc17f3d94a20f589", size = 303756 }, ] -[[package]] -name = "sahi" -version = "0.11.14" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "fire" }, - { name = "opencv-python" }, - { name = "pillow" }, - { name = "pybboxes" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "shapely" }, - { name = "terminaltables" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a8/59/336aceacd7ac0d50ba37b289c4844914aa79c69d01b51f48ae39fa9fc96f/sahi-0.11.14.tar.gz", hash = "sha256:52ca98a4f691e661a41d063142d8adc0849b2999a100e241fd584dfee26a9397", size = 84684 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/2b/026f0861137499edca43263679f9e4ce21314b67c34c8d74c12f2833b8c3/sahi-0.11.14-py3-none-any.whl", hash = "sha256:961ba328ebbac2302f1475149624d4b7eaff729eff698befa34e21b22b19ad2f", size = 104009 }, -] - [[package]] name = "scalecodec" version = "1.2.11" @@ -3931,29 +2731,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/df/989b2fd3f8ead6bcf89fc683fde94741eb3b291e41a3ce70cec08c80aa36/scipy-1.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:129f899ed275c0515d553b8d31696924e2ca87d1972421e46c376b9eb87de3d2", size = 43188844 }, ] -[[package]] -name = "seaborn" -version = "0.13.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "matplotlib" }, - { name = "numpy" }, - { name = "pandas" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914 }, -] - -[[package]] -name = "send2trash" -version = "1.8.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072 }, -] - [[package]] name = "sentence-transformers" version = "3.3.1" @@ -4026,29 +2803,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/88/70c5767a0e43eb4451c2200f07d042a4bcd7639276003a9c54a68cfcc1f8/setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4", size = 863432 }, ] -[[package]] -name = "shapely" -version = "2.0.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4a/89/0d20bac88016be35ff7d3c0c2ae64b477908f1b1dfa540c5d69ac7af07fe/shapely-2.0.6.tar.gz", hash = "sha256:997f6159b1484059ec239cacaa53467fd8b5564dabe186cd84ac2944663b0bf6", size = 282361 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/77/efd9f9d4b6a762f976f8b082f54c9be16f63050389500fb52e4f6cc07c1a/shapely-2.0.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cec9193519940e9d1b86a3b4f5af9eb6910197d24af02f247afbfb47bcb3fab0", size = 1450326 }, - { url = "https://files.pythonhosted.org/packages/68/53/5efa6e7a4036a94fe6276cf7bbb298afded51ca3396b03981ad680c8cc7d/shapely-2.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83b94a44ab04a90e88be69e7ddcc6f332da7c0a0ebb1156e1c4f568bbec983c3", size = 1298480 }, - { url = "https://files.pythonhosted.org/packages/88/a2/1be1db4fc262e536465a52d4f19d85834724fedf2299a1b9836bc82fe8fa/shapely-2.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:537c4b2716d22c92036d00b34aac9d3775e3691f80c7aa517c2c290351f42cd8", size = 2439311 }, - { url = "https://files.pythonhosted.org/packages/d5/7d/9a57e187cbf2fbbbdfd4044a4f9ce141c8d221f9963750d3b001f0ec080d/shapely-2.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fea108334be345c283ce74bf064fa00cfdd718048a8af7343c59eb40f59726", size = 2524835 }, - { url = "https://files.pythonhosted.org/packages/6d/0a/f407509ab56825f39bf8cfce1fb410238da96cf096809c3e404e5bc71ea1/shapely-2.0.6-cp312-cp312-win32.whl", hash = "sha256:42fd4cd4834747e4990227e4cbafb02242c0cffe9ce7ef9971f53ac52d80d55f", size = 1295613 }, - { url = "https://files.pythonhosted.org/packages/7b/b3/857afd9dfbfc554f10d683ac412eac6fa260d1f4cd2967ecb655c57e831a/shapely-2.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:665990c84aece05efb68a21b3523a6b2057e84a1afbef426ad287f0796ef8a48", size = 1442539 }, - { url = "https://files.pythonhosted.org/packages/34/e8/d164ef5b0eab86088cde06dee8415519ffd5bb0dd1bd9d021e640e64237c/shapely-2.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:42805ef90783ce689a4dde2b6b2f261e2c52609226a0438d882e3ced40bb3013", size = 1445344 }, - { url = "https://files.pythonhosted.org/packages/ce/e2/9fba7ac142f7831757a10852bfa465683724eadbc93d2d46f74a16f9af04/shapely-2.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6d2cb146191a47bd0cee8ff5f90b47547b82b6345c0d02dd8b25b88b68af62d7", size = 1296182 }, - { url = "https://files.pythonhosted.org/packages/cf/dc/790d4bda27d196cd56ec66975eaae3351c65614cafd0e16ddde39ec9fb92/shapely-2.0.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3fdef0a1794a8fe70dc1f514440aa34426cc0ae98d9a1027fb299d45741c381", size = 2423426 }, - { url = "https://files.pythonhosted.org/packages/af/b0/f8169f77eac7392d41e231911e0095eb1148b4d40c50ea9e34d999c89a7e/shapely-2.0.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c665a0301c645615a107ff7f52adafa2153beab51daf34587170d85e8ba6805", size = 2513249 }, - { url = "https://files.pythonhosted.org/packages/f6/1d/a8c0e9ab49ff2f8e4dedd71b0122eafb22a18ad7e9d256025e1f10c84704/shapely-2.0.6-cp313-cp313-win32.whl", hash = "sha256:0334bd51828f68cd54b87d80b3e7cee93f249d82ae55a0faf3ea21c9be7b323a", size = 1294848 }, - { url = "https://files.pythonhosted.org/packages/23/38/2bc32dd1e7e67a471d4c60971e66df0bdace88656c47a9a728ace0091075/shapely-2.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:d37d070da9e0e0f0a530a621e17c0b8c3c9d04105655132a87cfff8bd77cc4c2", size = 1441371 }, -] - [[package]] name = "shellingham" version = "1.5.4" @@ -4132,20 +2886,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/49/21633706dd6feb14cd3f7935fc00b60870ea057686035e1a99ae6d9d9d53/SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e", size = 1883787 }, ] -[[package]] -name = "stack-data" -version = "0.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asttokens" }, - { name = "executing" }, - { name = "pure-eval" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, -] - [[package]] name = "starlette" version = "0.37.2" @@ -4270,37 +3010,15 @@ wheels = [ ] [[package]] -name = "terminado" -version = "0.18.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ptyprocess", marker = "os_name != 'nt'" }, - { name = "pywinpty", marker = "os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux'" }, - { name = "tornado" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154 }, -] - -[[package]] -name = "terminaltables" -version = "3.1.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/fc/0b73d782f5ab7feba8d007573a3773c58255f223c5940a7b7085f02153c3/terminaltables-3.1.10.tar.gz", hash = "sha256:ba6eca5cb5ba02bba4c9f4f985af80c54ec3dccf94cfcd190154386255e47543", size = 12264 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/fb/ea621e0a19733e01fe4005d46087d383693c0f4a8f824b47d8d4122c87e0/terminaltables-3.1.10-py2.py3-none-any.whl", hash = "sha256:e4fdc4179c9e4aab5f674d80f09d76fa436b96fdc698a8505e0a36bf0804a874", size = 15155 }, -] - -[[package]] -name = "thop" -version = "0.1.1.post2209072238" +name = "tf-keras" +version = "2.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "torch" }, + { name = "tensorflow" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/a9/a4/7d0acc28cde2b29b8114552ce3258dafdc6b2186d24bf8e912de713dd74f/tf_keras-2.18.0.tar.gz", hash = "sha256:ebf744519b322afead33086a2aba872245473294affd40973694f3eb7c7ad77d", size = 1260765 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/0f/72beeab4ff5221dc47127c80f8834b4bcd0cb36f6ba91c0b1d04a1233403/thop-0.1.1.post2209072238-py3-none-any.whl", hash = "sha256:01473c225231927d2ad718351f78ebf7cffe6af3bed464c4f1ba1ef0f7cdda27", size = 15443 }, + { url = "https://files.pythonhosted.org/packages/8a/ed/e08afca471299b04a34cd548e64e89d0153eda0e6cf9b715356777e24774/tf_keras-2.18.0-py3-none-any.whl", hash = "sha256:c431d04027eef790fcd3261cf7fdf93eb74f3cb32e05078b57b7f5a54bd53262", size = 1725427 }, ] [[package]] @@ -4429,24 +3147,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c3/18/00993d420b1d6e88582e51d4bc82c824c99a2e9c045d50eaf9b34fff729a/torchvision-0.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:4a330422c36dbfc946d3a6c1caec3489db07ecdf3675d83369adb2e5a0ca17c4", size = 1562392 }, ] -[[package]] -name = "tornado" -version = "6.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/59/45/a0daf161f7d6f36c3ea5fc0c2de619746cc3dd4c76402e9db545bd920f63/tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b", size = 501135 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/7e/71f604d8cea1b58f82ba3590290b66da1e72d840aeb37e0d5f7291bd30db/tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1", size = 436299 }, - { url = "https://files.pythonhosted.org/packages/96/44/87543a3b99016d0bf54fdaab30d24bf0af2e848f1d13d34a3a5380aabe16/tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803", size = 434253 }, - { url = "https://files.pythonhosted.org/packages/cb/fb/fdf679b4ce51bcb7210801ef4f11fdac96e9885daa402861751353beea6e/tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec", size = 437602 }, - { url = "https://files.pythonhosted.org/packages/4f/3b/e31aeffffc22b475a64dbeb273026a21b5b566f74dee48742817626c47dc/tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946", size = 436972 }, - { url = "https://files.pythonhosted.org/packages/22/55/b78a464de78051a30599ceb6983b01d8f732e6f69bf37b4ed07f642ac0fc/tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf", size = 437173 }, - { url = "https://files.pythonhosted.org/packages/79/5e/be4fb0d1684eb822c9a62fb18a3e44a06188f78aa466b2ad991d2ee31104/tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634", size = 437892 }, - { url = "https://files.pythonhosted.org/packages/f5/33/4f91fdd94ea36e1d796147003b490fe60a0215ac5737b6f9c65e160d4fe0/tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73", size = 437334 }, - { url = "https://files.pythonhosted.org/packages/2b/ae/c1b22d4524b0e10da2f29a176fb2890386f7bd1f63aacf186444873a88a0/tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c", size = 437261 }, - { url = "https://files.pythonhosted.org/packages/b5/25/36dbd49ab6d179bcfc4c6c093a51795a4f3bed380543a8242ac3517a1751/tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482", size = 438463 }, - { url = "https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", size = 438907 }, -] - [[package]] name = "tqdm" version = "4.67.1" @@ -4515,15 +3215,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/cc/0a838ba5ca64dc832aa43f727bd586309846b0ffb2ce52422543e6075e8a/typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847", size = 44908 }, ] -[[package]] -name = "types-python-dateutil" -version = "2.9.0.20241206" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/60/47d92293d9bc521cd2301e423a358abfac0ad409b3a1606d8fbae1321961/types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb", size = 13802 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/b3/ca41df24db5eb99b00d97f89d7674a90cb6b3134c52fb8121b6d8d30f15c/types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53", size = 14384 }, -] - [[package]] name = "typing-extensions" version = "4.12.2" @@ -4542,59 +3233,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, ] -[[package]] -name = "ultralytics" -version = "8.0.43" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "matplotlib" }, - { name = "numpy" }, - { name = "opencv-python" }, - { name = "pandas" }, - { name = "pillow" }, - { name = "psutil" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "scipy" }, - { name = "seaborn" }, - { name = "sentry-sdk" }, - { name = "tensorboard" }, - { name = "thop" }, - { name = "torch" }, - { name = "torchvision" }, - { name = "tqdm" }, - { name = "wheel" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/25/e6/9e4797a242500e39b691789eafcd27ff9b3b109533643357b5a0e8f21ac0/ultralytics-8.0.43.tar.gz", hash = "sha256:028dd87f57b2b3669b1ae89872836fed4adc20d3abd5bcf6e489bd25d7f8a59e", size = 252731 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/48/c7a54a863f11142a21bc5af2a568859cb4f6359bb2de9bbef877ff69583b/ultralytics-8.0.43-py3-none-any.whl", hash = "sha256:c4fe980c1ed25d2769b6638d45c46c91f73b6d27b6329f90cf80844c8cef1636", size = 299603 }, -] - -[[package]] -name = "ultralyticsplus" -version = "0.0.28" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fire" }, - { name = "huggingface-hub" }, - { name = "pandas" }, - { name = "sahi" }, - { name = "ultralytics" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5c/f3/0b3a17b812566af0d681968959d44353edcfe6aae6dbb3218c3f83e99d36/ultralyticsplus-0.0.28.tar.gz", hash = "sha256:45221b592d1c2836b7b6d33321a71322cbc6a902a87e56a8e71499eb76be2b98", size = 10707 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/89/cff07d99887d30d87eba2400b1f4f93a61e997fcc7173ad43c46b6d52415/ultralyticsplus-0.0.28-py3-none-any.whl", hash = "sha256:f7f3eb87d64748b2499b807c665fbd843cb5b74071c2a30848d5964539fad87d", size = 11591 }, -] - -[[package]] -name = "uri-template" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140 }, -] - [[package]] name = "urllib3" version = "2.3.0" @@ -4617,15 +3255,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, ] -[[package]] -name = "validators" -version = "0.34.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/07/91582d69320f6f6daaf2d8072608a4ad8884683d4840e7e4f3a9dbdcc639/validators-0.34.0.tar.gz", hash = "sha256:647fe407b45af9a74d245b943b18e6a816acf4926974278f6dd617778e1e781f", size = 70955 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/78/36828a4d857b25896f9774c875714ba4e9b3bc8a92d2debe3f4df3a83d4f/validators-0.34.0-py3-none-any.whl", hash = "sha256:c804b476e3e6d3786fa07a30073a4ef694e617805eb1946ceee3fe5a9b8b1321", size = 43536 }, -] - [[package]] name = "wandb" version = "0.19.0" @@ -4676,28 +3305,21 @@ dependencies = [ { name = "bert-score" }, { name = "bittensor" }, { name = "bittensor-cli" }, - { name = "black" }, { name = "clip" }, { name = "colormath" }, { name = "datasets" }, { name = "ddt" }, { name = "duckduckgo-search" }, - { name = "easyocr" }, { name = "einops" }, - { name = "keras-ocr" }, { name = "lxml" }, { name = "matplotlib-inline" }, - { name = "mss" }, { name = "nltk" }, { name = "numpy" }, - { name = "object-detection" }, { name = "openai" }, { name = "opencv-python" }, { name = "peft" }, { name = "pip-chill" }, { name = "playwright" }, - { name = "proto-plus" }, - { name = "protobuf" }, { name = "pydantic" }, { name = "python-dotenv" }, { name = "scikit-image" }, @@ -4706,8 +3328,8 @@ dependencies = [ { name = "shtab" }, { name = "sqlalchemy" }, { name = "tensorflow" }, + { name = "tf-keras" }, { name = "tinycss2" }, - { name = "ultralyticsplus" }, { name = "wandb" }, ] @@ -4718,29 +3340,22 @@ requires-dist = [ { name = "bert-score", specifier = "==0.3.13" }, { name = "bittensor", specifier = "==8.5.1rc5" }, { name = "bittensor-cli", specifier = "==8.2.0rc8" }, - { name = "black", specifier = ">=24.10.0" }, { name = "clip", git = "https://github.com/openai/CLIP.git" }, { name = "colormath", specifier = ">=3.0.0" }, { name = "datasets" }, { name = "datasets", specifier = "==3.2.0" }, { name = "ddt", specifier = "==1.6.0" }, { name = "duckduckgo-search" }, - { name = "easyocr", specifier = ">=1.7.2" }, { name = "einops" }, - { name = "keras-ocr", specifier = ">=0.9.3" }, { name = "lxml", specifier = "==5.3.0" }, { name = "matplotlib-inline", specifier = "==0.1.7" }, - { name = "mss", specifier = ">=10.0.0" }, { name = "nltk" }, { name = "numpy", specifier = ">=2.0.2" }, - { name = "object-detection", specifier = ">=0.0.3" }, { name = "openai" }, { name = "opencv-python", specifier = "==4.10.0.84" }, { name = "peft" }, { name = "pip-chill", specifier = "==1.0.3" }, { name = "playwright", specifier = "==1.49.1" }, - { name = "proto-plus", specifier = ">=1.25.0" }, - { name = "protobuf", specifier = ">=5.29.3" }, { name = "pydantic", specifier = "==2.6" }, { name = "python-dotenv", specifier = "==1.0.1" }, { name = "scikit-image", specifier = ">=0.25.0" }, @@ -4749,20 +3364,11 @@ requires-dist = [ { name = "shtab", specifier = "==1.6.5" }, { name = "sqlalchemy" }, { name = "tensorflow", specifier = ">=2.18.0" }, + { name = "tf-keras" }, { name = "tinycss2", specifier = "==1.4.0" }, - { name = "ultralyticsplus", specifier = ">=0.0.28" }, { name = "wandb", specifier = "==0.19.0" }, ] -[[package]] -name = "webcolors" -version = "24.11.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934 }, -] - [[package]] name = "webencodings" version = "0.5.1" @@ -4833,15 +3439,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494 }, ] -[[package]] -name = "widgetsnbextension" -version = "4.0.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/56/fc/238c424fd7f4ebb25f8b1da9a934a3ad7c848286732ae04263661eb0fc03/widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6", size = 1164730 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/02/88b65cc394961a60c43c70517066b6b679738caf78506a5da7b88ffcb643/widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71", size = 2335872 }, -] - [[package]] name = "wrapt" version = "1.17.1" diff --git a/webgenie.db b/webgenie.db deleted file mode 100644 index 34e8234b78ae731ecde3be9c196dfd9a707ece1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmeI%?{CsT7zglnFmc3fyhGmInK=$ z-}=Y+(mNWm!gSed{5ENOKT3Pg=XuhkVQ^(fl9P{VHVY(qX*3MeG+q;87)C|y`)Xfr zx?f#))Y&Yx|JAG*&%cfikAEB0C%JL->-gtULp88L00Izz00bZa0SG_<0{@S|&C_bF z+3A=+rZV^ta~X&!6KR~wC{4s>Z@=qN%csP*&TL9H=SaJL5QU`g`1FE$#BqJ%3~l?A z)DLD+!ZViN&fX4AQaR;I^4{{gZ!Axbe+uF#3}l+IyL01oHs>QSdl2c$G9NLvTjsdv6vm0I)U#QaWI zsVQGmZdW(bQl7adDcgQ>Ip^gtud_5yLMF3ZPS`}w;_~E!4O9%$tTkIL^QOCsVWldH z9jzC}Bb)A9lS*o^4otbHl=g{e24XMgm6rPe|Q7U!M_8v7~wM-*QLjFZ8RLyY~N+rd|L Tuple[Task, bt.Synapse]: if is_empty_html(ground_truth_html): raise ValueError("Empty ground truth html") - base64_image = html_to_screenshot(ground_truth_html, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) + base64_image = await html_to_screenshot(ground_truth_html, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) # Save base64_image for debugging purposes import os diff --git a/webgenie/constants.py b/webgenie/constants.py index 455d097e..ebc51515 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -1,6 +1,9 @@ # backend api hotkey API_HOTKEY = "5DXDCYTuPfLqQXbxfvvnarG31SdTDtaubqpQrzjrcMgoP9dp" +# default load time +DEFAULT_LOAD_TIME = 1000 + # image task timeout IMAGE_TASK_TIMEOUT = 100 @@ -40,3 +43,6 @@ # work dir WORK_DIR = "work" +HTML_EXTENSION = ".html" +IMAGE_EXTENSION = ".png" + diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 1e829796..468742ed 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -1,7 +1,10 @@ import bittensor as bt +import asyncio import os +from playwright.async_api import async_playwright from bs4 import BeautifulSoup from lxml import etree +from PIL import Image import time import re import uuid @@ -69,17 +72,36 @@ def seperate_html_css(html_content: str): return cleaned_html, css -def html_to_screenshot(html: str, page_load_time: int = 1000) -> str: +async def html_to_screenshot(html: str, page_load_time: int = 1000) -> str: html_path = f"{WORK_DIR}/screenshot_{uuid.uuid4()}.html" with open(html_path, "w") as f: f.write(html) png_path = f"{WORK_DIR}/screenshot_{uuid.uuid4()}.png" - os.system(f"{PYTHON_CMD} {SCREENSHOT_SCRIPT_PATH} --html {html_path} --png {png_path} --page_load_time {page_load_time}") + url = f"file://{os.path.abspath(html_path)}" + + try: + async with async_playwright() as p: + # Choose a browser, e.g., Chromium, Firefox, or WebKit + browser = await p.chromium.launch() + page = await browser.new_page() + + # Navigate to the URL + await page.goto(url, timeout=60000) + await page.wait_for_timeout(page_load_time) + # Take the screenshot + await page.screenshot(path=png_path, full_page=True, animations="disabled", timeout=60000) + + await browser.close() + except Exception as e: + print(f"Failed to take screenshot due to: {e}. Generating a blank image.") + # Generate a blank image + img = Image.new('RGB', (1280, 960), color = 'white') + img.save(png_path) + + await asyncio.sleep(0.1) - time.sleep(0.1) base64_image = image_to_base64(png_path) - - time.sleep(0.1) + await asyncio.sleep(0.1) os.remove(html_path) os.remove(png_path) return base64_image diff --git a/webgenie/rewards/visual_reward/common/browser.py b/webgenie/rewards/visual_reward/common/browser.py new file mode 100644 index 00000000..f9e66bc4 --- /dev/null +++ b/webgenie/rewards/visual_reward/common/browser.py @@ -0,0 +1,21 @@ +from playwright.async_api import async_playwright + +web_player = { + "web_driver": None, + "browser": None, +} + +async def start_browser(): + global web_player + web_driver = await async_playwright().start() + browser = await web_driver.chromium.launch(headless=True) + web_player["web_driver"] = web_driver + web_player["browser"] = browser + +async def stop_browser(): + global web_player + await web_player["browser"].close() + await web_player["web_driver"].stop() + web_player["web_driver"] = None + web_player["browser"] = None + diff --git a/webgenie/rewards/visual_reward/common/color_diff.py b/webgenie/rewards/visual_reward/common/color_diff.py new file mode 100644 index 00000000..e381bced --- /dev/null +++ b/webgenie/rewards/visual_reward/common/color_diff.py @@ -0,0 +1,80 @@ + +from colormath.color_objects import sRGBColor, LabColor +from colormath.color_conversions import convert_color +import numpy as np + +def delta_e_cie2000(lab1, lab2): + # Extract components of Lab1 and Lab2 + L1, a1, b1 = lab1.lab_l, lab1.lab_a, lab1.lab_b + L2, a2, b2 = lab2.lab_l, lab2.lab_a, lab2.lab_b + + # 1. Calculate differences in lightness (L*) + delta_L = L1 - L2 + + # 2. Calculate chroma for both colors + C1 = np.sqrt(a1**2 + b1**2) + C2 = np.sqrt(a2**2 + b2**2) + + # 3. Calculate differences in chroma (C*) + delta_C = C1 - C2 + + # 4. Calculate hue for both colors + h1 = np.arctan2(b1, a1) # Hue of color 1 + h2 = np.arctan2(b2, a2) # Hue of color 2 + + # Ensure the hue difference is calculated in the range [0, 360) + delta_H = h1 - h2 + if delta_H < 0: + delta_H += 2 * np.pi # Wrap around 360° + if delta_H > np.pi: + delta_H -= 2 * np.pi + + # 5. Calculate the Hue difference (ΔH*) + delta_H_star = 2 * np.sqrt(C1 * C2) * np.sin(delta_H / 2) + + # 6. Calculate the rotation term R_T + delta_theta = h1 - h2 + np.radians(30) + R_T = -0.17 * np.cos(delta_theta) + 0.24 * np.cos(2 * h1 + np.radians(60)) \ + - 0.32 * np.cos(3 * h1 + np.radians(120)) + 0.2 * np.cos(4 * h1 - np.radians(63)) + + # 7. Calculate the final Delta E (CIE 2000) + term1 = delta_L**2 + term2 = delta_C**2 + term3 = delta_H_star**2 + term4 = R_T * delta_C * delta_H_star + + delta_E_00 = np.sqrt(term1 + term2 + term3 + term4) + + return delta_E_00 + + +def rgb_to_lab(rgb): + """ + Convert an RGB color to Lab color space. + RGB values should be in the range [0, 255]. + """ + # Create an sRGBColor object from RGB values + rgb_color = sRGBColor(rgb[0], rgb[1], rgb[2], is_upscaled=True) + + # Convert to Lab color space + lab_color = convert_color(rgb_color, LabColor) + + return lab_color + + + +def color_similarity_ciede2000(rgb1, rgb2): + """ + Calculate the color similarity between two RGB colors using the CIEDE2000 formula. + Returns a similarity score between 0 and 1, where 1 means identical. + """ + # Convert RGB colors to Lab + lab1 = rgb_to_lab(rgb1) + lab2 = rgb_to_lab(rgb2) + + # Calculate the Delta E (CIEDE2000) + delta_e = delta_e_cie2000(lab1, lab2) + + # Normalize the Delta E value to get a similarity score + similarity = max(0, 1 - (delta_e / 100)) + return similarity \ No newline at end of file diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py new file mode 100644 index 00000000..a2323abd --- /dev/null +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -0,0 +1,140 @@ +import os +import asyncio +import numpy as np +from PIL import Image +from pydantic import BaseModel, Field +from typing import Any +from skimage import io, color + +from webgenie.constants import DEFAULT_LOAD_TIME +from webgenie.rewards.visual_reward.common.browser import web_player +from webgenie.rewards.visual_reward.common.sift import extract_sift_from_roi + + +class HTMLElement(BaseModel): + text: str = Field(default="") + bounding_box: dict = Field(default={}) + scaled_bounding_box: dict = Field(default={}) + color: tuple[int, int, int] = Field(default=(0, 0, 0)) + input_type: str = Field(default="") + input_placeholder: str = Field(default="") + rendered_style: dict = Field(default={}) + + keypoints: Any = Field(default=None) + descriptors: Any = Field(default=None) + avg_color: tuple[int, int, int] = Field(default=(0, 0, 0)) + +def parse_rgb_string(rgb_str: str) -> tuple[int, int, int]: + """Convert RGB color string like 'rgb(23, 34, 45)' to (23, 34, 45) tuple.""" + # Extract numbers from rgb(r,g,b) format using string manipulation + rgb_values = rgb_str.strip().removeprefix('rgb(').removesuffix(')').split(',') + return tuple(int(v.strip()) for v in rgb_values) + + +async def extract_html_elements(file_path, load_time = DEFAULT_LOAD_TIME): + if os.path.exists(file_path): + url = f"file:///{os.path.abspath(file_path)}" + print(url) + text_elements = [] + button_elements = [] + input_elements = [] + anchor_elements = [] + try: + page = await web_player["browser"].new_page() + + await page.goto(url) + await page.wait_for_timeout(load_time) + screenshot_path = file_path.replace(".html", ".png") + await page.screenshot(path=screenshot_path, full_page=True, animations="disabled") + await asyncio.sleep(10) + with open(screenshot_path, "rb") as f: + screenshot = Image.open(f) + W, H = screenshot.size + + async def traverse(node): + text = await node.inner_text() + bounding_box = await node.bounding_box() + rendered_style = await node.evaluate( + """(el) => { + const styles = window.getComputedStyle(el); + return styles; + }""" + ) + tag_name = await node.evaluate("(node) => node.tagName.toLowerCase()") + + scaled_bounding_box = { + "x": bounding_box["x"] / W, + "y": bounding_box["y"] / H, + "width": bounding_box["width"] / W, + "height": bounding_box["height"] / H + } + if tag_name == "button": + button_elements.append( + HTMLElement( + text=text, + bounding_box=bounding_box, + scaled_bounding_box=scaled_bounding_box, + rendered_style=rendered_style, + ) + ) + elif tag_name == "input": + input_type = await node.evaluate("(node) => node.getAttribute('type') || 'text'") + input_placeholder = await node.evaluate("(node) => node.getAttribute('placeholder') || ''") + input_elements.append( + HTMLElement( + text=text, + bounding_box=bounding_box, + scaled_bounding_box=scaled_bounding_box, + rendered_style=rendered_style, + input_type=input_type, + input_placeholder=input_placeholder, + ) + ) + elif tag_name == "a": + anchor_elements.append( + HTMLElement( + text=text, + bounding_box=bounding_box, + scaled_bounding_box=scaled_bounding_box, + rendered_style=rendered_style, + ) + ) + + has_children = await node.evaluate("(node) => node.children.length > 0") + if has_children: + children = await node.query_selector_all(':scope > *') + for child in children: + await traverse(child) + else: + text_elements.append( + HTMLElement( + text=text, + bounding_box=bounding_box, + scaled_bounding_box=scaled_bounding_box, + color=parse_rgb_string(rendered_style["color"]), + rendered_style=rendered_style, + ) + ) + await traverse(await page.query_selector('body')) + await page.close() + except Exception as e: + print(e) + + preprocess_html_elements(file_path, button_elements) + preprocess_html_elements(file_path, input_elements) + preprocess_html_elements(file_path, anchor_elements) + return text_elements, button_elements, input_elements, anchor_elements + +def preprocess_html_elements(html_path, html_elements): + image_path = html_path.replace(".html", ".png") + color_image = io.imread(image_path) + for element in html_elements: + bbox = element.bounding_box + x, y, w, h = int(bbox["x"]), int(bbox["y"]), int(bbox["width"]), int(bbox["height"]) + element.avg_color = np.mean(color_image[y:y+h, x:x+w], axis=(0, 1)) + + gray_image = color.rgb2gray(color_image) + for element in html_elements: + bbox = element.bounding_box + x, y, w, h = int(bbox["x"]), int(bbox["y"]), int(bbox["width"]), int(bbox["height"]) + element.keypoints, element.descriptors = extract_sift_from_roi(gray_image, (x, y, w, h)) diff --git a/webgenie/rewards/visual_reward/common/inpaint_image.py b/webgenie/rewards/visual_reward/common/inpaint_image.py new file mode 100644 index 00000000..4e587cf4 --- /dev/null +++ b/webgenie/rewards/visual_reward/common/inpaint_image.py @@ -0,0 +1,36 @@ +import uuid + +from bs4 import BeautifulSoup + +from webgenie.constants import DEFAULT_LOAD_TIME, HTML_EXTENSION +from webgenie.rewards.visual_reward.common.take_screenshot import take_screenshot + + +def erase_texts(input_file_path, output_file_path): + # Read the input HTML file + with open(input_file_path, 'r') as file: + soup = BeautifulSoup(file, 'html.parser') + + def update_style(element, property_name, value): + # Update the element's style attribute with the given property and value + # Adding !important to ensure the style overrides others + important_value = f"{value} !important" + styles = element.attrs.get('style', '').split(';') + updated_styles = [s for s in styles if not s.strip().startswith(property_name) and len(s.strip()) > 0] + updated_styles.append(f"{property_name}: {important_value}") + element['style'] = '; '.join(updated_styles).strip() + + # Assign a unique color to text within each text-containing element + text_tags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'span', 'a', 'b', 'li', 'table', 'td', 'th', 'button', 'footer', 'header', 'figcaption', 'label'] # Add more tags as needed + for tag in soup.find_all(text_tags): + update_style(tag, 'color', 'transparent') + + # Write the modified HTML to a new file + with open(output_file_path, 'w') as file: + file.write(str(soup)) + +async def inpaint_image(url, output_file_path, load_time = DEFAULT_LOAD_TIME): + erased_html_path = f'{url.replace(HTML_EXTENSION, "_erased.html")}' + erase_texts(url, erased_html_path) + await take_screenshot(erased_html_path, output_file_path, load_time) + diff --git a/webgenie/rewards/visual_reward/common/sift.py b/webgenie/rewards/visual_reward/common/sift.py new file mode 100644 index 00000000..5aa443a5 --- /dev/null +++ b/webgenie/rewards/visual_reward/common/sift.py @@ -0,0 +1,41 @@ +import numpy as np +from skimage import io, color +from skimage.feature import SIFT +from scipy.spatial.distance import cdist +from scipy.optimize import linear_sum_assignment + +def extract_sift_from_roi(gray_image, roi): + # ROI: (x, y, w, h) + x, y, w, h = roi + # Crop the image to that sub-region + roi_image = gray_image[y : y + h, x : x + w] + + # Initialize SIFT + sift = SIFT() + sift.detect_and_extract(roi_image) + + # SIFT keypoints are relative to the top-left of the ROI + keypoints = sift.keypoints + descriptors = sift.descriptors + + return keypoints, descriptors + +def match_sift_features(kp1, desc1, kp2, desc2, distance_metric="euclidean", threshold=0.75): + if (desc1 is None or len(desc1) == 0) and (desc2 is None or len(desc2) == 0): + return 1 + elif (desc1 is None or len(desc1) == 0) or (desc2 is None or len(desc2) == 0): + return 0 + + # Compute the pairwise distance matrix between descriptors + cost_matrix = cdist(desc1, desc2, metric=distance_metric) + + # Solve the assignment problem + row_indices, col_indices = linear_sum_assignment(cost_matrix) + + # Apply a threshold to filter out matches with large distances + matches = [] + for i, j in zip(row_indices, col_indices): + if cost_matrix[i, j] < threshold: + matches.append((i, j)) + + return 1 - len(matches) / max(len(kp1), len(kp2)) diff --git a/webgenie/rewards/visual_reward/common/similarity.py b/webgenie/rewards/visual_reward/common/similarity.py new file mode 100644 index 00000000..60b793d1 --- /dev/null +++ b/webgenie/rewards/visual_reward/common/similarity.py @@ -0,0 +1,54 @@ +import cv2 +import numpy as np +from skimage import io, color +from skimage.feature import SIFT +from skimage.metrics import structural_similarity as ssim +from difflib import SequenceMatcher + +from webgenie.rewards.visual_reward.common.color_diff import color_similarity_ciede2000 +from webgenie.rewards.visual_reward.common.extract_html_elements import HTMLElement +from webgenie.rewards.visual_reward.common.sift import match_sift_features +from webgenie.rewards.visual_reward.common.color_diff import color_similarity_ciede2000 +# similarity is 1 if they are the same, 0 if they are completely different + +def calculate_color_similarity( + original_element: HTMLElement, + predicted_element: HTMLElement, + ): + return color_similarity_ciede2000(original_element.color, predicted_element.color) + +def calculate_text_similarity( + original_element: HTMLElement, + predicted_element: HTMLElement, + ): + if not original_element.text and not predicted_element.text: + return 1 + + if not original_element.text or not predicted_element.text: + return 0 + + return SequenceMatcher(None, original_element.text, predicted_element.text).ratio() + +def calculate_block_similarity( + original_element: HTMLElement, + predicted_element: HTMLElement, + ): + + x_shift = abs(predicted_element.scaled_bounding_box["x"] - original_element.scaled_bounding_box["x"]) + y_shift = abs(predicted_element.scaled_bounding_box["y"] - original_element.scaled_bounding_box["y"]) + xx_shift = abs(predicted_element.scaled_bounding_box["x"] + predicted_element.scaled_bounding_box["width"] + - original_element.scaled_bounding_box["x"] - original_element.scaled_bounding_box["width"]) + yy_shift = abs(predicted_element.scaled_bounding_box["y"] + predicted_element.scaled_bounding_box["height"] + - original_element.scaled_bounding_box["y"] - original_element.scaled_bounding_box["height"]) + + return 1 - (x_shift + y_shift + xx_shift + yy_shift) / 4 + +def calculate_visual_similarity( + predicted_element: HTMLElement, + original_element: HTMLElement, + ): + sift_score = match_sift_features(predicted_element.keypoints, predicted_element.descriptors, + original_element.keypoints, original_element.descriptors) + avg_color_score = color_similarity_ciede2000(predicted_element.avg_color, original_element.avg_color) + print(sift_score, avg_color_score) + return sift_score * 0.5 + avg_color_score * 0.5 diff --git a/webgenie/rewards/visual_reward/common/take_screenshot.py b/webgenie/rewards/visual_reward/common/take_screenshot.py new file mode 100644 index 00000000..574a2ad2 --- /dev/null +++ b/webgenie/rewards/visual_reward/common/take_screenshot.py @@ -0,0 +1,27 @@ +import os +from PIL import Image + +from webgenie.constants import DEFAULT_LOAD_TIME +from webgenie.rewards.visual_reward.common.browser import web_player + +async def take_screenshot(url, output_file_path, load_time = DEFAULT_LOAD_TIME, overwrite = False): + if os.path.exists(url): + url = f"file:///{os.path.abspath(url)}" + if os.path.exists(output_file_path) and not overwrite: + return + if os.path.exists(output_file_path) and overwrite: + os.remove(output_file_path) + print(f"Taking screenshot of {url} to {output_file_path}") + + try: + page = await web_player["browser"].new_page() + await page.goto(url) + await page.wait_for_timeout(load_time) + await page.screenshot(path=output_file_path, full_page=True, animations='disabled') + await page.close() + except Exception as e: + print(f"Failed to take screenshot due to: {e}. Generating a blank image.") + # Generate a blank image + img = Image.new('RGB', (1280, 960), color = 'white') + img.save(output_file_path) + diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/__init__.py b/webgenie/rewards/visual_reward/high_level_matching_score/__init__.py new file mode 100644 index 00000000..c7340e9f --- /dev/null +++ b/webgenie/rewards/visual_reward/high_level_matching_score/__init__.py @@ -0,0 +1 @@ +from .high_level_matching_score import high_level_matching_score \ No newline at end of file diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py b/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py new file mode 100644 index 00000000..b5c96a24 --- /dev/null +++ b/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py @@ -0,0 +1,71 @@ +import clip +import torch +from PIL import Image + +from webgenie.constants import HTML_EXTENSION, IMAGE_EXTENSION +from webgenie.rewards.visual_reward.common.inpaint_image import inpaint_image + +device = "cuda" if torch.cuda.is_available() else "cpu" +model, preprocess = clip.load("ViT-B/32", device=device) + + +def rescale(image_path): + # Load the image + with Image.open(image_path) as img: + width, height = img.size + + # Determine which side is shorter + if width < height: + # Width is shorter, scale height to match the width + new_size = (width, width) + else: + # Height is shorter, scale width to match the height + new_size = (height, height) + + # Resize the image while maintaining aspect ratio + img_resized = img.resize(new_size, Image.LANCZOS) + + return img_resized + + +def calculate_clip_similarity(image_path1, image_path2): + # Load and preprocess images + image1 = preprocess(rescale(image_path1)).unsqueeze(0).to(device) + image2 = preprocess(rescale(image_path2)).unsqueeze(0).to(device) + + # Calculate features + with torch.no_grad(): + image_features1 = model.encode_image(image1) + image_features2 = model.encode_image(image2) + + # Normalize features + image_features1 /= image_features1.norm(dim=-1, keepdim=True) + image_features2 /= image_features2.norm(dim=-1, keepdim=True) + + # Calculate cosine similarity + similarity = (image_features1 @ image_features2.T).item() + + return similarity + +def calculate_embedding_vector(image_path): + image = preprocess(rescale(image_path)).unsqueeze(0).to(device) + with torch.no_grad(): + image_features = model.encode_image(image) + image_features /= image_features.norm(dim=-1, keepdim=True) + return image_features + +async def calculate_clip_score(predict_html_path_list, original_html_path): + original_img_path = original_html_path.replace(HTML_EXTENSION, f"_inpainted{IMAGE_EXTENSION}") + await inpaint_image(original_html_path, original_img_path) + original_embedding_vector = calculate_embedding_vector(original_img_path) + + results = [] + for predict_html_path in predict_html_path_list: + predict_img_path = predict_html_path.replace(HTML_EXTENSION, f"_inpainted{IMAGE_EXTENSION}") + await inpaint_image(predict_html_path, predict_img_path) + predict_embedding_vector = calculate_embedding_vector(predict_img_path) + + score = (original_embedding_vector @ predict_embedding_vector.T).item() + results.append(score) + + return results \ No newline at end of file diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/high_level_matching_score.py b/webgenie/rewards/visual_reward/high_level_matching_score/high_level_matching_score.py new file mode 100644 index 00000000..fd339471 --- /dev/null +++ b/webgenie/rewards/visual_reward/high_level_matching_score/high_level_matching_score.py @@ -0,0 +1,13 @@ +import asyncio +import numpy as np + +from webgenie.rewards.visual_reward.high_level_matching_score.clip_matching_score import calculate_clip_score +from webgenie.rewards.visual_reward.high_level_matching_score.histogram import histogram_matching_score + + +async def high_level_matching_score(predict_html_path_list, original_html_path): + clip_score = await calculate_clip_score(predict_html_path_list, original_html_path) + histogram_score = await histogram_matching_score(predict_html_path_list, original_html_path) + + return np.array(clip_score) * 0.5 + np.array(histogram_score) * 0.5 + diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py b/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py new file mode 100644 index 00000000..19e96d89 --- /dev/null +++ b/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py @@ -0,0 +1,46 @@ +import numpy as np +from PIL import Image + +from webgenie.rewards.visual_reward.common.take_screenshot import take_screenshot +from webgenie.constants import HTML_EXTENSION, IMAGE_EXTENSION + +def compute_grayscale_histogram(image_path, bins=256): + """ + Load the image, convert to grayscale, compute histogram. + """ + # Load image and convert to grayscale + img = Image.open(image_path).convert("L") + np_img = np.array(img) + + # Compute histogram (range 0-255) + hist, edges = np.histogram(np_img, bins=bins, range=(0, 256)) + + # Normalize the histogram so it sums up to 1 (optional) + hist = hist.astype(float) + hist /= hist.sum() + + return hist + +def compare_histograms(hist1, hist2): + """ + Compare two 1D histograms (same bin count) using correlation coefficient. + """ + # Use numpy.corrcoef to get correlation + corr = np.corrcoef(hist1, hist2)[0, 1] + return (corr + 1) / 2 + + +async def histogram_matching_score(predict_html_path_list, original_html_path): + original_img_path = original_html_path.replace(HTML_EXTENSION, IMAGE_EXTENSION) + await take_screenshot(original_html_path, original_img_path) + original_hist = compute_grayscale_histogram(original_img_path) + + results = [] + for predict_html_path in predict_html_path_list: + predict_img_path = predict_html_path.replace(HTML_EXTENSION, IMAGE_EXTENSION) + await take_screenshot(predict_html_path, predict_img_path) + predict_hist = compute_grayscale_histogram(predict_img_path) + similarity = compare_histograms(original_hist, predict_hist) + results.append(similarity) + + return results \ No newline at end of file diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/__init__.py b/webgenie/rewards/visual_reward/low_level_matching_score/__init__.py new file mode 100644 index 00000000..e8b8cb9b --- /dev/null +++ b/webgenie/rewards/visual_reward/low_level_matching_score/__init__.py @@ -0,0 +1 @@ +from .low_level_matching_score import low_level_matching_score \ No newline at end of file diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/element_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/element_matching_score.py new file mode 100644 index 00000000..875f5fcc --- /dev/null +++ b/webgenie/rewards/visual_reward/low_level_matching_score/element_matching_score.py @@ -0,0 +1,46 @@ +import asyncio +import cv2 +import numpy as np + +from difflib import SequenceMatcher +from scipy.optimize import linear_sum_assignment +from skimage.metrics import structural_similarity as ssim + +from webgenie.rewards.visual_reward.common.extract_html_elements import HTMLElement +from webgenie.rewards.visual_reward.common.similarity import ( + calculate_text_similarity, + calculate_visual_similarity, + calculate_block_similarity, +) + + +def calculate_cost(predicted_element: HTMLElement, original_element: HTMLElement): + text_similarity = calculate_text_similarity(predicted_element, original_element) + visual_similarity = calculate_visual_similarity(predicted_element, original_element) + block_similarity = calculate_block_similarity(predicted_element, original_element) + return text_similarity * 0.5 + visual_similarity * 0.3 + block_similarity * 0.2 + + +def create_cost_matrix(predicted_elements, original_elements): + n = len(predicted_elements) + m = len(original_elements) + cost_matrix = np.zeros((n, m)) + for i in range(n): + for j in range(m): + cost_matrix[i][j] = -calculate_cost(predicted_elements[i], original_elements[j]) + return cost_matrix + + +def calculate_element_matching_similarity(predicted_elements, original_elements): + cost_matrix = create_cost_matrix(predicted_elements, original_elements) + row_ind, col_ind = linear_sum_assignment(cost_matrix) + similarity_sum = 0 + for i , j in zip(row_ind, col_ind): + similarity_sum += calculate_cost(predicted_elements[i], original_elements[j]) + + total_count = max(len(predicted_elements), len(original_elements)) + if total_count == 0: + return 1 + + return similarity_sum / total_count + diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/input_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/input_matching_score.py new file mode 100644 index 00000000..d0712e9b --- /dev/null +++ b/webgenie/rewards/visual_reward/low_level_matching_score/input_matching_score.py @@ -0,0 +1,49 @@ +import asyncio +import numpy as np +from math import sqrt +from difflib import SequenceMatcher +from scipy.optimize import linear_sum_assignment +from skimage.metrics import structural_similarity as ssim +import cv2 + +from webgenie.rewards.visual_reward.common.extract_html_elements import HTMLElement +from webgenie.rewards.visual_reward.common.similarity import ( + calculate_block_similarity, + calculate_visual_similarity, + calculate_color_similarity +) + + +def calculate_cost(predicted_element: HTMLElement, original_element: HTMLElement): + if predicted_element.input_type != original_element.input_type: + return 0 + placeholder_similarity = SequenceMatcher(None, predicted_element.input_placeholder, original_element.input_placeholder).ratio() + block_similarity = calculate_block_similarity(predicted_element, original_element) + visual_similarity = calculate_visual_similarity(predicted_element, original_element) + return placeholder_similarity * 0.5 + block_similarity * 0.3 + visual_similarity * 0.2 + + +def create_cost_matrix(predicted_elements, original_elements): + n = len(predicted_elements) + m = len(original_elements) + cost_matrix = np.zeros((n, m)) + for i in range(n): + for j in range(m): + cost_matrix[i][j] = -calculate_cost(predicted_elements[i], original_elements[j]) + return cost_matrix + + +def calculate_input_matching_similarity(predicted_elements, original_elements): + cost_matrix = create_cost_matrix(predicted_elements, original_elements) + row_ind, col_ind = linear_sum_assignment(cost_matrix) + similarity_sum = 0 + for i , j in zip(row_ind, col_ind): + similarity_sum += calculate_cost(predicted_elements[i], original_elements[j]) + + total_count = max(len(predicted_elements), len(original_elements)) + if total_count == 0: + return 1 + + return similarity_sum / total_count + + diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py new file mode 100644 index 00000000..4318135b --- /dev/null +++ b/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py @@ -0,0 +1,36 @@ +import numpy as np + +from webgenie.rewards.visual_reward.low_level_matching_score.element_matching_score import calculate_element_matching_similarity +from webgenie.rewards.visual_reward.low_level_matching_score.text_matching_score import calculate_text_matching_similarity +from webgenie.rewards.visual_reward.low_level_matching_score.input_matching_score import calculate_input_matching_similarity + +from webgenie.rewards.visual_reward.common.extract_html_elements import extract_html_elements + +async def low_level_matching_score(predict_html_path_list, original_html_path): + + ( + original_text_elements, + original_button_elements, + original_input_elements, + original_anchor_elements, + ) = await extract_html_elements(original_html_path) + + results = [] + for predict_html_path in predict_html_path_list: + ( + predicted_text_elements, + predicted_button_elements, + predicted_input_elements, + predicted_anchor_elements, + ) = await extract_html_elements(predict_html_path) + + button_score = calculate_element_matching_similarity(predicted_button_elements, original_button_elements) + anchor_score = calculate_element_matching_similarity(predicted_anchor_elements, original_anchor_elements) + + input_score = calculate_input_matching_similarity(predicted_input_elements, original_input_elements) + text_score = calculate_text_matching_similarity(predicted_text_elements, original_text_elements) + score = button_score * 0.25 + input_score * 0.25 + text_score * 0.25 + anchor_score * 0.25 + print(button_score, input_score, text_score, anchor_score) + results.append(score) + + return np.array(results) diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/text_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/text_matching_score.py new file mode 100644 index 00000000..2f743879 --- /dev/null +++ b/webgenie/rewards/visual_reward/low_level_matching_score/text_matching_score.py @@ -0,0 +1,62 @@ +import numpy as np +from scipy.optimize import linear_sum_assignment + +from webgenie.rewards.visual_reward.common.extract_html_elements import HTMLElement, extract_html_elements +from webgenie.rewards.visual_reward.common.similarity import ( + calculate_text_similarity, + calculate_block_similarity, + calculate_color_similarity, +) + + +def calculate_cost(predicted_element: HTMLElement, original_element: HTMLElement): + text_similarity = calculate_text_similarity(predicted_element, original_element) + block_similarity = calculate_block_similarity(predicted_element, original_element) + return text_similarity * 0.8 + block_similarity * 0.2 + + +def create_cost_matrix(predicted_elements, original_elements): + n = len(predicted_elements) + m = len(original_elements) + cost_matrix = np.zeros((n, m)) + for i in range(n): + for j in range(m): + cost_matrix[i][j] = -calculate_cost(predicted_elements[i], original_elements[j]) + return cost_matrix + + +def calculate_text_matching_similarity(predicted_elements, original_elements): + cost_matrix = create_cost_matrix(predicted_elements, original_elements) + row_ind, col_ind = linear_sum_assignment(cost_matrix) + + match_count = 0 + text_similarity_sum = 0 + block_similarity_sum = 0 + color_similarity_sum = 0 + + for i , j in zip(row_ind, col_ind): + print(predicted_elements[i].text, original_elements[j].text) + text_similarity = calculate_text_similarity(predicted_elements[i], original_elements[j]) + if text_similarity < 0.5: + continue + + block_similarity = calculate_block_similarity(predicted_elements[i], original_elements[j]) + color_similarity = calculate_color_similarity(predicted_elements[i], original_elements[j]) + + match_count += 1 + text_similarity_sum += text_similarity + block_similarity_sum += block_similarity + color_similarity_sum += color_similarity + + total_count = len(predicted_elements) + len(original_elements) - match_count + if total_count == 0: + return 1 + + text_similarity_score = text_similarity_sum / total_count + block_similarity_score = block_similarity_sum / total_count + color_similarity_score = color_similarity_sum / total_count + + return text_similarity_score * 0.5 + block_similarity_score * 0.3 + color_similarity_score * 0.2 + + + diff --git a/webgenie/rewards/visual_reward/metrics/__init__.py b/webgenie/rewards/visual_reward/metrics/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/webgenie/rewards/visual_reward/metrics/dedup_post_gen.py b/webgenie/rewards/visual_reward/metrics/dedup_post_gen.py deleted file mode 100644 index 2af9bd43..00000000 --- a/webgenie/rewards/visual_reward/metrics/dedup_post_gen.py +++ /dev/null @@ -1,69 +0,0 @@ -import difflib -import os -import re - -def map_positions(clean_text, original_text): - """ - Maps the positions from the clean text back to the original text. - """ - map_clean_to_original = [] - original_idx = 0 - - for clean_char in clean_text: - while original_text[original_idx] != clean_char: - original_idx += 1 - map_clean_to_original.append(original_idx) - original_idx += 1 - - return map_clean_to_original - -def check_repetitive_content(file_path, chunk_size=100, repetition_threshold=5, similarity_threshold=0.8, debug=False): - """ - Checks for repetitive content in a text file, considering both exact and similar chunks, - ignoring HTML tags but keeping the original position reference. - - :param file_path: Path to the text file. - :param chunk_size: The size of each chunk for comparison. - :param repetition_threshold: Minimum number of repetitions to consider it as repetitive content. - :param similarity_threshold: The threshold for considering two chunks as similar (0 to 1). - :return: A tuple indicating if repetitive content was found and the position where it starts in the original file. - """ - with open(file_path, 'r', encoding='utf-8') as file: - content = file.read() - - # Clean HTML content and keep a map of positions - content_no_html = re.sub('<.*?>', '', content) - position_map = map_positions(content_no_html, content) - - # Split content into chunks - chunks = [content_no_html[i:i + chunk_size] for i in range(0, len(content_no_html), chunk_size)] - - # Check for repetitive and similar chunks - seen = {} - repetitive_start = len(content_no_html) - for i, chunk in enumerate(chunks): - for seen_chunk, indexes in seen.items(): - similarity = difflib.SequenceMatcher(None, chunk, seen_chunk).ratio() - if similarity >= similarity_threshold: - indexes.append(i) - if len(indexes) >= repetition_threshold: - clean_start = min(repetitive_start, indexes[0] * chunk_size) - c_repetitive_start = position_map[clean_start] if clean_start < len(position_map) else len(content) - if c_repetitive_start < repetitive_start: - repetitive_start = c_repetitive_start - break - else: - seen[chunk] = [i] - - repetitive, start_position = repetitive_start != len(content_no_html), repetitive_start - - if repetitive: - print(f"[Warning] Repetitive content found in {file_path}, start at {start_position}") - print(f"[Warning] You might want to manually check whether the automatic repetition removal is correct.") - if not debug: - os.rename(file_path, file_path.replace(".html", "_old.txt")) - with open(file_path, 'w', encoding='utf-8') as file: - file.write(content[:start_position]) - else: - with open(file_path.replace(".html", "_new.html"), 'w', encoding='utf-8') as file: - file.write(content[:start_position]) diff --git a/webgenie/rewards/visual_reward/metrics/ocr_free_utils.py b/webgenie/rewards/visual_reward/metrics/ocr_free_utils.py deleted file mode 100644 index 4d0e17ea..00000000 --- a/webgenie/rewards/visual_reward/metrics/ocr_free_utils.py +++ /dev/null @@ -1,261 +0,0 @@ -import cv2 -import numpy as np -from PIL import Image, ImageColor -import os -from bs4 import BeautifulSoup, NavigableString, Tag, Comment -from pathlib import Path - -from webgenie.constants import PYTHON_CMD - -def rgb_to_hex(rgb): - """Convert an RGB tuple to hexadecimal format.""" - return '{:02X}{:02X}{:02X}'.format(*rgb) - - -class ColorPool: - def __init__(self, offset=0): - - color_values = list(range(10, 251, 16)) - color_list = [((r + offset) % 256, (g + offset) % 256, (b + offset) % 256) for r in color_values for g in color_values for b in color_values] - self.color_pool = [rgb_to_hex(color) for color in color_list] - - def pop_color(self): - if self.color_pool: - return self.color_pool.pop() - else: - raise NotImplementedError - - -def process_html(input_file_path, output_file_path, offset=0): - # Read the input HTML file - with open(input_file_path, 'r') as file: - soup = BeautifulSoup(file, 'html.parser') - - def update_style(element, property_name, value): - # Update the element's style attribute with the given property and value - # Adding !important to ensure the style overrides others - important_value = f"{value} !important" - styles = element.attrs.get('style', '').split(';') - updated_styles = [s for s in styles if not s.strip().startswith(property_name) and len(s.strip()) > 0] - updated_styles.append(f"{property_name}: {important_value}") - element['style'] = '; '.join(updated_styles).strip() - - # Set the background color of all elements to white - for element in soup.find_all(True): - update_style(element, 'background-color', 'rgba(255, 255, 255, 0.0)') - - color_pool = ColorPool(offset) - - # Assign a unique color to text within each text-containing element - text_tags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'span', 'a', 'b', 'li', 'table', 'td', 'th', 'button', 'footer', 'header', 'figcaption'] # Add more tags as needed - for tag in soup.find_all(text_tags): - color = f"#{color_pool.pop_color()}" - update_style(tag, 'color', color) - update_style(tag, 'opacity', 1.0) - - # Write the modified HTML to a new file - with open(output_file_path, 'w') as file: - file.write(str(soup)) - - -def similar(n1, n2): - if abs(n1 - n2) <= 8: - return True - else: - return False - - -def find_different_pixels(image1_path, image2_path): - # Open the images - img1 = Image.open(image1_path) - img2 = Image.open(image2_path) - - # Ensure both images are of the same size - if img1.size != img2.size: - print(f"[Warning] Images are not the same size, {image1_path}, {image2_path}") - return None - - # Convert images to RGB if they are not - img1 = img1.convert('RGB') - img2 = img2.convert('RGB') - - # Get pixel data - pixels1 = img1.load() - pixels2 = img2.load() - - # List to store coordinates of different pixels - different_pixels = [] - - # Iterate through each pixel - for x in range(img1.size[0]): - for y in range(img1.size[1]): - # Compare pixel colors - r1, g1, b1 = pixels1[x, y] - r2, g2, b2 = pixels2[x, y] - if similar((r1 + 50) % 256, r2) and similar((g1 + 50) % 256, g2) and similar((b1 + 50) % 256, b2): - different_pixels.append((y, x)) - - if len(different_pixels) > 0: - return np.stack(different_pixels) - else: - return None - - -def extract_text_with_color(html_file): - def get_color(tag): - if 'style' in tag.attrs: - styles = tag['style'].split(';') - color_style = [s for s in styles if 'color' in s and 'background-color' not in s] - if color_style: - color = color_style[-1].split(':')[1].strip().replace(" !important", "") - if color[0] == "#": - return color - else: - try: - if color.startswith('rgb'): - color = tuple(map(int, color[4:-1].split(','))) # Extract the RGB values - else: - color = ImageColor.getrgb(color) # Convert named color to RGB - return '#{:02x}{:02x}{:02x}'.format(*color) # Convert RGB to hexadecimal - except ValueError: - print(f"Warning: unable to identify or convert color in {html_file}...", color) - return None - return None - - def extract_text_recursive(element, parent_color='#000000'): - if isinstance(element, Comment): - return None - elif isinstance(element, NavigableString): - text = element.strip() - return (text, parent_color) if text else None - - elif isinstance(element, Tag): - current_color = get_color(element) or parent_color - children_texts = filter(None, [extract_text_recursive(child, current_color) for child in element.children]) - return list(children_texts) - - with open(html_file, 'r', encoding='utf-8') as file: - - soup = BeautifulSoup(file, 'html.parser') - body = soup.body - return extract_text_recursive(body) if body else [] - - -def flatten_tree(tree): - flat_list = [] - - # Helper function to recursively flatten the tree - def flatten(node): - if isinstance(node, list): - for item in node: - flatten(item) - else: - flat_list.append(node) - - # Flatten the tree - flatten(tree) - return flat_list - - -def average_color(image_path, coordinates): - """ - Calculates the average color of the specified coordinates in the given image. - - :param image: A PIL Image object. - :param coordinates: A 2D numpy array of coordinates, where each row represents [x, y]. - :return: A tuple representing the average color (R, G, B). - """ - # Convert image to numpy array - image_array = np.array(Image.open(image_path).convert('RGB')) - - # Extract colors at the specified coordinates - colors = [image_array[x, y] for x, y in coordinates] - - # Calculate the average color - avg_color = np.mean(colors, axis=0) - - return tuple(avg_color.astype(int)) - - -def get_blocks_from_image_diff_pixels(image_path, html_text_color_tree, different_pixels): - image = cv2.imread(image_path) - x_w = image.shape[0] - y_w = image.shape[1] - - def hex_to_bgr(hex_color): - """ - Converts a hex color string to a BGR color tuple. - """ - hex_color = hex_color.lstrip('#') - rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) - return rgb[::-1] - - - def get_intersect(arr1, arr2): - # Reshape the arrays to 1D - arr1_reshaped = arr1.view([('', arr1.dtype)] * arr1.shape[1]) - arr2_reshaped = arr2.view([('', arr2.dtype)] * arr2.shape[1]) - - # Find the intersection - common_rows = np.intersect1d(arr1_reshaped, arr2_reshaped) - - # Reshape the result back to 2D, if needed - common_rows = common_rows.view(arr1.dtype).reshape(-1, arr1.shape[1]) - return common_rows - - - blocks = [] - for item in html_text_color_tree: - try: - color = np.array(hex_to_bgr(item[1]), dtype="uint8") - except: - continue - - lower = color - 4 - upper = color + 4 - - mask = cv2.inRange(image, lower, upper) - - coords = np.column_stack(np.where(mask > 0)) - - coords = get_intersect(coords, different_pixels) - - if coords.size == 0: - continue - - x_min, y_min = np.min(coords, axis=0) - x_max, y_max = np.max(coords, axis=0) - color = average_color(image_path.replace("_p.png", ".png"), coords) - - blocks.append({'text': item[0].lower(), 'bbox': (y_min / y_w, x_min / x_w, (y_max - y_min + 1) / y_w, (x_max - x_min + 1) / x_w), 'color': color}) - return blocks - - -def get_itermediate_names(name): - return name.replace(".png", ".html"), name.replace(".png", "_p.html"), name.replace(".png", "_p_1.html"), name.replace(".png", "_p.png"), name.replace(".png", "_p_1.png") - -def get_blocks_ocr_free(image_path, page_load_time): - html, p_html, p_html_1, p_png, p_png_1 = get_itermediate_names(image_path) - process_html(html, p_html) - process_html(html, p_html_1, offset=50) - - os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {p_html} --png {p_png} --page_load_time {page_load_time}") - os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {p_html_1} --png {p_png_1} --page_load_time {page_load_time}") - - different_pixels = find_different_pixels(p_png, p_png_1) - - if different_pixels is None: - print(f"[Warning] Unable to get pixels with different colors from {p_png}, {p_png_1}...") - os.system(f"rm {p_html} {p_png} {p_html_1} {p_png_1}") - return [] - - html_text_color_tree = flatten_tree(extract_text_with_color(p_html)) - try: - blocks = get_blocks_from_image_diff_pixels(p_png, html_text_color_tree, different_pixels) - except: - print(f"[Warning] Unable to get blocks from {p_png}...") - os.system(f"rm {p_html} {p_png} {p_html_1} {p_png_1}") - return [] - - os.system(f"rm {p_html} {p_png} {p_html_1} {p_png_1}") - return blocks \ No newline at end of file diff --git a/webgenie/rewards/visual_reward/metrics/screenshot_multiple.py b/webgenie/rewards/visual_reward/metrics/screenshot_multiple.py deleted file mode 100644 index be542a76..00000000 --- a/webgenie/rewards/visual_reward/metrics/screenshot_multiple.py +++ /dev/null @@ -1,29 +0,0 @@ -import os -from playwright.sync_api import sync_playwright -from PIL import Image -from tqdm import tqdm - -def take_screenshots(html_files, output_files, page_load_time = 1000, do_it_again=False): - with sync_playwright() as p: - browser = p.chromium.launch() # You can also use 'firefox' or 'webkit' - for html_file, output_file in tqdm(zip(html_files, output_files), desc="Screenshoting HTML files"): - - if os.path.exists(html_file): - html_file = "file://" + os.path.abspath(html_file) - - if os.path.exists(output_file) and not do_it_again: - print(f"{output_file} exists!") - continue - - try: - page = browser.new_page() - page.goto(html_file, timeout=60000) - page.wait_for_timeout(page_load_time) - page.screenshot(path=output_file, full_page=True, animations="disabled", timeout=60000) - page.close() - except Exception as e: - print(f"Failed to take screenshot due to: {e}. Generating a blank image.") - # Generate a blank image - img = Image.new('RGB', (1280, 960), color = 'white') - img.save(output_file) - browser.close() diff --git a/webgenie/rewards/visual_reward/metrics/screenshot_single.py b/webgenie/rewards/visual_reward/metrics/screenshot_single.py deleted file mode 100644 index 8c4673b8..00000000 --- a/webgenie/rewards/visual_reward/metrics/screenshot_single.py +++ /dev/null @@ -1,52 +0,0 @@ -import os -import argparse -from playwright.sync_api import sync_playwright -from PIL import Image - - -def take_screenshot(url, output_file="screenshot.png", page_load_time = 1000, do_it_again=False): - # Convert local path to file:// URL if it's a file - if os.path.exists(url): - url = "file://" + os.path.abspath(url) - - if os.path.exists(output_file) and not do_it_again: - print(f"{output_file} exists!") - return - - try: - with sync_playwright() as p: - # Choose a browser, e.g., Chromium, Firefox, or WebKit - browser = p.chromium.launch() - page = browser.new_page() - - # Navigate to the URL - page.goto(url, timeout=60000) - - # Wait for 10 seconds to ensure page is fully loaded - page.wait_for_timeout(page_load_time) - - # Take the screenshot - page.screenshot(path=output_file, full_page=True, animations="disabled", timeout=60000) - - browser.close() - except Exception as e: - print(f"Failed to take screenshot due to: {e}. Generating a blank image.") - # Generate a blank image - img = Image.new('RGB', (1280, 960), color = 'white') - img.save(output_file) - - -if __name__ == "__main__": - - # Initialize the parser - parser = argparse.ArgumentParser(description='Process two path strings.') - - # Define the arguments - parser.add_argument('--html', type=str) - parser.add_argument('--png', type=str) - parser.add_argument('--page_load_time', type=int, default=1000) - - # Parse the arguments - args = parser.parse_args() - - take_screenshot(args.html, args.png, args.page_load_time, do_it_again=True) diff --git a/webgenie/rewards/visual_reward/metrics/visual_score.py b/webgenie/rewards/visual_reward/metrics/visual_score.py deleted file mode 100644 index fb1346cb..00000000 --- a/webgenie/rewards/visual_reward/metrics/visual_score.py +++ /dev/null @@ -1,596 +0,0 @@ -import cv2 -import numpy as np - -import sys -import os -# Add the current file's directory to Python path -current_dir = os.path.dirname(os.path.abspath(__file__)) -sys.path.append(current_dir) - -# This is a patch for color map, which is not updated for newer version of numpy -def patch_asscalar(a): - return a.item() -setattr(np, "asscalar", patch_asscalar) - -import matplotlib.pyplot as plt -from scipy.optimize import linear_sum_assignment -import random -import time -from sklearn.metrics.pairwise import cosine_similarity -from difflib import SequenceMatcher -from tqdm import tqdm - -from pathlib import Path -from PIL import Image, ImageDraw -import torch -import clip -from copy import deepcopy -from collections import Counter -from bs4 import BeautifulSoup, NavigableString, Comment -import re -import math -from colormath.color_objects import sRGBColor, LabColor -from colormath.color_conversions import convert_color -from colormath.color_diff import delta_e_cie2000 - -from ocr_free_utils import get_blocks_ocr_free -from dedup_post_gen import check_repetitive_content -from webgenie.constants import ( - PYTHON_CMD, - GROUND_TRUTH_HTML_LOAD_TIME, - MINER_HTML_LOAD_TIME, -) - -device = "cuda" if torch.cuda.is_available() else "cpu" -model, preprocess = clip.load("ViT-B/32", device=device) - -def calculate_similarity(block1, block2, max_distance=1.42): - text_similarity = SequenceMatcher(None, block1['text'], block2['text']).ratio() - return text_similarity - - -def adjust_cost_for_context(cost_matrix, consecutive_bonus=1.0, window_size=20): - if window_size <= 0: - return cost_matrix - - n, m = cost_matrix.shape - adjusted_cost_matrix = np.copy(cost_matrix) - - for i in range(n): - for j in range(m): - bonus = 0 - if adjusted_cost_matrix[i][j] >= -0.5: - continue - nearby_matrix = cost_matrix[max(0, i - window_size):min(n, i + window_size + 1), max(0, j - window_size):min(m, j + window_size + 1)] - flattened_array = nearby_matrix.flatten() - sorted_array = np.sort(flattened_array)[::-1] - sorted_array = np.delete(sorted_array, np.where(sorted_array == cost_matrix[i, j])[0][0]) - top_k_elements = sorted_array[- window_size * 2:] - sum_top_k = np.sum(top_k_elements) - bonus = consecutive_bonus * sum_top_k - adjusted_cost_matrix[i][j] += bonus - return adjusted_cost_matrix - -def create_cost_matrix(A, B): - n = len(A) - m = len(B) - cost_matrix = np.zeros((n, m)) - for i in range(n): - for j in range(m): - cost_matrix[i, j] = -calculate_similarity(A[i], B[j]) - return cost_matrix - - -def draw_matched_bboxes(img1, img2, matched_bboxes): - # Create copies of images to draw on - img1_drawn = img1.copy() - img2_drawn = img2.copy() - - h1, w1, _ = img1.shape - h2, w2, _ = img2.shape - - - # Iterate over matched bounding boxes - for bbox_pair in matched_bboxes: - # Random color for each pair - color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) - - # Ensure that the bounding box coordinates are integers - bbox1 = [int(bbox_pair[0][0] * w1), int(bbox_pair[0][1] * h1), int(bbox_pair[0][2] * w1), int(bbox_pair[0][3] * h1)] - bbox2 = [int(bbox_pair[1][0] * w2), int(bbox_pair[1][1] * h2), int(bbox_pair[1][2] * w2), int(bbox_pair[1][3] * h2)] - - # Draw bbox on the first image - top_left_1 = (bbox1[0], bbox1[1]) - bottom_right_1 = (bbox1[0] + bbox1[2], bbox1[1] + bbox1[3]) - img1_drawn = cv2.rectangle(img1_drawn, top_left_1, bottom_right_1, color, 2) - - # Draw bbox on the second image - top_left_2 = (bbox2[0], bbox2[1]) - bottom_right_2 = (bbox2[0] + bbox2[2], bbox2[1] + bbox2[3]) - img2_drawn = cv2.rectangle(img2_drawn, top_left_2, bottom_right_2, color, 2) - - return img1_drawn, img2_drawn - - -def calculate_distance_max_1d(x1, y1, x2, y2): - distance = max(abs(x2 - x1), abs(y2 - y1)) - return distance - - -def calculate_ratio(h1, h2): - return max(h1, h2) / min(h1, h2) - - -def rgb_to_lab(rgb): - """ - Convert an RGB color to Lab color space. - RGB values should be in the range [0, 255]. - """ - # Create an sRGBColor object from RGB values - rgb_color = sRGBColor(rgb[0], rgb[1], rgb[2], is_upscaled=True) - - # Convert to Lab color space - lab_color = convert_color(rgb_color, LabColor) - - return lab_color - -def color_similarity_ciede2000(rgb1, rgb2): - """ - Calculate the color similarity between two RGB colors using the CIEDE2000 formula. - Returns a similarity score between 0 and 1, where 1 means identical. - """ - # Convert RGB colors to Lab - lab1 = rgb_to_lab(rgb1) - lab2 = rgb_to_lab(rgb2) - - # Calculate the Delta E (CIEDE2000) - delta_e = delta_e_cie2000(lab1, lab2) - - # Normalize the Delta E value to get a similarity score - # Note: The normalization method here is arbitrary and can be adjusted based on your needs. - # A delta_e of 0 means identical colors. Higher values indicate more difference. - # For visualization purposes, we consider a delta_e of 100 to be completely different. - similarity = max(0, 1 - (delta_e / 100)) - - return similarity - - -def calculate_current_cost(cost_matrix, row_ind, col_ind): - return cost_matrix[row_ind, col_ind].sum() - - -def merge_blocks_wo_check(block1, block2): - # Concatenate text - merged_text = block1['text'] + " " + block2['text'] - - # Calculate bounding box - x_min = min(block1['bbox'][0], block2['bbox'][0]) - y_min = min(block1['bbox'][1], block2['bbox'][1]) - x_max = max(block1['bbox'][0] + block1['bbox'][2], block2['bbox'][0] + block2['bbox'][2]) - y_max = max(block1['bbox'][1] + block1['bbox'][3], block2['bbox'][1] + block2['bbox'][3]) - merged_bbox = (x_min, y_min, x_max - x_min, y_max - y_min) - - # Average color - merged_color = tuple( - (color1 + color2) // 2 for color1, color2 in zip(block1['color'], block2['color']) - ) - - return {'text': merged_text, 'bbox': merged_bbox, 'color': merged_color} - - -def calculate_current_cost(cost_matrix, row_ind, col_ind): - return cost_matrix[row_ind, col_ind].tolist() - - -def find_maximum_matching(A, B, consecutive_bonus, window_size): - cost_matrix = create_cost_matrix(A, B) - cost_matrix = adjust_cost_for_context(cost_matrix, consecutive_bonus, window_size) - row_ind, col_ind = linear_sum_assignment(cost_matrix) - current_cost = calculate_current_cost(cost_matrix, row_ind, col_ind) - return list(zip(row_ind, col_ind)), current_cost, cost_matrix - - -def remove_indices(lst, indices): - for index in sorted(indices, reverse=True): - if index < len(lst): - lst.pop(index) - return lst - - -def merge_blocks_by_list(blocks, merge_list): - pop_list = [] - while True: - if len(merge_list) == 0: - remove_indices(blocks, pop_list) - return blocks - - i = merge_list[0][0] - j = merge_list[0][1] - - blocks[i] = merge_blocks_wo_check(blocks[i], blocks[j]) - pop_list.append(j) - - merge_list.pop(0) - if len(merge_list) > 0: - new_merge_list = [] - for k in range(len(merge_list)): - if merge_list[k][0] != i and merge_list[k][1] != i and merge_list[k][0] != j and merge_list[k][1] != j: - new_merge_list.append(merge_list[k]) - merge_list = new_merge_list - - -def print_matching(matching, blocks1, blocks2, cost_matrix): - for i, j in matching: - print(f"{blocks1[i]} matched with {blocks2[j]}, cost {cost_matrix[i][j]}") - - -def difference_of_means(list1, list2): - counter1 = Counter(list1) - counter2 = Counter(list2) - - for element in set(list1) & set(list2): - common_count = min(counter1[element], counter2[element]) - counter1[element] -= common_count - counter2[element] -= common_count - - unique_list1 = [item for item in counter1.elements()] - unique_list2 = [item for item in counter2.elements()] - - mean_list1 = sum(unique_list1) / len(unique_list1) if unique_list1 else 0 - mean_list2 = sum(unique_list2) / len(unique_list2) if unique_list2 else 0 - - if mean_list1 - mean_list2 > 0: - if min(unique_list1) > min(unique_list2): - return mean_list1 - mean_list2 - else: - return 0.0 - else: - return mean_list1 - mean_list2 - - -def find_possible_merge(A, B, consecutive_bonus, window_size, debug=False): - merge_bonus = 0.0 - merge_windows = 1 - - def sortFn(value): - return value[2] - - while True: - A_changed = False - B_changed = False - - matching, current_cost, cost_matrix = find_maximum_matching(A, B, merge_bonus, merge_windows) - if debug: - print("Current cost of the solution:", current_cost) - print_matching(matching, A, B, cost_matrix) - - if len(A) >= 2: - merge_list = [] - for i in range(len(A) - 1): - new_A = deepcopy(A) - new_A[i] = merge_blocks_wo_check(new_A[i], new_A[i + 1]) - new_A.pop(i + 1) - - updated_matching, updated_cost, cost_matrix = find_maximum_matching(new_A, B, merge_bonus, merge_windows) - diff = difference_of_means(current_cost, updated_cost) - if diff > 0.05: - merge_list.append([i, i + 1, diff]) - if debug: - print(new_A[i]['text'], diff) - - merge_list.sort(key=sortFn, reverse=True) - if len(merge_list) > 0: - A_changed = True - A = merge_blocks_by_list(A, merge_list) - matching, current_cost, cost_matrix = find_maximum_matching(A, B, merge_bonus, merge_windows) - if debug: - print("Cost after optimization A:", current_cost) - - if len(B) >= 2: - merge_list = [] - for i in range(len(B) - 1): - new_B = deepcopy(B) - new_B[i] = merge_blocks_wo_check(new_B[i], new_B[i + 1]) - new_B.pop(i + 1) - - updated_matching, updated_cost, cost_matrix = find_maximum_matching(A, new_B, merge_bonus, merge_windows) - diff = difference_of_means(current_cost, updated_cost) - if diff > 0.05: - merge_list.append([i, i + 1, diff]) - if debug: - print(new_B[i]['text'], diff) - - merge_list.sort(key=sortFn, reverse=True) - if len(merge_list) > 0: - B_changed = True - B = merge_blocks_by_list(B, merge_list) - matching, current_cost, cost_matrix = find_maximum_matching(A, B, merge_bonus, merge_windows) - if debug: - print("Cost after optimization B:", current_cost) - - if not A_changed and not B_changed: - break - matching, _, _ = find_maximum_matching(A, B, consecutive_bonus, window_size) - return A, B, matching - - -def merge_blocks_by_bbox(blocks): - merged_blocks = {} - - # Traverse and merge blocks - for block in blocks: - bbox = tuple(block['bbox']) # Convert bbox to tuple for hashability - if bbox in merged_blocks: - # Merge with existing block - existing_block = merged_blocks[bbox] - existing_block['text'] += ' ' + block['text'] - existing_block['color'] = [(ec + c) / 2 for ec, c in zip(existing_block['color'], block['color'])] - else: - # Add new block - merged_blocks[bbox] = block - - return list(merged_blocks.values()) - - -def mask_bounding_boxes_with_inpainting(image, bounding_boxes): - # Convert PIL image to OpenCV format - image_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) - - # Create a black mask - mask = np.zeros(image_cv.shape[:2], dtype=np.uint8) - - height, width = image_cv.shape[:2] - - # Draw white rectangles on the mask - for bbox in bounding_boxes: - x_ratio, y_ratio, w_ratio, h_ratio = bbox - x = int(x_ratio * width) - y = int(y_ratio * height) - w = int(w_ratio * width) - h = int(h_ratio * height) - mask[y:y+h, x:x+w] = 255 - - # Use inpainting - inpainted_image = cv2.inpaint(image_cv, mask, 3, cv2.INPAINT_TELEA) - - # Convert back to PIL format - inpainted_image_pil = Image.fromarray(cv2.cvtColor(inpainted_image, cv2.COLOR_BGR2RGB)) - - return inpainted_image_pil - - -def rescale_and_mask(image_path, blocks): - # Load the image - with Image.open(image_path) as img: - if len(blocks) > 0: - # use inpainting instead of simple mask - img = mask_bounding_boxes_with_inpainting(img, blocks) - - width, height = img.size - - # Determine which side is shorter - if width < height: - # Width is shorter, scale height to match the width - new_size = (width, width) - else: - # Height is shorter, scale width to match the height - new_size = (height, height) - - # Resize the image while maintaining aspect ratio - img_resized = img.resize(new_size, Image.LANCZOS) - - return img_resized - - -def calculate_clip_similarity_with_blocks(image_path1, image_path2, blocks1, blocks2): - # Load and preprocess images - image1 = preprocess(rescale_and_mask(image_path1, [block['bbox'] for block in blocks1])).unsqueeze(0).to(device) - image2 = preprocess(rescale_and_mask(image_path2, [block['bbox'] for block in blocks2])).unsqueeze(0).to(device) - - # Calculate features - with torch.no_grad(): - image_features1 = model.encode_image(image1) - image_features2 = model.encode_image(image2) - - # Normalize features - image_features1 /= image_features1.norm(dim=-1, keepdim=True) - image_features2 /= image_features2.norm(dim=-1, keepdim=True) - - # Calculate cosine similarity - similarity = (image_features1 @ image_features2.T).item() - - return similarity - - -def truncate_repeated_html_elements(soup, max_count=50): - content_counts = {} - - for element in soup.find_all(True): - if isinstance(element, (NavigableString, Comment)): - continue - - try: - element_html = str(element) - except: - element.decompose() - continue - content_counts[element_html] = content_counts.get(element_html, 0) + 1 - - if content_counts[element_html] > max_count: - element.decompose() - - return str(soup) - - -def make_html(filename): - with open(filename, 'r') as file: - content = file.read() - - if not re.search(r']*>', content, re.IGNORECASE): - new_content = f'

{content}

' - with open(filename, 'w') as file: - file.write(new_content) - - -def pre_process(html_file): - #check_repetitive_content(html_file) - make_html(html_file) - with open(html_file, 'r') as file: - soup = BeautifulSoup(file, 'html.parser') - soup_str = truncate_repeated_html_elements(soup) - with open(html_file, 'w') as file: - file.write(soup_str) - - -def visual_eval_v3_multi(input_list, debug=False): - predict_html_list, original_html = input_list[0], input_list[1] - predict_img_list = [html.replace(".html", ".png") for html in predict_html_list] - # try: - predict_blocks_list = [] - for predict_html in tqdm(predict_html_list, desc="Screenshot HTML files and get OCR blocks"): - predict_img = predict_html.replace(".html", ".png") - # This will help fix some html syntax error - pre_process(predict_html) - os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {predict_html} --png {predict_img} --page_load_time {MINER_HTML_LOAD_TIME}") - predict_blocks = get_blocks_ocr_free(predict_img, page_load_time=MINER_HTML_LOAD_TIME) - predict_blocks_list.append(predict_blocks) - - original_img = original_html.replace(".html", ".png") - os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {original_html} --png {original_img} --page_load_time {GROUND_TRUTH_HTML_LOAD_TIME}") - original_blocks = get_blocks_ocr_free(original_img, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) - original_blocks = merge_blocks_by_bbox(original_blocks) - - # Consider context similarity for block matching - consecutive_bonus, window_size = 0.1, 1 - - return_score_list = [] - - for k, predict_blocks in tqdm(enumerate(predict_blocks_list), desc="Processing HTML files"): - # if len(predict_blocks) == 0: - # print("[Warning] No detected blocks in: ", predict_img_list[k]) - # final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) - # return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) - # continue - # elif len(original_blocks) == 0: - # print("[Warning] No detected blocks in: ", original_img) - # final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) - # return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) - # continue - print(len(predict_blocks)) - if debug: - print(predict_blocks) - print(original_blocks) - print("Merging blocks") - start_time = time.time() - predict_blocks = merge_blocks_by_bbox(predict_blocks) - predict_blocks_m, original_blocks_m, matching = find_possible_merge(predict_blocks, deepcopy(original_blocks), consecutive_bonus, window_size, debug=debug) - print(f"Merging blocks time: {time.time() - start_time:.2f} seconds") - - print("Filtering matching") - start_time = time.time() - filtered_matching = [] - for i, j in matching: - text_similarity = SequenceMatcher(None, predict_blocks_m[i]['text'], original_blocks_m[j]['text']).ratio() - # Filter out matching with low similarity - if text_similarity < 0.5: - continue - filtered_matching.append([i, j, text_similarity]) - matching = filtered_matching - print(f"Filtering matching time: {time.time() - start_time:.2f} seconds") - - indices1 = [item[0] for item in matching] - indices2 = [item[1] for item in matching] - - matched_list = [] - sum_areas = [] - matched_areas = [] - matched_text_scores = [] - position_scores = [] - text_color_scores = [] - - unmatched_area_1 = 0.0 - for i in range(len(predict_blocks_m)): - if i not in indices1: - unmatched_area_1 += predict_blocks_m[i]['bbox'][2] * predict_blocks_m[i]['bbox'][3] - unmatched_area_2 = 0.0 - for j in range(len(original_blocks_m)): - if j not in indices2: - unmatched_area_2 += original_blocks_m[j]['bbox'][2] * original_blocks_m[j]['bbox'][3] - sum_areas.append(unmatched_area_1 + unmatched_area_2) - - print("Calculating scores") - start_time = time.time() - for i, j, text_similarity in matching: - sum_block_area = predict_blocks_m[i]['bbox'][2] * predict_blocks_m[i]['bbox'][3] + original_blocks_m[j]['bbox'][2] * original_blocks_m[j]['bbox'][3] - - # Consider the max postion shift, either horizontally or vertically - position_similarity = 1 - calculate_distance_max_1d(predict_blocks_m[i]['bbox'][0] + predict_blocks_m[i]['bbox'][2] / 2, \ - predict_blocks_m[i]['bbox'][1] + predict_blocks_m[i]['bbox'][3] / 2, \ - original_blocks_m[j]['bbox'][0] + original_blocks_m[j]['bbox'][2] / 2, \ - original_blocks_m[j]['bbox'][1] + original_blocks_m[j]['bbox'][3] / 2) - # Normalized ciede2000 formula - - text_color_similarity = color_similarity_ciede2000(predict_blocks_m[i]['color'], original_blocks_m[j]['color']) - matched_list.append([predict_blocks_m[i]['bbox'], original_blocks_m[j]['bbox']]) - - # validation check - if min(predict_blocks_m[i]['bbox'][2], original_blocks_m[j]['bbox'][2], predict_blocks_m[i]['bbox'][3], original_blocks_m[j]['bbox'][3]) == 0: - print(f"{predict_blocks_m[i]} matched with {original_blocks_m[j]}") - assert calculate_ratio(predict_blocks_m[i]['bbox'][2], original_blocks_m[j]['bbox'][2]) > 0 and calculate_ratio(predict_blocks_m[i]['bbox'][3], original_blocks_m[j]['bbox'][3]) > 0, f"{predict_blocks_m[i]} matched with {original_blocks_m[j]}" - - sum_areas.append(sum_block_area) - matched_areas.append(sum_block_area) - matched_text_scores.append(text_similarity) - position_scores.append(position_similarity) - text_color_scores.append(text_color_similarity) - - if debug: - print(f"{predict_blocks_m[i]} matched with {original_blocks_m[j]}") - print(SequenceMatcher(None, predict_blocks_m[i]['text'], original_blocks_m[j]['text']).ratio()) - print("text similarity score", text_similarity) - print("position score", position_similarity) - print("color score", text_color_similarity) - print("----------------------------------") - pass - print(f"Calculating scores time: {time.time() - start_time:.2f} seconds") - """ - if debug: - img1 = cv2.imread(predict_img_list[k]) - img2 = cv2.imread(original_img) - img1_with_boxes, img2_with_boxes = draw_matched_bboxes(img1, img2, matched_list) - - plt.figure(figsize=(20, 10)) - plt.subplot(1, 2, 1) - plt.imshow(cv2.cvtColor(img1_with_boxes, cv2.COLOR_BGR2RGB)) - plt.axis('off') - plt.subplot(1, 2, 2) - plt.imshow(cv2.cvtColor(img2_with_boxes, cv2.COLOR_BGR2RGB)) - plt.axis('off') - plt.show() - # """ - - if len(matched_areas) > 0: - sum_sum_areas = np.sum(sum_areas) - - final_size_score = np.sum(matched_areas) / np.sum(sum_areas) - final_matched_text_score = np.mean(matched_text_scores) - final_position_score = np.mean(position_scores) - final_text_color_score = np.mean(text_color_scores) - final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) - final_score = 0.2 * (final_size_score + final_matched_text_score + final_position_score + final_text_color_score + final_clip_score) - return_score_list.append([sum_sum_areas, final_score, (final_size_score, final_matched_text_score, final_position_score, final_text_color_score, final_clip_score)]) - else: - print("[Warning] No matched blocks in: ", predict_img_list[k]) - final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) - return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) - return return_score_list - - # except: - # print("[Warning] Error not handled in: ", input_list) - # return [[0.0, 0.0, (0.0, 0.0, 0.0, 0.0, 0.0)] for _ in range(len(predict_html_list))] - - -if __name__ == "__main__": - print(visual_eval_v3_multi([ - ["work/miner1.html", "work/miner2.html", "work/miner3.html"], - "work/original.html"], debug=True)) diff --git a/webgenie/rewards/visual_reward/metrics_v2/__init__.py b/webgenie/rewards/visual_reward/metrics_v2/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/webgenie/rewards/visual_reward/metrics_v2/dedup_post_gen.py b/webgenie/rewards/visual_reward/metrics_v2/dedup_post_gen.py deleted file mode 100644 index 2af9bd43..00000000 --- a/webgenie/rewards/visual_reward/metrics_v2/dedup_post_gen.py +++ /dev/null @@ -1,69 +0,0 @@ -import difflib -import os -import re - -def map_positions(clean_text, original_text): - """ - Maps the positions from the clean text back to the original text. - """ - map_clean_to_original = [] - original_idx = 0 - - for clean_char in clean_text: - while original_text[original_idx] != clean_char: - original_idx += 1 - map_clean_to_original.append(original_idx) - original_idx += 1 - - return map_clean_to_original - -def check_repetitive_content(file_path, chunk_size=100, repetition_threshold=5, similarity_threshold=0.8, debug=False): - """ - Checks for repetitive content in a text file, considering both exact and similar chunks, - ignoring HTML tags but keeping the original position reference. - - :param file_path: Path to the text file. - :param chunk_size: The size of each chunk for comparison. - :param repetition_threshold: Minimum number of repetitions to consider it as repetitive content. - :param similarity_threshold: The threshold for considering two chunks as similar (0 to 1). - :return: A tuple indicating if repetitive content was found and the position where it starts in the original file. - """ - with open(file_path, 'r', encoding='utf-8') as file: - content = file.read() - - # Clean HTML content and keep a map of positions - content_no_html = re.sub('<.*?>', '', content) - position_map = map_positions(content_no_html, content) - - # Split content into chunks - chunks = [content_no_html[i:i + chunk_size] for i in range(0, len(content_no_html), chunk_size)] - - # Check for repetitive and similar chunks - seen = {} - repetitive_start = len(content_no_html) - for i, chunk in enumerate(chunks): - for seen_chunk, indexes in seen.items(): - similarity = difflib.SequenceMatcher(None, chunk, seen_chunk).ratio() - if similarity >= similarity_threshold: - indexes.append(i) - if len(indexes) >= repetition_threshold: - clean_start = min(repetitive_start, indexes[0] * chunk_size) - c_repetitive_start = position_map[clean_start] if clean_start < len(position_map) else len(content) - if c_repetitive_start < repetitive_start: - repetitive_start = c_repetitive_start - break - else: - seen[chunk] = [i] - - repetitive, start_position = repetitive_start != len(content_no_html), repetitive_start - - if repetitive: - print(f"[Warning] Repetitive content found in {file_path}, start at {start_position}") - print(f"[Warning] You might want to manually check whether the automatic repetition removal is correct.") - if not debug: - os.rename(file_path, file_path.replace(".html", "_old.txt")) - with open(file_path, 'w', encoding='utf-8') as file: - file.write(content[:start_position]) - else: - with open(file_path.replace(".html", "_new.html"), 'w', encoding='utf-8') as file: - file.write(content[:start_position]) diff --git a/webgenie/rewards/visual_reward/metrics_v2/ocr_free_utils.py b/webgenie/rewards/visual_reward/metrics_v2/ocr_free_utils.py deleted file mode 100644 index fdffeb41..00000000 --- a/webgenie/rewards/visual_reward/metrics_v2/ocr_free_utils.py +++ /dev/null @@ -1,268 +0,0 @@ -import cv2 -import numpy as np -from PIL import Image, ImageColor -import os -from bs4 import BeautifulSoup, NavigableString, Tag, Comment -from pathlib import Path - -from webgenie.constants import PYTHON_CMD - -def rgb_to_hex(rgb): - """Convert an RGB tuple to hexadecimal format.""" - return '{:02X}{:02X}{:02X}'.format(*rgb) - - -class ColorPool: - def __init__(self, offset=0): - - color_values = list(range(10, 251, 16)) - color_list = [((r + offset) % 256, (g + offset) % 256, (b + offset) % 256) for r in color_values for g in color_values for b in color_values] - self.color_pool = [rgb_to_hex(color) for color in color_list] - - def pop_color(self): - if self.color_pool: - return self.color_pool.pop() - else: - raise NotImplementedError - - -def process_html(input_file_path, output_file_path, offset=0): - # Read the input HTML file - with open(input_file_path, 'r') as file: - soup = BeautifulSoup(file, 'html.parser') - - def update_style(element, property_name, value): - # Update the element's style attribute with the given property and value - # Adding !important to ensure the style overrides others - important_value = f"{value} !important" - styles = element.attrs.get('style', '').split(';') - updated_styles = [s for s in styles if not s.strip().startswith(property_name) and len(s.strip()) > 0] - updated_styles.append(f"{property_name}: {important_value}") - element['style'] = '; '.join(updated_styles).strip() - - # Set the background color of all elements to white - for element in soup.find_all(True): - update_style(element, 'background-color', 'rgba(255, 255, 255, 0.0)') - - color_pool = ColorPool(offset) - - # Assign a unique color to text within each text-containing element - text_tags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'span', 'a', 'b', 'li', 'table', 'td', 'th', 'button', 'footer', 'header', 'figcaption'] # Add more tags as needed - for tag in soup.find_all(text_tags): - color = f"#{color_pool.pop_color()}" - update_style(tag, 'color', color) - update_style(tag, 'opacity', 1.0) - - # Write the modified HTML to a new file - with open(output_file_path, 'w') as file: - file.write(str(soup)) - - -def similar(n1, n2): - if abs(n1 - n2) <= 8: - return True - else: - return False - - -def find_different_pixels(image1_path, image2_path): - # Open the images - img1 = Image.open(image1_path) - img2 = Image.open(image2_path) - - # Ensure both images are of the same size - if img1.size != img2.size: - print(f"[Warning] Images are not the same size, {image1_path}, {image2_path}") - return None - - # Convert images to RGB if they are not - img1 = img1.convert('RGB') - img2 = img2.convert('RGB') - - # Get pixel data - pixels1 = img1.load() - pixels2 = img2.load() - - # List to store coordinates of different pixels - different_pixels = [] - - # Iterate through each pixel - for x in range(img1.size[0]): - for y in range(img1.size[1]): - # Compare pixel colors - r1, g1, b1 = pixels1[x, y] - r2, g2, b2 = pixels2[x, y] - if similar((r1 + 50) % 256, r2) and similar((g1 + 50) % 256, g2) and similar((b1 + 50) % 256, b2): - different_pixels.append((y, x)) - - if len(different_pixels) > 0: - return np.stack(different_pixels) - else: - return None - - -def extract_text_with_color(html_file): - def get_color(tag): - if 'style' in tag.attrs: - styles = tag['style'].split(';') - color_style = [s for s in styles if 'color' in s and 'background-color' not in s] - if color_style: - color = color_style[-1].split(':')[1].strip().replace(" !important", "") - if color[0] == "#": - return color - else: - try: - if color.startswith('rgb'): - color = tuple(map(int, color[4:-1].split(','))) # Extract the RGB values - else: - color = ImageColor.getrgb(color) # Convert named color to RGB - return '#{:02x}{:02x}{:02x}'.format(*color) # Convert RGB to hexadecimal - except ValueError: - print(f"Warning: unable to identify or convert color in {html_file}...", color) - return None - return None - - def extract_text_recursive(element, parent_color='#000000'): - if isinstance(element, Comment): - return None - elif isinstance(element, NavigableString): - text = element.strip() - return (text, parent_color) if text else None - - elif isinstance(element, Tag): - current_color = get_color(element) or parent_color - children_texts = filter(None, [extract_text_recursive(child, current_color) for child in element.children]) - return list(children_texts) - - with open(html_file, 'r', encoding='utf-8') as file: - soup = BeautifulSoup(file, 'html.parser') - body = soup.body - return extract_text_recursive(body) if body else [] - - -def flatten_tree(tree): - flat_list = [] - - # Helper function to recursively flatten the tree - def flatten(node): - if isinstance(node, list): - for item in node: - flatten(item) - else: - flat_list.append(node) - - # Flatten the tree - flatten(tree) - return flat_list - - -def average_color(image_path, coordinates): - """ - Calculates the average color of the specified coordinates in the given image. - - :param image: A PIL Image object. - :param coordinates: A 2D numpy array of coordinates, where each row represents [x, y]. - :return: A tuple representing the average color (R, G, B). - """ - # Convert image to numpy array - image_array = np.array(Image.open(image_path).convert('RGB')) - - # Extract colors at the specified coordinates - colors = [image_array[x, y] for x, y in coordinates] - - # Calculate the average color - avg_color = np.mean(colors, axis=0) - - return tuple(avg_color.astype(int)) - - -def get_blocks_from_image_diff_pixels(image_path, html_text_color_tree, different_pixels): - image = cv2.imread(image_path) - x_w = image.shape[0] - y_w = image.shape[1] - - def hex_to_bgr(hex_color): - """ - Converts a hex color string to a BGR color tuple. - """ - hex_color = hex_color.lstrip('#') - rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) - return rgb[::-1] - - - def get_intersect(arr1, arr2): - # Reshape the arrays to 1D - arr1_reshaped = arr1.view([('', arr1.dtype)] * arr1.shape[1]) - arr2_reshaped = arr2.view([('', arr2.dtype)] * arr2.shape[1]) - - # Find the intersection - common_rows = np.intersect1d(arr1_reshaped, arr2_reshaped) - - # Reshape the result back to 2D, if needed - common_rows = common_rows.view(arr1.dtype).reshape(-1, arr1.shape[1]) - return common_rows - - - blocks = [] - for item in html_text_color_tree: - try: - color = np.array(hex_to_bgr(item[1]), dtype="uint8") - except: - continue - - lower = color - 4 - upper = color + 4 - - mask = cv2.inRange(image, lower, upper) - - coords = np.column_stack(np.where(mask > 0)) - - coords = get_intersect(coords, different_pixels) - - if coords.size == 0: - continue - - x_min, y_min = np.min(coords, axis=0) - x_max, y_max = np.max(coords, axis=0) - color = average_color(image_path.replace("_p.png", ".png"), coords) - - blocks.append({'text': item[0].lower(), 'bbox': (y_min / y_w, x_min / x_w, (y_max - y_min + 1) / y_w, (x_max - x_min + 1) / x_w), 'color': color}) - return blocks - - -def get_itermediate_names(name): - return name.replace(".png", ".html"), name.replace(".png", "_p.html"), name.replace(".png", "_p_1.html"), name.replace(".png", "_p.png"), name.replace(".png", "_p_1.png") - -def get_files_to_screenshot(image_path): - html, p_html, p_html_1, p_png, p_png_1 = get_itermediate_names(image_path) - process_html(html, p_html) - process_html(html, p_html_1, offset=50) - - return [p_html, p_html_1], [p_png, p_png_1] - -def get_blocks_ocr_free(image_path, page_load_time, pre_screenshoted = False): - html, p_html, p_html_1, p_png, p_png_1 = get_itermediate_names(image_path) - - if not pre_screenshoted: - process_html(html, p_html) - process_html(html, p_html_1, offset=50) - os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {p_html} --png {p_png} --page_load_time {page_load_time}") - os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {p_html_1} --png {p_png_1} --page_load_time {page_load_time}") - - different_pixels = find_different_pixels(p_png, p_png_1) - - if different_pixels is None: - print(f"[Warning] Unable to get pixels with different colors from {p_png}, {p_png_1}...") - os.system(f"rm {p_html} {p_png} {p_html_1} {p_png_1}") - return [] - - html_text_color_tree = flatten_tree(extract_text_with_color(p_html)) - try: - blocks = get_blocks_from_image_diff_pixels(p_png, html_text_color_tree, different_pixels) - except: - print(f"[Warning] Unable to get blocks from {p_png}...") - os.system(f"rm {p_html} {p_png} {p_html_1} {p_png_1}") - return [] - - os.system(f"rm {p_html} {p_png} {p_html_1} {p_png_1}") - return blocks \ No newline at end of file diff --git a/webgenie/rewards/visual_reward/metrics_v2/screenshot_multiple.py b/webgenie/rewards/visual_reward/metrics_v2/screenshot_multiple.py deleted file mode 100644 index be542a76..00000000 --- a/webgenie/rewards/visual_reward/metrics_v2/screenshot_multiple.py +++ /dev/null @@ -1,29 +0,0 @@ -import os -from playwright.sync_api import sync_playwright -from PIL import Image -from tqdm import tqdm - -def take_screenshots(html_files, output_files, page_load_time = 1000, do_it_again=False): - with sync_playwright() as p: - browser = p.chromium.launch() # You can also use 'firefox' or 'webkit' - for html_file, output_file in tqdm(zip(html_files, output_files), desc="Screenshoting HTML files"): - - if os.path.exists(html_file): - html_file = "file://" + os.path.abspath(html_file) - - if os.path.exists(output_file) and not do_it_again: - print(f"{output_file} exists!") - continue - - try: - page = browser.new_page() - page.goto(html_file, timeout=60000) - page.wait_for_timeout(page_load_time) - page.screenshot(path=output_file, full_page=True, animations="disabled", timeout=60000) - page.close() - except Exception as e: - print(f"Failed to take screenshot due to: {e}. Generating a blank image.") - # Generate a blank image - img = Image.new('RGB', (1280, 960), color = 'white') - img.save(output_file) - browser.close() diff --git a/webgenie/rewards/visual_reward/metrics_v2/screenshot_single.py b/webgenie/rewards/visual_reward/metrics_v2/screenshot_single.py deleted file mode 100644 index 8c4673b8..00000000 --- a/webgenie/rewards/visual_reward/metrics_v2/screenshot_single.py +++ /dev/null @@ -1,52 +0,0 @@ -import os -import argparse -from playwright.sync_api import sync_playwright -from PIL import Image - - -def take_screenshot(url, output_file="screenshot.png", page_load_time = 1000, do_it_again=False): - # Convert local path to file:// URL if it's a file - if os.path.exists(url): - url = "file://" + os.path.abspath(url) - - if os.path.exists(output_file) and not do_it_again: - print(f"{output_file} exists!") - return - - try: - with sync_playwright() as p: - # Choose a browser, e.g., Chromium, Firefox, or WebKit - browser = p.chromium.launch() - page = browser.new_page() - - # Navigate to the URL - page.goto(url, timeout=60000) - - # Wait for 10 seconds to ensure page is fully loaded - page.wait_for_timeout(page_load_time) - - # Take the screenshot - page.screenshot(path=output_file, full_page=True, animations="disabled", timeout=60000) - - browser.close() - except Exception as e: - print(f"Failed to take screenshot due to: {e}. Generating a blank image.") - # Generate a blank image - img = Image.new('RGB', (1280, 960), color = 'white') - img.save(output_file) - - -if __name__ == "__main__": - - # Initialize the parser - parser = argparse.ArgumentParser(description='Process two path strings.') - - # Define the arguments - parser.add_argument('--html', type=str) - parser.add_argument('--png', type=str) - parser.add_argument('--page_load_time', type=int, default=1000) - - # Parse the arguments - args = parser.parse_args() - - take_screenshot(args.html, args.png, args.page_load_time, do_it_again=True) diff --git a/webgenie/rewards/visual_reward/metrics_v2/visual_score.py b/webgenie/rewards/visual_reward/metrics_v2/visual_score.py deleted file mode 100644 index 0d46e02b..00000000 --- a/webgenie/rewards/visual_reward/metrics_v2/visual_score.py +++ /dev/null @@ -1,599 +0,0 @@ -import cv2 -import numpy as np - -import sys -import os -# Add the current file's directory to Python path -current_dir = os.path.dirname(os.path.abspath(__file__)) -sys.path.append(current_dir) - -# This is a patch for color map, which is not updated for newer version of numpy -def patch_asscalar(a): - return a.item() -setattr(np, "asscalar", patch_asscalar) - -import matplotlib.pyplot as plt -from scipy.optimize import linear_sum_assignment -import random -from sklearn.metrics.pairwise import cosine_similarity -from difflib import SequenceMatcher -from tqdm import tqdm -from pathlib import Path -from PIL import Image, ImageDraw -import torch -import clip -from copy import deepcopy -from collections import Counter -from bs4 import BeautifulSoup, NavigableString, Comment -import re -import math -from colormath.color_objects import sRGBColor, LabColor -from colormath.color_conversions import convert_color -from colormath.color_diff import delta_e_cie2000 - -from ocr_free_utils import get_blocks_ocr_free, get_files_to_screenshot -from dedup_post_gen import check_repetitive_content -from screenshot_multiple import take_screenshots -from webgenie.constants import ( - PYTHON_CMD, - GROUND_TRUTH_HTML_LOAD_TIME, - MINER_HTML_LOAD_TIME, -) - -device = "cuda" if torch.cuda.is_available() else "cpu" -model, preprocess = clip.load("ViT-B/32", device=device) - -def calculate_similarity(block1, block2, max_distance=1.42): - text_similarity = SequenceMatcher(None, block1['text'], block2['text']).ratio() - return text_similarity - - -def adjust_cost_for_context(cost_matrix, consecutive_bonus=1.0, window_size=20): - if window_size <= 0: - return cost_matrix - - n, m = cost_matrix.shape - adjusted_cost_matrix = np.copy(cost_matrix) - - for i in range(n): - for j in range(m): - bonus = 0 - if adjusted_cost_matrix[i][j] >= -0.5: - continue - nearby_matrix = cost_matrix[max(0, i - window_size):min(n, i + window_size + 1), max(0, j - window_size):min(m, j + window_size + 1)] - flattened_array = nearby_matrix.flatten() - sorted_array = np.sort(flattened_array)[::-1] - sorted_array = np.delete(sorted_array, np.where(sorted_array == cost_matrix[i, j])[0][0]) - top_k_elements = sorted_array[- window_size * 2:] - sum_top_k = np.sum(top_k_elements) - bonus = consecutive_bonus * sum_top_k - adjusted_cost_matrix[i][j] += bonus - return adjusted_cost_matrix - -def create_cost_matrix(A, B): - n = len(A) - m = len(B) - cost_matrix = np.zeros((n, m)) - for i in range(n): - for j in range(m): - cost_matrix[i, j] = -calculate_similarity(A[i], B[j]) - return cost_matrix - - -def draw_matched_bboxes(img1, img2, matched_bboxes): - # Create copies of images to draw on - img1_drawn = img1.copy() - img2_drawn = img2.copy() - - h1, w1, _ = img1.shape - h2, w2, _ = img2.shape - - - # Iterate over matched bounding boxes - for bbox_pair in matched_bboxes: - # Random color for each pair - color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) - - # Ensure that the bounding box coordinates are integers - bbox1 = [int(bbox_pair[0][0] * w1), int(bbox_pair[0][1] * h1), int(bbox_pair[0][2] * w1), int(bbox_pair[0][3] * h1)] - bbox2 = [int(bbox_pair[1][0] * w2), int(bbox_pair[1][1] * h2), int(bbox_pair[1][2] * w2), int(bbox_pair[1][3] * h2)] - - # Draw bbox on the first image - top_left_1 = (bbox1[0], bbox1[1]) - bottom_right_1 = (bbox1[0] + bbox1[2], bbox1[1] + bbox1[3]) - img1_drawn = cv2.rectangle(img1_drawn, top_left_1, bottom_right_1, color, 2) - - # Draw bbox on the second image - top_left_2 = (bbox2[0], bbox2[1]) - bottom_right_2 = (bbox2[0] + bbox2[2], bbox2[1] + bbox2[3]) - img2_drawn = cv2.rectangle(img2_drawn, top_left_2, bottom_right_2, color, 2) - - return img1_drawn, img2_drawn - - -def calculate_distance_max_1d(x1, y1, x2, y2): - distance = max(abs(x2 - x1), abs(y2 - y1)) - return distance - - -def calculate_ratio(h1, h2): - return max(h1, h2) / min(h1, h2) - - -def rgb_to_lab(rgb): - """ - Convert an RGB color to Lab color space. - RGB values should be in the range [0, 255]. - """ - # Create an sRGBColor object from RGB values - rgb_color = sRGBColor(rgb[0], rgb[1], rgb[2], is_upscaled=True) - - # Convert to Lab color space - lab_color = convert_color(rgb_color, LabColor) - - return lab_color - -def color_similarity_ciede2000(rgb1, rgb2): - """ - Calculate the color similarity between two RGB colors using the CIEDE2000 formula. - Returns a similarity score between 0 and 1, where 1 means identical. - """ - # Convert RGB colors to Lab - lab1 = rgb_to_lab(rgb1) - lab2 = rgb_to_lab(rgb2) - - # Calculate the Delta E (CIEDE2000) - delta_e = delta_e_cie2000(lab1, lab2) - - # Normalize the Delta E value to get a similarity score - # Note: The normalization method here is arbitrary and can be adjusted based on your needs. - # A delta_e of 0 means identical colors. Higher values indicate more difference. - # For visualization purposes, we consider a delta_e of 100 to be completely different. - similarity = max(0, 1 - (delta_e / 100)) - - return similarity - - -def calculate_current_cost(cost_matrix, row_ind, col_ind): - return cost_matrix[row_ind, col_ind].sum() - - -def merge_blocks_wo_check(block1, block2): - # Concatenate text - merged_text = block1['text'] + " " + block2['text'] - - # Calculate bounding box - x_min = min(block1['bbox'][0], block2['bbox'][0]) - y_min = min(block1['bbox'][1], block2['bbox'][1]) - x_max = max(block1['bbox'][0] + block1['bbox'][2], block2['bbox'][0] + block2['bbox'][2]) - y_max = max(block1['bbox'][1] + block1['bbox'][3], block2['bbox'][1] + block2['bbox'][3]) - merged_bbox = (x_min, y_min, x_max - x_min, y_max - y_min) - - # Average color - merged_color = tuple( - (color1 + color2) // 2 for color1, color2 in zip(block1['color'], block2['color']) - ) - - return {'text': merged_text, 'bbox': merged_bbox, 'color': merged_color} - - -def calculate_current_cost(cost_matrix, row_ind, col_ind): - return cost_matrix[row_ind, col_ind].tolist() - - -def find_maximum_matching(A, B, consecutive_bonus, window_size): - cost_matrix = create_cost_matrix(A, B) - cost_matrix = adjust_cost_for_context(cost_matrix, consecutive_bonus, window_size) - row_ind, col_ind = linear_sum_assignment(cost_matrix) - current_cost = calculate_current_cost(cost_matrix, row_ind, col_ind) - return list(zip(row_ind, col_ind)), current_cost, cost_matrix - - -def remove_indices(lst, indices): - for index in sorted(indices, reverse=True): - if index < len(lst): - lst.pop(index) - return lst - - -def merge_blocks_by_list(blocks, merge_list): - pop_list = [] - while True: - if len(merge_list) == 0: - remove_indices(blocks, pop_list) - return blocks - - i = merge_list[0][0] - j = merge_list[0][1] - - blocks[i] = merge_blocks_wo_check(blocks[i], blocks[j]) - pop_list.append(j) - - merge_list.pop(0) - if len(merge_list) > 0: - new_merge_list = [] - for k in range(len(merge_list)): - if merge_list[k][0] != i and merge_list[k][1] != i and merge_list[k][0] != j and merge_list[k][1] != j: - new_merge_list.append(merge_list[k]) - merge_list = new_merge_list - - -def print_matching(matching, blocks1, blocks2, cost_matrix): - for i, j in matching: - print(f"{blocks1[i]} matched with {blocks2[j]}, cost {cost_matrix[i][j]}") - - -def difference_of_means(list1, list2): - counter1 = Counter(list1) - counter2 = Counter(list2) - - for element in set(list1) & set(list2): - common_count = min(counter1[element], counter2[element]) - counter1[element] -= common_count - counter2[element] -= common_count - - unique_list1 = [item for item in counter1.elements()] - unique_list2 = [item for item in counter2.elements()] - - mean_list1 = sum(unique_list1) / len(unique_list1) if unique_list1 else 0 - mean_list2 = sum(unique_list2) / len(unique_list2) if unique_list2 else 0 - - if mean_list1 - mean_list2 > 0: - if min(unique_list1) > min(unique_list2): - return mean_list1 - mean_list2 - else: - return 0.0 - else: - return mean_list1 - mean_list2 - - -def find_possible_merge(A, B, consecutive_bonus, window_size, debug=False): - merge_bonus = 0.0 - merge_windows = 1 - - def sortFn(value): - return value[2] - - while True: - A_changed = False - B_changed = False - - matching, current_cost, cost_matrix = find_maximum_matching(A, B, merge_bonus, merge_windows) - if debug: - print("Current cost of the solution:", current_cost) - print_matching(matching, A, B, cost_matrix) - - if len(A) >= 2: - merge_list = [] - for i in range(len(A) - 1): - new_A = deepcopy(A) - new_A[i] = merge_blocks_wo_check(new_A[i], new_A[i + 1]) - new_A.pop(i + 1) - - updated_matching, updated_cost, cost_matrix = find_maximum_matching(new_A, B, merge_bonus, merge_windows) - diff = difference_of_means(current_cost, updated_cost) - if diff > 0.05: - merge_list.append([i, i + 1, diff]) - if debug: - print(new_A[i]['text'], diff) - - merge_list.sort(key=sortFn, reverse=True) - if len(merge_list) > 0: - A_changed = True - A = merge_blocks_by_list(A, merge_list) - matching, current_cost, cost_matrix = find_maximum_matching(A, B, merge_bonus, merge_windows) - if debug: - print("Cost after optimization A:", current_cost) - - if len(B) >= 2: - merge_list = [] - for i in range(len(B) - 1): - new_B = deepcopy(B) - new_B[i] = merge_blocks_wo_check(new_B[i], new_B[i + 1]) - new_B.pop(i + 1) - - updated_matching, updated_cost, cost_matrix = find_maximum_matching(A, new_B, merge_bonus, merge_windows) - diff = difference_of_means(current_cost, updated_cost) - if diff > 0.05: - merge_list.append([i, i + 1, diff]) - if debug: - print(new_B[i]['text'], diff) - - merge_list.sort(key=sortFn, reverse=True) - if len(merge_list) > 0: - B_changed = True - B = merge_blocks_by_list(B, merge_list) - matching, current_cost, cost_matrix = find_maximum_matching(A, B, merge_bonus, merge_windows) - if debug: - print("Cost after optimization B:", current_cost) - - if not A_changed and not B_changed: - break - matching, _, _ = find_maximum_matching(A, B, consecutive_bonus, window_size) - return A, B, matching - - -def merge_blocks_by_bbox(blocks): - merged_blocks = {} - - # Traverse and merge blocks - for block in blocks: - bbox = tuple(block['bbox']) # Convert bbox to tuple for hashability - if bbox in merged_blocks: - # Merge with existing block - existing_block = merged_blocks[bbox] - existing_block['text'] += ' ' + block['text'] - existing_block['color'] = [(ec + c) / 2 for ec, c in zip(existing_block['color'], block['color'])] - else: - # Add new block - merged_blocks[bbox] = block - - return list(merged_blocks.values()) - - -def mask_bounding_boxes_with_inpainting(image, bounding_boxes): - # Convert PIL image to OpenCV format - image_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) - - # Create a black mask - mask = np.zeros(image_cv.shape[:2], dtype=np.uint8) - - height, width = image_cv.shape[:2] - - # Draw white rectangles on the mask - for bbox in bounding_boxes: - x_ratio, y_ratio, w_ratio, h_ratio = bbox - x = int(x_ratio * width) - y = int(y_ratio * height) - w = int(w_ratio * width) - h = int(h_ratio * height) - mask[y:y+h, x:x+w] = 255 - - # Use inpainting - inpainted_image = cv2.inpaint(image_cv, mask, 3, cv2.INPAINT_TELEA) - - # Convert back to PIL format - inpainted_image_pil = Image.fromarray(cv2.cvtColor(inpainted_image, cv2.COLOR_BGR2RGB)) - - return inpainted_image_pil - - -def rescale_and_mask(image_path, blocks): - # Load the image - with Image.open(image_path) as img: - if len(blocks) > 0: - # use inpainting instead of simple mask - img = mask_bounding_boxes_with_inpainting(img, blocks) - - width, height = img.size - - # Determine which side is shorter - if width < height: - # Width is shorter, scale height to match the width - new_size = (width, width) - else: - # Height is shorter, scale width to match the height - new_size = (height, height) - - # Resize the image while maintaining aspect ratio - img_resized = img.resize(new_size, Image.LANCZOS) - - return img_resized - - -def calculate_clip_similarity_with_blocks(image_path1, image_path2, blocks1, blocks2): - # Load and preprocess images - image1 = preprocess(rescale_and_mask(image_path1, [block['bbox'] for block in blocks1])).unsqueeze(0).to(device) - image2 = preprocess(rescale_and_mask(image_path2, [block['bbox'] for block in blocks2])).unsqueeze(0).to(device) - - # Calculate features - with torch.no_grad(): - image_features1 = model.encode_image(image1) - image_features2 = model.encode_image(image2) - - # Normalize features - image_features1 /= image_features1.norm(dim=-1, keepdim=True) - image_features2 /= image_features2.norm(dim=-1, keepdim=True) - - # Calculate cosine similarity - similarity = (image_features1 @ image_features2.T).item() - - return similarity - - -def truncate_repeated_html_elements(soup, max_count=50): - content_counts = {} - - for element in soup.find_all(True): - if isinstance(element, (NavigableString, Comment)): - continue - - try: - element_html = str(element) - except: - element.decompose() - continue - content_counts[element_html] = content_counts.get(element_html, 0) + 1 - - if content_counts[element_html] > max_count: - element.decompose() - - return str(soup) - - -def make_html(filename): - with open(filename, 'r') as file: - content = file.read() - - if not re.search(r']*>', content, re.IGNORECASE): - new_content = f'

{content}

' - with open(filename, 'w') as file: - file.write(new_content) - - -def pre_process(html_file): - #check_repetitive_content(html_file) - make_html(html_file) - with open(html_file, 'r') as file: - soup = BeautifulSoup(file, 'html.parser') - soup_str = truncate_repeated_html_elements(soup) - with open(html_file, 'w') as file: - file.write(soup_str) - - -def visual_eval_v3_multi(input_list, debug=False): - predict_html_list, original_html = input_list[0], input_list[1] - predict_img_list = [html.replace(".html", ".png") for html in predict_html_list] - - predict_blocks_list = [] - - to_screenshot_html_list, to_screenshot_img_list = [], [] - for predict_html in tqdm(predict_html_list, desc="Pre-screenshot HTML files"): - predict_img = predict_html.replace(".html", ".png") - # This will help fix some html syntax error - pre_process(predict_html) - to_screenshot_html_list.append(predict_html) - to_screenshot_img_list.append(predict_img) - - pre_screenshoted_html_list, pre_screenshoted_img_list = get_files_to_screenshot(predict_img) - to_screenshot_html_list.extend(pre_screenshoted_html_list) - to_screenshot_img_list.extend(pre_screenshoted_img_list) - - take_screenshots(to_screenshot_html_list, to_screenshot_img_list) - - for predict_html in tqdm(predict_html_list, desc="Screenshot HTML files and get OCR blocks"): - predict_img = predict_html.replace(".html", ".png") - # This will help fix some html syntax error - predict_blocks = get_blocks_ocr_free(predict_img, page_load_time=MINER_HTML_LOAD_TIME, pre_screenshoted=True) - predict_blocks_list.append(predict_blocks) - - original_img = original_html.replace(".html", ".png") - os.system(f"{PYTHON_CMD} {Path(__file__).parent}/screenshot_single.py --html {original_html} --png {original_img} --page_load_time {GROUND_TRUTH_HTML_LOAD_TIME}") - original_blocks = get_blocks_ocr_free(original_img, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME, pre_screenshoted=False) - original_blocks = merge_blocks_by_bbox(original_blocks) - - # Consider context similarity for block matching - consecutive_bonus, window_size = 0.1, 1 - - return_score_list = [] - - for k, predict_blocks in tqdm(enumerate(predict_blocks_list), desc="Processing HTML files"): - if len(predict_blocks) == 0: - print("[Warning] No detected blocks in: ", predict_img_list[k]) - final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) - return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) - continue - elif len(original_blocks) == 0: - print("[Warning] No detected blocks in: ", original_img) - final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) - return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) - continue - - if debug: - print(predict_blocks) - print(original_blocks) - - predict_blocks = merge_blocks_by_bbox(predict_blocks) - predict_blocks_m, original_blocks_m, matching = find_possible_merge(predict_blocks, deepcopy(original_blocks), consecutive_bonus, window_size, debug=debug) - - filtered_matching = [] - for i, j in matching: - text_similarity = SequenceMatcher(None, predict_blocks_m[i]['text'], original_blocks_m[j]['text']).ratio() - # Filter out matching with low similarity - if text_similarity < 0.5: - continue - filtered_matching.append([i, j, text_similarity]) - matching = filtered_matching - - indices1 = [item[0] for item in matching] - indices2 = [item[1] for item in matching] - - matched_list = [] - sum_areas = [] - matched_areas = [] - matched_text_scores = [] - position_scores = [] - text_color_scores = [] - - unmatched_area_1 = 0.0 - for i in range(len(predict_blocks_m)): - if i not in indices1: - unmatched_area_1 += predict_blocks_m[i]['bbox'][2] * predict_blocks_m[i]['bbox'][3] - unmatched_area_2 = 0.0 - for j in range(len(original_blocks_m)): - if j not in indices2: - unmatched_area_2 += original_blocks_m[j]['bbox'][2] * original_blocks_m[j]['bbox'][3] - sum_areas.append(unmatched_area_1 + unmatched_area_2) - - for i, j, text_similarity in matching: - sum_block_area = predict_blocks_m[i]['bbox'][2] * predict_blocks_m[i]['bbox'][3] + original_blocks_m[j]['bbox'][2] * original_blocks_m[j]['bbox'][3] - - # Consider the max postion shift, either horizontally or vertically - position_similarity = 1 - calculate_distance_max_1d(predict_blocks_m[i]['bbox'][0] + predict_blocks_m[i]['bbox'][2] / 2, \ - predict_blocks_m[i]['bbox'][1] + predict_blocks_m[i]['bbox'][3] / 2, \ - original_blocks_m[j]['bbox'][0] + original_blocks_m[j]['bbox'][2] / 2, \ - original_blocks_m[j]['bbox'][1] + original_blocks_m[j]['bbox'][3] / 2) - # Normalized ciede2000 formula - text_color_similarity = color_similarity_ciede2000(predict_blocks_m[i]['color'], original_blocks_m[j]['color']) - matched_list.append([predict_blocks_m[i]['bbox'], original_blocks_m[j]['bbox']]) - - # validation check - if min(predict_blocks_m[i]['bbox'][2], original_blocks_m[j]['bbox'][2], predict_blocks_m[i]['bbox'][3], original_blocks_m[j]['bbox'][3]) == 0: - print(f"{predict_blocks_m[i]} matched with {original_blocks_m[j]}") - assert calculate_ratio(predict_blocks_m[i]['bbox'][2], original_blocks_m[j]['bbox'][2]) > 0 and calculate_ratio(predict_blocks_m[i]['bbox'][3], original_blocks_m[j]['bbox'][3]) > 0, f"{predict_blocks_m[i]} matched with {original_blocks_m[j]}" - - sum_areas.append(sum_block_area) - matched_areas.append(sum_block_area) - matched_text_scores.append(text_similarity) - position_scores.append(position_similarity) - text_color_scores.append(text_color_similarity) - - if debug: - print(f"{predict_blocks_m[i]} matched with {original_blocks_m[j]}") - print(SequenceMatcher(None, predict_blocks_m[i]['text'], original_blocks_m[j]['text']).ratio()) - print("text similarity score", text_similarity) - print("position score", position_similarity) - print("color score", text_color_similarity) - print("----------------------------------") - pass - """ - if debug: - img1 = cv2.imread(predict_img_list[k]) - img2 = cv2.imread(original_img) - img1_with_boxes, img2_with_boxes = draw_matched_bboxes(img1, img2, matched_list) - - plt.figure(figsize=(20, 10)) - plt.subplot(1, 2, 1) - plt.imshow(cv2.cvtColor(img1_with_boxes, cv2.COLOR_BGR2RGB)) - plt.axis('off') - plt.subplot(1, 2, 2) - plt.imshow(cv2.cvtColor(img2_with_boxes, cv2.COLOR_BGR2RGB)) - plt.axis('off') - plt.show() - # """ - - if len(matched_areas) > 0: - sum_sum_areas = np.sum(sum_areas) - - final_size_score = np.sum(matched_areas) / np.sum(sum_areas) - final_matched_text_score = np.mean(matched_text_scores) - final_position_score = np.mean(position_scores) - final_text_color_score = np.mean(text_color_scores) - final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) - final_score = 0.2 * (final_size_score + final_matched_text_score + final_position_score + final_text_color_score + final_clip_score) - return_score_list.append([sum_sum_areas, final_score, (final_size_score, final_matched_text_score, final_position_score, final_text_color_score, final_clip_score)]) - else: - print("[Warning] No matched blocks in: ", predict_img_list[k]) - final_clip_score = calculate_clip_similarity_with_blocks(predict_img_list[k], original_img, predict_blocks, original_blocks) - return_score_list.append([0.0, 0.2 * final_clip_score, (0.0, 0.0, 0.0, 0.0, final_clip_score)]) - return return_score_list - - # except: - # print("[Warning] Error not handled in: ", input_list) - # return [[0.0, 0.0, (0.0, 0.0, 0.0, 0.0, 0.0)] for _ in range(len(predict_html_list))] - - -if __name__ == "__main__": - print(visual_eval_v3_multi([ - ["work/miner1.html", "work/miner2.html", "work/miner3.html"], - "work/original.html"], debug=True)) diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index ce2461b6..70d5c84e 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -8,7 +8,9 @@ from webgenie.constants import WORK_DIR from webgenie.rewards.reward import Reward -from webgenie.rewards.visual_reward.metrics.visual_score import visual_eval_v3_multi +from webgenie.rewards.visual_reward.common.browser import start_browser, stop_browser +from webgenie.rewards.visual_reward.high_level_matching_score import high_level_matching_score +from webgenie.rewards.visual_reward.low_level_matching_score import low_level_matching_score from webgenie.tasks import Task, ImageTask, Solution @@ -19,7 +21,7 @@ def __init__(self): async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: if not isinstance(task, ImageTask): raise ValueError(f"Task is not a ImageTask: {type(task)}") - + await start_browser() bt.logging.info(f"Rewarding image task in visual reward") original_html_path = f"{WORK_DIR}/original_{uuid.uuid4()}.html" @@ -33,7 +35,12 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: f.write(solution.html) miner_html_paths.append(path) - visual_scores = visual_eval_v3_multi([miner_html_paths, original_html_path]) - bt.logging.debug(f"Visual scores: {visual_scores}") + high_level_scores = await high_level_matching_score(miner_html_paths, original_html_path) + low_level_scores = await low_level_matching_score(miner_html_paths, original_html_path) - return np.array([score[1] for score in visual_scores]) + bt.logging.debug(f"Visual scores: {high_level_scores}") + bt.logging.debug(f"Visual scores: {low_level_scores}") + + scores = high_level_scores * 0.3 + low_level_scores * 0.7 + await stop_browser() + return scores From d77ee846be69bbe0eebe3f462d3c09a5ca1dbe3a Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 14 Jan 2025 22:44:24 -0600 Subject: [PATCH 193/554] feat: test --- .gitignore | 1 + tests/test_reward.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 130ff60f..7819271b 100644 --- a/.gitignore +++ b/.gitignore @@ -180,6 +180,7 @@ wandb/ # work dir work/ work_save/ +tests/work/ *.png # scripts diff --git a/tests/test_reward.py b/tests/test_reward.py index d27d7276..5fcb0258 100644 --- a/tests/test_reward.py +++ b/tests/test_reward.py @@ -43,7 +43,7 @@ async def calculate_scores(task: Task, solutions: List[Solution]) -> dict[str, n async def main(): - ground_truth_html_path = "work/original_f7e98ea8-cc04-44ea-8f40-432914b0f0ea.html" + ground_truth_html_path = "tests/work/original.html" with open(ground_truth_html_path, "r") as f: ground_truth_html = f.read() From 8fb635e8a283a965cac7ad5c0b547493b8886217 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 15 Jan 2025 06:20:34 -0600 Subject: [PATCH 194/554] chore: refactor extract html elements --- .../common/extract_html_elements.py | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index a2323abd..485e1c88 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -34,7 +34,6 @@ def parse_rgb_string(rgb_str: str) -> tuple[int, int, int]: async def extract_html_elements(file_path, load_time = DEFAULT_LOAD_TIME): if os.path.exists(file_path): url = f"file:///{os.path.abspath(file_path)}" - print(url) text_elements = [] button_elements = [] input_elements = [] @@ -50,8 +49,8 @@ async def extract_html_elements(file_path, load_time = DEFAULT_LOAD_TIME): with open(screenshot_path, "rb") as f: screenshot = Image.open(f) W, H = screenshot.size - - async def traverse(node): + + async def add_element(node, has_children): text = await node.inner_text() bounding_box = await node.bounding_box() rendered_style = await node.evaluate( @@ -62,6 +61,11 @@ async def traverse(node): ) tag_name = await node.evaluate("(node) => node.tagName.toLowerCase()") + if bounding_box is None: + return + if bounding_box["width"] == 0 or bounding_box["height"] == 0: + return + scaled_bounding_box = { "x": bounding_box["x"] / W, "y": bounding_box["y"] / H, @@ -100,12 +104,7 @@ async def traverse(node): ) ) - has_children = await node.evaluate("(node) => node.children.length > 0") - if has_children: - children = await node.query_selector_all(':scope > *') - for child in children: - await traverse(child) - else: + if not has_children: text_elements.append( HTMLElement( text=text, @@ -115,11 +114,20 @@ async def traverse(node): rendered_style=rendered_style, ) ) + + async def traverse(node): + children = await node.query_selector_all(':scope > *') + has_children = False + for child in children: + await traverse(child) + has_children = True + + await add_element(node, has_children) + await traverse(await page.query_selector('body')) await page.close() except Exception as e: print(e) - preprocess_html_elements(file_path, button_elements) preprocess_html_elements(file_path, input_elements) preprocess_html_elements(file_path, anchor_elements) From 5ebcadd2b577f99194de99f9c23958114be8c26d Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 15 Jan 2025 07:27:12 -0600 Subject: [PATCH 195/554] chore: remove save randome image --- .../competitions/image_task_competition.py | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/webgenie/competitions/image_task_competition.py b/webgenie/competitions/image_task_competition.py index bc89bcd7..df835675 100644 --- a/webgenie/competitions/image_task_competition.py +++ b/webgenie/competitions/image_task_competition.py @@ -61,25 +61,7 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: if is_empty_html(ground_truth_html): raise ValueError("Empty ground truth html") - base64_image = await html_to_screenshot(ground_truth_html, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) - - # Save base64_image for debugging purposes - import os - import base64 - from datetime import datetime - - debug_dir = "debug_images" - os.makedirs(debug_dir, exist_ok=True) - - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - filename = os.path.join(debug_dir, f"image_{timestamp}.png") - - try: - image = base64_to_image(base64_image) - image.save(filename) - except Exception as e: - bt.logging.error(f"Failed to save debug image: {e}") - + base64_image = await html_to_screenshot(ground_truth_html, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) return ( ImageTask( base64_image=base64_image, From bf9eca3c0f84f02f88f66a21500dec99feb9b83e Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 15 Jan 2025 07:27:32 -0600 Subject: [PATCH 196/554] chore: add task id --- webgenie/tasks/task.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webgenie/tasks/task.py b/webgenie/tasks/task.py index bcbe8958..992c71a6 100644 --- a/webgenie/tasks/task.py +++ b/webgenie/tasks/task.py @@ -1,8 +1,10 @@ +import uuid from typing import Any from pydantic import BaseModel, Field class Task(BaseModel): + task_id: str = Field(default_factory=lambda: str(uuid.uuid4())) timeout: float = Field(default=50) competition: Any = Field(default=None) From dd1e453f3eb537d7e09b330d2b036276c7633082 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 15 Jan 2025 07:28:03 -0600 Subject: [PATCH 197/554] chore: make work dir for every task --- tests/test_reward.py | 2 +- webgenie/rewards/visual_reward/visual_reward.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_reward.py b/tests/test_reward.py index 5fcb0258..5f30248e 100644 --- a/tests/test_reward.py +++ b/tests/test_reward.py @@ -68,7 +68,7 @@ async def main(): print(synapse.html) execution_time = time.time() - start_time print(f"Execution time: {execution_time:.2f} seconds") - solutions = [Solution(html=synapse.html) for _ in range(1)] + solutions = [Solution(html=synapse.html) for _ in range(60)] print("Calculate scores") start_time = time.time() diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index 70d5c84e..47fd3516 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -2,6 +2,7 @@ # (https://arxiv.org/pdf/2403.03163) is our inspiration for this reward. import bittensor as bt +import os import numpy as np from typing import List import uuid @@ -21,16 +22,20 @@ def __init__(self): async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: if not isinstance(task, ImageTask): raise ValueError(f"Task is not a ImageTask: {type(task)}") + + current_work_dir = f"{WORK_DIR}/{task.task_id}" + os.makedirs(current_work_dir, exist_ok=True) + await start_browser() bt.logging.info(f"Rewarding image task in visual reward") - original_html_path = f"{WORK_DIR}/original_{uuid.uuid4()}.html" + original_html_path = f"{current_work_dir}/original_{uuid.uuid4()}.html" with open(original_html_path, "w") as f: f.write(task.ground_truth_html) miner_html_paths = [] for solution in solutions: - path = f"{WORK_DIR}/miner{solution.miner_uid}_{uuid.uuid4()}.html" + path = f"{current_work_dir}/miner{solution.miner_uid}_{uuid.uuid4()}.html" with open(path, "w") as f: f.write(solution.html) miner_html_paths.append(path) From 909c75a38b48299c7d6a6740971b086e2d5c7654 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 15 Jan 2025 08:47:06 -0600 Subject: [PATCH 198/554] feat: implement multiprocessing for scoring --- webgenie/constants.py | 2 +- .../lighthouse_reward/get_lighthouse_score.py | 5 +-- .../lighthouse_reward/lighthouse_reward.py | 36 ++++++++++++++-- .../rewards/visual_reward/common/browser.py | 3 ++ .../visual_reward/common/color_diff.py | 2 +- .../common/extract_html_elements.py | 4 +- .../visual_reward/common/inpaint_image.py | 1 + webgenie/rewards/visual_reward/common/sift.py | 2 + .../visual_reward/common/similarity.py | 4 ++ .../visual_reward/common/take_screenshot.py | 1 + .../clip_matching_score.py | 15 ++++--- .../high_level_matching_score/histogram.py | 2 + .../low_level_matching_score.py | 1 + .../rewards/visual_reward/visual_reward.py | 43 +++++++++++++++---- 14 files changed, 96 insertions(+), 25 deletions(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index ebc51515..671fc60e 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -11,7 +11,7 @@ TEXT_TASK_TIMEOUT = 100 # lighthouse server port -LIGHTHOUSE_SERVER_PORT = 8001 +LIGHTHOUSE_SERVER_PORT = 5000 # max competition history size MAX_COMPETETION_HISTORY_SIZE = 30 diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index f795f0bb..89762983 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -12,7 +12,7 @@ httpd = None -def get_lighthouse_score(htmls: List[str]) -> List[Dict[str, float]]: +def get_lighthouse_score(htmls: List[str], port: int = LIGHTHOUSE_SERVER_PORT) -> List[Dict[str, float]]: class CustomHandler(SimpleHTTPRequestHandler): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -63,8 +63,7 @@ def run_server(port=8000): httpd = HTTPServer(server_address, CustomHandler) bt.logging.info(f"Starting server on port {port}...") httpd.serve_forever() - - port = LIGHTHOUSE_SERVER_PORT + server_thread = threading.Thread(target=run_server, args=(port,), daemon=True) server_thread.start() diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py index 2f781ea4..76640267 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py @@ -2,21 +2,25 @@ # (https://arxiv.org/pdf/2402.08699#page=11&zoom=100,384,458) is our inspiration for this reward. import bittensor as bt +import os +import multiprocessing import numpy as np from typing import List +from webgenie.constants import LIGHTHOUSE_SERVER_PORT from webgenie.rewards.reward import Reward from webgenie.tasks import Task, Solution + from .get_lighthouse_score import get_lighthouse_score class LighthouseReward(Reward): + def __init__(self): + pass - async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: - bt.logging.info(f"Rewarding lighthouse task") - htmls = [solution.html for solution in solutions] - scores_dict = get_lighthouse_score(htmls) + def sync_reward_worker(self, htmls: List[str], port: int = LIGHTHOUSE_SERVER_PORT) -> List[float]: + scores_dict = get_lighthouse_score(htmls, port) scores = [] weights = [0, 0.25, 0.25, 0.5] for score_dict in scores_dict: @@ -27,4 +31,28 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: score_dict['seo'] * weights[3] ) scores.append(score) + return scores + + async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: + bt.logging.info(f"Rewarding lighthouse task") + htmls = [solution.html for solution in solutions] + + # Use ProcessPoolExecutor for parallel processing + with multiprocessing.Pool(processes=os.cpu_count()) as pool: + # Convert solutions into chunks for parallel processing + chunk_size = max(1, len(htmls) // os.cpu_count()) + html_chunks = [htmls[i:i + chunk_size] for i in range(0, len(htmls), chunk_size)] + + # Create partial tasks for each chunk + futures = [] + port = LIGHTHOUSE_SERVER_PORT + for chunk in html_chunks: + future = pool.apply_async(self.sync_reward_worker, args=(chunk, port)) + futures.append(future) + port += 1 + + # Gather all results + scores = [] + for future in futures: + scores.extend(future.get()) return np.array(scores) diff --git a/webgenie/rewards/visual_reward/common/browser.py b/webgenie/rewards/visual_reward/common/browser.py index f9e66bc4..ad40bbba 100644 --- a/webgenie/rewards/visual_reward/common/browser.py +++ b/webgenie/rewards/visual_reward/common/browser.py @@ -1,10 +1,12 @@ from playwright.async_api import async_playwright + web_player = { "web_driver": None, "browser": None, } + async def start_browser(): global web_player web_driver = await async_playwright().start() @@ -12,6 +14,7 @@ async def start_browser(): web_player["web_driver"] = web_driver web_player["browser"] = browser + async def stop_browser(): global web_player await web_player["browser"].close() diff --git a/webgenie/rewards/visual_reward/common/color_diff.py b/webgenie/rewards/visual_reward/common/color_diff.py index e381bced..477b1974 100644 --- a/webgenie/rewards/visual_reward/common/color_diff.py +++ b/webgenie/rewards/visual_reward/common/color_diff.py @@ -3,6 +3,7 @@ from colormath.color_conversions import convert_color import numpy as np + def delta_e_cie2000(lab1, lab2): # Extract components of Lab1 and Lab2 L1, a1, b1 = lab1.lab_l, lab1.lab_a, lab1.lab_b @@ -62,7 +63,6 @@ def rgb_to_lab(rgb): return lab_color - def color_similarity_ciede2000(rgb1, rgb2): """ Calculate the color similarity between two RGB colors using the CIEDE2000 formula. diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index 485e1c88..4b616566 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -24,6 +24,7 @@ class HTMLElement(BaseModel): descriptors: Any = Field(default=None) avg_color: tuple[int, int, int] = Field(default=(0, 0, 0)) + def parse_rgb_string(rgb_str: str) -> tuple[int, int, int]: """Convert RGB color string like 'rgb(23, 34, 45)' to (23, 34, 45) tuple.""" # Extract numbers from rgb(r,g,b) format using string manipulation @@ -63,7 +64,7 @@ async def add_element(node, has_children): if bounding_box is None: return - if bounding_box["width"] == 0 or bounding_box["height"] == 0: + if bounding_box["width"] <= 0 or bounding_box["height"] <= 0: return scaled_bounding_box = { @@ -133,6 +134,7 @@ async def traverse(node): preprocess_html_elements(file_path, anchor_elements) return text_elements, button_elements, input_elements, anchor_elements + def preprocess_html_elements(html_path, html_elements): image_path = html_path.replace(".html", ".png") color_image = io.imread(image_path) diff --git a/webgenie/rewards/visual_reward/common/inpaint_image.py b/webgenie/rewards/visual_reward/common/inpaint_image.py index 4e587cf4..51334973 100644 --- a/webgenie/rewards/visual_reward/common/inpaint_image.py +++ b/webgenie/rewards/visual_reward/common/inpaint_image.py @@ -29,6 +29,7 @@ def update_style(element, property_name, value): with open(output_file_path, 'w') as file: file.write(str(soup)) + async def inpaint_image(url, output_file_path, load_time = DEFAULT_LOAD_TIME): erased_html_path = f'{url.replace(HTML_EXTENSION, "_erased.html")}' erase_texts(url, erased_html_path) diff --git a/webgenie/rewards/visual_reward/common/sift.py b/webgenie/rewards/visual_reward/common/sift.py index 5aa443a5..35db6aaa 100644 --- a/webgenie/rewards/visual_reward/common/sift.py +++ b/webgenie/rewards/visual_reward/common/sift.py @@ -4,6 +4,7 @@ from scipy.spatial.distance import cdist from scipy.optimize import linear_sum_assignment + def extract_sift_from_roi(gray_image, roi): # ROI: (x, y, w, h) x, y, w, h = roi @@ -20,6 +21,7 @@ def extract_sift_from_roi(gray_image, roi): return keypoints, descriptors + def match_sift_features(kp1, desc1, kp2, desc2, distance_metric="euclidean", threshold=0.75): if (desc1 is None or len(desc1) == 0) and (desc2 is None or len(desc2) == 0): return 1 diff --git a/webgenie/rewards/visual_reward/common/similarity.py b/webgenie/rewards/visual_reward/common/similarity.py index 60b793d1..56d410aa 100644 --- a/webgenie/rewards/visual_reward/common/similarity.py +++ b/webgenie/rewards/visual_reward/common/similarity.py @@ -11,12 +11,14 @@ from webgenie.rewards.visual_reward.common.color_diff import color_similarity_ciede2000 # similarity is 1 if they are the same, 0 if they are completely different + def calculate_color_similarity( original_element: HTMLElement, predicted_element: HTMLElement, ): return color_similarity_ciede2000(original_element.color, predicted_element.color) + def calculate_text_similarity( original_element: HTMLElement, predicted_element: HTMLElement, @@ -29,6 +31,7 @@ def calculate_text_similarity( return SequenceMatcher(None, original_element.text, predicted_element.text).ratio() + def calculate_block_similarity( original_element: HTMLElement, predicted_element: HTMLElement, @@ -43,6 +46,7 @@ def calculate_block_similarity( return 1 - (x_shift + y_shift + xx_shift + yy_shift) / 4 + def calculate_visual_similarity( predicted_element: HTMLElement, original_element: HTMLElement, diff --git a/webgenie/rewards/visual_reward/common/take_screenshot.py b/webgenie/rewards/visual_reward/common/take_screenshot.py index 574a2ad2..e199ca11 100644 --- a/webgenie/rewards/visual_reward/common/take_screenshot.py +++ b/webgenie/rewards/visual_reward/common/take_screenshot.py @@ -4,6 +4,7 @@ from webgenie.constants import DEFAULT_LOAD_TIME from webgenie.rewards.visual_reward.common.browser import web_player + async def take_screenshot(url, output_file_path, load_time = DEFAULT_LOAD_TIME, overwrite = False): if os.path.exists(url): url = f"file:///{os.path.abspath(url)}" diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py b/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py index b5c96a24..47064647 100644 --- a/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py +++ b/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py @@ -5,9 +5,6 @@ from webgenie.constants import HTML_EXTENSION, IMAGE_EXTENSION from webgenie.rewards.visual_reward.common.inpaint_image import inpaint_image -device = "cuda" if torch.cuda.is_available() else "cpu" -model, preprocess = clip.load("ViT-B/32", device=device) - def rescale(image_path): # Load the image @@ -28,7 +25,7 @@ def rescale(image_path): return img_resized -def calculate_clip_similarity(image_path1, image_path2): +def calculate_clip_similarity(image_path1, image_path2, model, preprocess, device): # Load and preprocess images image1 = preprocess(rescale(image_path1)).unsqueeze(0).to(device) image2 = preprocess(rescale(image_path2)).unsqueeze(0).to(device) @@ -47,23 +44,27 @@ def calculate_clip_similarity(image_path1, image_path2): return similarity -def calculate_embedding_vector(image_path): + +def calculate_embedding_vector(image_path, model, preprocess, device): image = preprocess(rescale(image_path)).unsqueeze(0).to(device) with torch.no_grad(): image_features = model.encode_image(image) image_features /= image_features.norm(dim=-1, keepdim=True) return image_features + async def calculate_clip_score(predict_html_path_list, original_html_path): + device = "cuda" if torch.cuda.is_available() else "cpu" + model, preprocess = clip.load("ViT-B/32", device=device) original_img_path = original_html_path.replace(HTML_EXTENSION, f"_inpainted{IMAGE_EXTENSION}") await inpaint_image(original_html_path, original_img_path) - original_embedding_vector = calculate_embedding_vector(original_img_path) + original_embedding_vector = calculate_embedding_vector(original_img_path, model, preprocess, device) results = [] for predict_html_path in predict_html_path_list: predict_img_path = predict_html_path.replace(HTML_EXTENSION, f"_inpainted{IMAGE_EXTENSION}") await inpaint_image(predict_html_path, predict_img_path) - predict_embedding_vector = calculate_embedding_vector(predict_img_path) + predict_embedding_vector = calculate_embedding_vector(predict_img_path, model, preprocess, device) score = (original_embedding_vector @ predict_embedding_vector.T).item() results.append(score) diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py b/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py index 19e96d89..f2a96a28 100644 --- a/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py +++ b/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py @@ -4,6 +4,7 @@ from webgenie.rewards.visual_reward.common.take_screenshot import take_screenshot from webgenie.constants import HTML_EXTENSION, IMAGE_EXTENSION + def compute_grayscale_histogram(image_path, bins=256): """ Load the image, convert to grayscale, compute histogram. @@ -21,6 +22,7 @@ def compute_grayscale_histogram(image_path, bins=256): return hist + def compare_histograms(hist1, hist2): """ Compare two 1D histograms (same bin count) using correlation coefficient. diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py index 4318135b..11743cd9 100644 --- a/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py +++ b/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py @@ -6,6 +6,7 @@ from webgenie.rewards.visual_reward.common.extract_html_elements import extract_html_elements + async def low_level_matching_score(predict_html_path_list, original_html_path): ( diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index 47fd3516..06f5bbaf 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -3,9 +3,12 @@ import bittensor as bt import os +import asyncio +import multiprocessing import numpy as np -from typing import List import uuid +from datetime import datetime +from typing import List from webgenie.constants import WORK_DIR from webgenie.rewards.reward import Reward @@ -19,13 +22,7 @@ class VisualReward(Reward): def __init__(self): pass - async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: - if not isinstance(task, ImageTask): - raise ValueError(f"Task is not a ImageTask: {type(task)}") - - current_work_dir = f"{WORK_DIR}/{task.task_id}" - os.makedirs(current_work_dir, exist_ok=True) - + async def reward_worker(self, task: Task, solutions: List[Solution], current_work_dir: str) -> np.ndarray: await start_browser() bt.logging.info(f"Rewarding image task in visual reward") @@ -49,3 +46,33 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: scores = high_level_scores * 0.3 + low_level_scores * 0.7 await stop_browser() return scores + def sync_reward_worker(self, task: Task, solutions: List[Solution], current_work_dir: str) -> np.ndarray: + return asyncio.run(self.reward_worker(task, solutions, current_work_dir)) + + async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: + if not isinstance(task, ImageTask): + raise ValueError(f"Task is not a ImageTask: {type(task)}") + + timestamp = datetime.now().strftime("%Y_%m_%d_%H_%M") + current_work_dir = f"{WORK_DIR}/task_{timestamp}_{task.task_id}" + os.makedirs(current_work_dir, exist_ok=True) + + # Use ProcessPoolExecutor for parallel processing + with multiprocessing.Pool(processes=os.cpu_count()) as pool: + # Convert solutions into chunks for parallel processing + chunk_size = max(1, len(solutions) // os.cpu_count()) + solution_chunks = [solutions[i:i + chunk_size] for i in range(0, len(solutions), chunk_size)] + + # Create partial tasks for each chunk + futures = [] + for chunk in solution_chunks: + future = pool.apply_async(self.sync_reward_worker, args=(task, chunk, current_work_dir)) + futures.append(future) + + # Gather all results + chunk_scores = [] + for future in futures: + chunk_scores.extend(future.get()) + + scores = np.array(chunk_scores) + return scores From d308fcb13d124aea77e1443c4f0fee47a29d039f Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 15 Jan 2025 08:56:30 -0600 Subject: [PATCH 199/554] chore: make code quality llm deterministic --- tests/test_reward.py | 4 ++-- webgenie/helpers/llms.py | 21 +++++++++++++++------ webgenie/rewards/quality_reward.py | 1 + 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/tests/test_reward.py b/tests/test_reward.py index 5f30248e..045a8916 100644 --- a/tests/test_reward.py +++ b/tests/test_reward.py @@ -23,8 +23,8 @@ from neurons.miners.openai_miner import OpenaiMiner metrics = { - ACCURACY_METRIC_NAME: VisualReward(), - SEO_METRIC_NAME: LighthouseReward(), + # ACCURACY_METRIC_NAME: VisualReward(), +# SEO_METRIC_NAME: LighthouseReward(), QUALITY_METRIC_NAME: QualityReward(), } diff --git a/webgenie/helpers/llms.py b/webgenie/helpers/llms.py index 7b815878..bf8d755b 100644 --- a/webgenie/helpers/llms.py +++ b/webgenie/helpers/llms.py @@ -15,14 +15,23 @@ base_url=base_url, ) -async def openai_call(messages, response_format, retries=3): +async def openai_call(messages, response_format, deterministic=False, retries=3): for _ in range(retries): try: - completion = await client.beta.chat.completions.parse( - model=model, - messages= messages, - response_format=response_format, - ) + if deterministic: + completion = await client.beta.chat.completions.parse( + model=model, + messages= messages, + response_format=response_format, + temperature=0, + ) + else: + completion = await client.beta.chat.completions.parse( + model=model, + messages= messages, + response_format=response_format, + temperature=0.7, + ) return completion.choices[0].message.parsed except Exception as e: bt.logging.error(f"Error calling OpenAI: {e}") diff --git a/webgenie/rewards/quality_reward.py b/webgenie/rewards/quality_reward.py index 8becdd0b..e0cf1c17 100644 --- a/webgenie/rewards/quality_reward.py +++ b/webgenie/rewards/quality_reward.py @@ -25,6 +25,7 @@ async def _get_score(self, solution: Solution) -> float: {"role": "system", "content": PROMPT_QUALITY.format(html=solution.html)}, ], response_format = ScoreResponse, + deterministic=True, ) return response.score / 100 From f7c70131faa3cc135c0834f8c42526bc0e59e774 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 15 Jan 2025 09:17:29 -0600 Subject: [PATCH 200/554] style: add empty line --- webgenie/helpers/images.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webgenie/helpers/images.py b/webgenie/helpers/images.py index 45801378..26cfe883 100644 --- a/webgenie/helpers/images.py +++ b/webgenie/helpers/images.py @@ -4,6 +4,7 @@ from webgenie.constants import MAX_DEBUG_IMAGE_STRING_LENGTH + def pil_image_to_base64(img: Image.Image) -> str: buffered = io.BytesIO() img.save(buffered, format="jpeg") From f8a2609666b0dd00792911fc39ed61c5c9faa4ac Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 15 Jan 2025 09:29:37 -0600 Subject: [PATCH 201/554] chore: add func to get validator index --- webgenie/utils/uids.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/webgenie/utils/uids.py b/webgenie/utils/uids.py index 8e07094f..b7478ab8 100644 --- a/webgenie/utils/uids.py +++ b/webgenie/utils/uids.py @@ -4,6 +4,21 @@ from typing import List +def is_validator(metagraph: "bt.metagraph.Metagraph", uid: int, vpermit_tao_limit: int) -> bool: + return metagraph.S[uid] >= vpermit_tao_limit + + +def get_validator_index(self, uid: int) -> int: + validator_uids = [] + for uid in range(self.metagraph.n.item()): + if is_validator(self.metagraph, uid, self.config.neuron.vpermit_tao_limit): + validator_uids.append(uid) + try: + return validator_uids.index(uid) + except ValueError: + return -1 + + def check_uid_availability( metagraph: "bt.metagraph.Metagraph", uid: int, vpermit_tao_limit: int ) -> bool: From bdb12733d000cba8e58b99513fc058cc2939314b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 16 Jan 2025 06:11:39 -0600 Subject: [PATCH 202/554] feat: implement thread-based-loop --- neurons/validators/validator.py | 117 ++++++++++++++++++++------------ webgenie/base/validator.py | 9 --- 2 files changed, 74 insertions(+), 52 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 8fbca4fa..7bda2e4a 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -4,11 +4,13 @@ import bittensor as bt import asyncio +import threading +import time from dotenv import load_dotenv, find_dotenv load_dotenv(find_dotenv(filename=".env.validator")) -from typing import Tuple +from typing import Tuple, Union from webgenie.base.validator import BaseValidatorNeuron from webgenie.constants import API_HOTKEY @@ -30,9 +32,26 @@ def __init__(self, config=None): super(Validator, self).__init__(config=config) if not self.config.axon_off: self.serve_axon() + + + # Create asyncio event loop to manage async tasks. + self.synthensize_task_event_loop = asyncio.new_event_loop() + self.query_miners_event_loop = asyncio.new_event_loop() + self.score_event_loop = asyncio.new_event_loop() + self.set_weights_event_loop = asyncio.new_event_loop() + + # Instantiate runners + self.should_exit: bool = False + self.is_running: bool = False + self.synthensize_task_thread: Union[threading.Thread, None] = None + self.query_miners_thread: Union[threading.Thread, None] = None + self.score_thread: Union[threading.Thread, None] = None + self.set_weights_thread: Union[threading.Thread, None] = None + self.lock = asyncio.Lock() self.genie_validator = GenieValidator(neuron=self) + async def blacklist_text(self, synapse: WebgenieTextSynapse) -> Tuple[bool, str]: """ Only allow the backend owner to send synapse to the validator. @@ -77,72 +96,84 @@ def serve_axon(self): bt.logging.info(f"Validator running in organic mode on port {self.config.neuron.axon_port}") except Exception as e: bt.logging.error(f"Failed to serve Axon with exception: {e}") - pass - - async def query_miners(self): - return await self.genie_validator.query_miners() - - async def concurrent_query(self): - coroutines = [ - self.query_miners() - for _ in range(self.config.neuron.num_concurrent_forwards) - ] - await asyncio.gather(*coroutines) - async def query_miners_loop(self): + def query_miners_loop(self): bt.logging.info(f"Validator starting at block: {self.block}") self.sync() while True: try: - self.loop.run_until_complete(self.concurrent_query()) + self.query_miners_event_loop.run_until_complete(self.genie_validator.query_miners()) self.sync() except Exception as e: bt.logging.error(f"Error during forward loop: {str(e)}") - await asyncio.sleep(1) + if self.should_exit: + break + time.sleep(1) - async def score_loop(self): + def score_loop(self): bt.logging.info(f"Scoring loop starting") + self.sync() while True: try: - await self.genie_validator.score() + self.score_event_loop.run_until_complete(self.genie_validator.score()) self.sync() + except KeyboardInterrupt: + bt.logging.info("Keyboard interrupt detected, stopping scoring loop") + break except Exception as e: bt.logging.error(f"Error during scoring: {str(e)}") - await asyncio.sleep(1) + if self.should_exit: + break + time.sleep(1) - async def synthensize_task_loop(self): + def synthensize_task_loop(self): bt.logging.info(f"Synthensize task loop starting") + self.sync() while True: try: - await self.genie_validator.synthensize_task() + self.synthensize_task_event_loop.run_until_complete(self.genie_validator.synthensize_task()) + self.sync() + except KeyboardInterrupt: + bt.logging.info("Keyboard interrupt detected, stopping synthensize task loop") + break except Exception as e: bt.logging.error(f"Error during synthensize task: {str(e)}") - await asyncio.sleep(1) - - async def __aenter__(self): - self.loop.create_task(self.synthensize_task_loop()) - self.loop.create_task(self.query_miners_loop()) - self.loop.create_task(self.score_loop()) - self.is_running = True + if self.should_exit: + break - bt.logging.debug("Starting validator in background thread") - return self - - async def __aexit__(self, exc_type, exc_value, traceback): + def run_background_threads(self): if not self.is_running: - return + bt.logging.info("Starting validator in background thread") + self.is_running = True + self.should_exit = False + self.synthensize_task_thread = threading.Thread(target=self.synthensize_task_loop) + self.query_miners_thread = threading.Thread(target=self.query_miners_loop) + self.score_thread = threading.Thread(target=self.score_loop) + self.set_weights_thread = threading.Thread(target=self.set_weights_loop) + bt.logging.info("Started background threads") + + def stop_background_threads(self): + if self.is_running: + bt.logging.info("Stopping background threads") + self.should_exit = True + self.is_running = False + self.synthensize_task_thread.join(5) + self.query_miners_thread.join(5) + self.score_thread.join(5) + self.set_weights_thread.join(5) + bt.logging.info("Stopped background threads") + + def __enter__(self): + self.run_background_threads() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.stop_background_threads() - self.should_exit = True - self.is_running = False - bt.logging.debug("Stopping validator in background thread") - -async def main(): - async with Validator() as validator: - while validator.is_running and not validator.should_exit: - await asyncio.sleep(15) - - # The main function parses the configuration and runs the validator. if __name__ == "__main__": - asyncio.run(main()) + with Validator() as validator: + while True: + bt.logging.info("Validator is running ... {time.time()}") + time.sleep(5) diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index 2b838d31..091959ea 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -65,15 +65,6 @@ def __init__(self, config=None): # Init sync with the network. Updates the metagraph. self.sync() - # Create asyncio event loop to manage async tasks. - self.loop = asyncio.get_event_loop() - - # Instantiate runners - self.should_exit: bool = False - self.is_running: bool = False - self.thread: Union[threading.Thread, None] = None - self.lock = asyncio.Lock() - def set_weights(self): """ Sets the validator weights to the metagraph hotkeys based on the scores it has received from the miners. The weights determine the trust and incentive level the validator assigns to miner nodes on the network. From b8f1a8f728966d3131b6c520c5016336f90cfe64 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 16 Jan 2025 06:18:30 -0600 Subject: [PATCH 203/554] feat: customize set_weights logic --- neurons/validators/genie_validator.py | 5 ----- neurons/validators/validator.py | 19 ++++++++++++++++++- webgenie/base/neuron.py | 3 --- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 07faf7fc..d0a0f37f 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -92,11 +92,6 @@ async def query_miners(self): bt.logging.error(f"Error in query_miners: {e}") raise e - async def foward(self): - await self.synthensize_task() - await self.query_miners() - await self.score() - async def score(self): if not self.competitions: return diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 7bda2e4a..46e7aeca 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -38,7 +38,6 @@ def __init__(self, config=None): self.synthensize_task_event_loop = asyncio.new_event_loop() self.query_miners_event_loop = asyncio.new_event_loop() self.score_event_loop = asyncio.new_event_loop() - self.set_weights_event_loop = asyncio.new_event_loop() # Instantiate runners self.should_exit: bool = False @@ -104,6 +103,9 @@ def query_miners_loop(self): try: self.query_miners_event_loop.run_until_complete(self.genie_validator.query_miners()) self.sync() + except KeyboardInterrupt: + bt.logging.info("Keyboard interrupt detected, stopping query miners loop") + break except Exception as e: bt.logging.error(f"Error during forward loop: {str(e)}") if self.should_exit: @@ -140,6 +142,21 @@ def synthensize_task_loop(self): bt.logging.error(f"Error during synthensize task: {str(e)}") if self.should_exit: break + + def set_weights_loop(self): + bt.logging.info(f"Set weights loop starting") + self.sync() + while True: + try: + self.set_weights() + self.sync() + except KeyboardInterrupt: + bt.logging.info("Keyboard interrupt detected, stopping set weights loop") + break + except Exception as e: + bt.logging.error(f"Error during set weights: {str(e)}") + if self.should_exit: + break def run_background_threads(self): if not self.is_running: diff --git a/webgenie/base/neuron.py b/webgenie/base/neuron.py index 3fffe10b..9b9cdf6e 100644 --- a/webgenie/base/neuron.py +++ b/webgenie/base/neuron.py @@ -118,9 +118,6 @@ def sync(self): if self.should_sync_metagraph(): self.resync_metagraph() - if self.should_set_weights(): - self.set_weights() - # Always save state. self.save_state() From e95ff5da2c6e24c66b14a0e34c0328dbed744741 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 16 Jan 2025 06:35:53 -0600 Subject: [PATCH 204/554] feat: customize set_weights logic --- neurons/validators/validator.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 46e7aeca..331cc686 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -144,11 +144,26 @@ def synthensize_task_loop(self): break def set_weights_loop(self): + """ + Every three tempos, set the weights. + """ bt.logging.info(f"Set weights loop starting") + + BLOCK_IN_SECONDS = 12 + TEMPO_BLOCK_NUMBER = 60 + THREE_TEMPO_BLOCK_NUMBER = TEMPO_BLOCK_NUMBER * 3 + self.sync() while True: try: - self.set_weights() + current_block = self.block + set_weights_end_block = (current_block + THREE_TEMPO_BLOCK_NUMBER - 1) // THREE_TEMPO_BLOCK_NUMBER * THREE_TEMPO_BLOCK_NUMBER + set_weights_start_block = set_weights_end_block - 5 + if current_block >= set_weights_start_block and current_block < set_weights_end_block: + bt.logging.info(f"Setting weights at block {current_block}") + self.set_weights() + else: + time.sleep((set_weights_start_block - current_block) * BLOCK_IN_SECONDS) self.sync() except KeyboardInterrupt: bt.logging.info("Keyboard interrupt detected, stopping set weights loop") From 09bc3dbaf8709772ef3f3ae113f9bc661d2c449e Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 16 Jan 2025 06:36:47 -0600 Subject: [PATCH 205/554] chore: implement mutex for multiple threads --- neurons/validators/genie_validator.py | 42 ++++++++++++++++----------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index d0a0f37f..ff898179 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -2,6 +2,7 @@ import bittensor as bt import numpy as np import random +import threading from typing import Union from webgenie.base.neuron import BaseNeuron @@ -42,7 +43,8 @@ def __init__(self, neuron: BaseNeuron): (ImageTaskSeoCompetition(), 0.3), (ImageTaskQualityCompetition(), 0.3), ] - + + self.lock = threading.Lock() self.make_work_dir() def make_work_dir(self): @@ -52,15 +54,16 @@ def make_work_dir(self): async def query_miners(self): try: - if len(self.competitions) > MAX_COMPETETION_HISTORY_SIZE: - return - - if not self.synthetic_tasks: - return + with self.lock: + if len(self.competitions) > MAX_COMPETETION_HISTORY_SIZE: + return - bt.logging.info("querying miners") + if not self.synthetic_tasks: + return - task, synapse = self.synthetic_tasks.pop(0) + task, synapse = self.synthetic_tasks.pop(0) + + bt.logging.info("querying miners") miner_uids = get_all_available_uids(self.neuron) if len(miner_uids) == 0: bt.logging.warning("No miners available") @@ -87,18 +90,20 @@ async def query_miners(self): ) ) bt.logging.info(f"Received {len(solutions)} solutions") - self.competitions.append((task, solutions)) + with self.lock: + self.competitions.append((task, solutions)) except Exception as e: bt.logging.error(f"Error in query_miners: {e}") raise e async def score(self): - if not self.competitions: - return + with self.lock: + if not self.competitions: + return - task, solutions = self.competitions.pop(0) - if not solutions: - return + task, solutions = self.competitions.pop(0) + if not solutions: + return best_miner = -1 best_final_score = 0.0 @@ -137,8 +142,9 @@ async def score(self): async def synthensize_task(self): try: - if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: - return + with self.lock: + if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: + return bt.logging.info(f"Synthensize task") @@ -148,7 +154,9 @@ async def synthensize_task(self): )[0] task, synapse = await competition.generate_task() - self.synthetic_tasks.append((task, synapse)) + with self.lock: + self.synthetic_tasks.append((task, synapse)) + except Exception as e: bt.logging.error(f"Error in synthensize_task: {e}") From 497352201afaf7baebe1432d82eb22b406498150 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 16 Jan 2025 06:40:41 -0600 Subject: [PATCH 206/554] chore: get validator index by stake --- webgenie/utils/uids.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webgenie/utils/uids.py b/webgenie/utils/uids.py index b7478ab8..d15ede9c 100644 --- a/webgenie/utils/uids.py +++ b/webgenie/utils/uids.py @@ -13,6 +13,7 @@ def get_validator_index(self, uid: int) -> int: for uid in range(self.metagraph.n.item()): if is_validator(self.metagraph, uid, self.config.neuron.vpermit_tao_limit): validator_uids.append(uid) + validator_uids.sort(key=lambda uid: self.metagraph.S[uid], reverse=True) try: return validator_uids.index(uid) except ValueError: From d45ce796e16ba97bed039fc99d01c0d37421126b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 16 Jan 2025 06:47:47 -0600 Subject: [PATCH 207/554] style: style the code --- neurons/validators/validator.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 331cc686..23f07a59 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -156,14 +156,30 @@ def set_weights_loop(self): self.sync() while True: try: + # Get current block number current_block = self.block - set_weights_end_block = (current_block + THREE_TEMPO_BLOCK_NUMBER - 1) // THREE_TEMPO_BLOCK_NUMBER * THREE_TEMPO_BLOCK_NUMBER + + # Calculate the end block number for the next weight setting period + # This aligns with 3 tempo boundaries + set_weights_end_block = ( + (current_block + THREE_TEMPO_BLOCK_NUMBER - 1) + // THREE_TEMPO_BLOCK_NUMBER + * THREE_TEMPO_BLOCK_NUMBER + ) + + # Start setting weights 5 blocks before the end set_weights_start_block = set_weights_end_block - 5 - if current_block >= set_weights_start_block and current_block < set_weights_end_block: + + # Check if we're in the weight setting window + if (current_block >= set_weights_start_block and + current_block < set_weights_end_block): bt.logging.info(f"Setting weights at block {current_block}") self.set_weights() else: - time.sleep((set_weights_start_block - current_block) * BLOCK_IN_SECONDS) + # Sleep until next weight setting window + sleep_blocks = set_weights_start_block - current_block + time.sleep(sleep_blocks * BLOCK_IN_SECONDS) + self.sync() except KeyboardInterrupt: bt.logging.info("Keyboard interrupt detected, stopping set weights loop") From 76b79c92dd5ee75b8c31cf3f181cb6e74dc12bf1 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 16 Jan 2025 07:11:24 -0600 Subject: [PATCH 208/554] feat: implement query time frame --- neurons/validators/genie_validator.py | 7 ++- neurons/validators/validator.py | 62 +++++++++++++++++++-------- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index ff898179..3114c834 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -28,7 +28,7 @@ from webgenie.helpers.images import image_debug_str from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.tasks import Solution -from webgenie.utils.uids import get_all_available_uids, get_most_available_uid +from webgenie.utils.uids import get_all_available_uids class GenieValidator: @@ -62,7 +62,7 @@ async def query_miners(self): return task, synapse = self.synthetic_tasks.pop(0) - + bt.logging.info("querying miners") miner_uids = get_all_available_uids(self.neuron) if len(miner_uids) == 0: @@ -159,6 +159,9 @@ async def synthensize_task(self): except Exception as e: bt.logging.error(f"Error in synthensize_task: {e}") + + async def set_weights(self): + self.neuron.set_weights() async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImageSynapse]): if isinstance(synapse, WebgenieTextSynapse): diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 23f07a59..0a3780ed 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -15,10 +15,21 @@ from webgenie.base.validator import BaseValidatorNeuron from webgenie.constants import API_HOTKEY from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse +from webgenie.utils.uids import get_validator_index from neurons.validators.genie_validator import GenieValidator +# Constants for block timing +BLOCK_IN_SECONDS = 12 +TEMPO_BLOCKS = 60 +MAX_VALIDATORS = 12 +VALIDATOR_QUERY_PERIOD_BLOCKS = 10 +ALL_VALIDATOR_QUERY_PERIOD_BLOCKS = MAX_VALIDATORS * VALIDATOR_QUERY_PERIOD_BLOCKS +COMPETITION_PERIOD_BLOCKS = TEMPO_BLOCKS * 3 +SET_WEIGHTS_PERIOD_BLOCKS = 5 + + class Validator(BaseValidatorNeuron): """ Your validator neuron class. You should use this class to define your validator's behavior. In particular, you should replace the forward function with your own logic. @@ -38,6 +49,7 @@ def __init__(self, config=None): self.synthensize_task_event_loop = asyncio.new_event_loop() self.query_miners_event_loop = asyncio.new_event_loop() self.score_event_loop = asyncio.new_event_loop() + self.set_weights_event_loop = asyncio.new_event_loop() # Instantiate runners self.should_exit: bool = False @@ -96,13 +108,36 @@ def serve_axon(self): except Exception as e: bt.logging.error(f"Failed to serve Axon with exception: {e}") - def query_miners_loop(self): + def query_miners_loop(self): bt.logging.info(f"Validator starting at block: {self.block}") - self.sync() while True: try: - self.query_miners_event_loop.run_until_complete(self.genie_validator.query_miners()) self.sync() + validator_index = get_validator_index(self.neuron.metagraph, self.neuron.uid) + if validator_index == -1: + continue + + # Only allow first N validators to query miners + if validator_index > MAX_VALIDATORS: + continue + + # Calculate query period blocks + current_block = self.neuron.block + start_period_block = ( + (current_block // ALL_VALIDATOR_QUERY_PERIOD_BLOCKS) * ALL_VALIDATOR_QUERY_PERIOD_BLOCKS + + validator_index * VALIDATOR_QUERY_PERIOD_BLOCKS + ) + end_period_block = start_period_block + ALL_VALIDATOR_QUERY_PERIOD_BLOCKS / 2 + + # Sleep if outside query window + if current_block < start_period_block: + time.sleep((start_period_block - current_block) * BLOCK_IN_SECONDS) + elif current_block >= end_period_block: + sleep_time = (start_period_block - current_block + ALL_VALIDATOR_QUERY_PERIOD_BLOCKS) * BLOCK_IN_SECONDS + time.sleep(sleep_time) + continue + + self.query_miners_event_loop.run_until_complete(self.genie_validator.query_miners()) except KeyboardInterrupt: bt.logging.info("Keyboard interrupt detected, stopping query miners loop") break @@ -114,11 +149,10 @@ def query_miners_loop(self): def score_loop(self): bt.logging.info(f"Scoring loop starting") - self.sync() while True: try: - self.score_event_loop.run_until_complete(self.genie_validator.score()) self.sync() + self.score_event_loop.run_until_complete(self.genie_validator.score()) except KeyboardInterrupt: bt.logging.info("Keyboard interrupt detected, stopping scoring loop") break @@ -130,11 +164,10 @@ def score_loop(self): def synthensize_task_loop(self): bt.logging.info(f"Synthensize task loop starting") - self.sync() while True: try: - self.synthensize_task_event_loop.run_until_complete(self.genie_validator.synthensize_task()) self.sync() + self.synthensize_task_event_loop.run_until_complete(self.genie_validator.synthensize_task()) except KeyboardInterrupt: bt.logging.info("Keyboard interrupt detected, stopping synthensize task loop") break @@ -149,22 +182,18 @@ def set_weights_loop(self): """ bt.logging.info(f"Set weights loop starting") - BLOCK_IN_SECONDS = 12 - TEMPO_BLOCK_NUMBER = 60 - THREE_TEMPO_BLOCK_NUMBER = TEMPO_BLOCK_NUMBER * 3 - - self.sync() while True: try: + self.sync() # Get current block number current_block = self.block # Calculate the end block number for the next weight setting period # This aligns with 3 tempo boundaries set_weights_end_block = ( - (current_block + THREE_TEMPO_BLOCK_NUMBER - 1) - // THREE_TEMPO_BLOCK_NUMBER - * THREE_TEMPO_BLOCK_NUMBER + (current_block + COMPETITION_PERIOD_BLOCKS - 1) + // COMPETITION_PERIOD_BLOCKS + * COMPETITION_PERIOD_BLOCKS ) # Start setting weights 5 blocks before the end @@ -174,13 +203,12 @@ def set_weights_loop(self): if (current_block >= set_weights_start_block and current_block < set_weights_end_block): bt.logging.info(f"Setting weights at block {current_block}") - self.set_weights() + self.set_weights_event_loop.run_until_complete(self.genie_validator.set_weights()) else: # Sleep until next weight setting window sleep_blocks = set_weights_start_block - current_block time.sleep(sleep_blocks * BLOCK_IN_SECONDS) - self.sync() except KeyboardInterrupt: bt.logging.info("Keyboard interrupt detected, stopping set weights loop") break From 2456b6635d087e6eca435756ec1baac0d7815f84 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 16 Jan 2025 08:06:21 -0600 Subject: [PATCH 209/554] chore: add competition type to protocol --- webgenie/protocol.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/webgenie/protocol.py b/webgenie/protocol.py index 83b080f5..06e37abb 100644 --- a/webgenie/protocol.py +++ b/webgenie/protocol.py @@ -15,6 +15,12 @@ class WebgenieTextSynapse(bt.Synapse): description="The prompt to be sent to miners.", ) + competition_type: str = pydantic.Field( + "", + title="Competition Type", + description="The competition type.", + ) + html: str = pydantic.Field( "", title="HTML", @@ -31,6 +37,12 @@ class WebgenieImageSynapse(bt.Synapse): title="Base64 Image", description="The base64 image to be sent to miners.", ) + + competition_type: str = pydantic.Field( + "", + title="Competition Type", + description="The competition type.", + ) html: str = pydantic.Field( "", From 3b0bf1eea995e1ea9a9ef7b432e4b4d6ef01bfdd Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Thu, 16 Jan 2025 09:16:51 -0600 Subject: [PATCH 210/554] feat: database design --- storage/database.py | 13 ++++++++ storage/models.py | 78 +++++++++++++++++++++++++++++++++++++++++++ webgenie/constants.py | 2 ++ 3 files changed, 93 insertions(+) create mode 100644 storage/database.py create mode 100644 storage/models.py diff --git a/storage/database.py b/storage/database.py new file mode 100644 index 00000000..69e60429 --- /dev/null +++ b/storage/database.py @@ -0,0 +1,13 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import DeclarativeBase +# Create the database engine +engine = create_engine('sqlite:///webgenie-validator.db', echo=True) + +# Create the session maker +Session = sessionmaker(engine) + +# Create the base class for SQLAlchemy models +class Base(DeclarativeBase): + pass + diff --git a/storage/models.py b/storage/models.py new file mode 100644 index 00000000..e01fd13f --- /dev/null +++ b/storage/models.py @@ -0,0 +1,78 @@ +from sqlalchemy import Column, DateTime, ForeignKey, JSON +from sqlalchemy.orm import relationship, Mapped, mapped_column +from datetime import datetime +from database import Base, engine + +class Neuron(Base): + __tablename__ = "neurons" + id: Mapped[int] = mapped_column(primary_key=True) + coldkey: Mapped[str] + hotkey: Mapped[str] = mapped_column(index=True) + +class Competition(Base): + __tablename__ = "competitions" + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(index=True) + + # Relationships + sessions: Mapped[list["LeaderboardSession"]] = relationship(back_populates="competition") + +class LeaderboardSession(Base): + __tablename__ = "leaderboard_sessions" + id: Mapped[int] = mapped_column(primary_key=True) + created_at = Column(DateTime, default=datetime.utcnow, index=True) + competition_id: Mapped[int] = mapped_column(ForeignKey("competitions.id"), index=True) + + # Relationships + competition: Mapped["Competition"] = relationship(back_populates="sessions") + challenges: Mapped[list["Challenge"]] = relationship(back_populates="session") + +class Challenge(Base): + __tablename__ = "challenges" + id: Mapped[int] = mapped_column(primary_key=True) + session_id: Mapped[int] = mapped_column(ForeignKey("leaderboard_sessions.id"), index=True) + ground_truth_html: Mapped[str] + + # Relationships + session: Mapped["LeaderboardSession"] = relationship(back_populates="challenges") + solutions: Mapped[list["TaskSolution"]] = relationship(back_populates="challenge") + + +class Judgement(Base): + __tablename__ = "judgements" + id: Mapped[int] = mapped_column(primary_key=True) + validator_id: Mapped[int] = mapped_column(ForeignKey("neurons.id"), index=True) + miner_id: Mapped[int] = mapped_column(ForeignKey("neurons.id"), index=True) + +class EvaluationType(Base): + __tablename__ = "evaluation_types" + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(index=True) + + # Relationship + solution_scores: Mapped[list["SolutionEvaluation"]] = relationship(back_populates="score_type") + +class TaskSolution(Base): + __tablename__ = "task_solutions" + id: Mapped[int] = mapped_column(primary_key=True) + created_at = Column(DateTime, default=datetime.utcnow) + challenge_id: Mapped[int] = mapped_column(ForeignKey("challenges.id"), index=True) + miner_answer: Mapped[dict] = mapped_column(JSON) + + # Relationship + challenge: Mapped["Challenge"] = relationship(back_populates="solutions") + solution_scores: Mapped[list["SolutionEvaluation"]] = relationship(back_populates="solution") + +class SolutionEvaluation(Base): + __tablename__ = "solution_evaluations" + id: Mapped[int] = mapped_column(primary_key=True) + solution_id: Mapped[int] = mapped_column(ForeignKey("task_solutions.id"), index=True) + score_type_id: Mapped[int] = mapped_column(ForeignKey("evaluation_types.id"), index=True) + judgement_id: Mapped[int] = mapped_column(ForeignKey("judgements.id"), index=True) + value: Mapped[float] + + # Relationships + score_type: Mapped["EvaluationType"] = relationship(back_populates="solution_scores") + solution: Mapped["TaskSolution"] = relationship(back_populates="solution_scores") + +Base.metadata.create_all(engine) diff --git a/webgenie/constants.py b/webgenie/constants.py index 1c68ae3a..a1cb6f51 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -28,3 +28,5 @@ # work dir WORK_DIR = "work" + + From c97b1c8bcc4798a81c112e23e1f2db08d9810309 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Thu, 16 Jan 2025 09:22:15 -0600 Subject: [PATCH 211/554] feat: utility interfaces for the storage access --- storage/utils.py | 101 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 storage/utils.py diff --git a/storage/utils.py b/storage/utils.py new file mode 100644 index 00000000..cdefdf9c --- /dev/null +++ b/storage/utils.py @@ -0,0 +1,101 @@ +from database import Session as DBSession +from models import Neuron, LeaderboardSession, Competition, Challenge, Judgement, EvaluationType, TaskSolution, SolutionEvaluation +from datetime import datetime, timedelta +import logging +from sqlalchemy import and_ +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm import Session + +# Setup basic configuration for logging +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" +) +# session period tempos +SESSION_PERIOD = 2 + +# Create a new session +session = DBSession() + +def create_record(session: Session, model_class, **kwargs): + try: + new_record = model_class(**kwargs) # Create an instance of the model + session.add(new_record) # Add it to the session + session.commit() # Commit the session + return new_record.id # Return the new record's ID + except Exception as e: + session.rollback() # Rollback in case of error + logging.error(f"An error occurred: {e}") + return None # Return None to indicate failure + finally: + session.close() # Close the session + +def add_neuron(coldkey: str, hotkey: str): + return create_record(session, Neuron, coldkey=coldkey, hotkey=hotkey) + +def get_neuron_id(hotkey: str): + try: + neuron = session.query(Neuron).filter_by(hotkey=hotkey).first() + if neuron: + return neuron.id + else: + return None # Return None if no matching neuron is found + except SQLAlchemyError as e: + logging.error(f"An error occurred while fetching neuron: {e}") + return None + finally: + session.close() # Ensure the session is closed + +def create_leaderboard_session(created_at: datetime, competition_id: int): + return create_record(session, LeaderboardSession, created_at=created_at, competition_id=competition_id) + +def query_leaderboard_session(timestamp: datetime): + # Calculate the time range + interval = SESSION_PERIOD * 72 * 60 + try: + # Query the LeaderboardSession where created_at is within the specified range + leaderboard_session = session.query(LeaderboardSession).filter( + and_( + LeaderboardSession.created_at <= timestamp, + LeaderboardSession.created_at + timedelta(seconds=interval) > timestamp + ) + ).first() # Get the first matching session + + # Return the session_id if found, otherwise None + return leaderboard_session.id if leaderboard_session else None + + except Exception as e: + logging.error(f"An error occurred while querying leaderboard session: {e}") + return None # Return None in case of error + finally: + session.close() # Close the session + +def create_competition(name: str): + return create_record(session, Competition, name=name) + +def create_challenge(session_id: int, ground_truth_html: str): + return create_record(session, Challenge, session_id=session_id, ground_truth_html=ground_truth_html) + +def create_judgement(validator_id: int, miner_id: int): + return create_record(session, Judgement, validator_id=validator_id, miner_id=miner_id) + +def create_evaluation_type(name: str): + return create_record(session, EvaluationType, name=name) + +def create_task_solution(miner_answer: str, challenge_id: int, created_at: datetime): + return create_record(session, TaskSolution, miner_answer=miner_answer, challenge_id=challenge_id, created_at=created_at) + +def create_solution_evaluation(solution_id: int, score_type_id: int, judgement_id: int, value: float): + return create_record(session, SolutionEvaluation, solution_id=solution_id, score_type_id=score_type_id, judgement_id=judgement_id, value=value) + +if __name__ == "__main__": + neuron_id = add_neuron("5GKH9FPPnWSUoeeTJp19wVtd84XqFW4pyK2ijV2GsFbhTrP1", "5F4tQyWrhfGVcNhoqeiNsR6KjD4wMZ2kfhLj4oHYuyHbZAc3") + logging.info(f"neuron_id: {neuron_id}") + + html = "
test
" + + create_competition("Accuracy") + create_competition("SEO") + create_competition("CODE_QUALITY") + create_competition("WEIGHTED_SCORE") + session_id = create_leaderboard_session(datetime.now(), 1) + challenge = create_challenge(session_id, html) From ae55467138528c2ecbf0badd9af77d9b35ac2959 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 16 Jan 2025 12:14:17 -0600 Subject: [PATCH 212/554] feat: update scoring system --- neurons/validators/genie_validator.py | 91 ++++----- neurons/validators/score_manager.py | 177 ++++++++++++++++++ neurons/validators/validator.py | 56 +++++- tests/test_reward.py | 2 +- webgenie/base/validator.py | 173 +---------------- webgenie/challenges/__init__.py | 12 ++ webgenie/challenges/challenge.py | 57 ++++++ webgenie/challenges/challenge_types.py | 3 + webgenie/competitions/__init__.py | 25 --- webgenie/tasks/__init__.py | 5 +- .../image_task_generator.py} | 42 +---- webgenie/tasks/metric_types.py | 3 + webgenie/tasks/task.py | 2 +- .../task_generator.py} | 16 +- .../text_task_generator.py} | 32 +--- webgenie/utils/config.py | 2 +- 16 files changed, 357 insertions(+), 341 deletions(-) create mode 100644 neurons/validators/score_manager.py create mode 100644 webgenie/challenges/__init__.py create mode 100644 webgenie/challenges/challenge.py create mode 100644 webgenie/challenges/challenge_types.py delete mode 100644 webgenie/competitions/__init__.py rename webgenie/{competitions/image_task_competition.py => tasks/image_task_generator.py} (56%) create mode 100644 webgenie/tasks/metric_types.py rename webgenie/{competitions/competition.py => tasks/task_generator.py} (63%) rename webgenie/{competitions/text_task_competition.py => tasks/text_task_generator.py} (52%) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 3114c834..a390c7fd 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -11,14 +11,10 @@ MAX_SYNTHETIC_TASK_SIZE, WORK_DIR, ) -from webgenie.competitions import ( - ImageTaskAccuracyCompetition, - ImageTaskQualityCompetition, - ImageTaskSeoCompetition, - RESERVED_WEIGHTS, - ACCURACY_METRIC_NAME, - QUALITY_METRIC_NAME, - SEO_METRIC_NAME, +from webgenie.challenges import ( + AccuracyChallenge, + QualityChallenge, + SeoChallenge, ) from webgenie.storage import ( upload_competition, @@ -27,7 +23,7 @@ from webgenie.helpers.htmls import preprocess_html, validate_resources from webgenie.helpers.images import image_debug_str from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse -from webgenie.tasks import Solution +from webgenie.tasks import Solution, ImageTaskGenerator from webgenie.utils.uids import get_all_available_uids @@ -35,13 +31,11 @@ class GenieValidator: def __init__(self, neuron: BaseNeuron): self.neuron = neuron self.config = neuron.config - self.competitions = [] + self.miner_results = [] self.synthetic_tasks = [] - self.avail_competitions = [ - (ImageTaskAccuracyCompetition(), 0.4), - (ImageTaskSeoCompetition(), 0.3), - (ImageTaskQualityCompetition(), 0.3), + self.task_generators = [ + (ImageTaskGenerator(), 1.0), ] self.lock = threading.Lock() @@ -52,10 +46,10 @@ def make_work_dir(self): os.makedirs(WORK_DIR) bt.logging.info(f"Created work directory at {WORK_DIR}") - async def query_miners(self): + async def query_miners(self, session_number: int): try: with self.lock: - if len(self.competitions) > MAX_COMPETETION_HISTORY_SIZE: + if len(self.miner_results) > MAX_COMPETETION_HISTORY_SIZE: return if not self.synthetic_tasks: @@ -69,6 +63,15 @@ async def query_miners(self): bt.logging.warning("No miners available") return + available_challenges_classes = [ + AccuracyChallenge, + QualityChallenge, + SeoChallenge, + ] + challenge_class = available_challenges_classes[session_number % len(available_challenges_classes)] + challenge = challenge_class(task=task, session_number=session_number) + synapse.competition_type = challenge.competition_type + bt.logging.debug(f"Querying {len(miner_uids)} miners") async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: all_synapse_results = await dendrite( @@ -89,56 +92,34 @@ async def query_miners(self): process_time = processed_synapse.dendrite.process_time, ) ) + challenge.solutions = solutions + bt.logging.info(f"Received {len(solutions)} solutions") with self.lock: - self.competitions.append((task, solutions)) + self.miner_results.append(challenge) except Exception as e: bt.logging.error(f"Error in query_miners: {e}") raise e - async def score(self): + async def score(self, session_number: int): with self.lock: - if not self.competitions: + if not self.miner_results: return - task, solutions = self.competitions.pop(0) - if not solutions: - return + challenge = self.miner_results.pop(0) - best_miner = -1 - best_final_score = 0.0 + if not challenge.solutions: + return - solutions.sort(key=lambda solution: solution.process_time) - competition = task.competition - upload_competition({ - "competition_type": competition.COMPETITION_TYPE, - "task": task, - }) + if challenge.session_number != session_number: + return + solutions = challenge.solutions miner_uids = [solution.miner_uid for solution in solutions] - - final_scores, scores = await competition.calculate_final_scores(task, solutions) - bt.logging.success(f"Final scores for {miner_uids}: {final_scores}") - - for i in range(len(miner_uids)): - upload_competition_result({ - "miner_uid": miner_uids[i], - "final_score": final_scores[i], - "accuracy": scores[ACCURACY_METRIC_NAME][i], - "quality": scores[QUALITY_METRIC_NAME][i], - "seo": scores[SEO_METRIC_NAME][i], - "html": solutions[i].html, - }) - - if final_scores[i] > best_final_score: - best_final_score = final_scores[i] - best_miner = miner_uids[i] + aggregated_scores, scores = await challenge.calculate_scores() - if best_miner == -1: - return - - self.neuron.update_scores([RESERVED_WEIGHTS[competition.COMPETITION_TYPE]], [best_miner]) - self.neuron.step += 1 + bt.logging.success(f"Final scores for {miner_uids}: {aggregated_scores}") + self.neuron.score_manager.update_scores(miner_uids, scores, challenge.session_number) async def synthensize_task(self): try: @@ -149,8 +130,8 @@ async def synthensize_task(self): bt.logging.info(f"Synthensize task") competition, _ = random.choices( - self.avail_competitions, - weights=[weight for _, weight in self.avail_competitions], + self.task_generators, + weights=[weight for _, weight in self.task_generators], )[0] task, synapse = await competition.generate_task() @@ -161,7 +142,7 @@ async def synthensize_task(self): bt.logging.error(f"Error in synthensize_task: {e}") async def set_weights(self): - self.neuron.set_weights() + self.neuron.score_manager.set_weights() async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImageSynapse]): if isinstance(synapse, WebgenieTextSynapse): diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py new file mode 100644 index 00000000..7307b7eb --- /dev/null +++ b/neurons/validators/score_manager.py @@ -0,0 +1,177 @@ +import bittensor as bt +import copy +import numpy as np + +from typing import List + + +from webgenie.base.utils.weight_utils import ( + process_weights_for_netuid, + convert_weights_and_uids_for_emit, +) +from webgenie.base.neuron import BaseNeuron + +class ScoreManager: + def __init__(self, neuron: BaseNeuron): + self.neuron = neuron + self.session_number = 0 + self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) + self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + self.tempo_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + + def load_scores(self): + bt.logging.info("Loading scores") + state = np.load(self.neuron.config.neuron.full_path + "/state.npz") + try: + self.scores = state["scores"] + self.hotkeys = state["hotkeys"] + self.session_number = state["session_number"] + self.tempo_accumulated_scores = state["tempo_accumulated_scores"] + except Exception as e: + bt.logging.error(f"Error loading scores: {e}") + self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) + self.session_number = 0 + self.tempo_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + + def save_scores(self): + bt.logging.info("Saving scores") + np.savez( + self.neuron.config.neuron.full_path + "/state.npz", + scores=self.scores, + hotkeys=self.hotkeys, + session_number=self.session_number, + tempo_accumulated_scores=self.tempo_accumulated_scores, + ) + + def set_new_hotkeys(self, new_hotkeys: List[str]): + bt.logging.info( + "Hotkeys updated, re-syncing scores" + ) + # Zero out all hotkeys that have been replaced. + for uid, hotkey in enumerate(self.hotkeys): + if hotkey != new_hotkeys[uid]: + self.tempo_accumulated_scores[uid] = 0 + self.scores[uid] = 0 # hotkey has been replaced + + # Check to see if the metagraph has changed size. + # If so, we need to add new hotkeys and moving averages. + if len(self.hotkeys) < len(new_hotkeys): + new_scores = np.zeros((len(new_hotkeys))) + min_len = min(len(self.hotkeys), len(self.scores)) + new_scores[:min_len] = self.scores[:min_len] + self.scores = new_scores + + new_tempo_accumulated_scores = np.zeros((len(new_hotkeys))) + min_len = min(len(self.hotkeys), len(self.tempo_accumulated_scores)) + new_tempo_accumulated_scores[:min_len] = self.tempo_accumulated_scores[:min_len] + self.tempo_accumulated_scores = new_tempo_accumulated_scores + + # Update the hotkeys. + self.hotkeys = copy.deepcopy(new_hotkeys) + + def update_scores(self, rewards: np.ndarray, uids: List[int], session_number: int): + if self.scoring_session_number != session_number: + # In the new session, reset the scores + self.scoring_session_number = session_number + self.tempo_accumulated_scores = np.zeros_like(self.scores) + + # Check if rewards contains NaN values. + if np.isnan(rewards).any(): + bt.logging.warning(f"NaN values detected in rewards: {rewards}") + rewards = np.nan_to_num(rewards, nan=0) + + # Ensure rewards is a numpy array. + rewards = np.asarray(rewards) + + # Check if `uids` is already a numpy array and copy it to avoid the warning. + if isinstance(uids, np.ndarray): + uids_array = uids.copy() + else: + uids_array = np.array(uids) + + # Handle edge case: If either rewards or uids_array is empty. + if rewards.size == 0 or uids_array.size == 0: + bt.logging.info(f"rewards: {rewards}, uids_array: {uids_array}") + bt.logging.warning( + "Either rewards or uids_array is empty. No updates will be performed." + ) + return + + # Check if sizes of rewards and uids_array match. + if rewards.size != uids_array.size: + raise ValueError( + f"Shape mismatch: rewards array of shape {rewards.shape} " + f"cannot be broadcast to uids array of shape {uids_array.shape}" + ) + + # Compute forward pass rewards, assumes uids are mutually exclusive. + # shape: [ metagraph.n ] + scattered_rewards: np.ndarray = np.zeros_like(self.scores) + scattered_rewards[uids_array] = rewards + bt.logging.debug(f"Scattered rewards: {scattered_rewards}") + + self.tempo_accumulated_scores: np.ndarray = scattered_rewards + self.tempo_accumulated_scores + bt.logging.debug(f"Updated scores: {self.tempo_accumulated_scores}") + + def set_weights(self): + """ + Sets the validator weights to the metagraph hotkeys based on the scores it has received from the miners. The weights determine the trust and incentive level the validator assigns to miner nodes on the network. + """ + self.scores = np.zeros_like(self.scores) + best_index = np.argmax(self.tempo_accumulated_scores) + self.scores[best_index] = 1 + + # Calculate the average reward for each uid across non-zero values. + # Replace any NaN values with 0. + # Compute the norm of the scores + norm = np.linalg.norm(self.scores, ord=1, axis=0, keepdims=True) + + # Check if the norm is zero or contains NaN values + if np.any(norm == 0) or np.isnan(norm).any(): + norm = np.ones_like(norm) # Avoid division by zero or NaN + + # Compute raw_weights safely + raw_weights = self.scores / norm + + bt.logging.debug("raw_weights", raw_weights) + bt.logging.debug("raw_weight_uids", str(self.metagraph.uids.tolist())) + # Process the raw weights to final_weights via subtensor limitations. + ( + processed_weight_uids, + processed_weights, + ) = process_weights_for_netuid( + uids=self.metagraph.uids, + weights=raw_weights, + netuid=self.config.netuid, + subtensor=self.subtensor, + metagraph=self.metagraph, + ) + bt.logging.debug("processed_weights", processed_weights) + bt.logging.debug("processed_weight_uids", processed_weight_uids) + + # Convert to uint16 weights and uids. + ( + uint_uids, + uint_weights, + ) = convert_weights_and_uids_for_emit( + uids=processed_weight_uids, weights=processed_weights + ) + bt.logging.debug("uint_weights", uint_weights) + bt.logging.debug("uint_uids", uint_uids) + + # Set the weights on chain via our subtensor connection. + result, msg = self.neuron.subtensor.set_weights( + wallet=self.neuron.wallet, + netuid=self.neuron.config.netuid, + uids=uint_uids, + weights=uint_weights, + wait_for_finalization=False, + wait_for_inclusion=False, + version_key=self.neuron.spec_version, + ) + if result is True: + bt.logging.info("set_weights on chain successfully!") + else: + bt.logging.error("set_weights failed", msg) + \ No newline at end of file diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 0a3780ed..174b56af 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -4,6 +4,8 @@ import bittensor as bt import asyncio +import copy +import numpy as np import threading import time @@ -18,7 +20,7 @@ from webgenie.utils.uids import get_validator_index from neurons.validators.genie_validator import GenieValidator - +from neurons.validators.score_manager import ScoreManager # Constants for block timing BLOCK_IN_SECONDS = 12 @@ -27,7 +29,7 @@ VALIDATOR_QUERY_PERIOD_BLOCKS = 10 ALL_VALIDATOR_QUERY_PERIOD_BLOCKS = MAX_VALIDATORS * VALIDATOR_QUERY_PERIOD_BLOCKS COMPETITION_PERIOD_BLOCKS = TEMPO_BLOCKS * 3 -SET_WEIGHTS_PERIOD_BLOCKS = 5 +SET_WEIGHTS_PERIOD_BLOCKS = 50 # 50 blocks = 10 minutes class Validator(BaseValidatorNeuron): @@ -41,9 +43,9 @@ class Validator(BaseValidatorNeuron): def __init__(self, config=None): super(Validator, self).__init__(config=config) - if not self.config.axon_off: - self.serve_axon() + bt.logging.info("load_state()") + self.load_state() # Create asyncio event loop to manage async tasks. self.synthensize_task_event_loop = asyncio.new_event_loop() @@ -61,8 +63,40 @@ def __init__(self, config=None): self.lock = asyncio.Lock() self.genie_validator = GenieValidator(neuron=self) + self.score_manager = ScoreManager(neuron=self) + + self.sync() + + if not self.config.axon_off: + self.serve_axon() + def resync_metagraph(self): + """Resyncs the metagraph and updates the hotkeys and moving averages based on the new metagraph.""" + # Copies state of metagraph before syncing. + previous_metagraph = copy.deepcopy(self.metagraph) + # Sync the metagraph. + self.metagraph.sync(subtensor=self.subtensor) + + # Check if the metagraph axon info has changed. + if previous_metagraph.axons == self.metagraph.axons: + return + + bt.logging.info( + "Metagraph updated, re-syncing hotkeys, dendrite pool and moving averages" + ) + + self.score_manager.set_new_hotkeys(self.metagraph.hotkeys) + + def save_state(self): + """Saves the state of the validator to a file.""" + self.score_manager.save_scores() + + def load_state(self): + """Loads the state of the validator from a file.""" + bt.logging.info("Loading validator state.") + self.score_manager.load_scores() + async def blacklist_text(self, synapse: WebgenieTextSynapse) -> Tuple[bool, str]: """ Only allow the backend owner to send synapse to the validator. @@ -136,13 +170,14 @@ def query_miners_loop(self): sleep_time = (start_period_block - current_block + ALL_VALIDATOR_QUERY_PERIOD_BLOCKS) * BLOCK_IN_SECONDS time.sleep(sleep_time) continue - - self.query_miners_event_loop.run_until_complete(self.genie_validator.query_miners()) + + session_number = self.block // COMPETITION_PERIOD_BLOCKS + self.query_miners_event_loop.run_until_complete(self.genie_validator.query_miners(session_number)) except KeyboardInterrupt: bt.logging.info("Keyboard interrupt detected, stopping query miners loop") break except Exception as e: - bt.logging.error(f"Error during forward loop: {str(e)}") + bt.logging.error(f"Error during query miners loop: {str(e)}") if self.should_exit: break time.sleep(1) @@ -152,7 +187,8 @@ def score_loop(self): while True: try: self.sync() - self.score_event_loop.run_until_complete(self.genie_validator.score()) + session_number = self.block // COMPETITION_PERIOD_BLOCKS + self.score_event_loop.run_until_complete(self.genie_validator.score(session_number)) except KeyboardInterrupt: bt.logging.info("Keyboard interrupt detected, stopping scoring loop") break @@ -196,8 +232,8 @@ def set_weights_loop(self): * COMPETITION_PERIOD_BLOCKS ) - # Start setting weights 5 blocks before the end - set_weights_start_block = set_weights_end_block - 5 + # Start setting weights 50 blocks before the end + set_weights_start_block = set_weights_end_block - SET_WEIGHTS_PERIOD_BLOCKS # Check if we're in the weight setting window if (current_block >= set_weights_start_block and diff --git a/tests/test_reward.py b/tests/test_reward.py index 045a8916..2af59705 100644 --- a/tests/test_reward.py +++ b/tests/test_reward.py @@ -13,7 +13,7 @@ ) from webgenie.protocol import WebgenieImageSynapse -from webgenie.competitions.competition import ( +from webgenie.tasks.competition import ( ACCURACY_METRIC_NAME, SEO_METRIC_NAME, QUALITY_METRIC_NAME, diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index 091959ea..ab729a96 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -28,10 +28,7 @@ from traceback import print_exception from webgenie.base.neuron import BaseNeuron -from webgenie.base.utils.weight_utils import ( - process_weights_for_netuid, - convert_weights_and_uids_for_emit, -) # TODO: Replace when bittensor switches to numpy + from webgenie.helpers.weights import init_wandb from webgenie.mock import MockDendrite from webgenie.utils.config import add_validator_args @@ -58,171 +55,3 @@ def __init__(self, config=None): else: self.dendrite = bt.dendrite(wallet=self.wallet) bt.logging.info(f"Dendrite: {self.dendrite}") - - bt.logging.info("load_state()") - self.load_state() - - # Init sync with the network. Updates the metagraph. - self.sync() - - def set_weights(self): - """ - Sets the validator weights to the metagraph hotkeys based on the scores it has received from the miners. The weights determine the trust and incentive level the validator assigns to miner nodes on the network. - """ - - # Check if self.scores contains any NaN values and log a warning if it does. - if np.isnan(self.scores).any(): - bt.logging.warning( - f"Scores contain NaN values. This may be due to a lack of responses from miners, or a bug in your reward functions." - ) - - # Calculate the average reward for each uid across non-zero values. - # Replace any NaN values with 0. - # Compute the norm of the scores - norm = np.linalg.norm(self.scores, ord=1, axis=0, keepdims=True) - - # Check if the norm is zero or contains NaN values - if np.any(norm == 0) or np.isnan(norm).any(): - norm = np.ones_like(norm) # Avoid division by zero or NaN - - # Compute raw_weights safely - raw_weights = self.scores / norm - - bt.logging.debug("raw_weights", raw_weights) - bt.logging.debug("raw_weight_uids", str(self.metagraph.uids.tolist())) - # Process the raw weights to final_weights via subtensor limitations. - ( - processed_weight_uids, - processed_weights, - ) = process_weights_for_netuid( - uids=self.metagraph.uids, - weights=raw_weights, - netuid=self.config.netuid, - subtensor=self.subtensor, - metagraph=self.metagraph, - ) - bt.logging.debug("processed_weights", processed_weights) - bt.logging.debug("processed_weight_uids", processed_weight_uids) - - # Convert to uint16 weights and uids. - ( - uint_uids, - uint_weights, - ) = convert_weights_and_uids_for_emit( - uids=processed_weight_uids, weights=processed_weights - ) - bt.logging.debug("uint_weights", uint_weights) - bt.logging.debug("uint_uids", uint_uids) - - # Set the weights on chain via our subtensor connection. - result, msg = self.subtensor.set_weights( - wallet=self.wallet, - netuid=self.config.netuid, - uids=uint_uids, - weights=uint_weights, - wait_for_finalization=False, - wait_for_inclusion=False, - version_key=self.spec_version, - ) - if result is True: - bt.logging.info("set_weights on chain successfully!") - else: - bt.logging.error("set_weights failed", msg) - - def resync_metagraph(self): - """Resyncs the metagraph and updates the hotkeys and moving averages based on the new metagraph.""" - # Copies state of metagraph before syncing. - previous_metagraph = copy.deepcopy(self.metagraph) - - # Sync the metagraph. - self.metagraph.sync(subtensor=self.subtensor) - - # Check if the metagraph axon info has changed. - if previous_metagraph.axons == self.metagraph.axons: - return - - bt.logging.info( - "Metagraph updated, re-syncing hotkeys, dendrite pool and moving averages" - ) - # Zero out all hotkeys that have been replaced. - for uid, hotkey in enumerate(self.hotkeys): - if hotkey != self.metagraph.hotkeys[uid]: - self.scores[uid] = 0 # hotkey has been replaced - - # Check to see if the metagraph has changed size. - # If so, we need to add new hotkeys and moving averages. - if len(self.hotkeys) < len(self.metagraph.hotkeys): - new_moving_average = np.zeros((self.metagraph.n)) - min_len = min(len(self.hotkeys), len(self.scores)) - new_moving_average[:min_len] = self.scores[:min_len] - self.scores = new_moving_average - - # Update the hotkeys. - self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) - - def update_scores(self, rewards: np.ndarray, uids: List[int]): - # Check if rewards contains NaN values. - if np.isnan(rewards).any(): - bt.logging.warning(f"NaN values detected in rewards: {rewards}") - rewards = np.nan_to_num(rewards, nan=0) - - # Ensure rewards is a numpy array. - rewards = np.asarray(rewards) - - # Check if `uids` is already a numpy array and copy it to avoid the warning. - if isinstance(uids, np.ndarray): - uids_array = uids.copy() - else: - uids_array = np.array(uids) - - # Handle edge case: If either rewards or uids_array is empty. - if rewards.size == 0 or uids_array.size == 0: - bt.logging.info(f"rewards: {rewards}, uids_array: {uids_array}") - bt.logging.warning( - "Either rewards or uids_array is empty. No updates will be performed." - ) - return - - # Check if sizes of rewards and uids_array match. - if rewards.size != uids_array.size: - raise ValueError( - f"Shape mismatch: rewards array of shape {rewards.shape} " - f"cannot be broadcast to uids array of shape {uids_array.shape}" - ) - - # Compute forward pass rewards, assumes uids are mutually exclusive. - # shape: [ metagraph.n ] - scattered_rewards: np.ndarray = np.zeros_like(self.scores) - scattered_rewards[uids_array] = rewards - bt.logging.debug(f"Scattered rewards: {scattered_rewards}") - - self.scores: np.ndarray = scattered_rewards + self.scores - bt.logging.debug(f"Updated scores: {self.scores}") - - def save_state(self): - """Saves the state of the validator to a file.""" - - # Save the state of the validator to file. - np.savez( - self.config.neuron.full_path + "/state.npz", - step=self.step, - scores=self.scores, - hotkeys=self.hotkeys, - ) - - def load_state(self): - """Loads the state of the validator from a file.""" - bt.logging.info("Loading validator state.") - - # Load the state of the validator from file. - try: - state = np.load(self.config.neuron.full_path + "/state.npz") - self.step = state["step"] - self.scores = state["scores"] - self.hotkeys = state["hotkeys"] - except Exception as e: - self.step = 0 - self.scores = np.zeros(self.metagraph.n, dtype=np.float32) - self.hotkeys = copy.deepcopy(self.metagraph.hotkeys) - - bt.logging.debug(f"Loaded state: step={self.step}, scores={self.scores}") diff --git a/webgenie/challenges/__init__.py b/webgenie/challenges/__init__.py new file mode 100644 index 00000000..aa3adb9d --- /dev/null +++ b/webgenie/challenges/__init__.py @@ -0,0 +1,12 @@ +from .challenge import ( + Challenge, + AccuracyChallenge, + QualityChallenge, + SeoChallenge, +) + +from .competition_types import ( + ACCURACY_COMPETITION_TYPE, + QUALITY_COMPETITION_TYPE, + SEO_COMPETITION_TYPE, +) \ No newline at end of file diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py new file mode 100644 index 00000000..83181998 --- /dev/null +++ b/webgenie/challenges/challenge.py @@ -0,0 +1,57 @@ +import numpy as np +from typing import List, Optional +from pydantic import BaseModel, Field + +from webgenie.challenges.challenge_types import ( + ACCURACY_COMPETITION_TYPE, + QUALITY_COMPETITION_TYPE, + SEO_COMPETITION_TYPE, +) +from webgenie.tasks.metric_types import ( + ACCURACY_METRIC_NAME, + QUALITY_METRIC_NAME, + SEO_METRIC_NAME, +) +from webgenie.tasks.task import Task +from webgenie.tasks.solution import Solution + + +class Challenge(BaseModel): + task: Optional[Task] = Field(default=None, description="The task to be solved") + solutions: List[Solution] = Field(default=[], description="The solutions to the task") + competition_type: str = Field(default="", description="The type of competition") + session_number: int = Field(default=0, description="The session number") + + async def calculate_scores(self) -> dict[str, np.ndarray]: + pass + + +class AccuracyChallenge(Challenge): + competition_type = ACCURACY_COMPETITION_TYPE + + async def calculate_scores(self) -> dict[str, np.ndarray]: + scores = await self.task_generator.calculate_scores(self.task, self.solutions) + aggregated_scores = scores[ACCURACY_METRIC_NAME] * 0.9 + scores[QUALITY_METRIC_NAME] * 0.1 + return aggregated_scores, scores + + +class SeoChallenge(Challenge): + competition_type = SEO_COMPETITION_TYPE + + async def calculate_scores(self) -> dict[str, np.ndarray]: + scores = await self.task_generator.calculate_scores(self.task, self.solutions) + accuracy_scores = scores[ACCURACY_METRIC_NAME] + seo_scores = scores[SEO_METRIC_NAME] + aggregated_scores = np.where(accuracy_scores > 0.7, seo_scores, 0) + return aggregated_scores, scores + + +class QualityChallenge(Challenge): + competition_type = QUALITY_COMPETITION_TYPE + + async def calculate_scores(self) -> dict[str, np.ndarray]: + scores = await self.task_generator.calculate_scores(self.task, self.solutions) + accuracy_scores = scores[ACCURACY_METRIC_NAME] + quality_scores = scores[QUALITY_METRIC_NAME] + aggregated_scores = np.where(accuracy_scores > 0.7, quality_scores, 0) + return aggregated_scores, scores diff --git a/webgenie/challenges/challenge_types.py b/webgenie/challenges/challenge_types.py new file mode 100644 index 00000000..2cb7bbb0 --- /dev/null +++ b/webgenie/challenges/challenge_types.py @@ -0,0 +1,3 @@ +ACCURACY_COMPETITION_TYPE = "accuracy_competition" +QUALITY_COMPETITION_TYPE = "quality_competition" +SEO_COMPETITION_TYPE = "seo_competition" diff --git a/webgenie/competitions/__init__.py b/webgenie/competitions/__init__.py deleted file mode 100644 index 8c1fe62e..00000000 --- a/webgenie/competitions/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -from webgenie.competitions.competition import ( - Competition, - ACCURACY_METRIC_NAME, - QUALITY_METRIC_NAME, - SEO_METRIC_NAME, -) -from webgenie.competitions.text_task_competition import ( - TextTaskCompetition, - TextTaskAccuracyCompetition, - TextTaskQualityCompetition, -) -from webgenie.competitions.image_task_competition import ( - ImageTaskCompetition, - ImageTaskAccuracyCompetition, - ImageTaskQualityCompetition, - ImageTaskSeoCompetition, -) - - -RESERVED_WEIGHTS = { - ImageTaskAccuracyCompetition.COMPETITION_TYPE: 90, - ImageTaskQualityCompetition.COMPETITION_TYPE: 10, - ImageTaskSeoCompetition.COMPETITION_TYPE: 20, -} - diff --git a/webgenie/tasks/__init__.py b/webgenie/tasks/__init__.py index 8bfd415d..1cc72146 100644 --- a/webgenie/tasks/__init__.py +++ b/webgenie/tasks/__init__.py @@ -1,2 +1,5 @@ from .solution import Solution -from .task import Task, ImageTask, TextTask \ No newline at end of file +from .task import Task, ImageTask, TextTask +from .task_generator import TaskGenerator +from .image_task_generator import ImageTaskGenerator +from .text_task_generator import TextTaskGenerator diff --git a/webgenie/competitions/image_task_competition.py b/webgenie/tasks/image_task_generator.py similarity index 56% rename from webgenie/competitions/image_task_competition.py rename to webgenie/tasks/image_task_generator.py index df835675..bb828a6f 100644 --- a/webgenie/competitions/image_task_competition.py +++ b/webgenie/tasks/image_task_generator.py @@ -3,12 +3,12 @@ import random from typing import Tuple, List -from webgenie.competitions.competition import ( - Competition, +from webgenie.tasks.metric_types import ( ACCURACY_METRIC_NAME, QUALITY_METRIC_NAME, SEO_METRIC_NAME, ) +from webgenie.tasks.task_generator import TaskGenerator from webgenie.constants import IMAGE_TASK_TIMEOUT, GROUND_TRUTH_HTML_LOAD_TIME from webgenie.helpers.htmls import ( html_to_screenshot, @@ -17,7 +17,8 @@ ) from webgenie.helpers.images import base64_to_image from webgenie.protocol import WebgenieImageSynapse -from webgenie.tasks import Task, ImageTask, Solution +from webgenie.tasks.solution import Solution +from webgenie.tasks.task import Task, ImageTask from webgenie.rewards import ( QualityReward, VisualReward, @@ -30,9 +31,7 @@ ) -class ImageTaskCompetition(Competition): - COMPETITION_TYPE = "ImageTaskCompetition" - +class ImageTaskGenerator(TaskGenerator): def __init__(self): super().__init__() @@ -67,36 +66,7 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: base64_image=base64_image, ground_truth_html=ground_truth_html, timeout=IMAGE_TASK_TIMEOUT, - competition=self, + generator=self, ), WebgenieImageSynapse(base64_image=base64_image), ) - - -class ImageTaskAccuracyCompetition(ImageTaskCompetition): - COMPETITION_TYPE = "ImageTaskAccuracyCompetition" - - async def calculate_final_scores(self, task: Task, solutions: List[Solution]) -> np.ndarray: - scores = await self.calculate_scores(task, solutions) - return scores[ACCURACY_METRIC_NAME] * 0.9 + scores[QUALITY_METRIC_NAME] * 0.1, scores - - -class ImageTaskQualityCompetition(ImageTaskCompetition): - COMPETITION_TYPE = "ImageTaskQualityCompetition" - - async def calculate_final_scores(self, task: Task, solutions: List[Solution]) -> np.ndarray: - scores = await self.calculate_scores(task, solutions) - accuracy_scores = scores[ACCURACY_METRIC_NAME] - quality_scores = scores[QUALITY_METRIC_NAME] - final_scores = np.where(accuracy_scores > 0.7, quality_scores, 0) - return final_scores, scores - -class ImageTaskSeoCompetition(ImageTaskCompetition): - COMPETITION_TYPE = "ImageTaskSeoCompetition" - - async def calculate_final_scores(self, task: Task, solutions: List[Solution]) -> np.ndarray: - scores = await self.calculate_scores(task, solutions) - accuracy_scores = scores[ACCURACY_METRIC_NAME] - seo_scores = scores[SEO_METRIC_NAME] - final_scores = np.where(accuracy_scores > 0.7, seo_scores, 0) - return final_scores, scores diff --git a/webgenie/tasks/metric_types.py b/webgenie/tasks/metric_types.py new file mode 100644 index 00000000..4f4c6291 --- /dev/null +++ b/webgenie/tasks/metric_types.py @@ -0,0 +1,3 @@ +ACCURACY_METRIC_NAME = "Accuracy" +SEO_METRIC_NAME = "Seo" +QUALITY_METRIC_NAME = "Quality" diff --git a/webgenie/tasks/task.py b/webgenie/tasks/task.py index 992c71a6..05493020 100644 --- a/webgenie/tasks/task.py +++ b/webgenie/tasks/task.py @@ -6,7 +6,7 @@ class Task(BaseModel): task_id: str = Field(default_factory=lambda: str(uuid.uuid4())) timeout: float = Field(default=50) - competition: Any = Field(default=None) + generator: Any = Field(default=None) class ImageTask(Task): diff --git a/webgenie/competitions/competition.py b/webgenie/tasks/task_generator.py similarity index 63% rename from webgenie/competitions/competition.py rename to webgenie/tasks/task_generator.py index b0707004..e9241842 100644 --- a/webgenie/competitions/competition.py +++ b/webgenie/tasks/task_generator.py @@ -3,17 +3,11 @@ from typing import List, Tuple from webgenie.rewards import Reward -from webgenie.tasks import Task, Solution +from webgenie.tasks.solution import Solution +from webgenie.tasks.task import Task -ACCURACY_METRIC_NAME = "Accuracy" -SEO_METRIC_NAME = "Seo" -QUALITY_METRIC_NAME = "Quality" - - -class Competition: - COMPETITION_TYPE = "UnknownCompetition" - +class TaskGenerator: def __init__(self): self.metrics: dict[str, Reward] = {} @@ -26,7 +20,3 @@ async def calculate_scores(self, task: Task, solutions: List[Solution]) -> dict[ reward_scores = await reward_model.reward(task, solutions) scores[metric_name] = reward_scores return scores - - async def calculate_final_scores(self, task: Task, solutions: List[Solution]) -> Tuple[np.ndarray, dict[str, np.ndarray]]: - pass - diff --git a/webgenie/competitions/text_task_competition.py b/webgenie/tasks/text_task_generator.py similarity index 52% rename from webgenie/competitions/text_task_competition.py rename to webgenie/tasks/text_task_generator.py index 4dac769e..134c850e 100644 --- a/webgenie/competitions/text_task_competition.py +++ b/webgenie/tasks/text_task_generator.py @@ -3,11 +3,11 @@ import random from typing import Tuple, List -from webgenie.competitions.competition import ( - Competition, +from webgenie.tasks.metric_types import ( ACCURACY_METRIC_NAME, QUALITY_METRIC_NAME, ) +from webgenie.tasks.task_generator import TaskGenerator from webgenie.constants import TEXT_TASK_TIMEOUT from webgenie.datasets import ( SyntheticDataset, @@ -17,12 +17,11 @@ QualityReward, RtcReward, ) -from webgenie.tasks import Task, TextTask, Solution +from webgenie.tasks.solution import Solution +from webgenie.tasks.task import Task, TextTask -class TextTaskCompetition(Competition): - COMPETITION_TYPE = "TextTaskCompetition" - +class TextTaskGenerator(TaskGenerator): def __init__(self): super().__init__() @@ -45,26 +44,7 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: prompt=dataset_entry.prompt, ground_truth_html=dataset_entry.ground_truth_html, timeout=TEXT_TASK_TIMEOUT, - competition=self, + generator=self, ), WebgenieTextSynapse(prompt=dataset_entry.prompt), ) - - -class TextTaskAccuracyCompetition(TextTaskCompetition): - COMPETITION_TYPE = "TextTaskAccuracyCompetition" - - async def calculate_final_scores(self, task: Task, solutions: List[Solution]) -> np.ndarray: - scores = await self.calculate_scores(task, solutions) - return scores[ACCURACY_METRIC_NAME] * 0.9 + scores[QUALITY_METRIC_NAME] * 0.1, scores - - -class TextTaskQualityCompetition(TextTaskCompetition): - COMPETITION_TYPE = "TextTaskQualityCompetition" - - async def calculate_final_scores(self, task: Task, solutions: List[Solution]) -> np.ndarray: - scores = await self.calculate_scores(task, solutions) - accuracy_scores = scores[ACCURACY_METRIC_NAME] - quality_scores = scores[QUALITY_METRIC_NAME] - final_scores = np.where(accuracy_scores > 0.7, quality_scores, 0) - return final_scores, scores diff --git a/webgenie/utils/config.py b/webgenie/utils/config.py index 059fac05..fdf23e61 100644 --- a/webgenie/utils/config.py +++ b/webgenie/utils/config.py @@ -81,7 +81,7 @@ def add_args(cls, parser): "--neuron.epoch_length", type=int, help="The default epoch length (how often we set weights, measured in 12 second blocks).", - default=100, + default=50, ) parser.add_argument( From 4214aa068a3efbcac0f3a9a5772cf446b86c2bb6 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 16 Jan 2025 12:51:21 -0600 Subject: [PATCH 213/554] chore: add session_number property --- neurons/validators/genie_validator.py | 13 +++++-------- neurons/validators/score_manager.py | 8 ++++---- neurons/validators/validator.py | 12 +++++++----- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index a390c7fd..be5aa409 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -46,7 +46,7 @@ def make_work_dir(self): os.makedirs(WORK_DIR) bt.logging.info(f"Created work directory at {WORK_DIR}") - async def query_miners(self, session_number: int): + async def query_miners(self): try: with self.lock: if len(self.miner_results) > MAX_COMPETETION_HISTORY_SIZE: @@ -68,8 +68,8 @@ async def query_miners(self, session_number: int): QualityChallenge, SeoChallenge, ] - challenge_class = available_challenges_classes[session_number % len(available_challenges_classes)] - challenge = challenge_class(task=task, session_number=session_number) + challenge_class = available_challenges_classes[self.neuron.session_number % len(available_challenges_classes)] + challenge = challenge_class(task=task, session_number=self.neuron.session_number) synapse.competition_type = challenge.competition_type bt.logging.debug(f"Querying {len(miner_uids)} miners") @@ -101,7 +101,7 @@ async def query_miners(self, session_number: int): bt.logging.error(f"Error in query_miners: {e}") raise e - async def score(self, session_number: int): + async def score(self): with self.lock: if not self.miner_results: return @@ -111,7 +111,7 @@ async def score(self, session_number: int): if not challenge.solutions: return - if challenge.session_number != session_number: + if challenge.session_number != self.neuron.session_number: return solutions = challenge.solutions @@ -140,9 +140,6 @@ async def synthensize_task(self): except Exception as e: bt.logging.error(f"Error in synthensize_task: {e}") - - async def set_weights(self): - self.neuron.score_manager.set_weights() async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImageSynapse]): if isinstance(synapse, WebgenieTextSynapse): diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 7307b7eb..4f2c85d9 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -14,7 +14,7 @@ class ScoreManager: def __init__(self, neuron: BaseNeuron): self.neuron = neuron - self.session_number = 0 + self.scoring_session_number = 0 self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.tempo_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) @@ -25,13 +25,13 @@ def load_scores(self): try: self.scores = state["scores"] self.hotkeys = state["hotkeys"] - self.session_number = state["session_number"] + self.scoring_session_number = state["scoring_session_number"] self.tempo_accumulated_scores = state["tempo_accumulated_scores"] except Exception as e: bt.logging.error(f"Error loading scores: {e}") self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) - self.session_number = 0 + self.scoring_session_number = 0 self.tempo_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) def save_scores(self): @@ -40,7 +40,7 @@ def save_scores(self): self.neuron.config.neuron.full_path + "/state.npz", scores=self.scores, hotkeys=self.hotkeys, - session_number=self.session_number, + scoring_session_number=self.scoring_session_number, tempo_accumulated_scores=self.tempo_accumulated_scores, ) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 174b56af..3f44d3fd 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -40,6 +40,10 @@ class Validator(BaseValidatorNeuron): This class provides reasonable default behavior for a validator such as keeping a moving average of the scores of the miners and using them to set weights at the end of each epoch. Additionally, the scores are reset for new hotkeys at the end of each epoch. """ + + @property + def session_number(self): + return self.block // COMPETITION_PERIOD_BLOCKS def __init__(self, config=None): super(Validator, self).__init__(config=config) @@ -171,8 +175,7 @@ def query_miners_loop(self): time.sleep(sleep_time) continue - session_number = self.block // COMPETITION_PERIOD_BLOCKS - self.query_miners_event_loop.run_until_complete(self.genie_validator.query_miners(session_number)) + self.query_miners_event_loop.run_until_complete(self.genie_validator.query_miners()) except KeyboardInterrupt: bt.logging.info("Keyboard interrupt detected, stopping query miners loop") break @@ -187,8 +190,7 @@ def score_loop(self): while True: try: self.sync() - session_number = self.block // COMPETITION_PERIOD_BLOCKS - self.score_event_loop.run_until_complete(self.genie_validator.score(session_number)) + self.score_event_loop.run_until_complete(self.genie_validator.score()) except KeyboardInterrupt: bt.logging.info("Keyboard interrupt detected, stopping scoring loop") break @@ -239,7 +241,7 @@ def set_weights_loop(self): if (current_block >= set_weights_start_block and current_block < set_weights_end_block): bt.logging.info(f"Setting weights at block {current_block}") - self.set_weights_event_loop.run_until_complete(self.genie_validator.set_weights()) + self.set_weights_event_loop.run_until_complete(self.score_manager.set_weights()) else: # Sleep until next weight setting window sleep_blocks = set_weights_start_block - current_block From 122b286d1a280efcd25649ed5cc996699f036948 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 16 Jan 2025 18:47:20 -0600 Subject: [PATCH 214/554] chore: reduce epoch_length --- webgenie/utils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/utils/config.py b/webgenie/utils/config.py index fdf23e61..c13ec43f 100644 --- a/webgenie/utils/config.py +++ b/webgenie/utils/config.py @@ -81,7 +81,7 @@ def add_args(cls, parser): "--neuron.epoch_length", type=int, help="The default epoch length (how often we set weights, measured in 12 second blocks).", - default=50, + default=25, ) parser.add_argument( From 9e3ed1dc49f97410e9087c71b80098b6e18d62cb Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 16 Jan 2025 18:47:33 -0600 Subject: [PATCH 215/554] fix: fix bugs --- neurons/validators/validator.py | 19 +++++++++++++++---- webgenie.db | Bin 0 -> 20480 bytes webgenie/challenges/__init__.py | 2 +- webgenie/challenges/challenge.py | 6 +++--- 4 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 webgenie.db diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 3f44d3fd..3f73e3d6 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -47,9 +47,6 @@ def session_number(self): def __init__(self, config=None): super(Validator, self).__init__(config=config) - - bt.logging.info("load_state()") - self.load_state() # Create asyncio event loop to manage async tasks. self.synthensize_task_event_loop = asyncio.new_event_loop() @@ -69,6 +66,9 @@ def __init__(self, config=None): self.genie_validator = GenieValidator(neuron=self) self.score_manager = ScoreManager(neuron=self) + bt.logging.info("load_state()") + self.load_state() + self.sync() if not self.config.axon_off: @@ -260,10 +260,16 @@ def run_background_threads(self): bt.logging.info("Starting validator in background thread") self.is_running = True self.should_exit = False + self.synthensize_task_thread = threading.Thread(target=self.synthensize_task_loop) self.query_miners_thread = threading.Thread(target=self.query_miners_loop) self.score_thread = threading.Thread(target=self.score_loop) self.set_weights_thread = threading.Thread(target=self.set_weights_loop) + + self.synthensize_task_thread.start() + self.query_miners_thread.start() + self.score_thread.start() + self.set_weights_thread.start() bt.logging.info("Started background threads") def stop_background_threads(self): @@ -271,10 +277,16 @@ def stop_background_threads(self): bt.logging.info("Stopping background threads") self.should_exit = True self.is_running = False + self.synthensize_task_thread.join(5) self.query_miners_thread.join(5) self.score_thread.join(5) self.set_weights_thread.join(5) + + self.synthensize_task_thread = None + self.query_miners_thread = None + self.score_thread = None + self.set_weights_thread = None bt.logging.info("Stopped background threads") def __enter__(self): @@ -289,5 +301,4 @@ def __exit__(self, exc_type, exc_value, traceback): if __name__ == "__main__": with Validator() as validator: while True: - bt.logging.info("Validator is running ... {time.time()}") time.sleep(5) diff --git a/webgenie.db b/webgenie.db new file mode 100644 index 0000000000000000000000000000000000000000..7f82c3a3e4dc91fff10bde2320b0d5ab89c3ab57 GIT binary patch literal 20480 zcmeI%?{AYp7zc1zJL?*^^0L=P+Uc^_^-BWW4+{5uo+l)v!wV;toP5mkX(-7{qh*+;@tP3BFluVwRr_kw z{rak-&Ss_kuV&47{&lv0_}i#IDU5?(hd&Qms(}px5P$##AOHafKmY;|_PNow z2j{kbN#4>+(v2fakM%vD4$fRX*4^O=Z2HN?j90_F%CjPkn9K_~VG}t`s*?{kP%%ie(e8B2o8B^prK%{8 zJ8SDM$L?G4dI9~dJd$Xmnqt9neiaMJbCscl)a0)27MbBBR%tJ)?-8qW)^ zi+NR_BX@8yqAF)`bWF-csnk8&dDMQ@F^xEl_!lu(HOE<4$ZXkXWvuK)dDh?ZkM#>@ zUmsfffDHl=fB*y_009U<00Izz00bZafo&CN>cH^*zpd#-To8Z&1Rwwb2tWV=5P$## zAOL}-0RI0+AwU2E5P$##AOHafKmY;|fB*!xUjX0#+n-}Z2muH{00Izz00bZa0SG_< H0ucBEHhCdm literal 0 HcmV?d00001 diff --git a/webgenie/challenges/__init__.py b/webgenie/challenges/__init__.py index aa3adb9d..771b0857 100644 --- a/webgenie/challenges/__init__.py +++ b/webgenie/challenges/__init__.py @@ -5,7 +5,7 @@ SeoChallenge, ) -from .competition_types import ( +from .challenge_types import ( ACCURACY_COMPETITION_TYPE, QUALITY_COMPETITION_TYPE, SEO_COMPETITION_TYPE, diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py index 83181998..76f64854 100644 --- a/webgenie/challenges/challenge.py +++ b/webgenie/challenges/challenge.py @@ -27,7 +27,7 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: class AccuracyChallenge(Challenge): - competition_type = ACCURACY_COMPETITION_TYPE + competition_type: str = Field(default=ACCURACY_COMPETITION_TYPE, description="The type of competition") async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task_generator.calculate_scores(self.task, self.solutions) @@ -36,7 +36,7 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: class SeoChallenge(Challenge): - competition_type = SEO_COMPETITION_TYPE + competition_type: str = Field(default=SEO_COMPETITION_TYPE, description="The type of competition") async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task_generator.calculate_scores(self.task, self.solutions) @@ -47,7 +47,7 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: class QualityChallenge(Challenge): - competition_type = QUALITY_COMPETITION_TYPE + competition_type: str = Field(default=QUALITY_COMPETITION_TYPE, description="The type of competition") async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task_generator.calculate_scores(self.task, self.solutions) From f3752e81df6547807f8e429e901d3b8a02795aea Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 17 Jan 2025 06:34:12 -0600 Subject: [PATCH 216/554] fix: fix bugs --- neurons/validators/genie_validator.py | 28 +++--- neurons/validators/score_manager.py | 126 ++++++++++---------------- neurons/validators/validator.py | 93 +++++++++++-------- webgenie/base/validator.py | 1 + webgenie/challenges/challenge.py | 6 +- webgenie/utils/uids.py | 3 +- 6 files changed, 125 insertions(+), 132 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index be5aa409..7b5adbd5 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -38,7 +38,6 @@ def __init__(self, neuron: BaseNeuron): (ImageTaskGenerator(), 1.0), ] - self.lock = threading.Lock() self.make_work_dir() def make_work_dir(self): @@ -48,7 +47,7 @@ def make_work_dir(self): async def query_miners(self): try: - with self.lock: + with self.neuron.lock: if len(self.miner_results) > MAX_COMPETETION_HISTORY_SIZE: return @@ -67,9 +66,11 @@ async def query_miners(self): AccuracyChallenge, QualityChallenge, SeoChallenge, - ] - challenge_class = available_challenges_classes[self.neuron.session_number % len(available_challenges_classes)] - challenge = challenge_class(task=task, session_number=self.neuron.session_number) + ] + with self.neuron.lock: + session_number = self.neuron.session_number + challenge_class = available_challenges_classes[session_number % len(available_challenges_classes)] + challenge = challenge_class(task=task, session_number=session_number) synapse.competition_type = challenge.competition_type bt.logging.debug(f"Querying {len(miner_uids)} miners") @@ -95,14 +96,14 @@ async def query_miners(self): challenge.solutions = solutions bt.logging.info(f"Received {len(solutions)} solutions") - with self.lock: + with self.neuron.lock: self.miner_results.append(challenge) except Exception as e: bt.logging.error(f"Error in query_miners: {e}") raise e async def score(self): - with self.lock: + with self.neuron.lock: if not self.miner_results: return @@ -111,9 +112,11 @@ async def score(self): if not challenge.solutions: return - if challenge.session_number != self.neuron.session_number: - return - + with self.neuron.lock: + if challenge.session_number != self.neuron.session_number: + return + + bt.logging.info("Scoring") solutions = challenge.solutions miner_uids = [solution.miner_uid for solution in solutions] aggregated_scores, scores = await challenge.calculate_scores() @@ -123,8 +126,9 @@ async def score(self): async def synthensize_task(self): try: - with self.lock: + with self.neuron.lock: if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: + bt.logging.info(f"Synthetic task size {len(self.synthetic_tasks)} exceeds max size {MAX_SYNTHETIC_TASK_SIZE}, skipping") return bt.logging.info(f"Synthensize task") @@ -135,7 +139,7 @@ async def synthensize_task(self): )[0] task, synapse = await competition.generate_task() - with self.lock: + with self.neuron.lock: self.synthetic_tasks.append((task, synapse)) except Exception as e: diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 4f2c85d9..29683892 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -18,6 +18,8 @@ def __init__(self, neuron: BaseNeuron): self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.tempo_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + self.should_save = False + self.should_set_weights = False def load_scores(self): bt.logging.info("Loading scores") @@ -35,6 +37,9 @@ def load_scores(self): self.tempo_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) def save_scores(self): + if not self.should_save: + return + self.should_save = False bt.logging.info("Saving scores") np.savez( self.neuron.config.neuron.full_path + "/state.npz", @@ -69,55 +74,30 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): # Update the hotkeys. self.hotkeys = copy.deepcopy(new_hotkeys) + self.should_save = True + self.should_set_weights = True def update_scores(self, rewards: np.ndarray, uids: List[int], session_number: int): if self.scoring_session_number != session_number: - # In the new session, reset the scores self.scoring_session_number = session_number self.tempo_accumulated_scores = np.zeros_like(self.scores) - # Check if rewards contains NaN values. - if np.isnan(rewards).any(): - bt.logging.warning(f"NaN values detected in rewards: {rewards}") - rewards = np.nan_to_num(rewards, nan=0) - - # Ensure rewards is a numpy array. - rewards = np.asarray(rewards) - - # Check if `uids` is already a numpy array and copy it to avoid the warning. - if isinstance(uids, np.ndarray): - uids_array = uids.copy() - else: - uids_array = np.array(uids) - - # Handle edge case: If either rewards or uids_array is empty. - if rewards.size == 0 or uids_array.size == 0: - bt.logging.info(f"rewards: {rewards}, uids_array: {uids_array}") - bt.logging.warning( - "Either rewards or uids_array is empty. No updates will be performed." - ) - return - - # Check if sizes of rewards and uids_array match. - if rewards.size != uids_array.size: - raise ValueError( - f"Shape mismatch: rewards array of shape {rewards.shape} " - f"cannot be broadcast to uids array of shape {uids_array.shape}" - ) - - # Compute forward pass rewards, assumes uids are mutually exclusive. - # shape: [ metagraph.n ] scattered_rewards: np.ndarray = np.zeros_like(self.scores) - scattered_rewards[uids_array] = rewards - bt.logging.debug(f"Scattered rewards: {scattered_rewards}") - + scattered_rewards[uids] = rewards self.tempo_accumulated_scores: np.ndarray = scattered_rewards + self.tempo_accumulated_scores bt.logging.debug(f"Updated scores: {self.tempo_accumulated_scores}") + + self.should_save = True + self.should_set_weights = True def set_weights(self): """ Sets the validator weights to the metagraph hotkeys based on the scores it has received from the miners. The weights determine the trust and incentive level the validator assigns to miner nodes on the network. """ + if not self.should_set_weights: + return + self.should_set_weights = False + self.scores = np.zeros_like(self.scores) best_index = np.argmax(self.tempo_accumulated_scores) self.scores[best_index] = 1 @@ -133,45 +113,39 @@ def set_weights(self): # Compute raw_weights safely raw_weights = self.scores / norm + + with self.neuron.lock: + # Process the raw weights to final_weights via subtensor limitations. + ( + processed_weight_uids, + processed_weights, + ) = process_weights_for_netuid( + uids=self.neuron.metagraph.uids, + weights=raw_weights, + netuid=self.neuron.config.netuid, + subtensor=self.neuron.subtensor, + metagraph=self.neuron.metagraph, + ) - bt.logging.debug("raw_weights", raw_weights) - bt.logging.debug("raw_weight_uids", str(self.metagraph.uids.tolist())) - # Process the raw weights to final_weights via subtensor limitations. - ( - processed_weight_uids, - processed_weights, - ) = process_weights_for_netuid( - uids=self.metagraph.uids, - weights=raw_weights, - netuid=self.config.netuid, - subtensor=self.subtensor, - metagraph=self.metagraph, - ) - bt.logging.debug("processed_weights", processed_weights) - bt.logging.debug("processed_weight_uids", processed_weight_uids) - - # Convert to uint16 weights and uids. - ( - uint_uids, - uint_weights, - ) = convert_weights_and_uids_for_emit( - uids=processed_weight_uids, weights=processed_weights - ) - bt.logging.debug("uint_weights", uint_weights) - bt.logging.debug("uint_uids", uint_uids) - - # Set the weights on chain via our subtensor connection. - result, msg = self.neuron.subtensor.set_weights( - wallet=self.neuron.wallet, - netuid=self.neuron.config.netuid, - uids=uint_uids, - weights=uint_weights, - wait_for_finalization=False, - wait_for_inclusion=False, - version_key=self.neuron.spec_version, - ) - if result is True: - bt.logging.info("set_weights on chain successfully!") - else: - bt.logging.error("set_weights failed", msg) - \ No newline at end of file + # Convert to uint16 weights and uids. + ( + uint_uids, + uint_weights, + ) = convert_weights_and_uids_for_emit( + uids=processed_weight_uids, weights=processed_weights + ) + # Set the weights on chain via our subtensor connection. + result, msg = self.neuron.subtensor.set_weights( + wallet=self.neuron.wallet, + netuid=self.neuron.config.netuid, + uids=uint_uids, + weights=uint_weights, + wait_for_finalization=False, + wait_for_inclusion=False, + version_key=self.neuron.spec_version, + ) + if result is True: + bt.logging.info("set_weights on chain successfully!") + else: + bt.logging.error("set_weights failed", msg) + \ No newline at end of file diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 3f73e3d6..e0a00e7f 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -25,7 +25,7 @@ # Constants for block timing BLOCK_IN_SECONDS = 12 TEMPO_BLOCKS = 60 -MAX_VALIDATORS = 12 +MAX_VALIDATORS = 1 VALIDATOR_QUERY_PERIOD_BLOCKS = 10 ALL_VALIDATOR_QUERY_PERIOD_BLOCKS = MAX_VALIDATORS * VALIDATOR_QUERY_PERIOD_BLOCKS COMPETITION_PERIOD_BLOCKS = TEMPO_BLOCKS * 3 @@ -61,7 +61,7 @@ def __init__(self, config=None): self.query_miners_thread: Union[threading.Thread, None] = None self.score_thread: Union[threading.Thread, None] = None self.set_weights_thread: Union[threading.Thread, None] = None - self.lock = asyncio.Lock() + self.lock = threading.Lock() self.genie_validator = GenieValidator(neuron=self) self.score_manager = ScoreManager(neuron=self) @@ -89,7 +89,6 @@ def resync_metagraph(self): bt.logging.info( "Metagraph updated, re-syncing hotkeys, dendrite pool and moving averages" ) - self.score_manager.set_new_hotkeys(self.metagraph.hotkeys) def save_state(self): @@ -147,53 +146,58 @@ def serve_axon(self): bt.logging.error(f"Failed to serve Axon with exception: {e}") def query_miners_loop(self): - bt.logging.info(f"Validator starting at block: {self.block}") + bt.logging.info(f"Query miners loop starting") while True: + time.sleep(1) try: - self.sync() - validator_index = get_validator_index(self.neuron.metagraph, self.neuron.uid) - if validator_index == -1: - continue + with self.lock: + self.sync() + validator_index = get_validator_index(self, self.uid) + if validator_index == -1: + continue + validator_index = 0 # Only allow first N validators to query miners - if validator_index > MAX_VALIDATORS: - continue + # if validator_index > MAX_VALIDATORS: + # continue # Calculate query period blocks - current_block = self.neuron.block + with self.lock: + current_block = self.block + start_period_block = ( (current_block // ALL_VALIDATOR_QUERY_PERIOD_BLOCKS) * ALL_VALIDATOR_QUERY_PERIOD_BLOCKS + validator_index * VALIDATOR_QUERY_PERIOD_BLOCKS ) end_period_block = start_period_block + ALL_VALIDATOR_QUERY_PERIOD_BLOCKS / 2 - + bt.logging.info(f"Query period blocks - " + f"Start: {start_period_block}, " + f"End: {end_period_block}, " + f"Current: {current_block}") # Sleep if outside query window if current_block < start_period_block: + bt.logging.info(f"Sleeping for {start_period_block - current_block} blocks before querying miners") time.sleep((start_period_block - current_block) * BLOCK_IN_SECONDS) elif current_block >= end_period_block: - sleep_time = (start_period_block - current_block + ALL_VALIDATOR_QUERY_PERIOD_BLOCKS) * BLOCK_IN_SECONDS - time.sleep(sleep_time) + sleep_blocks = (start_period_block - current_block + ALL_VALIDATOR_QUERY_PERIOD_BLOCKS) + bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") + time.sleep(sleep_blocks * BLOCK_IN_SECONDS) continue self.query_miners_event_loop.run_until_complete(self.genie_validator.query_miners()) - except KeyboardInterrupt: - bt.logging.info("Keyboard interrupt detected, stopping query miners loop") - break except Exception as e: bt.logging.error(f"Error during query miners loop: {str(e)}") if self.should_exit: break - time.sleep(1) def score_loop(self): bt.logging.info(f"Scoring loop starting") while True: + time.sleep(1) try: - self.sync() + with self.lock: + self.sync() self.score_event_loop.run_until_complete(self.genie_validator.score()) - except KeyboardInterrupt: - bt.logging.info("Keyboard interrupt detected, stopping scoring loop") - break except Exception as e: bt.logging.error(f"Error during scoring: {str(e)}") if self.should_exit: @@ -203,12 +207,12 @@ def score_loop(self): def synthensize_task_loop(self): bt.logging.info(f"Synthensize task loop starting") while True: + time.sleep(1) try: - self.sync() + with self.lock: + self.sync() + self.synthensize_task_event_loop.run_until_complete(self.genie_validator.synthensize_task()) - except KeyboardInterrupt: - bt.logging.info("Keyboard interrupt detected, stopping synthensize task loop") - break except Exception as e: bt.logging.error(f"Error during synthensize task: {str(e)}") if self.should_exit: @@ -221,10 +225,11 @@ def set_weights_loop(self): bt.logging.info(f"Set weights loop starting") while True: + time.sleep(1) try: - self.sync() - # Get current block number - current_block = self.block + with self.lock: + self.sync() + current_block = self.block # Calculate the end block number for the next weight setting period # This aligns with 3 tempo boundaries @@ -236,20 +241,23 @@ def set_weights_loop(self): # Start setting weights 50 blocks before the end set_weights_start_block = set_weights_end_block - SET_WEIGHTS_PERIOD_BLOCKS - + bt.logging.info( + f"Set weights blocks - " + f"Start: {set_weights_start_block}, " + f"End: {set_weights_end_block}, " + f"Current: {current_block}" + ) # Check if we're in the weight setting window if (current_block >= set_weights_start_block and current_block < set_weights_end_block): + bt.logging.info(f"Setting weights at block {current_block}") - self.set_weights_event_loop.run_until_complete(self.score_manager.set_weights()) + self.score_manager.set_weights() else: # Sleep until next weight setting window sleep_blocks = set_weights_start_block - current_block + bt.logging.info(f"Sleeping for {sleep_blocks} blocks before setting weights") time.sleep(sleep_blocks * BLOCK_IN_SECONDS) - - except KeyboardInterrupt: - bt.logging.info("Keyboard interrupt detected, stopping set weights loop") - break except Exception as e: bt.logging.error(f"Error during set weights: {str(e)}") if self.should_exit: @@ -261,10 +269,10 @@ def run_background_threads(self): self.is_running = True self.should_exit = False - self.synthensize_task_thread = threading.Thread(target=self.synthensize_task_loop) - self.query_miners_thread = threading.Thread(target=self.query_miners_loop) - self.score_thread = threading.Thread(target=self.score_loop) - self.set_weights_thread = threading.Thread(target=self.set_weights_loop) + self.synthensize_task_thread = threading.Thread(target=self.synthensize_task_loop, daemon=True) + self.query_miners_thread = threading.Thread(target=self.query_miners_loop, daemon=True) + self.score_thread = threading.Thread(target=self.score_loop, daemon=True) + self.set_weights_thread = threading.Thread(target=self.set_weights_loop, daemon=True) self.synthensize_task_thread.start() self.query_miners_thread.start() @@ -301,4 +309,9 @@ def __exit__(self, exc_type, exc_value, traceback): if __name__ == "__main__": with Validator() as validator: while True: - time.sleep(5) + try: + time.sleep(5) + except KeyboardInterrupt: + bt.logging.info("Keyboard interrupt detected, stopping main loop") + break + diff --git a/webgenie/base/validator.py b/webgenie/base/validator.py index ab729a96..17dc6588 100644 --- a/webgenie/base/validator.py +++ b/webgenie/base/validator.py @@ -33,6 +33,7 @@ from webgenie.mock import MockDendrite from webgenie.utils.config import add_validator_args + class BaseValidatorNeuron(BaseNeuron): """ Base class for Bittensor validators. Your validator should inherit from this class. diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py index 76f64854..820ac4d2 100644 --- a/webgenie/challenges/challenge.py +++ b/webgenie/challenges/challenge.py @@ -30,7 +30,7 @@ class AccuracyChallenge(Challenge): competition_type: str = Field(default=ACCURACY_COMPETITION_TYPE, description="The type of competition") async def calculate_scores(self) -> dict[str, np.ndarray]: - scores = await self.task_generator.calculate_scores(self.task, self.solutions) + scores = await self.task.task_generator.calculate_scores(self.task, self.solutions) aggregated_scores = scores[ACCURACY_METRIC_NAME] * 0.9 + scores[QUALITY_METRIC_NAME] * 0.1 return aggregated_scores, scores @@ -39,7 +39,7 @@ class SeoChallenge(Challenge): competition_type: str = Field(default=SEO_COMPETITION_TYPE, description="The type of competition") async def calculate_scores(self) -> dict[str, np.ndarray]: - scores = await self.task_generator.calculate_scores(self.task, self.solutions) + scores = await self.task.task_generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] seo_scores = scores[SEO_METRIC_NAME] aggregated_scores = np.where(accuracy_scores > 0.7, seo_scores, 0) @@ -50,7 +50,7 @@ class QualityChallenge(Challenge): competition_type: str = Field(default=QUALITY_COMPETITION_TYPE, description="The type of competition") async def calculate_scores(self) -> dict[str, np.ndarray]: - scores = await self.task_generator.calculate_scores(self.task, self.solutions) + scores = await self.task.task_generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] quality_scores = scores[QUALITY_METRIC_NAME] aggregated_scores = np.where(accuracy_scores > 0.7, quality_scores, 0) diff --git a/webgenie/utils/uids.py b/webgenie/utils/uids.py index d15ede9c..ee12fe15 100644 --- a/webgenie/utils/uids.py +++ b/webgenie/utils/uids.py @@ -5,7 +5,8 @@ def is_validator(metagraph: "bt.metagraph.Metagraph", uid: int, vpermit_tao_limit: int) -> bool: - return metagraph.S[uid] >= vpermit_tao_limit + return True + #return metagraph.S[uid] >= vpermit_tao_limit def get_validator_index(self, uid: int) -> int: From e9390989ca179f6a6c807932c976d9c022f68569 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 17 Jan 2025 07:02:46 -0600 Subject: [PATCH 217/554] fix: fix bugs --- webgenie/challenges/challenge.py | 6 +- webgenie/datasets/random_website_dataset.py | 145 ++++++++++---------- 2 files changed, 78 insertions(+), 73 deletions(-) diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py index 820ac4d2..8545320b 100644 --- a/webgenie/challenges/challenge.py +++ b/webgenie/challenges/challenge.py @@ -30,7 +30,7 @@ class AccuracyChallenge(Challenge): competition_type: str = Field(default=ACCURACY_COMPETITION_TYPE, description="The type of competition") async def calculate_scores(self) -> dict[str, np.ndarray]: - scores = await self.task.task_generator.calculate_scores(self.task, self.solutions) + scores = await self.task.generator.calculate_scores(self.task, self.solutions) aggregated_scores = scores[ACCURACY_METRIC_NAME] * 0.9 + scores[QUALITY_METRIC_NAME] * 0.1 return aggregated_scores, scores @@ -39,7 +39,7 @@ class SeoChallenge(Challenge): competition_type: str = Field(default=SEO_COMPETITION_TYPE, description="The type of competition") async def calculate_scores(self) -> dict[str, np.ndarray]: - scores = await self.task.task_generator.calculate_scores(self.task, self.solutions) + scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] seo_scores = scores[SEO_METRIC_NAME] aggregated_scores = np.where(accuracy_scores > 0.7, seo_scores, 0) @@ -50,7 +50,7 @@ class QualityChallenge(Challenge): competition_type: str = Field(default=QUALITY_COMPETITION_TYPE, description="The type of competition") async def calculate_scores(self) -> dict[str, np.ndarray]: - scores = await self.task.task_generator.calculate_scores(self.task, self.solutions) + scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] quality_scores = scores[QUALITY_METRIC_NAME] aggregated_scores = np.where(accuracy_scores > 0.7, quality_scores, 0) diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index a115e2ff..4cf002bb 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -37,78 +37,83 @@ async def get_random_website_url(self, retries: int = 3) -> Optional[str]: return None async def get_rendered_html(self, url): - async with async_playwright() as p: - browser = await p.chromium.launch() - page = await browser.new_page() - await page.goto(url) - # Wait for 10 seconds to ensure content loads - await page.wait_for_timeout(GROUND_TRUTH_HTML_LOAD_TIME) - rendered_html = await page.content() # Get the rendered HTML - await browser.close() - - # Parse the HTML with BeautifulSoup - soup = BeautifulSoup(rendered_html, 'html.parser') - - # Attributes that need to be absolute - attributes = ['href', 'src', 'srcset'] - - # Find all elements with 'href', 'src', or 'srcset' attributes - for attr in attributes: - for element in soup.find_all(attrs={attr: True}): - original_attr = element[attr] - # Handle 'srcset' differently because it can contain multiple URLs - if attr == 'srcset': - new_urls = [] - parts = original_attr.split(',') - for part in parts: - # Split on whitespace and check if there is a descriptor - pieces = part.strip().split(maxsplit=1) - if len(pieces) == 2: - url_part, descriptor = pieces - else: - url_part = pieces[0] - descriptor = '' - - new_url = urljoin(url, url_part.strip()) - if descriptor: - new_urls.append(f"{new_url} {descriptor}") - else: - new_urls.append(new_url) - - element[attr] = ', '.join(new_urls) - else: - element[attr] = urljoin(url, original_attr) - - # Return the modified HTML as a string - return str(soup) + try: + async with async_playwright() as p: + browser = await p.chromium.launch() + page = await browser.new_page() + await page.goto(url) + # Wait for 10 seconds to ensure content loads + await page.wait_for_timeout(GROUND_TRUTH_HTML_LOAD_TIME) + rendered_html = await page.content() # Get the rendered HTML + await browser.close() + + # Parse the HTML with BeautifulSoup + soup = BeautifulSoup(rendered_html, 'html.parser') + + # Attributes that need to be absolute + attributes = ['href', 'src', 'srcset'] + + # Find all elements with 'href', 'src', or 'srcset' attributes + for attr in attributes: + for element in soup.find_all(attrs={attr: True}): + original_attr = element[attr] + # Handle 'srcset' differently because it can contain multiple URLs + if attr == 'srcset': + new_urls = [] + parts = original_attr.split(',') + for part in parts: + # Split on whitespace and check if there is a descriptor + pieces = part.strip().split(maxsplit=1) + if len(pieces) == 2: + url_part, descriptor = pieces + else: + url_part = pieces[0] + descriptor = '' + + new_url = urljoin(url, url_part.strip()) + if descriptor: + new_urls.append(f"{new_url} {descriptor}") + else: + new_urls.append(new_url) + + element[attr] = ', '.join(new_urls) + else: + element[attr] = urljoin(url, original_attr) + + # Return the modified HTML as a string + return str(soup) + except Exception as e: + bt.logging.error(f"Error in get_rendered_html: {e}") + raise Exception(f"Error in get_rendered_html: {e}") async def shorten_html(self, html: str, max_children: int = 20, max_text_length: int = 400)->str: - soup = BeautifulSoup(html, "html.parser") - def traverse(node): - # If it’s a tag, we might need to limit its children - if isinstance(node, Tag): - # If node has too many children, remove the extras - if len(node.contents) > max_children: - # Keep only the first max_children - node.contents = node.contents[:max_children] - - # Recurse on each child - for child in node.contents: - traverse(child) - - # If it’s a text node, shorten if needed - elif isinstance(node, NavigableString): - text_str = str(node) - if len(text_str) > max_text_length: - shortened = text_str[:max_text_length] + "..." - node.replace_with(shortened) - - # Start traversal from the root soup element (html, body, etc.) - traverse(soup) - - return str(soup) - - + try: + soup = BeautifulSoup(html, "html.parser") + def traverse(node): + # If it’s a tag, we might need to limit its children + if isinstance(node, Tag): + # If node has too many children, remove the extras + if len(node.contents) > max_children: + # Keep only the first max_children + node.contents = node.contents[:max_children] + + # Recurse on each child + for child in node.contents: + traverse(child) + + # If it’s a text node, shorten if needed + elif isinstance(node, NavigableString): + text_str = str(node) + if len(text_str) > max_text_length: + shortened = text_str[:max_text_length] + "..." + node.replace_with(shortened) + + # Start traversal from the root soup element (html, body, etc.) + traverse(soup) + return str(soup) + except Exception as e: + bt.logging.error(f"Error in shorten_html: {e}") + raise Exception(f"Error in shorten_html: {e}") async def generate_context(self)->DatasetEntry: try: From 2eb6310c7466580edbb0eb6f593448b090fb9cba Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 17 Jan 2025 07:40:06 -0600 Subject: [PATCH 218/554] chore: update html helper func --- neurons/validators/genie_validator.py | 4 +- webgenie/helpers/htmls.py | 93 +++++++++++++++------------ 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 7b5adbd5..98bc49eb 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -20,7 +20,7 @@ upload_competition, upload_competition_result, ) -from webgenie.helpers.htmls import preprocess_html, validate_resources +from webgenie.helpers.htmls import preprocess_html, is_valid_resources from webgenie.helpers.images import image_debug_str from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.tasks import Solution, ImageTaskGenerator @@ -184,7 +184,7 @@ async def process_synapse(self, synapse: bt.Synapse) -> bt.Synapse: html = preprocess_html(synapse.html) if not html: return None - if not validate_resources(html): + if not is_valid_resources(html): return None synapse.html = html return synapse diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 468742ed..bc1f8086 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -1,24 +1,26 @@ import bittensor as bt import asyncio import os -from playwright.async_api import async_playwright +import re +import uuid + from bs4 import BeautifulSoup from lxml import etree +from lxml.etree import XMLSyntaxError from PIL import Image -import time -import re -import uuid +from playwright.async_api import async_playwright from webgenie.constants import ( - SCREENSHOT_SCRIPT_PATH, WORK_DIR, PLACE_HOLDER_IMAGE_URL, - PYTHON_CMD, ) from webgenie.helpers.images import image_to_base64 + - -def validate_resources(html: str) -> bool: +def is_valid_resources(html_content: str) -> bool: + """ + Check if the resources in the HTML content are valid. + """ # List of allowed patterns for CSS and JavaScript resources allowed_patterns = [ r"https?://cdn.jsdelivr.net/npm/tailwindcss@[^/]+/dist/tailwind.min.css", @@ -27,7 +29,7 @@ def validate_resources(html: str) -> bool: r"https?://stackpath.bootstrapcdn.com/bootstrap/[^/]+/js/bootstrap.bundle.min.js", ] - soup = BeautifulSoup(html, 'html.parser') + soup = BeautifulSoup(html_content, 'html.parser') resources = soup.find_all(['link', 'script']) for resource in resources: @@ -43,16 +45,27 @@ def validate_resources(html: str) -> bool: return True -def is_valid_html(html: str): +def is_valid_html(html_content: str) -> bool: + """ + Check if the HTML is valid. + """ try: - soup = BeautifulSoup(html, 'html.parser') - return True + # Use XML parser to enforce strict validation + # Force the document to follow XML standards, which requires properly closed tags + etree.XML(html_content) # Strict XML parser to catch malformed tags + return True # If parsing succeeds, the HTML is valid. + + except XMLSyntaxError: + return False # If parsing fails, the HTML is invalid due to malformed tags. except Exception as e: - bt.logging.debug(f"Error during HTML parsing: {e}") - return False + print(f"An error occurred: {e}") + return False # Any other error, return False. -def seperate_html_css(html_content: str): +def seperate_html_css(html_content: str) -> tuple[str, str]: + """ + Seperate the HTML and CSS from the HTML content. + """ soup = BeautifulSoup(html_content, 'html.parser') css = '' @@ -72,10 +85,13 @@ def seperate_html_css(html_content: str): return cleaned_html, css -async def html_to_screenshot(html: str, page_load_time: int = 1000) -> str: +async def html_to_screenshot(html_content: str, page_load_time: int = 1000) -> str: + """ + Take a screenshot of the HTML content. + """ html_path = f"{WORK_DIR}/screenshot_{uuid.uuid4()}.html" with open(html_path, "w") as f: - f.write(html) + f.write(html_content) png_path = f"{WORK_DIR}/screenshot_{uuid.uuid4()}.png" url = f"file://{os.path.abspath(html_path)}" @@ -99,20 +115,26 @@ async def html_to_screenshot(html: str, page_load_time: int = 1000) -> str: img.save(png_path) await asyncio.sleep(0.1) - base64_image = image_to_base64(png_path) + await asyncio.sleep(0.1) os.remove(html_path) os.remove(png_path) return base64_image -def beautify_html(html: str) -> str: - soup = BeautifulSoup(html, 'html.parser') +def format_html(html_content: str) -> str: + """ + Format the HTML content. + """ + soup = BeautifulSoup(html_content, 'html.parser') return soup.prettify() -def replace_image_sources(html_content, new_url=PLACE_HOLDER_IMAGE_URL): +def replace_image_sources(html_content: str, new_url: str = PLACE_HOLDER_IMAGE_URL) -> str: + """ + Replace the image sources in the HTML content. + """ soup = BeautifulSoup(html_content, 'html.parser') # Replace 'src' attribute in tags @@ -144,15 +166,18 @@ def replace_image_sources(html_content, new_url=PLACE_HOLDER_IMAGE_URL): return str(soup) -def preprocess_html(html: str) -> str: - if not is_valid_html(html): +def preprocess_html(html_content: str) -> str: + """ + Preprocess the HTML content. + """ + if not is_valid_html(html_content): return "" - html = beautify_html(html) - html = replace_image_sources(html) - return html + html_content = format_html(html_content) + html_content = replace_image_sources(html_content) + return html_content -def is_empty_html(html: str) -> bool: +def is_empty_html(html_content: str) -> bool: """Check if HTML body is empty or missing. Args: @@ -161,7 +186,7 @@ def is_empty_html(html: str) -> bool: Returns: bool: True if body is empty or missing, False otherwise """ - soup = BeautifulSoup(html, 'html.parser') + soup = BeautifulSoup(html_content, 'html.parser') body = soup.find('body') if not body: @@ -171,15 +196,3 @@ def is_empty_html(html: str) -> bool: return True return False - - -if __name__ == "__main__": - html = """ - - -

Hello, World!

- - - """ - - print(preprocess_html(html)) \ No newline at end of file From 44b579e4f77bebec30296d5704dbe8e5541a08c2 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 17 Jan 2025 08:32:23 -0600 Subject: [PATCH 219/554] fix: fix bugs --- neurons/validators/genie_validator.py | 12 ++++++++---- neurons/validators/score_manager.py | 1 + neurons/validators/validator.py | 2 +- webgenie/datasets/random_website_dataset.py | 4 +++- webgenie/helpers/htmls.py | 13 ++++--------- webgenie/rewards/visual_reward/visual_reward.py | 4 ++-- 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 98bc49eb..ad0f659d 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -120,9 +120,13 @@ async def score(self): solutions = challenge.solutions miner_uids = [solution.miner_uid for solution in solutions] aggregated_scores, scores = await challenge.calculate_scores() - + bt.logging.success(f"scores: {scores}") bt.logging.success(f"Final scores for {miner_uids}: {aggregated_scores}") - self.neuron.score_manager.update_scores(miner_uids, scores, challenge.session_number) + self.neuron.score_manager.update_scores( + scores, + miner_uids, + challenge.session_number, + ) async def synthensize_task(self): try: @@ -133,12 +137,12 @@ async def synthensize_task(self): bt.logging.info(f"Synthensize task") - competition, _ = random.choices( + task_generator, _ = random.choices( self.task_generators, weights=[weight for _, weight in self.task_generators], )[0] - task, synapse = await competition.generate_task() + task, synapse = await task_generator.generate_task() with self.neuron.lock: self.synthetic_tasks.append((task, synapse)) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 29683892..e963ad18 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -11,6 +11,7 @@ ) from webgenie.base.neuron import BaseNeuron + class ScoreManager: def __init__(self, neuron: BaseNeuron): self.neuron = neuron diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index e0a00e7f..e1a640e2 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -251,7 +251,7 @@ def set_weights_loop(self): if (current_block >= set_weights_start_block and current_block < set_weights_end_block): - bt.logging.info(f"Setting weights at block {current_block}") + bt.logging.info(f"Trying to set weights at block {current_block}") self.score_manager.set_weights() else: # Sleep until next weight setting window diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index 4cf002bb..e5bcc9a2 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -79,7 +79,9 @@ async def get_rendered_html(self, url): element[attr] = ', '.join(new_urls) else: element[attr] = urljoin(url, original_attr) - + # Remove all script tags + for script in soup.find_all('script'): + script.decompose() # Return the modified HTML as a string return str(soup) except Exception as e: diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index bc1f8086..23f19c41 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -50,16 +50,11 @@ def is_valid_html(html_content: str) -> bool: Check if the HTML is valid. """ try: - # Use XML parser to enforce strict validation - # Force the document to follow XML standards, which requires properly closed tags - etree.XML(html_content) # Strict XML parser to catch malformed tags - return True # If parsing succeeds, the HTML is valid. - - except XMLSyntaxError: - return False # If parsing fails, the HTML is invalid due to malformed tags. + soup = BeautifulSoup(html_content, 'html.parser') + return True except Exception as e: - print(f"An error occurred: {e}") - return False # Any other error, return False. + bt.logging.error(f"An error occurred: {e}") + return False def seperate_html_css(html_content: str) -> tuple[str, str]: diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index 06f5bbaf..06c8ae1d 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -40,8 +40,8 @@ async def reward_worker(self, task: Task, solutions: List[Solution], current_wor high_level_scores = await high_level_matching_score(miner_html_paths, original_html_path) low_level_scores = await low_level_matching_score(miner_html_paths, original_html_path) - bt.logging.debug(f"Visual scores: {high_level_scores}") - bt.logging.debug(f"Visual scores: {low_level_scores}") + bt.logging.debug(f"High level visual scores: {high_level_scores}") + bt.logging.debug(f"Low level visual scores: {low_level_scores}") scores = high_level_scores * 0.3 + low_level_scores * 0.7 await stop_browser() From 49ad6ecf167f346b6c037bd20eccd3e1a47648e0 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 17 Jan 2025 09:55:47 -0600 Subject: [PATCH 220/554] chore: fix bugs --- webgenie.db | Bin 20480 -> 0 bytes webgenie/datasets/random_website_dataset.py | 55 ++++++++++++-------- 2 files changed, 32 insertions(+), 23 deletions(-) delete mode 100644 webgenie.db diff --git a/webgenie.db b/webgenie.db deleted file mode 100644 index 7f82c3a3e4dc91fff10bde2320b0d5ab89c3ab57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmeI%?{AYp7zc1zJL?*^^0L=P+Uc^_^-BWW4+{5uo+l)v!wV;toP5mkX(-7{qh*+;@tP3BFluVwRr_kw z{rak-&Ss_kuV&47{&lv0_}i#IDU5?(hd&Qms(}px5P$##AOHafKmY;|_PNow z2j{kbN#4>+(v2fakM%vD4$fRX*4^O=Z2HN?j90_F%CjPkn9K_~VG}t`s*?{kP%%ie(e8B2o8B^prK%{8 zJ8SDM$L?G4dI9~dJd$Xmnqt9neiaMJbCscl)a0)27MbBBR%tJ)?-8qW)^ zi+NR_BX@8yqAF)`bWF-csnk8&dDMQ@F^xEl_!lu(HOE<4$ZXkXWvuK)dDh?ZkM#>@ zUmsfffDHl=fB*y_009U<00Izz00bZafo&CN>cH^*zpd#-To8Z&1Rwwb2tWV=5P$## zAOL}-0RI0+AwU2E5P$##AOHafKmY;|fB*!xUjX0#+n-}Z2muH{00Izz00bZa0SG_< H0ucBEHhCdm diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index e5bcc9a2..ab305d7a 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -88,34 +88,43 @@ async def get_rendered_html(self, url): bt.logging.error(f"Error in get_rendered_html: {e}") raise Exception(f"Error in get_rendered_html: {e}") - async def shorten_html(self, html: str, max_children: int = 20, max_text_length: int = 400)->str: - try: - soup = BeautifulSoup(html, "html.parser") - def traverse(node): - # If it’s a tag, we might need to limit its children - if isinstance(node, Tag): - # If node has too many children, remove the extras - if len(node.contents) > max_children: - # Keep only the first max_children - node.contents = node.contents[:max_children] - - # Recurse on each child - for child in node.contents: - traverse(child) + + async def shorten_html(self, html_content, max_p_count = 10, max_text_length = 200): + """ + Removes excess

tags and trims text inside

tags if the text length exceeds the max limit. - # If it’s a text node, shorten if needed - elif isinstance(node, NavigableString): - text_str = str(node) - if len(text_str) > max_text_length: - shortened = text_str[:max_text_length] + "..." - node.replace_with(shortened) + :param html_content: The HTML content as a string. + :param max_p_count: The maximum number of

tags allowed in any parent tag. + :param max_text_length: The maximum length of text allowed inside

tags. + :return: Modified HTML content with excess

tags removed and text inside

tags shortened. + """ + try: + soup = BeautifulSoup(html_content, 'html.parser') + + # Find all tags that contain

as direct children + for tag in soup.find_all(True): # True will find all tags + # Find only

tags as direct children (not nested

tags) + p_tags = [child for child in tag.find_all('p', recursive=False)] + + if len(p_tags) > max_p_count: + # Remove excess

tags + excess_p_tags = p_tags[max_p_count:] + for p_tag in excess_p_tags: + p_tag.decompose() # Remove the excess

tag + + # Traverse through all

tags and handle text nodes inside them + for p_tag in soup.find_all('p'): # Find all

tags + for child in p_tag.contents: + if isinstance(child, NavigableString): + text_str = str(child) + if len(text_str) > max_text_length: + shortened = text_str[:max_text_length] + "..." # Shorten the text + child.replace_with(shortened) # Replace the original text with the shortened version - # Start traversal from the root soup element (html, body, etc.) - traverse(soup) return str(soup) except Exception as e: bt.logging.error(f"Error in shorten_html: {e}") - raise Exception(f"Error in shorten_html: {e}") + raise e async def generate_context(self)->DatasetEntry: try: From 6b3ce87af33e3763e140742dab5ba1f3ea081d6f Mon Sep 17 00:00:00 2001 From: Sangar <8627971+sangar-1028@users.noreply.github.com> Date: Fri, 17 Jan 2025 09:58:42 -0600 Subject: [PATCH 221/554] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e368b8ea..a4441663 100644 --- a/README.md +++ b/README.md @@ -146,10 +146,10 @@ pm2 start --name auto_update auto_update.sh - Enable Text & image inputs - [x] Incentive mechanism v1 - Generate pure HTML/CSS web pages from text & image based prompts -- [ ] Begin marketing for brand awareness and interest -- [ ] Launch on mainnet +- [x] Begin marketing for brand awareness and interest ### Phase 2: Upgrade (Q1 2025) +- [ ] Launch on mainnet - [ ] Build a leaderboard to track miner performance and progress - [ ] Upgrade front-end application to v2 - Online IDE like code sandbox and auto-deployment with one click From 36d9cf7243e3c3e1339dc97ddbc71fcac7f0f0fd Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Fri, 17 Jan 2025 10:37:37 -0600 Subject: [PATCH 222/554] chore: udpate readme --- README.md | 71 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index a4441663..ab654080 100644 --- a/README.md +++ b/README.md @@ -41,60 +41,63 @@ The WebGenieAI subnet incentivizes miners and validators to ensure high-quality - Evaluation: Validators evaluate the outputs produced by miners. The evaluation criteria include accuracy, code quality, and performance metrics. - Ranking and Rewarding: Validators rank the miners according to their performance. The Bittensor blockchain’s Yuma Consensus mechanism determines the TAO rewards distribution based on these rankings. -## Evaluation Process +![Webgenie Subnet workflow](docs/webgenie-workflow.png "WebGenieAI workflow") -1) Image to HTML Model +### Evaluation Process -### Automatic evaluation of ImageToHTML task for design-wise [Ref: [[1]](#references)] -We automatically evaluate generated webpages by calculating the similarity between the original input image and the rendered screenshot of generated webpage. -We break down the evaluation into both high-level visual similarity and low-level element matching. +1. Image to HTML Model -#### High-level Visual Similarity +- ### Automatic evaluation of ImageToHTML task for design-wise [Ref: [[1]](#references)] + We automatically evaluate generated webpages by calculating the similarity between the original input image and the rendered screenshot of generated webpage. + We break down the evaluation into both high-level visual similarity and low-level element matching. -To evaluate the visual similarity of $I_R$ and $I_G$, we use the similarity of their CLIP embedding, denoted as CLIP($I_R$, $I_G$). Specifically, we extract features by CLIP-ViT-B/32 after resizing screenshots to squares. -To rule out the texts in the screenshots, we use the inpainting algorithm from [Telea](https://docs.opencv.org/4.3.0/df/d3d/tutorial_py_inpainting.html) to mask all detected text boxes using their bounding box coordinates. + - #### High-level Visual Similarity -#### Low-level Element Matching + To evaluate the visual similarity of $I_R$ and $I_G$, we use the similarity of their CLIP embedding, denoted as CLIP($I_R$, $I_G$). Specifically, we extract features by CLIP-ViT-B/32 after resizing screenshots to squares. + To rule out the texts in the screenshots, we use the inpainting algorithm from [Telea](https://docs.opencv.org/4.3.0/df/d3d/tutorial_py_inpainting.html) to mask all detected text boxes using their bounding box coordinates. -Metrics like CLIP similarity only capture the similarity of the overall images rather than the matching of all the details like text. Moreover, the metric itself does not offer any fine-grained breakdown to help diagnose model weaknesses. + - #### Low-level Element Matching -To complement that, we introduce a suite of element-matching metrics. Specifically, we consider whether the generated webpages manage to recall all visual elements, and whether the corresponding visual elements in the input image and generated webpages have aligned text content, position, and color. + Metrics like CLIP similarity only capture the similarity of the overall images rather than the matching of all the details like text. Moreover, the metric itself does not offer any fine-grained breakdown to help diagnose model weaknesses. -Given a reference webpage screenshot $I_R$ and a generated webpage screenshot $I_G$, we use a text detection module to output a set of detected visual element blocks for each: R = { $r_1$, $r_2$, ..., $r_m$ } and G = { $g_1$, $g_2$, ..., $g_n$ }, where each block contains its textual content and bounding box coordinates. + To complement that, we introduce a suite of element-matching metrics. Specifically, we consider whether the generated webpages manage to recall all visual elements, and whether the corresponding visual elements in the input image and generated webpages have aligned text content, position, and color. -Based on the two sets of detected blocks, we use the Jonker-Volgenant algorithm to get the optimal matching M between R and G based on text similarity, where (p, q) ∈ M indicates $r_p$ is matched with $g_q$. + Given a reference webpage screenshot $I_R$ and a generated webpage screenshot $I_G$, we use a text detection module to output a set of detected visual element blocks for each: R = { $r_1$, $r_2$, ..., $r_m$ } and G = { $g_1$, $g_2$, ..., $g_n$ }, where each block contains its textual content and bounding box coordinates. -Given R, G, and matched pairs in M, we evaluate similarity along the following aspects: -- **Block-Match**: The first desideratum of the task is that all visual elements from the image should be reproduced in the generated webpage, and the generated webpage should not hallucinate non-existent new elements. We measure this by computing the total sizes of all matched blocks divided by the total sizes of all blocks, including unmatched ones (either because the generated webpages missed them or because the generated webpages contain hallucinated blocks): + Based on the two sets of detected blocks, we use the Jonker-Volgenant algorithm to get the optimal matching M between R and G based on text similarity, where (p, q) ∈ M indicates $r_p$ is matched with $g_q$. -![Incentive Mechanism Formula](docs/incentive-fomula.png "WebGenieAI Incentive Formula") + Given R, G, and matched pairs in M, we evaluate similarity along the following aspects: + - **Block-Match**: The first desideratum of the task is that all visual elements from the image should be reproduced in the generated webpage, and the generated webpage should not hallucinate non-existent new elements. We measure this by computing the total sizes of all matched blocks divided by the total sizes of all blocks, including unmatched ones (either because the generated webpages missed them or because the generated webpages contain hallucinated blocks): -where S(·) returns the size of the blocks, $U_R$ and $U_G$ denotes the unmatched blocks in R -and G. The intuition here is that unmatched blocks will lower the score as they indicate -missing original blocks or generating hallucinated blocks, and the larger the unmatched -blocks are, the lower this score is. + ![Incentive Mechanism Formula](docs/incentive-fomula.png "WebGenieAI Incentive Formula") -- **Text**: Given two strings from two matched blocks $r_p$ and $g_q$, the text similarity **sim**text($r_p$, $g_q$) is calculated as twice the number of overlapping characters divided by the total number of characters in the two strings (character-level Sørensen-Dice similarity). The overall score is averaged across all matched pairs. + where S(·) returns the size of the blocks, $U_R$ and $U_G$ denotes the unmatched blocks in R + and G. The intuition here is that unmatched blocks will lower the score as they indicate + missing original blocks or generating hallucinated blocks, and the larger the unmatched + blocks are, the lower this score is. -- Position: The positioning of the blocks largely impacts the overall layout. For each matched pair (p, q), we calculate the position similarity **sim**pos($r_p$, $g_q$) = 1 − max(abs($x_q$ − $x_p$), abs($y_q$ − $y_p$)), where ($x_p$, $y_p$) and ($x_q$, $y_q$) are normalized coordinates (in [0, 1]) of $r_p$ and $g_q$’s centors. The overall score is averaged across all matched pairs. + - **Text**: Given two strings from two matched blocks $r_p$ and $g_q$, the text similarity **sim**text($r_p$, $g_q$) is calculated as twice the number of overlapping characters divided by the total number of characters in the two strings (character-level Sørensen-Dice similarity). The overall score is averaged across all matched pairs. -- Color: We use the [CIEDE2000](https://en.wikipedia.org/wiki/Color_difference) color difference formula to assess the perceptual difference between the colors of the generated text in block $g_q$ and the reference text in block $r_p$, denoted as **sim**color(rp, gq), where the formula considers the complexities of human color vision. The overall score is averaged across all matched pairs. + - Position: The positioning of the blocks largely impacts the overall layout. For each matched pair (p, q), we calculate the position similarity **sim**pos($r_p$, $g_q$) = 1 − max(abs($x_q$ − $x_p$), abs($y_q$ − $y_p$)), where ($x_p$, $y_p$) and ($x_q$, $y_q$) are normalized coordinates (in [0, 1]) of $r_p$ and $g_q$’s centors. The overall score is averaged across all matched pairs. -2) Text Prompt to Html Model + - Color: We use the [CIEDE2000](https://en.wikipedia.org/wiki/Color_difference) color difference formula to assess the perceptual difference between the colors of the generated text in block $g_q$ and the reference text in block $r_p$, denoted as **sim**color(rp, gq), where the formula considers the complexities of human color vision. The overall score is averaged across all matched pairs. -### Unsupervised Evaluation of Model by Round-Trip Correctness (Ref: [[2]](#references)) -We draw inspiration from a software testing technique known as property-based testing. It allows defining properties that must hold between inputs and outputs of a program (e.g., all items in the input list must also appear in the output list) Round-trip correctness is one such property (e.g., compressing and subsequently decompressing data must yield the original data). +2. Text Prompt to Html Model -Consider two forms of data X and Y, such as text prompt and HTML and two (probabilistic) models whose task is to “translate” from one form of data to the other, i.e., a forward model M : X → Y and a backward model M-1: Y → X. These models could be a single LLM prompted differently. +- ### Unsupervised Evaluation of Model by Round-Trip Correctness (Ref: [[2]](#references)) + We draw inspiration from a software testing technique known as property-based testing. It allows defining properties that must hold between inputs and outputs of a program (e.g., all items in the input list must also appear in the output list) Round-trip correctness is one such property (e.g., compressing and subsequently decompressing data must yield the original data). + + Consider two forms of data X and Y, such as text prompt and HTML and two (probabilistic) models whose task is to “translate” from one form of data to the other, i.e., a forward model M : X → Y and a backward model M-1: Y → X. These models could be a single LLM prompted differently. + + The central idea for unsupervised evaluation is the concept of round-trip correctness (RTC). Intuitively, for a “good” forward and backward model we expect ̂x =M-1 M(x) to be semantically equivalent to x. For example, we can describe the HTML code with text prompt in the forward pass and then generate back the code from the text prompt. To compute RTC we need some function sim(x, ̂x) that estimates the semantic equivalence between the original x and each predicted sample ̂x. Such functions may include discrete or continuous metrics such as exact match, BLEU and so on. + +- ### Supervised Evaluation of Model by CodeBERTScore + Let x is prompt, y is the ground truth html, ̂y is the generated html. + To evaluate the performance of the model, we can use [CodeBERTScore](https://github.com/neulab/code-bert-score). sim(y, ̂y ) = bert_score(y, ̂y) + CodeBERTScore is an evaluation metric for code generation, which builds on BERTScore. Instead of encoding only the generated tokens as in [BERTScore](https://huggingface.co/spaces/evaluate-metric/bertscore), CodeBERTScore also encodes the natural language input preceding the generated code, thus modeling the consistency between the generated code and its given natural language context as well. -The central idea for unsupervised evaluation is the concept of round-trip correctness (RTC). Intuitively, for a “good” forward and backward model we expect ̂x =M-1 M(x) to be semantically equivalent to x. For example, we can describe the HTML code with text prompt in the forward pass and then generate back the code from the text prompt. To compute RTC we need some function sim(x, ̂x) that estimates the semantic equivalence between the original x and each predicted sample ̂x. Such functions may include discrete or continuous metrics such as exact match, BLEU and so on. -### Supervised Evaluation of Model by CodeBERTScore -Let x is prompt, y is the ground truth html, ̂y is the generated html. -To evaluate the performance of the model, we can use [CodeBERTScore](https://github.com/neulab/code-bert-score). sim(y, ̂y ) = bert_score(y, ̂y) -CodeBERTScore is an evaluation metric for code generation, which builds on BERTScore. Instead of encoding only the generated tokens as in [BERTScore](https://huggingface.co/spaces/evaluate-metric/bertscore), CodeBERTScore also encodes the natural language input preceding the generated code, thus modeling the consistency between the generated code and its given natural language context as well. -![Webgenie Subnet workflow](docs/webgenie-workflow.png "WebGenieAI workflow") ### Example Scenario @@ -103,7 +106,7 @@ CodeBERTScore is an evaluation metric for code generation, which builds on BERTS - Generation: The miner generates the code for the application and submits it. - Evaluation: Validators review the submission: - Accuracy: Does the application have all the features mentioned in the prompt? - - Efficiency: Is the code optimized for performance? + - Efficiency: Is the code optimized for performance(Code quality and lighthouse score)? - Innovation: Does the application include any additional features or optimizations not explicitly requested but beneficial? - Ranking: Validators rank this submission against others. - Rewarding: Based on the ranking, the miner receives TAO rewards. From bfe39503b125f18b953b5606776ae1276d2215cc Mon Sep 17 00:00:00 2001 From: cardoso-topdev Date: Fri, 17 Jan 2025 10:42:19 -0600 Subject: [PATCH 223/554] chore: udpate the workflow image --- docs/webgenie-workflow.png | Bin 334436 -> 328738 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/webgenie-workflow.png b/docs/webgenie-workflow.png index 2a74fd929f4b392171ebbf6dc2e470d7f6511ac4..d5bf36f34803a8bfc0a9f5ae409cbcf7c1e1af52 100644 GIT binary patch literal 328738 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
NzPio}GsOz+=Jf0`b= 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 literal 334436 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 l*O2Yu-l6(g%h}5ZAR^ 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 Date: Fri, 17 Jan 2025 11:43:53 -0600 Subject: [PATCH 224/554] fix: fix bugs --- neurons/validators/genie_validator.py | 5 ++--- neurons/validators/validator.py | 3 ++- .../rewards/visual_reward/common/similarity.py | 1 - .../visual_reward/common/take_screenshot.py | 1 - .../low_level_matching_score.py | 1 - .../text_matching_score.py | 6 +----- webgenie/rewards/visual_reward/visual_reward.py | 14 +++++++++++--- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index ad0f659d..0630c2aa 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -95,7 +95,7 @@ async def query_miners(self): ) challenge.solutions = solutions - bt.logging.info(f"Received {len(solutions)} solutions") + bt.logging.info(f"Received {len(solutions)} valid solutions") with self.neuron.lock: self.miner_results.append(challenge) except Exception as e: @@ -123,7 +123,7 @@ async def score(self): bt.logging.success(f"scores: {scores}") bt.logging.success(f"Final scores for {miner_uids}: {aggregated_scores}") self.neuron.score_manager.update_scores( - scores, + aggregated_scores, miner_uids, challenge.session_number, ) @@ -183,7 +183,6 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag return synapse async def process_synapse(self, synapse: bt.Synapse) -> bt.Synapse: - bt.logging.debug(f"Processing synapse: {synapse.dendrite.status_code}") if synapse.dendrite.status_code == 200: html = preprocess_html(synapse.html) if not html: diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index e1a640e2..10bdfb17 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -249,7 +249,7 @@ def set_weights_loop(self): ) # Check if we're in the weight setting window if (current_block >= set_weights_start_block and - current_block < set_weights_end_block): + current_block <= set_weights_end_block): bt.logging.info(f"Trying to set weights at block {current_block}") self.score_manager.set_weights() @@ -279,6 +279,7 @@ def run_background_threads(self): self.score_thread.start() self.set_weights_thread.start() bt.logging.info("Started background threads") + bt.logging.info("=" * 40) def stop_background_threads(self): if self.is_running: diff --git a/webgenie/rewards/visual_reward/common/similarity.py b/webgenie/rewards/visual_reward/common/similarity.py index 56d410aa..20fd05c6 100644 --- a/webgenie/rewards/visual_reward/common/similarity.py +++ b/webgenie/rewards/visual_reward/common/similarity.py @@ -54,5 +54,4 @@ def calculate_visual_similarity( sift_score = match_sift_features(predicted_element.keypoints, predicted_element.descriptors, original_element.keypoints, original_element.descriptors) avg_color_score = color_similarity_ciede2000(predicted_element.avg_color, original_element.avg_color) - print(sift_score, avg_color_score) return sift_score * 0.5 + avg_color_score * 0.5 diff --git a/webgenie/rewards/visual_reward/common/take_screenshot.py b/webgenie/rewards/visual_reward/common/take_screenshot.py index e199ca11..84509b3f 100644 --- a/webgenie/rewards/visual_reward/common/take_screenshot.py +++ b/webgenie/rewards/visual_reward/common/take_screenshot.py @@ -12,7 +12,6 @@ async def take_screenshot(url, output_file_path, load_time = DEFAULT_LOAD_TIME, return if os.path.exists(output_file_path) and overwrite: os.remove(output_file_path) - print(f"Taking screenshot of {url} to {output_file_path}") try: page = await web_player["browser"].new_page() diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py index 11743cd9..08084612 100644 --- a/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py +++ b/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py @@ -31,7 +31,6 @@ async def low_level_matching_score(predict_html_path_list, original_html_path): input_score = calculate_input_matching_similarity(predicted_input_elements, original_input_elements) text_score = calculate_text_matching_similarity(predicted_text_elements, original_text_elements) score = button_score * 0.25 + input_score * 0.25 + text_score * 0.25 + anchor_score * 0.25 - print(button_score, input_score, text_score, anchor_score) results.append(score) return np.array(results) diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/text_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/text_matching_score.py index 2f743879..27067b26 100644 --- a/webgenie/rewards/visual_reward/low_level_matching_score/text_matching_score.py +++ b/webgenie/rewards/visual_reward/low_level_matching_score/text_matching_score.py @@ -35,7 +35,6 @@ def calculate_text_matching_similarity(predicted_elements, original_elements): color_similarity_sum = 0 for i , j in zip(row_ind, col_ind): - print(predicted_elements[i].text, original_elements[j].text) text_similarity = calculate_text_similarity(predicted_elements[i], original_elements[j]) if text_similarity < 0.5: continue @@ -56,7 +55,4 @@ def calculate_text_matching_similarity(predicted_elements, original_elements): block_similarity_score = block_similarity_sum / total_count color_similarity_score = color_similarity_sum / total_count - return text_similarity_score * 0.5 + block_similarity_score * 0.3 + color_similarity_score * 0.2 - - - + return text_similarity_score * 0.5 + block_similarity_score * 0.3 + color_similarity_score * 0.2 \ No newline at end of file diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index 06c8ae1d..1e31c1e0 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -36,9 +36,16 @@ async def reward_worker(self, task: Task, solutions: List[Solution], current_wor with open(path, "w") as f: f.write(solution.html) miner_html_paths.append(path) - - high_level_scores = await high_level_matching_score(miner_html_paths, original_html_path) - low_level_scores = await low_level_matching_score(miner_html_paths, original_html_path) + try: + high_level_scores = await high_level_matching_score(miner_html_paths, original_html_path) + except Exception as e: + bt.logging.error(f"Error in high_level_matching_score: {e}") + high_level_scores = np.zeros(len(miner_html_paths)) + try: + low_level_scores = await low_level_matching_score(miner_html_paths, original_html_path) + except Exception as e: + bt.logging.error(f"Error in low_level_matching_score: {e}") + low_level_scores = np.zeros(len(miner_html_paths)) bt.logging.debug(f"High level visual scores: {high_level_scores}") bt.logging.debug(f"Low level visual scores: {low_level_scores}") @@ -46,6 +53,7 @@ async def reward_worker(self, task: Task, solutions: List[Solution], current_wor scores = high_level_scores * 0.3 + low_level_scores * 0.7 await stop_browser() return scores + def sync_reward_worker(self, task: Task, solutions: List[Solution], current_work_dir: str) -> np.ndarray: return asyncio.run(self.reward_worker(task, solutions, current_work_dir)) From 44219d3aba3d3c3ee4f6caf8a1cdd3b95bd9aefc Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 17 Jan 2025 13:04:03 -0600 Subject: [PATCH 225/554] fix: fix bugs --- neurons/validators/score_manager.py | 4 +- .../common/extract_html_elements.py | 42 ++++++++++++++----- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index e963ad18..82726c94 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -97,7 +97,6 @@ def set_weights(self): """ if not self.should_set_weights: return - self.should_set_weights = False self.scores = np.zeros_like(self.scores) best_index = np.argmax(self.tempo_accumulated_scores) @@ -146,7 +145,8 @@ def set_weights(self): version_key=self.neuron.spec_version, ) if result is True: - bt.logging.info("set_weights on chain successfully!") + bt.logging.success("set_weights on chain successfully!") + self.should_set_weights = False else: bt.logging.error("set_weights failed", msg) \ No newline at end of file diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index 4b616566..6a9e31df 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -26,27 +26,38 @@ class HTMLElement(BaseModel): def parse_rgb_string(rgb_str: str) -> tuple[int, int, int]: - """Convert RGB color string like 'rgb(23, 34, 45)' to (23, 34, 45) tuple.""" - # Extract numbers from rgb(r,g,b) format using string manipulation - rgb_values = rgb_str.strip().removeprefix('rgb(').removesuffix(')').split(',') - return tuple(int(v.strip()) for v in rgb_values) + """Convert RGB/RGBA color string like 'rgb(23, 34, 45)' or 'rgba(23, 34, 45, 0.5)' to (23, 34, 45) tuple.""" + # Handle both rgb and rgba formats + rgb_str = rgb_str.strip() + if rgb_str.startswith('rgba'): + rgb_str = rgb_str.removeprefix('rgba(') + else: + rgb_str = rgb_str.removeprefix('rgb(') + rgb_str = rgb_str.removesuffix(')') + + # Split and take only the RGB values, ignoring alpha if present + values = rgb_str.split(',')[:3] + return tuple(int(v.strip()) for v in values) async def extract_html_elements(file_path, load_time = DEFAULT_LOAD_TIME): if os.path.exists(file_path): url = f"file:///{os.path.abspath(file_path)}" + + screenshot_path = file_path.replace(".html", ".png") + text_elements = [] button_elements = [] input_elements = [] - anchor_elements = [] + anchor_elements = [] try: page = await web_player["browser"].new_page() await page.goto(url) await page.wait_for_timeout(load_time) - screenshot_path = file_path.replace(".html", ".png") await page.screenshot(path=screenshot_path, full_page=True, animations="disabled") await asyncio.sleep(10) + with open(screenshot_path, "rb") as f: screenshot = Image.open(f) W, H = screenshot.size @@ -127,11 +138,11 @@ async def traverse(node): await traverse(await page.query_selector('body')) await page.close() + preprocess_html_elements(file_path, button_elements) + preprocess_html_elements(file_path, input_elements) + preprocess_html_elements(file_path, anchor_elements) except Exception as e: print(e) - preprocess_html_elements(file_path, button_elements) - preprocess_html_elements(file_path, input_elements) - preprocess_html_elements(file_path, anchor_elements) return text_elements, button_elements, input_elements, anchor_elements @@ -141,10 +152,19 @@ def preprocess_html_elements(html_path, html_elements): for element in html_elements: bbox = element.bounding_box x, y, w, h = int(bbox["x"]), int(bbox["y"]), int(bbox["width"]), int(bbox["height"]) - element.avg_color = np.mean(color_image[y:y+h, x:x+w], axis=(0, 1)) + try: + element.avg_color = np.mean(color_image[y:y+h, x:x+w], axis=(0, 1)) + except Exception as e: + print(e) + element.avg_color = (0, 0, 0) gray_image = color.rgb2gray(color_image) for element in html_elements: bbox = element.bounding_box x, y, w, h = int(bbox["x"]), int(bbox["y"]), int(bbox["width"]), int(bbox["height"]) - element.keypoints, element.descriptors = extract_sift_from_roi(gray_image, (x, y, w, h)) + try: + element.keypoints, element.descriptors = extract_sift_from_roi(gray_image, (x, y, w, h)) + except Exception as e: + print(e) + element.keypoints = None + element.descriptors = None From 46d36e048065433903a68cd0984a497e7c1a0204 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 17 Jan 2025 14:19:57 -0600 Subject: [PATCH 226/554] feat: implement store_to_database logic --- neurons/validators/genie_validator.py | 18 +++++++++++ neurons/validators/score_manager.py | 24 +++++++------- neurons/validators/validator.py | 45 +++++++++++++++------------ storage/__init__.py | 1 + storage/utils.py | 9 ++++-- webgenie/helpers/dashboard.py | 2 -- 6 files changed, 63 insertions(+), 36 deletions(-) create mode 100644 storage/__init__.py delete mode 100644 webgenie/helpers/dashboard.py diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 0630c2aa..335f3886 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -23,6 +23,7 @@ from webgenie.helpers.htmls import preprocess_html, is_valid_resources from webgenie.helpers.images import image_debug_str from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse +from webgenie.storage import store_results_to_database from webgenie.tasks import Solution, ImageTaskGenerator from webgenie.utils.uids import get_all_available_uids @@ -67,8 +68,10 @@ async def query_miners(self): QualityChallenge, SeoChallenge, ] + with self.neuron.lock: session_number = self.neuron.session_number + challenge_class = available_challenges_classes[session_number % len(available_challenges_classes)] challenge = challenge_class(task=task, session_number=session_number) synapse.competition_type = challenge.competition_type @@ -98,6 +101,7 @@ async def query_miners(self): bt.logging.info(f"Received {len(solutions)} valid solutions") with self.neuron.lock: self.miner_results.append(challenge) + except Exception as e: bt.logging.error(f"Error in query_miners: {e}") raise e @@ -119,9 +123,23 @@ async def score(self): bt.logging.info("Scoring") solutions = challenge.solutions miner_uids = [solution.miner_uid for solution in solutions] + aggregated_scores, scores = await challenge.calculate_scores() + bt.logging.success(f"scores: {scores}") bt.logging.success(f"Final scores for {miner_uids}: {aggregated_scores}") + + store_results_to_database( + { + "neuron": self.neuron, + "miner_uids": miner_uids, + "solutions": solutions, + "scores": scores, + "aggregated_scores": aggregated_scores, + "challenge": challenge, + } + ) + self.neuron.score_manager.update_scores( aggregated_scores, miner_uids, diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 82726c94..a0ee4d8c 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -18,7 +18,7 @@ def __init__(self, neuron: BaseNeuron): self.scoring_session_number = 0 self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - self.tempo_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + self.session_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.should_save = False self.should_set_weights = False @@ -29,13 +29,13 @@ def load_scores(self): self.scores = state["scores"] self.hotkeys = state["hotkeys"] self.scoring_session_number = state["scoring_session_number"] - self.tempo_accumulated_scores = state["tempo_accumulated_scores"] + self.session_accumulated_scores = state["tempo_accumulated_scores"] except Exception as e: bt.logging.error(f"Error loading scores: {e}") self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.scoring_session_number = 0 - self.tempo_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + self.session_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) def save_scores(self): if not self.should_save: @@ -47,7 +47,7 @@ def save_scores(self): scores=self.scores, hotkeys=self.hotkeys, scoring_session_number=self.scoring_session_number, - tempo_accumulated_scores=self.tempo_accumulated_scores, + tempo_accumulated_scores=self.session_accumulated_scores, ) def set_new_hotkeys(self, new_hotkeys: List[str]): @@ -57,7 +57,7 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): # Zero out all hotkeys that have been replaced. for uid, hotkey in enumerate(self.hotkeys): if hotkey != new_hotkeys[uid]: - self.tempo_accumulated_scores[uid] = 0 + self.session_accumulated_scores[uid] = 0 self.scores[uid] = 0 # hotkey has been replaced # Check to see if the metagraph has changed size. @@ -69,9 +69,9 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): self.scores = new_scores new_tempo_accumulated_scores = np.zeros((len(new_hotkeys))) - min_len = min(len(self.hotkeys), len(self.tempo_accumulated_scores)) - new_tempo_accumulated_scores[:min_len] = self.tempo_accumulated_scores[:min_len] - self.tempo_accumulated_scores = new_tempo_accumulated_scores + min_len = min(len(self.hotkeys), len(self.session_accumulated_scores)) + new_tempo_accumulated_scores[:min_len] = self.session_accumulated_scores[:min_len] + self.session_accumulated_scores = new_tempo_accumulated_scores # Update the hotkeys. self.hotkeys = copy.deepcopy(new_hotkeys) @@ -81,12 +81,12 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): def update_scores(self, rewards: np.ndarray, uids: List[int], session_number: int): if self.scoring_session_number != session_number: self.scoring_session_number = session_number - self.tempo_accumulated_scores = np.zeros_like(self.scores) + self.session_accumulated_scores = np.zeros_like(self.scores) scattered_rewards: np.ndarray = np.zeros_like(self.scores) scattered_rewards[uids] = rewards - self.tempo_accumulated_scores: np.ndarray = scattered_rewards + self.tempo_accumulated_scores - bt.logging.debug(f"Updated scores: {self.tempo_accumulated_scores}") + self.session_accumulated_scores: np.ndarray = scattered_rewards + self.session_accumulated_scores + bt.logging.debug(f"Updated scores: {self.session_accumulated_scores}") self.should_save = True self.should_set_weights = True @@ -99,7 +99,7 @@ def set_weights(self): return self.scores = np.zeros_like(self.scores) - best_index = np.argmax(self.tempo_accumulated_scores) + best_index = np.argmax(self.session_accumulated_scores) self.scores[best_index] = 1 # Calculate the average reward for each uid across non-zero values. diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 10bdfb17..def9becb 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -22,14 +22,15 @@ from neurons.validators.genie_validator import GenieValidator from neurons.validators.score_manager import ScoreManager -# Constants for block timing + +MAX_COUNT_VALIDATORS = 1 + BLOCK_IN_SECONDS = 12 -TEMPO_BLOCKS = 60 -MAX_VALIDATORS = 1 -VALIDATOR_QUERY_PERIOD_BLOCKS = 10 -ALL_VALIDATOR_QUERY_PERIOD_BLOCKS = MAX_VALIDATORS * VALIDATOR_QUERY_PERIOD_BLOCKS -COMPETITION_PERIOD_BLOCKS = TEMPO_BLOCKS * 3 -SET_WEIGHTS_PERIOD_BLOCKS = 50 # 50 blocks = 10 minutes +TEMPO_BLOCKS = 360 +SESSION_WINDOW_BLOCKS = TEMPO_BLOCKS * 3 + +QUERING_WINDOW_BLOCKS = 10 +WEIGHT_SETTING_WINDOW_BLOCKS = 50 # 50 blocks = 10 minutes class Validator(BaseValidatorNeuron): @@ -43,7 +44,7 @@ class Validator(BaseValidatorNeuron): @property def session_number(self): - return self.block // COMPETITION_PERIOD_BLOCKS + return self.block // SESSION_WINDOW_BLOCKS def __init__(self, config=None): super(Validator, self).__init__(config=config) @@ -165,21 +166,26 @@ def query_miners_loop(self): with self.lock: current_block = self.block + all_validator_query_period_blocks = MAX_COUNT_VALIDATORS * QUERING_WINDOW_BLOCKS + # Calculate query period blocks start_period_block = ( - (current_block // ALL_VALIDATOR_QUERY_PERIOD_BLOCKS) * ALL_VALIDATOR_QUERY_PERIOD_BLOCKS + - validator_index * VALIDATOR_QUERY_PERIOD_BLOCKS + (current_block // all_validator_query_period_blocks) * + all_validator_query_period_blocks + + validator_index * QUERING_WINDOW_BLOCKS ) - end_period_block = start_period_block + ALL_VALIDATOR_QUERY_PERIOD_BLOCKS / 2 + end_period_block = start_period_block + QUERING_WINDOW_BLOCKS / 2 bt.logging.info(f"Query period blocks - " f"Start: {start_period_block}, " f"End: {end_period_block}, " f"Current: {current_block}") # Sleep if outside query window if current_block < start_period_block: - bt.logging.info(f"Sleeping for {start_period_block - current_block} blocks before querying miners") - time.sleep((start_period_block - current_block) * BLOCK_IN_SECONDS) - elif current_block >= end_period_block: - sleep_blocks = (start_period_block - current_block + ALL_VALIDATOR_QUERY_PERIOD_BLOCKS) + sleep_blocks = start_period_block - current_block + bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") + time.sleep(sleep_blocks * BLOCK_IN_SECONDS) + continue + elif current_block > end_period_block: + sleep_blocks = (start_period_block - current_block + all_validator_query_period_blocks) bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") time.sleep(sleep_blocks * BLOCK_IN_SECONDS) continue @@ -202,7 +208,6 @@ def score_loop(self): bt.logging.error(f"Error during scoring: {str(e)}") if self.should_exit: break - time.sleep(1) def synthensize_task_loop(self): bt.logging.info(f"Synthensize task loop starting") @@ -234,13 +239,13 @@ def set_weights_loop(self): # Calculate the end block number for the next weight setting period # This aligns with 3 tempo boundaries set_weights_end_block = ( - (current_block + COMPETITION_PERIOD_BLOCKS - 1) - // COMPETITION_PERIOD_BLOCKS - * COMPETITION_PERIOD_BLOCKS + (current_block + SESSION_WINDOW_BLOCKS - 1) + // SESSION_WINDOW_BLOCKS + * SESSION_WINDOW_BLOCKS ) # Start setting weights 50 blocks before the end - set_weights_start_block = set_weights_end_block - SET_WEIGHTS_PERIOD_BLOCKS + set_weights_start_block = set_weights_end_block - WEIGHT_SETTING_WINDOW_BLOCKS bt.logging.info( f"Set weights blocks - " f"Start: {set_weights_start_block}, " diff --git a/storage/__init__.py b/storage/__init__.py new file mode 100644 index 00000000..a21fc17d --- /dev/null +++ b/storage/__init__.py @@ -0,0 +1 @@ +from .utils import store_results_to_database \ No newline at end of file diff --git a/storage/utils.py b/storage/utils.py index cdefdf9c..390bcc8b 100644 --- a/storage/utils.py +++ b/storage/utils.py @@ -45,8 +45,10 @@ def get_neuron_id(hotkey: str): finally: session.close() # Ensure the session is closed -def create_leaderboard_session(created_at: datetime, competition_id: int): - return create_record(session, LeaderboardSession, created_at=created_at, competition_id=competition_id) +def create_leaderboard_session(session_number: int, created_at: datetime, competition_id: int): + return create_record(session, LeaderboardSession, + id=session_number, + created_at=created_at, competition_id=competition_id) def query_leaderboard_session(timestamp: datetime): # Calculate the time range @@ -87,6 +89,9 @@ def create_task_solution(miner_answer: str, challenge_id: int, created_at: datet def create_solution_evaluation(solution_id: int, score_type_id: int, judgement_id: int, value: float): return create_record(session, SolutionEvaluation, solution_id=solution_id, score_type_id=score_type_id, judgement_id=judgement_id, value=value) +def store_results_to_database(results: dict): + pass + if __name__ == "__main__": neuron_id = add_neuron("5GKH9FPPnWSUoeeTJp19wVtd84XqFW4pyK2ijV2GsFbhTrP1", "5F4tQyWrhfGVcNhoqeiNsR6KjD4wMZ2kfhLj4oHYuyHbZAc3") logging.info(f"neuron_id: {neuron_id}") diff --git a/webgenie/helpers/dashboard.py b/webgenie/helpers/dashboard.py deleted file mode 100644 index 6166a0b1..00000000 --- a/webgenie/helpers/dashboard.py +++ /dev/null @@ -1,2 +0,0 @@ -def upload_competition_result(): - pass \ No newline at end of file From 96d5592e329f1a9d415782e1579a17cde5d2dec7 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 17 Jan 2025 23:53:21 -0600 Subject: [PATCH 227/554] chore: update logging info --- neurons/validators/genie_validator.py | 32 +++---- neurons/validators/validator.py | 4 +- storage/__init__.py | 1 - storage/models.py | 78 ---------------- webgenie.db | Bin 0 -> 20480 bytes webgenie/storage/__init__.py | 1 - {storage => webgenie/storage}/database.py | 0 webgenie/storage/model_gpt.py | 77 ---------------- webgenie/storage/models.py | 103 ++++++++++++++-------- webgenie/storage/storage.py | 37 -------- {storage => webgenie/storage}/utils.py | 0 webgenie/tasks/image_task_generator.py | 1 + webgenie/tasks/task.py | 2 +- 13 files changed, 88 insertions(+), 248 deletions(-) delete mode 100644 storage/__init__.py delete mode 100644 storage/models.py create mode 100644 webgenie.db rename {storage => webgenie/storage}/database.py (100%) delete mode 100644 webgenie/storage/model_gpt.py delete mode 100644 webgenie/storage/storage.py rename {storage => webgenie/storage}/utils.py (100%) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 335f3886..c0fd5d88 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -16,14 +16,10 @@ QualityChallenge, SeoChallenge, ) -from webgenie.storage import ( - upload_competition, - upload_competition_result, -) from webgenie.helpers.htmls import preprocess_html, is_valid_resources from webgenie.helpers.images import image_debug_str from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse -from webgenie.storage import store_results_to_database +#from webgenie.storage.utils import store_results_to_database from webgenie.tasks import Solution, ImageTaskGenerator from webgenie.utils.uids import get_all_available_uids @@ -50,9 +46,11 @@ async def query_miners(self): try: with self.neuron.lock: if len(self.miner_results) > MAX_COMPETETION_HISTORY_SIZE: + bt.logging.info(f"Competition history size {len(self.miner_results)} exceeds max size {MAX_COMPETETION_HISTORY_SIZE}, skipping") return if not self.synthetic_tasks: + bt.logging.info("No synthetic tasks available, skipping") return task, synapse = self.synthetic_tasks.pop(0) @@ -126,19 +124,21 @@ async def score(self): aggregated_scores, scores = await challenge.calculate_scores() - bt.logging.success(f"scores: {scores}") + bt.logging.success(f"Task Source: {challenge.task.src}") + bt.logging.success(f"Competition Type: {challenge.competition_type}") + bt.logging.success(f"Scores: {scores}") bt.logging.success(f"Final scores for {miner_uids}: {aggregated_scores}") - store_results_to_database( - { - "neuron": self.neuron, - "miner_uids": miner_uids, - "solutions": solutions, - "scores": scores, - "aggregated_scores": aggregated_scores, - "challenge": challenge, - } - ) + # store_results_to_database( + # { + # "neuron": self.neuron, + # "miner_uids": miner_uids, + # "solutions": solutions, + # "scores": scores, + # "aggregated_scores": aggregated_scores, + # "challenge": challenge, + # } + # ) self.neuron.score_manager.update_scores( aggregated_scores, diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index def9becb..759e16d8 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -174,7 +174,7 @@ def query_miners_loop(self): validator_index * QUERING_WINDOW_BLOCKS ) end_period_block = start_period_block + QUERING_WINDOW_BLOCKS / 2 - bt.logging.info(f"Query period blocks - " + bt.logging.info(f"Query window - " f"Start: {start_period_block}, " f"End: {end_period_block}, " f"Current: {current_block}") @@ -247,7 +247,7 @@ def set_weights_loop(self): # Start setting weights 50 blocks before the end set_weights_start_block = set_weights_end_block - WEIGHT_SETTING_WINDOW_BLOCKS bt.logging.info( - f"Set weights blocks - " + f"Set weights window - " f"Start: {set_weights_start_block}, " f"End: {set_weights_end_block}, " f"Current: {current_block}" diff --git a/storage/__init__.py b/storage/__init__.py deleted file mode 100644 index a21fc17d..00000000 --- a/storage/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .utils import store_results_to_database \ No newline at end of file diff --git a/storage/models.py b/storage/models.py deleted file mode 100644 index e01fd13f..00000000 --- a/storage/models.py +++ /dev/null @@ -1,78 +0,0 @@ -from sqlalchemy import Column, DateTime, ForeignKey, JSON -from sqlalchemy.orm import relationship, Mapped, mapped_column -from datetime import datetime -from database import Base, engine - -class Neuron(Base): - __tablename__ = "neurons" - id: Mapped[int] = mapped_column(primary_key=True) - coldkey: Mapped[str] - hotkey: Mapped[str] = mapped_column(index=True) - -class Competition(Base): - __tablename__ = "competitions" - id: Mapped[int] = mapped_column(primary_key=True) - name: Mapped[str] = mapped_column(index=True) - - # Relationships - sessions: Mapped[list["LeaderboardSession"]] = relationship(back_populates="competition") - -class LeaderboardSession(Base): - __tablename__ = "leaderboard_sessions" - id: Mapped[int] = mapped_column(primary_key=True) - created_at = Column(DateTime, default=datetime.utcnow, index=True) - competition_id: Mapped[int] = mapped_column(ForeignKey("competitions.id"), index=True) - - # Relationships - competition: Mapped["Competition"] = relationship(back_populates="sessions") - challenges: Mapped[list["Challenge"]] = relationship(back_populates="session") - -class Challenge(Base): - __tablename__ = "challenges" - id: Mapped[int] = mapped_column(primary_key=True) - session_id: Mapped[int] = mapped_column(ForeignKey("leaderboard_sessions.id"), index=True) - ground_truth_html: Mapped[str] - - # Relationships - session: Mapped["LeaderboardSession"] = relationship(back_populates="challenges") - solutions: Mapped[list["TaskSolution"]] = relationship(back_populates="challenge") - - -class Judgement(Base): - __tablename__ = "judgements" - id: Mapped[int] = mapped_column(primary_key=True) - validator_id: Mapped[int] = mapped_column(ForeignKey("neurons.id"), index=True) - miner_id: Mapped[int] = mapped_column(ForeignKey("neurons.id"), index=True) - -class EvaluationType(Base): - __tablename__ = "evaluation_types" - id: Mapped[int] = mapped_column(primary_key=True) - name: Mapped[str] = mapped_column(index=True) - - # Relationship - solution_scores: Mapped[list["SolutionEvaluation"]] = relationship(back_populates="score_type") - -class TaskSolution(Base): - __tablename__ = "task_solutions" - id: Mapped[int] = mapped_column(primary_key=True) - created_at = Column(DateTime, default=datetime.utcnow) - challenge_id: Mapped[int] = mapped_column(ForeignKey("challenges.id"), index=True) - miner_answer: Mapped[dict] = mapped_column(JSON) - - # Relationship - challenge: Mapped["Challenge"] = relationship(back_populates="solutions") - solution_scores: Mapped[list["SolutionEvaluation"]] = relationship(back_populates="solution") - -class SolutionEvaluation(Base): - __tablename__ = "solution_evaluations" - id: Mapped[int] = mapped_column(primary_key=True) - solution_id: Mapped[int] = mapped_column(ForeignKey("task_solutions.id"), index=True) - score_type_id: Mapped[int] = mapped_column(ForeignKey("evaluation_types.id"), index=True) - judgement_id: Mapped[int] = mapped_column(ForeignKey("judgements.id"), index=True) - value: Mapped[float] - - # Relationships - score_type: Mapped["EvaluationType"] = relationship(back_populates="solution_scores") - solution: Mapped["TaskSolution"] = relationship(back_populates="solution_scores") - -Base.metadata.create_all(engine) diff --git a/webgenie.db b/webgenie.db new file mode 100644 index 0000000000000000000000000000000000000000..7f82c3a3e4dc91fff10bde2320b0d5ab89c3ab57 GIT binary patch literal 20480 zcmeI%?{AYp7zc1zJL?*^^0L=P+Uc^_^-BWW4+{5uo+l)v!wV;toP5mkX(-7{qh*+;@tP3BFluVwRr_kw z{rak-&Ss_kuV&47{&lv0_}i#IDU5?(hd&Qms(}px5P$##AOHafKmY;|_PNow z2j{kbN#4>+(v2fakM%vD4$fRX*4^O=Z2HN?j90_F%CjPkn9K_~VG}t`s*?{kP%%ie(e8B2o8B^prK%{8 zJ8SDM$L?G4dI9~dJd$Xmnqt9neiaMJbCscl)a0)27MbBBR%tJ)?-8qW)^ zi+NR_BX@8yqAF)`bWF-csnk8&dDMQ@F^xEl_!lu(HOE<4$ZXkXWvuK)dDh?ZkM#>@ zUmsfffDHl=fB*y_009U<00Izz00bZafo&CN>cH^*zpd#-To8Z&1Rwwb2tWV=5P$## zAOL}-0RI0+AwU2E5P$##AOHafKmY;|fB*!xUjX0#+n-}Z2muH{00Izz00bZa0SG_< H0ucBEHhCdm literal 0 HcmV?d00001 diff --git a/webgenie/storage/__init__.py b/webgenie/storage/__init__.py index c14e1e33..e69de29b 100644 --- a/webgenie/storage/__init__.py +++ b/webgenie/storage/__init__.py @@ -1 +0,0 @@ -from .storage import * \ No newline at end of file diff --git a/storage/database.py b/webgenie/storage/database.py similarity index 100% rename from storage/database.py rename to webgenie/storage/database.py diff --git a/webgenie/storage/model_gpt.py b/webgenie/storage/model_gpt.py deleted file mode 100644 index 0397fd39..00000000 --- a/webgenie/storage/model_gpt.py +++ /dev/null @@ -1,77 +0,0 @@ -from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relationship, sessionmaker -from datetime import datetime - -Base = declarative_base() - -class Competition(Base): - __tablename__ = 'competitions' - id = Column(Integer, primary_key=True) - uuid = Column(String, unique=True) - type = Column(String) - ground_truth_html = Column(String) - - # Relationship to LeaderboardSessions - sessions = relationship("LeaderboardSession", order_by="LeaderboardSession.id", back_populates="competition") - -class LeaderboardSession(Base): - __tablename__ = 'leaderboard_sessions' - id = Column(Integer, primary_key=True) - competition_uuid = Column(String, ForeignKey('competitions.uuid')) - created_at = Column(DateTime, default=datetime.utcnow) - - # Relationships - competition = relationship("Competition", back_populates="sessions") - solutions = relationship("TaskSolution", back_populates="session") - -class TaskSolution(Base): - __tablename__ = 'task_solutions' - id = Column(Integer, primary_key=True) - session_id = Column(Integer, ForeignKey('leaderboard_sessions.id')) - miner_uid = Column(Integer) - miner_hotkey = Column(String) - validator_hotkey = Column(String) - score = Column(Float) - accuracy_score = Column(Float) - quality_score = Column(Float) - seo_score = Column(Float) - miner_answer = Column(String) - created_at = Column(DateTime, default=datetime.utcnow) - - # Relationship - session = relationship("LeaderboardSession", back_populates="solutions") - -# Database setup -engine = create_engine('sqlite:///competition.db', echo=True) -Base.metadata.create_all(engine) - -# Example function to illustrate usage -def add_competition_data(): - Session = sessionmaker(bind=engine) - session = Session() - - # Create a new competition - competition = Competition(uuid="comp-1234", type="data science") - - # Create a leaderboard session - leaderboard_session = LeaderboardSession(competition_uuid=competition.uuid) - - # Create task solutions - solution1 = TaskSolution(session=leaderboard_session, miner_uid=1, score=95.5) - solution2 = TaskSolution(session=leaderboard_session, miner_uid=2, score=88.0) - - # Create leaderboard entry - leaderboard_entry = LeaderboardEntry(session=leaderboard_session, average_score=91.75, max_score=95.5, min_score=88.0) - - # Add all to session and commit - session.add(competition) - session.add(leaderboard_session) - session.add(solution1) - session.add(solution2) - session.add(leaderboard_entry) - session.commit() - session.close() - -if __name__ == "__main__": - add_competition_data() diff --git a/webgenie/storage/models.py b/webgenie/storage/models.py index b3a586e2..e01fd13f 100644 --- a/webgenie/storage/models.py +++ b/webgenie/storage/models.py @@ -1,45 +1,78 @@ -from sqlalchemy import Column, Integer, Float, String, DateTime, Boolean, UniqueConstraint, ForeignKey -from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import Column, DateTime, ForeignKey, JSON +from sqlalchemy.orm import relationship, Mapped, mapped_column +from datetime import datetime +from database import Base, engine +class Neuron(Base): + __tablename__ = "neurons" + id: Mapped[int] = mapped_column(primary_key=True) + coldkey: Mapped[str] + hotkey: Mapped[str] = mapped_column(index=True) -Base = declarative_base() +class Competition(Base): + __tablename__ = "competitions" + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(index=True) + # Relationships + sessions: Mapped[list["LeaderboardSession"]] = relationship(back_populates="competition") -class Miner(Base): - __tablename__ = 'miners' +class LeaderboardSession(Base): + __tablename__ = "leaderboard_sessions" + id: Mapped[int] = mapped_column(primary_key=True) + created_at = Column(DateTime, default=datetime.utcnow, index=True) + competition_id: Mapped[int] = mapped_column(ForeignKey("competitions.id"), index=True) - id = Column(Integer, primary_key=True) - uid = Column(Integer) - hotkey = Column(String) - coldkey = Column(String) - is_registered = Column(Boolean) - last_updated = Column(DateTime) - __table_args__ = ( - UniqueConstraint('uid', 'hotkey', name='unique_uid_hotkey'), - ) - + # Relationships + competition: Mapped["Competition"] = relationship(back_populates="sessions") + challenges: Mapped[list["Challenge"]] = relationship(back_populates="session") -class Competition(Base): - __tablename__ = 'competitions' +class Challenge(Base): + __tablename__ = "challenges" + id: Mapped[int] = mapped_column(primary_key=True) + session_id: Mapped[int] = mapped_column(ForeignKey("leaderboard_sessions.id"), index=True) + ground_truth_html: Mapped[str] + + # Relationships + session: Mapped["LeaderboardSession"] = relationship(back_populates="challenges") + solutions: Mapped[list["TaskSolution"]] = relationship(back_populates="challenge") + + +class Judgement(Base): + __tablename__ = "judgements" + id: Mapped[int] = mapped_column(primary_key=True) + validator_id: Mapped[int] = mapped_column(ForeignKey("neurons.id"), index=True) + miner_id: Mapped[int] = mapped_column(ForeignKey("neurons.id"), index=True) - id = Column(Integer, primary_key=True) - uuid = Column(String) - type = Column(String) - ground_truth_html = Column(String) - last_updated = Column(DateTime) +class EvaluationType(Base): + __tablename__ = "evaluation_types" + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(index=True) + # Relationship + solution_scores: Mapped[list["SolutionEvaluation"]] = relationship(back_populates="score_type") class TaskSolution(Base): - __tablename__ = 'task_solutions' - - id = Column(Integer, primary_key=True) - miner_uid = Column(Integer) - miner_hotkey = Column(String) - validator_hotkey = Column(String) - competition_uuid = Column(String, ForeignKey('competitions.uuid')) - score = Column(Float) - accuracy_score = Column(Float) - quality_score = Column(Float) - seo_score = Column(Float) - miner_answer = Column(String) - last_updated = Column(DateTime) + __tablename__ = "task_solutions" + id: Mapped[int] = mapped_column(primary_key=True) + created_at = Column(DateTime, default=datetime.utcnow) + challenge_id: Mapped[int] = mapped_column(ForeignKey("challenges.id"), index=True) + miner_answer: Mapped[dict] = mapped_column(JSON) + + # Relationship + challenge: Mapped["Challenge"] = relationship(back_populates="solutions") + solution_scores: Mapped[list["SolutionEvaluation"]] = relationship(back_populates="solution") + +class SolutionEvaluation(Base): + __tablename__ = "solution_evaluations" + id: Mapped[int] = mapped_column(primary_key=True) + solution_id: Mapped[int] = mapped_column(ForeignKey("task_solutions.id"), index=True) + score_type_id: Mapped[int] = mapped_column(ForeignKey("evaluation_types.id"), index=True) + judgement_id: Mapped[int] = mapped_column(ForeignKey("judgements.id"), index=True) + value: Mapped[float] + + # Relationships + score_type: Mapped["EvaluationType"] = relationship(back_populates="solution_scores") + solution: Mapped["TaskSolution"] = relationship(back_populates="solution_scores") + +Base.metadata.create_all(engine) diff --git a/webgenie/storage/storage.py b/webgenie/storage/storage.py deleted file mode 100644 index 4a25bd07..00000000 --- a/webgenie/storage/storage.py +++ /dev/null @@ -1,37 +0,0 @@ -import bittensor as bt - -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker - -from .models import Miner, Base - - -engine = create_engine('sqlite:///webgenie.db') -Session = sessionmaker(bind=engine) -Base.metadata.create_all(engine) - - -def upload_competition(row: dict): - try: - session = Session() - competition = Competition(**row) - session.add(competition) - session.commit() - except Exception as e: - bt.logging.error(f"Database error: {e}") - session.rollback() - finally: - session.close() - - -def upload_competition_result(result: dict): - try: - session = Session() - miner = Miner(**result) - session.add(miner) - session.commit() - except Exception as e: - bt.logging.error(f"Database error: {e}") - session.rollback() - finally: - session.close() diff --git a/storage/utils.py b/webgenie/storage/utils.py similarity index 100% rename from storage/utils.py rename to webgenie/storage/utils.py diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index bb828a6f..f98a69be 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -67,6 +67,7 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: ground_truth_html=ground_truth_html, timeout=IMAGE_TASK_TIMEOUT, generator=self, + src=dataset_entry.src, ), WebgenieImageSynapse(base64_image=base64_image), ) diff --git a/webgenie/tasks/task.py b/webgenie/tasks/task.py index 05493020..d588bc15 100644 --- a/webgenie/tasks/task.py +++ b/webgenie/tasks/task.py @@ -7,7 +7,7 @@ class Task(BaseModel): task_id: str = Field(default_factory=lambda: str(uuid.uuid4())) timeout: float = Field(default=50) generator: Any = Field(default=None) - + src: str = Field(default="Unknown", description="The source of the task") class ImageTask(Task): base64_image: str = Field(default="", description="The base64 encoded image") From 41858303f1c65e055c9caab3d5d6cc7ebcb05d85 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 18 Jan 2025 00:28:07 -0600 Subject: [PATCH 228/554] chore: add timeout logic for async tasks --- neurons/validators/validator.py | 27 ++++++++++++++++++--- webgenie/datasets/random_website_dataset.py | 1 + webgenie/helpers/htmls.py | 2 +- webgenie/tasks/image_task_generator.py | 4 +++ 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 759e16d8..e3a34ed9 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -189,8 +189,14 @@ def query_miners_loop(self): bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") time.sleep(sleep_blocks * BLOCK_IN_SECONDS) continue - - self.query_miners_event_loop.run_until_complete(self.genie_validator.query_miners()) + + QUERY_MINERS_TIMEOUT = 60 * 15 + self.query_miners_event_loop.run_until_complete( + asyncio.wait_for( + self.genie_validator.query_miners(), + timeout=QUERY_MINERS_TIMEOUT + ) + ) except Exception as e: bt.logging.error(f"Error during query miners loop: {str(e)}") if self.should_exit: @@ -203,7 +209,14 @@ def score_loop(self): try: with self.lock: self.sync() - self.score_event_loop.run_until_complete(self.genie_validator.score()) + + SCORE_TIMEOUT = 60 * 60 + self.score_event_loop.run_until_complete( + asyncio.wait_for( + self.genie_validator.score(), + timeout=SCORE_TIMEOUT + ) + ) except Exception as e: bt.logging.error(f"Error during scoring: {str(e)}") if self.should_exit: @@ -217,7 +230,13 @@ def synthensize_task_loop(self): with self.lock: self.sync() - self.synthensize_task_event_loop.run_until_complete(self.genie_validator.synthensize_task()) + SYNTHETIC_TASK_TIMEOUT = 60 * 15 + self.synthensize_task_event_loop.run_until_complete( + asyncio.wait_for( + self.genie_validator.synthensize_task(), + timeout=SYNTHETIC_TASK_TIMEOUT + ) + ) except Exception as e: bt.logging.error(f"Error during synthensize task: {str(e)}") if self.should_exit: diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index ab305d7a..6d8e8c88 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -45,6 +45,7 @@ async def get_rendered_html(self, url): # Wait for 10 seconds to ensure content loads await page.wait_for_timeout(GROUND_TRUTH_HTML_LOAD_TIME) rendered_html = await page.content() # Get the rendered HTML + await page.close() await browser.close() # Parse the HTML with BeautifulSoup diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 23f19c41..1b11e04f 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -101,7 +101,7 @@ async def html_to_screenshot(html_content: str, page_load_time: int = 1000) -> s await page.wait_for_timeout(page_load_time) # Take the screenshot await page.screenshot(path=png_path, full_page=True, animations="disabled", timeout=60000) - + await page.close() await browser.close() except Exception as e: print(f"Failed to take screenshot due to: {e}. Generating a blank image.") diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index f98a69be..f6af8b23 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -52,8 +52,10 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: dataset, _ = random.choices(self.datasets, weights=[weight for _, weight in self.datasets])[0] dataset_entry = await dataset.generate_context() + bt.logging.debug(f"Generated dataset entry: {dataset_entry.src}") ground_truth_html = preprocess_html(dataset_entry.ground_truth_html) + bt.logging.info(f"Preprocessed ground truth html") if not ground_truth_html : raise ValueError("Invalid ground truth html") @@ -61,6 +63,8 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: raise ValueError("Empty ground truth html") base64_image = await html_to_screenshot(ground_truth_html, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) + bt.logging.debug(f"Screenshot generated for {dataset_entry.src}") + return ( ImageTask( base64_image=base64_image, From f1aaefd1b96ee0e4aa15dbfb566a0f7627375798 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 18 Jan 2025 06:30:28 -0600 Subject: [PATCH 229/554] chore: add some execption handling --- neurons/validators/genie_validator.py | 2 ++ neurons/validators/score_manager.py | 3 ++ webgenie/base/neuron.py | 4 --- webgenie/datasets/random_website_dataset.py | 4 ++- .../lighthouse_reward/get_lighthouse_score.py | 2 ++ .../lighthouse_reward/lighthouse_reward.py | 28 +++++++++++-------- .../rewards/visual_reward/visual_reward.py | 15 +++++++++- webgenie/tasks/image_task_generator.py | 1 - webgenie/utils/config.py | 2 +- 9 files changed, 41 insertions(+), 20 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index c0fd5d88..b6f416a1 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -163,6 +163,8 @@ async def synthensize_task(self): task, synapse = await task_generator.generate_task() with self.neuron.lock: self.synthetic_tasks.append((task, synapse)) + + bt.logging.success(f"Successfully generated task for {task.src}") except Exception as e: bt.logging.error(f"Error in synthensize_task: {e}") diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index a0ee4d8c..4974edde 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -97,6 +97,9 @@ def set_weights(self): """ if not self.should_set_weights: return + + if not self.neuron.should_set_weights(): + return self.scores = np.zeros_like(self.scores) best_index = np.argmax(self.session_accumulated_scores) diff --git a/webgenie/base/neuron.py b/webgenie/base/neuron.py index 9b9cdf6e..05f61bc9 100644 --- a/webgenie/base/neuron.py +++ b/webgenie/base/neuron.py @@ -142,10 +142,6 @@ def should_sync_metagraph(self): ) > self.config.neuron.epoch_length def should_set_weights(self) -> bool: - # Don't set weights on initialization. - if self.step == 0: - return False - # Check if enough epoch blocks have elapsed since the last epoch. if self.config.neuron.disable_set_weights: return False diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index 6d8e8c88..ad81aa1f 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -67,9 +67,11 @@ async def get_rendered_html(self, url): pieces = part.strip().split(maxsplit=1) if len(pieces) == 2: url_part, descriptor = pieces - else: + elif len(pieces) == 1: url_part = pieces[0] descriptor = '' + else: + continue new_url = urljoin(url, url_part.strip()) if descriptor: diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 89762983..cd211dd2 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -68,6 +68,7 @@ def run_server(port=8000): server_thread.start() def get_lighthouse_score_from_subprocess(url): + bt.logging.info(f"Getting lighthouse score from {url}...") try: result = subprocess.run( ['lighthouse', url, '--output=json', '--quiet', '--chrome-flags="--headless --no-sandbox"'], @@ -94,6 +95,7 @@ def get_lighthouse_score_from_subprocess(url): } time.sleep(1) # Give the server time to start + bt.logging.info(f"Getting lighthouse scores from {port}...") scores = [] for i in tqdm(range(len(htmls)), desc="Getting lighthouse scores"): url = f"http://localhost:{port}/lighthouse_score/{i}" diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py index 76640267..42a1438c 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py @@ -20,18 +20,22 @@ def __init__(self): pass def sync_reward_worker(self, htmls: List[str], port: int = LIGHTHOUSE_SERVER_PORT) -> List[float]: - scores_dict = get_lighthouse_score(htmls, port) - scores = [] - weights = [0, 0.25, 0.25, 0.5] - for score_dict in scores_dict: - score = ( - score_dict['performance'] * weights[0] + - score_dict['accessibility'] * weights[1] + - score_dict['best-practices'] * weights[2] + - score_dict['seo'] * weights[3] - ) - scores.append(score) - return scores + try: + scores_dict = get_lighthouse_score(htmls, port) + scores = [] + weights = [0, 0.25, 0.25, 0.5] + for score_dict in scores_dict: + score = ( + score_dict['performance'] * weights[0] + + score_dict['accessibility'] * weights[1] + + score_dict['best-practices'] * weights[2] + + score_dict['seo'] * weights[3] + ) + scores.append(score) + return scores + except Exception as e: + bt.logging.error(f"Error getting lighthouse score: {e}") + return [0] * len(htmls) async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: bt.logging.info(f"Rewarding lighthouse task") diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index 1e31c1e0..0c364c25 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -55,7 +55,20 @@ async def reward_worker(self, task: Task, solutions: List[Solution], current_wor return scores def sync_reward_worker(self, task: Task, solutions: List[Solution], current_work_dir: str) -> np.ndarray: - return asyncio.run(self.reward_worker(task, solutions, current_work_dir)) + try: + # Timeout of 1 hour for visual reward processing + VISUAL_REWARD_TIMEOUT = 60 * 60 # seconds + + # Run the async reward worker with timeout + return asyncio.run( + asyncio.wait_for( + self.reward_worker(task, solutions, current_work_dir), + timeout=VISUAL_REWARD_TIMEOUT + ) + ) + except Exception as e: + bt.logging.error(f"Error in sync_reward_worker: {e}") + return [0] * len(solutions) async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: if not isinstance(task, ImageTask): diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index f6af8b23..6642aa94 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -64,7 +64,6 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: base64_image = await html_to_screenshot(ground_truth_html, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) bt.logging.debug(f"Screenshot generated for {dataset_entry.src}") - return ( ImageTask( base64_image=base64_image, diff --git a/webgenie/utils/config.py b/webgenie/utils/config.py index c13ec43f..8db499d2 100644 --- a/webgenie/utils/config.py +++ b/webgenie/utils/config.py @@ -81,7 +81,7 @@ def add_args(cls, parser): "--neuron.epoch_length", type=int, help="The default epoch length (how often we set weights, measured in 12 second blocks).", - default=25, + default=10, ) parser.add_argument( From c0e99be436ef8b6475c9bf17c00bdb932cec4e8b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 18 Jan 2025 06:47:36 -0600 Subject: [PATCH 230/554] chore: update setting weights on chain logic --- neurons/validators/score_manager.py | 8 +------- webgenie/utils/config.py | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 4974edde..2be1eb62 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -20,7 +20,6 @@ def __init__(self, neuron: BaseNeuron): self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.session_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.should_save = False - self.should_set_weights = False def load_scores(self): bt.logging.info("Loading scores") @@ -40,6 +39,7 @@ def load_scores(self): def save_scores(self): if not self.should_save: return + self.should_save = False bt.logging.info("Saving scores") np.savez( @@ -76,7 +76,6 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): # Update the hotkeys. self.hotkeys = copy.deepcopy(new_hotkeys) self.should_save = True - self.should_set_weights = True def update_scores(self, rewards: np.ndarray, uids: List[int], session_number: int): if self.scoring_session_number != session_number: @@ -89,15 +88,11 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], session_number: in bt.logging.debug(f"Updated scores: {self.session_accumulated_scores}") self.should_save = True - self.should_set_weights = True def set_weights(self): """ Sets the validator weights to the metagraph hotkeys based on the scores it has received from the miners. The weights determine the trust and incentive level the validator assigns to miner nodes on the network. """ - if not self.should_set_weights: - return - if not self.neuron.should_set_weights(): return @@ -149,7 +144,6 @@ def set_weights(self): ) if result is True: bt.logging.success("set_weights on chain successfully!") - self.should_set_weights = False else: bt.logging.error("set_weights failed", msg) \ No newline at end of file diff --git a/webgenie/utils/config.py b/webgenie/utils/config.py index 8db499d2..c13ec43f 100644 --- a/webgenie/utils/config.py +++ b/webgenie/utils/config.py @@ -81,7 +81,7 @@ def add_args(cls, parser): "--neuron.epoch_length", type=int, help="The default epoch length (how often we set weights, measured in 12 second blocks).", - default=10, + default=25, ) parser.add_argument( From 03157600da322a83031c4f943ee8d2638aa8d622 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 18 Jan 2025 08:37:25 -0600 Subject: [PATCH 231/554] chore: add timeout for playwright --- neurons/validators/genie_validator.py | 22 +++++++++---------- webgenie/constants.py | 10 +++++++-- webgenie/datasets/random_website_dataset.py | 4 ++-- webgenie/helpers/htmls.py | 10 +++++++-- .../common/extract_html_elements.py | 11 +++++++--- .../visual_reward/common/take_screenshot.py | 11 +++++++--- webgenie/storage/__init__.py | 4 ++++ webgenie/storage/utils.py | 4 +--- 8 files changed, 50 insertions(+), 26 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index b6f416a1..d3eb2a31 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -19,7 +19,7 @@ from webgenie.helpers.htmls import preprocess_html, is_valid_resources from webgenie.helpers.images import image_debug_str from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse -#from webgenie.storage.utils import store_results_to_database +from webgenie.storage import store_results_to_database from webgenie.tasks import Solution, ImageTaskGenerator from webgenie.utils.uids import get_all_available_uids @@ -129,16 +129,16 @@ async def score(self): bt.logging.success(f"Scores: {scores}") bt.logging.success(f"Final scores for {miner_uids}: {aggregated_scores}") - # store_results_to_database( - # { - # "neuron": self.neuron, - # "miner_uids": miner_uids, - # "solutions": solutions, - # "scores": scores, - # "aggregated_scores": aggregated_scores, - # "challenge": challenge, - # } - # ) + store_results_to_database( + { + "neuron": self.neuron, + "miner_uids": miner_uids, + "solutions": solutions, + "scores": scores, + "aggregated_scores": aggregated_scores, + "challenge": challenge, + } + ) self.neuron.score_manager.update_scores( aggregated_scores, diff --git a/webgenie/constants.py b/webgenie/constants.py index a87d71da..0bd93249 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -34,6 +34,9 @@ # max page load time GROUND_TRUTH_HTML_LOAD_TIME = 20000 +# miner html load time +CHROME_HTML_LOAD_TIME = 60000 + # miner html load time MINER_HTML_LOAD_TIME = 2000 @@ -43,8 +46,11 @@ # work dir WORK_DIR = "work" - +# html extension HTML_EXTENSION = ".html" -IMAGE_EXTENSION = ".png" +# image extension +IMAGE_EXTENSION = ".png" +# image extension +IMAGE_EXTENSION = ".png" diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index ad81aa1f..e387b0fb 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -11,7 +11,7 @@ from typing import Optional from webgenie.datasets.dataset import Dataset, DatasetEntry -from webgenie.constants import GROUND_TRUTH_HTML_LOAD_TIME +from webgenie.constants import GROUND_TRUTH_HTML_LOAD_TIME, CHROME_HTML_LOAD_TIME class RandomWebsiteDataset(Dataset): def __init__(self , **kwargs): @@ -41,7 +41,7 @@ async def get_rendered_html(self, url): async with async_playwright() as p: browser = await p.chromium.launch() page = await browser.new_page() - await page.goto(url) + await page.goto(url, timeout=CHROME_HTML_LOAD_TIME) # Wait for 10 seconds to ensure content loads await page.wait_for_timeout(GROUND_TRUTH_HTML_LOAD_TIME) rendered_html = await page.content() # Get the rendered HTML diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 1b11e04f..bf266e1c 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -12,6 +12,7 @@ from webgenie.constants import ( WORK_DIR, + CHROME_HTML_LOAD_TIME, PLACE_HOLDER_IMAGE_URL, ) from webgenie.helpers.images import image_to_base64 @@ -97,10 +98,15 @@ async def html_to_screenshot(html_content: str, page_load_time: int = 1000) -> s page = await browser.new_page() # Navigate to the URL - await page.goto(url, timeout=60000) + await page.goto(url, timeout=CHROME_HTML_LOAD_TIME) await page.wait_for_timeout(page_load_time) # Take the screenshot - await page.screenshot(path=png_path, full_page=True, animations="disabled", timeout=60000) + await page.screenshot( + path=png_path, + full_page=True, + animations="disabled", + timeout=CHROME_HTML_LOAD_TIME, + ) await page.close() await browser.close() except Exception as e: diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index 6a9e31df..f2c72856 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -6,7 +6,7 @@ from typing import Any from skimage import io, color -from webgenie.constants import DEFAULT_LOAD_TIME +from webgenie.constants import DEFAULT_LOAD_TIME, CHROME_HTML_LOAD_TIME from webgenie.rewards.visual_reward.common.browser import web_player from webgenie.rewards.visual_reward.common.sift import extract_sift_from_roi @@ -53,9 +53,14 @@ async def extract_html_elements(file_path, load_time = DEFAULT_LOAD_TIME): try: page = await web_player["browser"].new_page() - await page.goto(url) + await page.goto(url, timeout=CHROME_HTML_LOAD_TIME) await page.wait_for_timeout(load_time) - await page.screenshot(path=screenshot_path, full_page=True, animations="disabled") + await page.screenshot( + path=screenshot_path, + full_page=True, + animations="disabled", + timeout=CHROME_HTML_LOAD_TIME, + ) await asyncio.sleep(10) with open(screenshot_path, "rb") as f: diff --git a/webgenie/rewards/visual_reward/common/take_screenshot.py b/webgenie/rewards/visual_reward/common/take_screenshot.py index 84509b3f..bfb11f9d 100644 --- a/webgenie/rewards/visual_reward/common/take_screenshot.py +++ b/webgenie/rewards/visual_reward/common/take_screenshot.py @@ -1,7 +1,7 @@ import os from PIL import Image -from webgenie.constants import DEFAULT_LOAD_TIME +from webgenie.constants import DEFAULT_LOAD_TIME, CHROME_HTML_LOAD_TIME from webgenie.rewards.visual_reward.common.browser import web_player @@ -15,9 +15,14 @@ async def take_screenshot(url, output_file_path, load_time = DEFAULT_LOAD_TIME, try: page = await web_player["browser"].new_page() - await page.goto(url) + await page.goto(url, timeout=CHROME_HTML_LOAD_TIME) await page.wait_for_timeout(load_time) - await page.screenshot(path=output_file_path, full_page=True, animations='disabled') + await page.screenshot( + path=output_file_path, + full_page=True, + animations='disabled', + timeout=CHROME_HTML_LOAD_TIME, + ) await page.close() except Exception as e: print(f"Failed to take screenshot due to: {e}. Generating a blank image.") diff --git a/webgenie/storage/__init__.py b/webgenie/storage/__init__.py index e69de29b..f3d1ef85 100644 --- a/webgenie/storage/__init__.py +++ b/webgenie/storage/__init__.py @@ -0,0 +1,4 @@ +import bittensor as bt + +def store_results_to_database(results: dict): + bt.logging.info(results) diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index 390bcc8b..d2078c09 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -1,3 +1,4 @@ +import bittensor as bt from database import Session as DBSession from models import Neuron, LeaderboardSession, Competition, Challenge, Judgement, EvaluationType, TaskSolution, SolutionEvaluation from datetime import datetime, timedelta @@ -89,9 +90,6 @@ def create_task_solution(miner_answer: str, challenge_id: int, created_at: datet def create_solution_evaluation(solution_id: int, score_type_id: int, judgement_id: int, value: float): return create_record(session, SolutionEvaluation, solution_id=solution_id, score_type_id=score_type_id, judgement_id=judgement_id, value=value) -def store_results_to_database(results: dict): - pass - if __name__ == "__main__": neuron_id = add_neuron("5GKH9FPPnWSUoeeTJp19wVtd84XqFW4pyK2ijV2GsFbhTrP1", "5F4tQyWrhfGVcNhoqeiNsR6KjD4wMZ2kfhLj4oHYuyHbZAc3") logging.info(f"neuron_id: {neuron_id}") From 4396354ab273a9d030bcdb6efc195de8cde07d58 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 18 Jan 2025 13:26:15 -0600 Subject: [PATCH 232/554] chore: add logs for debug --- webgenie/datasets/random_website_dataset.py | 8 +++++++- .../lighthouse_reward/get_lighthouse_score.py | 4 ++-- webgenie/rewards/visual_reward/common/browser.py | 4 +++- .../visual_reward/common/take_screenshot.py | 16 ++++++++++------ .../clip_matching_score.py | 3 +++ .../high_level_matching_score.py | 3 +++ .../high_level_matching_score/histogram.py | 2 ++ .../low_level_matching_score.py | 2 ++ 8 files changed, 32 insertions(+), 10 deletions(-) diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index e387b0fb..15a9619f 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -42,9 +42,15 @@ async def get_rendered_html(self, url): browser = await p.chromium.launch() page = await browser.new_page() await page.goto(url, timeout=CHROME_HTML_LOAD_TIME) + # Wait for 10 seconds to ensure content loads - await page.wait_for_timeout(GROUND_TRUTH_HTML_LOAD_TIME) + # await page.wait_for_timeout(GROUND_TRUTH_HTML_LOAD_TIME) + + await page.wait_for_load_state('networkidle') + await page.wait_for_timeout(1000) + rendered_html = await page.content() # Get the rendered HTML + await page.close() await browser.close() diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index cd211dd2..d76a56ca 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -4,7 +4,6 @@ import threading import time -from tqdm import tqdm from http.server import SimpleHTTPRequestHandler, HTTPServer from typing import List, Dict @@ -97,10 +96,11 @@ def get_lighthouse_score_from_subprocess(url): time.sleep(1) # Give the server time to start bt.logging.info(f"Getting lighthouse scores from {port}...") scores = [] - for i in tqdm(range(len(htmls)), desc="Getting lighthouse scores"): + for i in range(len(htmls)): url = f"http://localhost:{port}/lighthouse_score/{i}" scores.append(get_lighthouse_score_from_subprocess(url)) + bt.logging.info("Shutting down server...") if httpd: httpd.shutdown() server_thread.join(timeout=10) diff --git a/webgenie/rewards/visual_reward/common/browser.py b/webgenie/rewards/visual_reward/common/browser.py index ad40bbba..2111489b 100644 --- a/webgenie/rewards/visual_reward/common/browser.py +++ b/webgenie/rewards/visual_reward/common/browser.py @@ -1,3 +1,4 @@ +import bittensor as bt from playwright.async_api import async_playwright @@ -13,6 +14,7 @@ async def start_browser(): browser = await web_driver.chromium.launch(headless=True) web_player["web_driver"] = web_driver web_player["browser"] = browser + bt.logging.info(f"Started browser.") async def stop_browser(): @@ -21,4 +23,4 @@ async def stop_browser(): await web_player["web_driver"].stop() web_player["web_driver"] = None web_player["browser"] = None - + bt.logging.info(f"Stopped browser.") diff --git a/webgenie/rewards/visual_reward/common/take_screenshot.py b/webgenie/rewards/visual_reward/common/take_screenshot.py index bfb11f9d..c47c99a3 100644 --- a/webgenie/rewards/visual_reward/common/take_screenshot.py +++ b/webgenie/rewards/visual_reward/common/take_screenshot.py @@ -1,3 +1,4 @@ +import bittensor as bt import os from PIL import Image @@ -6,12 +7,15 @@ async def take_screenshot(url, output_file_path, load_time = DEFAULT_LOAD_TIME, overwrite = False): + bt.logging.info(f"Taking screenshot from {url} to {output_file_path}.") if os.path.exists(url): url = f"file:///{os.path.abspath(url)}" - if os.path.exists(output_file_path) and not overwrite: - return - if os.path.exists(output_file_path) and overwrite: - os.remove(output_file_path) + + if os.path.exists(output_file_path): + if not overwrite: + return + else: + os.remove(output_file_path) try: page = await web_player["browser"].new_page() @@ -25,8 +29,8 @@ async def take_screenshot(url, output_file_path, load_time = DEFAULT_LOAD_TIME, ) await page.close() except Exception as e: - print(f"Failed to take screenshot due to: {e}. Generating a blank image.") + bt.logging.error(f"Failed to take screenshot due to: {e}. Generating a blank image.") # Generate a blank image img = Image.new('RGB', (1280, 960), color = 'white') img.save(output_file_path) - + \ No newline at end of file diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py b/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py index 47064647..577693be 100644 --- a/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py +++ b/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py @@ -1,3 +1,4 @@ +import bittensor as bt import clip import torch from PIL import Image @@ -54,6 +55,8 @@ def calculate_embedding_vector(image_path, model, preprocess, device): async def calculate_clip_score(predict_html_path_list, original_html_path): + bt.logging.info(f"Calculating clip score.") + device = "cuda" if torch.cuda.is_available() else "cpu" model, preprocess = clip.load("ViT-B/32", device=device) original_img_path = original_html_path.replace(HTML_EXTENSION, f"_inpainted{IMAGE_EXTENSION}") diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/high_level_matching_score.py b/webgenie/rewards/visual_reward/high_level_matching_score/high_level_matching_score.py index fd339471..04698f17 100644 --- a/webgenie/rewards/visual_reward/high_level_matching_score/high_level_matching_score.py +++ b/webgenie/rewards/visual_reward/high_level_matching_score/high_level_matching_score.py @@ -1,3 +1,4 @@ +import bittensor as bt import asyncio import numpy as np @@ -6,6 +7,8 @@ async def high_level_matching_score(predict_html_path_list, original_html_path): + bt.logging.info(f"Calculating high level matching score.") + clip_score = await calculate_clip_score(predict_html_path_list, original_html_path) histogram_score = await histogram_matching_score(predict_html_path_list, original_html_path) diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py b/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py index f2a96a28..f3c1ba81 100644 --- a/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py +++ b/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py @@ -1,3 +1,4 @@ +import bittensor as bt import numpy as np from PIL import Image @@ -33,6 +34,7 @@ def compare_histograms(hist1, hist2): async def histogram_matching_score(predict_html_path_list, original_html_path): + bt.logging.info(f"Calculating histogram score.") original_img_path = original_html_path.replace(HTML_EXTENSION, IMAGE_EXTENSION) await take_screenshot(original_html_path, original_img_path) original_hist = compute_grayscale_histogram(original_img_path) diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py index 08084612..0b6b4edf 100644 --- a/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py +++ b/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py @@ -1,3 +1,4 @@ +import bittensor as bt import numpy as np from webgenie.rewards.visual_reward.low_level_matching_score.element_matching_score import calculate_element_matching_similarity @@ -15,6 +16,7 @@ async def low_level_matching_score(predict_html_path_list, original_html_path): original_input_elements, original_anchor_elements, ) = await extract_html_elements(original_html_path) + bt.logging.info(f"Extracted original html elements.") results = [] for predict_html_path in predict_html_path_list: From 4cb081844d0b01d6439fe8c65abb5fee8ecdc9a5 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sun, 19 Jan 2025 22:51:56 -0600 Subject: [PATCH 233/554] feat: logic to fill up storage with data --- neurons/validators/validator.py | 19 ++++---- storage/models.py | 2 +- storage/utils.py | 86 +++++++++++++++++++++++---------- webgenie/constants.py | 8 +++ 4 files changed, 79 insertions(+), 36 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index def9becb..0559fff9 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -15,22 +15,21 @@ from typing import Tuple, Union from webgenie.base.validator import BaseValidatorNeuron -from webgenie.constants import API_HOTKEY +from webgenie.constants import ( + API_HOTKEY, + MAX_COUNT_VALIDATORS, + BLOCK_IN_SECONDS, + SESSION_WINDOW_BLOCKS, + QUERING_WINDOW_BLOCKS, + WEIGHT_SETTING_WINDOW_BLOCKS +) from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from webgenie.utils.uids import get_validator_index from neurons.validators.genie_validator import GenieValidator from neurons.validators.score_manager import ScoreManager - -MAX_COUNT_VALIDATORS = 1 - -BLOCK_IN_SECONDS = 12 -TEMPO_BLOCKS = 360 -SESSION_WINDOW_BLOCKS = TEMPO_BLOCKS * 3 - -QUERING_WINDOW_BLOCKS = 10 -WEIGHT_SETTING_WINDOW_BLOCKS = 50 # 50 blocks = 10 minutes + class Validator(BaseValidatorNeuron): diff --git a/storage/models.py b/storage/models.py index e01fd13f..aead7b71 100644 --- a/storage/models.py +++ b/storage/models.py @@ -57,6 +57,7 @@ class TaskSolution(Base): id: Mapped[int] = mapped_column(primary_key=True) created_at = Column(DateTime, default=datetime.utcnow) challenge_id: Mapped[int] = mapped_column(ForeignKey("challenges.id"), index=True) + judgement_id: Mapped[int] = mapped_column(ForeignKey("judgements.id"), index=True) miner_answer: Mapped[dict] = mapped_column(JSON) # Relationship @@ -68,7 +69,6 @@ class SolutionEvaluation(Base): id: Mapped[int] = mapped_column(primary_key=True) solution_id: Mapped[int] = mapped_column(ForeignKey("task_solutions.id"), index=True) score_type_id: Mapped[int] = mapped_column(ForeignKey("evaluation_types.id"), index=True) - judgement_id: Mapped[int] = mapped_column(ForeignKey("judgements.id"), index=True) value: Mapped[float] # Relationships diff --git a/storage/utils.py b/storage/utils.py index 390bcc8b..e128c75d 100644 --- a/storage/utils.py +++ b/storage/utils.py @@ -1,11 +1,10 @@ from database import Session as DBSession from models import Neuron, LeaderboardSession, Competition, Challenge, Judgement, EvaluationType, TaskSolution, SolutionEvaluation -from datetime import datetime, timedelta +from datetime import datetime import logging from sqlalchemy import and_ from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session - # Setup basic configuration for logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" @@ -30,6 +29,13 @@ def create_record(session: Session, model_class, **kwargs): session.close() # Close the session def add_neuron(coldkey: str, hotkey: str): + # Check if the session with the given id already exists + existing_neuron = session.query(Neuron).filter_by(hotkey=hotkey).first() + + if existing_neuron: + logging.info(f"neuron with hotkey {hotkey} already exists. Skipping creation.") + return existing_neuron.id # Return the existing session id + return create_record(session, Neuron, coldkey=coldkey, hotkey=hotkey) def get_neuron_id(hotkey: str): @@ -46,32 +52,24 @@ def get_neuron_id(hotkey: str): session.close() # Ensure the session is closed def create_leaderboard_session(session_number: int, created_at: datetime, competition_id: int): + # Check if the session with the given id already exists + existing_session = session.query(LeaderboardSession).filter_by(id=session_number).first() + + if existing_session: + logging.info(f"Session with id {session_number} already exists. Skipping creation.") + return existing_session.id # Return the existing session id + return create_record(session, LeaderboardSession, id=session_number, created_at=created_at, competition_id=competition_id) -def query_leaderboard_session(timestamp: datetime): - # Calculate the time range - interval = SESSION_PERIOD * 72 * 60 - try: - # Query the LeaderboardSession where created_at is within the specified range - leaderboard_session = session.query(LeaderboardSession).filter( - and_( - LeaderboardSession.created_at <= timestamp, - LeaderboardSession.created_at + timedelta(seconds=interval) > timestamp - ) - ).first() # Get the first matching session - - # Return the session_id if found, otherwise None - return leaderboard_session.id if leaderboard_session else None - - except Exception as e: - logging.error(f"An error occurred while querying leaderboard session: {e}") - return None # Return None in case of error - finally: - session.close() # Close the session - def create_competition(name: str): + # Check if the competition with the given name already exists + existing_competition = session.query(Competition).filter_by(name=name).first() + if existing_competition: + logging.info(f"Competition with name {name} already exists. Skipping creation.") + return existing_competition.id # Return the existing competition id + return create_record(session, Competition, name=name) def create_challenge(session_id: int, ground_truth_html: str): @@ -81,15 +79,53 @@ def create_judgement(validator_id: int, miner_id: int): return create_record(session, Judgement, validator_id=validator_id, miner_id=miner_id) def create_evaluation_type(name: str): + # Check if the competition with the given name already exists + existing_evaluation_type = session.query(EvaluationType).filter_by(name=name).first() + if existing_evaluation_type: + logging.info(f"Evaluation type with name {name} already exists. Skipping creation.") + return existing_evaluation_type.id # Return the existing evaluation type id + return create_record(session, EvaluationType, name=name) -def create_task_solution(miner_answer: str, challenge_id: int, created_at: datetime): - return create_record(session, TaskSolution, miner_answer=miner_answer, challenge_id=challenge_id, created_at=created_at) +def create_task_solution(miner_answer: dict, judgement_id, challenge_id: int, created_at: datetime): + return create_record(session, TaskSolution, miner_answer=miner_answer, judgement_id=judgement_id, challenge_id=challenge_id, created_at=created_at) def create_solution_evaluation(solution_id: int, score_type_id: int, judgement_id: int, value: float): return create_record(session, SolutionEvaluation, solution_id=solution_id, score_type_id=score_type_id, judgement_id=judgement_id, value=value) def store_results_to_database(results: dict): + neuron = results["neuron"] + vali_coldkey, vali_hotkey = results["validator"] + miner_uids = results["miner_uids"] + solutions = results["solutions"] + scores = results["scores"] + challenge = results["challenge"] + session_number = challenge["session_number"] + block_start_datetime = results["block_start_datetime"] + ground_truth_html = challenge["task"] + competition_type = challenge["competition_type"] + competition_id = create_competition(competition_type) + session_id = create_leaderboard_session(session_number, block_start_datetime, competition_id) + challenge_id = create_challenge(session_id, ground_truth_html) + + # Iterate over miner_uids to store TaskSolution data + for miner_uid, solution, score in zip(miner_uids, solutions, scores): + coldkey = miner_uid["coldkey"] + hotkey = miner_uid["hotkey"] + miner_answer = solution['miner_answer'] + neuron_validator_id = add_neuron(vali_coldkey, vali_hotkey) + neuron_miner_id = add_neuron(coldkey, hotkey) + judgement_id = create_judgement(neuron_validator_id, neuron_miner_id) + solution_id = create_task_solution(miner_answer, judgement_id, challenge_id) + # for type in evaluation_types: + # # Check if the evaluation type already exists + # evaluation_type_id = create_evaluation_type(type) + # create_solution_evaluation(solution_id, evaluation_type_id, judgement_id, score[type]) + # Collect evaluation types and their scores + for eval_type, score_value in score.items(): + evaluation_type_id = create_evaluation_type(eval_type) + create_solution_evaluation(solution_id, evaluation_type_id, judgement_id, score_value) + pass if __name__ == "__main__": diff --git a/webgenie/constants.py b/webgenie/constants.py index a87d71da..b87cd750 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -48,3 +48,11 @@ IMAGE_EXTENSION = ".png" +MAX_COUNT_VALIDATORS = 1 + +BLOCK_IN_SECONDS = 12 +TEMPO_BLOCKS = 360 +SESSION_WINDOW_BLOCKS = TEMPO_BLOCKS * 3 + +QUERING_WINDOW_BLOCKS = 10 +WEIGHT_SETTING_WINDOW_BLOCKS = 50 # 50 blocks = 10 minutes \ No newline at end of file From 3228279fbb92c5939b43a34daa3b473070a204e1 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 05:00:33 +0000 Subject: [PATCH 234/554] chore: remove opencv --- neurons/validators/genie_validator.py | 7 ++ pyproject.toml | 1 - tests/rewards/test_visual_score.py | 1 - webgenie/constants.py | 3 + .../rewards/lighthouse_reward/__init__.py | 3 +- .../lighthouse_reward/get_lighthouse_score.py | 83 +++---------------- .../lighthouse_reward/lighthouse_server.py | 44 ++++++++++ .../visual_reward/common/similarity.py | 1 - .../element_matching_score.py | 1 - .../input_matching_score.py | 1 - webgenie/utils/config.py | 2 +- 11 files changed, 67 insertions(+), 80 deletions(-) create mode 100644 webgenie/rewards/lighthouse_reward/lighthouse_server.py diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index d3eb2a31..8876ed84 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -10,6 +10,7 @@ MAX_COMPETETION_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE, WORK_DIR, + LIGHTHOUSE_SERVER_WORK_DIR, ) from webgenie.challenges import ( AccuracyChallenge, @@ -20,6 +21,7 @@ from webgenie.helpers.images import image_debug_str from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.storage import store_results_to_database +from webgenie.rewards.lighthouse_reward import start_lighthouse_server_thread from webgenie.tasks import Solution, ImageTaskGenerator from webgenie.utils.uids import get_all_available_uids @@ -36,12 +38,17 @@ def __init__(self, neuron: BaseNeuron): ] self.make_work_dir() + start_lighthouse_server_thread() def make_work_dir(self): if not os.path.exists(WORK_DIR): os.makedirs(WORK_DIR) bt.logging.info(f"Created work directory at {WORK_DIR}") + if not os.path.exists(LIGHTHOUSE_SERVER_WORK_DIR): + os.makedirs(LIGHTHOUSE_SERVER_WORK_DIR) + bt.logging.info(f"Created lighthouse server work directory at {LIGHTHOUSE_SERVER_WORK_DIR}") + async def query_miners(self): try: with self.neuron.lock: diff --git a/pyproject.toml b/pyproject.toml index 804e2216..a4b019cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ dependencies = [ "matplotlib-inline==0.1.7", "nltk", "openai", - "opencv-python==4.10.0.84", "peft", "pip-chill==1.0.3", "playwright==1.49.1", diff --git a/tests/rewards/test_visual_score.py b/tests/rewards/test_visual_score.py index a79d7038..13c3fe1a 100644 --- a/tests/rewards/test_visual_score.py +++ b/tests/rewards/test_visual_score.py @@ -1,7 +1,6 @@ import asyncio import sys import os -import cv2 import numpy as np from dotenv import load_dotenv, find_dotenv def init_test(): diff --git a/webgenie/constants.py b/webgenie/constants.py index 0bd93249..d915897c 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -46,6 +46,9 @@ # work dir WORK_DIR = "work" +# lighthouse server work dir +LIGHTHOUSE_SERVER_WORK_DIR = f"{WORK_DIR}/lighthouse_server_work" + # html extension HTML_EXTENSION = ".html" diff --git a/webgenie/rewards/lighthouse_reward/__init__.py b/webgenie/rewards/lighthouse_reward/__init__.py index d3792dc4..a61db9e3 100644 --- a/webgenie/rewards/lighthouse_reward/__init__.py +++ b/webgenie/rewards/lighthouse_reward/__init__.py @@ -1 +1,2 @@ -from .lighthouse_reward import LighthouseReward \ No newline at end of file +from .lighthouse_reward import LighthouseReward +from .lighthouse_server import start_lighthouse_server_thread \ No newline at end of file diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index d76a56ca..453bb863 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -8,64 +8,9 @@ from typing import List, Dict from webgenie.constants import LIGHTHOUSE_SERVER_PORT +from webgenie.rewards.lighthouse_reward.lighthouse_server import httpd -httpd = None - -def get_lighthouse_score(htmls: List[str], port: int = LIGHTHOUSE_SERVER_PORT) -> List[Dict[str, float]]: - class CustomHandler(SimpleHTTPRequestHandler): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def do_GET(self): - # Add CORS headers - self.send_response(200) - self.send_header('Access-Control-Allow-Origin', '*') - self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') - self.send_header('Access-Control-Allow-Headers', 'Content-Type') - if self.path == '/favicon.ico': - self.send_response(200) - self.send_header('Content-type', 'image/x-icon') - self.end_headers() - self.wfile.write(b'') # send a blank byte or actual icon data - elif self.path == '/robots.txt': - self.send_response(200) - self.send_header('Content-type', 'text/plain') - self.end_headers() - self.wfile.write(b'User-agent: *\nDisallow: /') # Example content - elif self.path.startswith('/lighthouse_score'): - try: - html_index = int(self.path.split('/')[-1]) - self.send_header('Content-type', 'text/html') - self.end_headers() - self.wfile.write(htmls[html_index].encode('utf-8')) - except Exception as e: - bt.logging.error(f"Error getting lighthouse score: {e}") - self.send_response(404) - self.end_headers() - self.wfile.write(b"Not Found") - else: - self.send_response(404) - self.end_headers() - self.wfile.write(b"Not Found") - - def do_OPTIONS(self): - # Handle preflight requests - self.send_response(200) - self.send_header('Access-Control-Allow-Origin', '*') - self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') - self.send_header('Access-Control-Allow-Headers', 'Content-Type') - self.end_headers() - - def run_server(port=8000): - global httpd - server_address = ('', port) - httpd = HTTPServer(server_address, CustomHandler) - bt.logging.info(f"Starting server on port {port}...") - httpd.serve_forever() - - server_thread = threading.Thread(target=run_server, args=(port,), daemon=True) - server_thread.start() - +def get_lighthouse_score(htmls: List[str]) -> List[Dict[str, float]]: def get_lighthouse_score_from_subprocess(url): bt.logging.info(f"Getting lighthouse score from {url}...") try: @@ -93,24 +38,16 @@ def get_lighthouse_score_from_subprocess(url): 'seo': 0 } - time.sleep(1) # Give the server time to start - bt.logging.info(f"Getting lighthouse scores from {port}...") + bt.logging.info(f"Getting lighthouse scores from localhost:{LIGHTHOUSE_SERVER_PORT}...") scores = [] for i in range(len(htmls)): - url = f"http://localhost:{port}/lighthouse_score/{i}" - scores.append(get_lighthouse_score_from_subprocess(url)) + file_name = f"{uuid.uuid4()}.html" + with open(f"{LIGHTHOUSE_SERVER_WORK_DIR}/{file_name}", "w") as f: + f.write(htmls[i]) - bt.logging.info("Shutting down server...") - if httpd: - httpd.shutdown() - server_thread.join(timeout=10) + url = f"http://localhost:{LIGHTHOUSE_SERVER_PORT}/lighthouse_score/{file_name}" + scores.append(get_lighthouse_score_from_subprocess(url)) - if server_thread.is_alive(): - bt.logging.error("Server did not shut down properly.") - else: - bt.logging.info("Server stopped successfully.") - - return scores + os.remove(f"{LIGHTHOUSE_SERVER_WORK_DIR}/{file_name}") - - \ No newline at end of file + return scores \ No newline at end of file diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_server.py b/webgenie/rewards/lighthouse_reward/lighthouse_server.py new file mode 100644 index 00000000..74f6f889 --- /dev/null +++ b/webgenie/rewards/lighthouse_reward/lighthouse_server.py @@ -0,0 +1,44 @@ +import bittensor as bt +import os +import threading +from http.server import SimpleHTTPRequestHandler, HTTPServer + +from webgenie.constants import ( + LIGHTHOUSE_SERVER_WORK_DIR, + LIGHTHOUSE_SERVER_PORT, +) + + +httpd = None + + +class CustomHTTPRequestHandler(SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + self.directory = LIGHTHOUSE_SERVER_WORK_DIR + super().__init__(*args, **kwargs) + + +def start_lighthouse_server(): + global httpd + try: + httpd = HTTPServer(('localhost', LIGHTHOUSE_SERVER_PORT), CustomHTTPRequestHandler) + httpd.serve_forever() + bt.logging.success(f"Lighthouse server started on port {LIGHTHOUSE_SERVER_PORT}") + except KeyboardInterrupt: + bt.logging.info("Keyboard interrupt detected, stopping server") + httpd.shutdown() + httpd.server_close() + httpd = None + bt.logging.info("Server stopped") + + +def start_lighthouse_server_thread(): + global httpd + try: + lighthouse_server_thread = threading.Thread(target=start_lighthouse_server, daemon=True) + lighthouse_server_thread.start() + except KeyboardInterrupt: + bt.logging.info("Keyboard interrupt detected, stopping server") + httpd.shutdown() + httpd.server_close() + bt.logging.info("Server stopped") diff --git a/webgenie/rewards/visual_reward/common/similarity.py b/webgenie/rewards/visual_reward/common/similarity.py index 20fd05c6..9459a032 100644 --- a/webgenie/rewards/visual_reward/common/similarity.py +++ b/webgenie/rewards/visual_reward/common/similarity.py @@ -1,4 +1,3 @@ -import cv2 import numpy as np from skimage import io, color from skimage.feature import SIFT diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/element_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/element_matching_score.py index 875f5fcc..04305b00 100644 --- a/webgenie/rewards/visual_reward/low_level_matching_score/element_matching_score.py +++ b/webgenie/rewards/visual_reward/low_level_matching_score/element_matching_score.py @@ -1,5 +1,4 @@ import asyncio -import cv2 import numpy as np from difflib import SequenceMatcher diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/input_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/input_matching_score.py index d0712e9b..5f505bde 100644 --- a/webgenie/rewards/visual_reward/low_level_matching_score/input_matching_score.py +++ b/webgenie/rewards/visual_reward/low_level_matching_score/input_matching_score.py @@ -4,7 +4,6 @@ from difflib import SequenceMatcher from scipy.optimize import linear_sum_assignment from skimage.metrics import structural_similarity as ssim -import cv2 from webgenie.rewards.visual_reward.common.extract_html_elements import HTMLElement from webgenie.rewards.visual_reward.common.similarity import ( diff --git a/webgenie/utils/config.py b/webgenie/utils/config.py index c13ec43f..c57fb08a 100644 --- a/webgenie/utils/config.py +++ b/webgenie/utils/config.py @@ -239,7 +239,7 @@ def add_validator_args(cls, parser): "--neuron.vpermit_tao_limit", type=int, help="The maximum number of TAO allowed to query a validator with a vpermit.", - default=4096, + default=5096, ) parser.add_argument( From f20da7cc73376a429e55e41702b996d0fcb76b03 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 05:08:19 +0000 Subject: [PATCH 235/554] chore: fix bug when there is no state file --- neurons/validators/score_manager.py | 6 +++--- uv.lock | 31 ----------------------------- 2 files changed, 3 insertions(+), 34 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 2be1eb62..456e3dc8 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -22,15 +22,15 @@ def __init__(self, neuron: BaseNeuron): self.should_save = False def load_scores(self): - bt.logging.info("Loading scores") - state = np.load(self.neuron.config.neuron.full_path + "/state.npz") try: + bt.logging.info("Loading scores") + state = np.load(self.neuron.config.neuron.full_path + "/state.npz") self.scores = state["scores"] self.hotkeys = state["hotkeys"] self.scoring_session_number = state["scoring_session_number"] self.session_accumulated_scores = state["tempo_accumulated_scores"] except Exception as e: - bt.logging.error(f"Error loading scores: {e}") + bt.logging.warning(f"Error loading scores: {e}") self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.scoring_session_number = 0 diff --git a/uv.lock b/uv.lock index bb565851..10de647e 100644 --- a/uv.lock +++ b/uv.lock @@ -1710,7 +1710,6 @@ name = "nvidia-cublas-cu12" version = "12.4.5.8" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/7f/7fbae15a3982dc9595e49ce0f19332423b260045d0a6afe93cdbe2f1f624/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0f8aa1706812e00b9f19dfe0cdb3999b092ccb8ca168c0db5b8ea712456fd9b3", size = 363333771 }, { url = "https://files.pythonhosted.org/packages/ae/71/1c91302526c45ab494c23f61c7a84aa568b8c1f9d196efa5993957faf906/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b", size = 363438805 }, ] @@ -1719,7 +1718,6 @@ name = "nvidia-cuda-cupti-cu12" version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/b5/9fb3d00386d3361b03874246190dfec7b206fd74e6e287b26a8fcb359d95/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:79279b35cf6f91da114182a5ce1864997fd52294a87a16179ce275773799458a", size = 12354556 }, { url = "https://files.pythonhosted.org/packages/67/42/f4f60238e8194a3106d06a058d494b18e006c10bb2b915655bd9f6ea4cb1/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb", size = 13813957 }, ] @@ -1728,7 +1726,6 @@ name = "nvidia-cuda-nvrtc-cu12" version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/aa/083b01c427e963ad0b314040565ea396f914349914c298556484f799e61b/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0eedf14185e04b76aa05b1fea04133e59f465b6f960c0cbf4e37c3cb6b0ea198", size = 24133372 }, { url = "https://files.pythonhosted.org/packages/2c/14/91ae57cd4db3f9ef7aa99f4019cfa8d54cb4caa7e00975df6467e9725a9f/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338", size = 24640306 }, ] @@ -1737,7 +1734,6 @@ name = "nvidia-cuda-runtime-cu12" version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/aa/b656d755f474e2084971e9a297def515938d56b466ab39624012070cb773/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:961fe0e2e716a2a1d967aab7caee97512f71767f852f67432d572e36cb3a11f3", size = 894177 }, { url = "https://files.pythonhosted.org/packages/ea/27/1795d86fe88ef397885f2e580ac37628ed058a92ed2c39dc8eac3adf0619/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5", size = 883737 }, ] @@ -1760,7 +1756,6 @@ dependencies = [ { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/8a/0e728f749baca3fbeffad762738276e5df60851958be7783af121a7221e7/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5dad8008fc7f92f5ddfa2101430917ce2ffacd86824914c82e28990ad7f00399", size = 211422548 }, { url = "https://files.pythonhosted.org/packages/27/94/3266821f65b92b3138631e9c8e7fe1fb513804ac934485a8d05776e1dd43/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9", size = 211459117 }, ] @@ -1769,7 +1764,6 @@ name = "nvidia-curand-cu12" version = "10.3.5.147" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/9c/a79180e4d70995fdf030c6946991d0171555c6edf95c265c6b2bf7011112/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1f173f09e3e3c76ab084aba0de819c49e56614feae5c12f69883f4ae9bb5fad9", size = 56314811 }, { url = "https://files.pythonhosted.org/packages/8a/6d/44ad094874c6f1b9c654f8ed939590bdc408349f137f9b98a3a23ccec411/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b", size = 56305206 }, ] @@ -1783,7 +1777,6 @@ dependencies = [ { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/46/6b/a5c33cf16af09166845345275c34ad2190944bcc6026797a39f8e0a282e0/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d338f155f174f90724bbde3758b7ac375a70ce8e706d70b018dd3375545fc84e", size = 127634111 }, { url = "https://files.pythonhosted.org/packages/3a/e1/5b9089a4b2a4790dfdea8b3a006052cfecff58139d5a4e34cb1a51df8d6f/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260", size = 127936057 }, ] @@ -1795,7 +1788,6 @@ dependencies = [ { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/96/a9/c0d2f83a53d40a4a41be14cea6a0bf9e668ffcf8b004bd65633f433050c0/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9d32f62896231ebe0480efd8a7f702e143c98cfaa0e8a76df3386c1ba2b54df3", size = 207381987 }, { url = "https://files.pythonhosted.org/packages/db/f7/97a9ea26ed4bbbfc2d470994b8b4f338ef663be97b8f677519ac195e113d/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1", size = 207454763 }, ] @@ -1812,7 +1804,6 @@ name = "nvidia-nvjitlink-cu12" version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/45/239d52c05074898a80a900f49b1615d81c07fceadd5ad6c4f86a987c0bc4/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4abe7fef64914ccfa909bc2ba39739670ecc9e820c83ccc7a6ed414122599b83", size = 20552510 }, { url = "https://files.pythonhosted.org/packages/ff/ff/847841bacfbefc97a00036e0fce5a0f086b640756dc38caea5e1bb002655/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57", size = 21066810 }, ] @@ -1821,7 +1812,6 @@ name = "nvidia-nvtx-cu12" version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/39/471f581edbb7804b39e8063d92fc8305bdc7a80ae5c07dbe6ea5c50d14a5/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3", size = 100417 }, { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 }, ] @@ -1844,23 +1834,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4b/a2/a64f495c016234ca4269005b19eb9193a925dcad01af95eb8fea3de4ee9c/openai-1.59.5-py3-none-any.whl", hash = "sha256:e646b44856b0dda9345d3c43639e056334d792d1690e99690313c0ef7ca4d8cc", size = 454815 }, ] -[[package]] -name = "opencv-python" -version = "4.10.0.84" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/b70a2d9ab205110d715906fc8ec83fbb00404aeb3a37a0654fdb68eb0c8c/opencv-python-4.10.0.84.tar.gz", hash = "sha256:72d234e4582e9658ffea8e9cae5b63d488ad06994ef12d81dc303b17472f3526", size = 95103981 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/66/82/564168a349148298aca281e342551404ef5521f33fba17b388ead0a84dc5/opencv_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc182f8f4cda51b45f01c64e4cbedfc2f00aff799debebc305d8d0210c43f251", size = 54835524 }, - { url = "https://files.pythonhosted.org/packages/64/4a/016cda9ad7cf18c58ba074628a4eaae8aa55f3fd06a266398cef8831a5b9/opencv_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:71e575744f1d23f79741450254660442785f45a0797212852ee5199ef12eed98", size = 56475426 }, - { url = "https://files.pythonhosted.org/packages/81/e4/7a987ebecfe5ceaf32db413b67ff18eb3092c598408862fff4d7cc3fd19b/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a332b50488e2dda866a6c5573ee192fe3583239fb26ff2f7f9ceb0bc119ea6", size = 41746971 }, - { url = "https://files.pythonhosted.org/packages/3f/a4/d2537f47fd7fcfba966bd806e3ec18e7ee1681056d4b0a9c8d983983e4d5/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ace140fc6d647fbe1c692bcb2abce768973491222c067c131d80957c595b71f", size = 62548253 }, - { url = "https://files.pythonhosted.org/packages/1e/39/bbf57e7b9dab623e8773f6ff36385456b7ae7fa9357a5e53db732c347eac/opencv_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2db02bb7e50b703f0a2d50c50ced72e95c574e1e5a0bb35a8a86d0b35c98c236", size = 28737688 }, - { url = "https://files.pythonhosted.org/packages/ec/6c/fab8113424af5049f85717e8e527ca3773299a3c6b02506e66436e19874f/opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe", size = 38842521 }, -] - [[package]] name = "opt-einsum" version = "3.4.0" @@ -2295,8 +2268,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/1b/d0b013bf7d1af7cf0a6a4fce13f5fe5813ab225313755367b36e714a63f8/pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93", size = 2254397 }, { url = "https://files.pythonhosted.org/packages/14/71/4cbd3870d3e926c34706f705d6793159ac49d9a213e3ababcdade5864663/pycryptodome-3.21.0-cp36-abi3-win32.whl", hash = "sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764", size = 1775641 }, { url = "https://files.pythonhosted.org/packages/43/1d/81d59d228381576b92ecede5cd7239762c14001a828bdba30d64896e9778/pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53", size = 1812863 }, - { url = "https://files.pythonhosted.org/packages/25/b3/09ff7072e6d96c9939c24cf51d3c389d7c345bf675420355c22402f71b68/pycryptodome-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca", size = 1691593 }, - { url = "https://files.pythonhosted.org/packages/a8/91/38e43628148f68ba9b68dedbc323cf409e537fd11264031961fd7c744034/pycryptodome-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd", size = 1765997 }, ] [[package]] @@ -3316,7 +3287,6 @@ dependencies = [ { name = "nltk" }, { name = "numpy" }, { name = "openai" }, - { name = "opencv-python" }, { name = "peft" }, { name = "pip-chill" }, { name = "playwright" }, @@ -3352,7 +3322,6 @@ requires-dist = [ { name = "nltk" }, { name = "numpy", specifier = ">=2.0.2" }, { name = "openai" }, - { name = "opencv-python", specifier = "==4.10.0.84" }, { name = "peft" }, { name = "pip-chill", specifier = "==1.0.3" }, { name = "playwright", specifier = "==1.49.1" }, From d489bb452f8bee25dd2b0ea0f56b7f29aa83a074 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sun, 19 Jan 2025 23:10:45 -0600 Subject: [PATCH 236/554] fix: storage models logic --- storage/models.py | 2 +- storage/utils.py | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/storage/models.py b/storage/models.py index aead7b71..dfe1da62 100644 --- a/storage/models.py +++ b/storage/models.py @@ -57,7 +57,6 @@ class TaskSolution(Base): id: Mapped[int] = mapped_column(primary_key=True) created_at = Column(DateTime, default=datetime.utcnow) challenge_id: Mapped[int] = mapped_column(ForeignKey("challenges.id"), index=True) - judgement_id: Mapped[int] = mapped_column(ForeignKey("judgements.id"), index=True) miner_answer: Mapped[dict] = mapped_column(JSON) # Relationship @@ -68,6 +67,7 @@ class SolutionEvaluation(Base): __tablename__ = "solution_evaluations" id: Mapped[int] = mapped_column(primary_key=True) solution_id: Mapped[int] = mapped_column(ForeignKey("task_solutions.id"), index=True) + judgement_id: Mapped[int] = mapped_column(ForeignKey("judgements.id"), index=True) score_type_id: Mapped[int] = mapped_column(ForeignKey("evaluation_types.id"), index=True) value: Mapped[float] diff --git a/storage/utils.py b/storage/utils.py index e128c75d..6fdbf8ca 100644 --- a/storage/utils.py +++ b/storage/utils.py @@ -87,8 +87,8 @@ def create_evaluation_type(name: str): return create_record(session, EvaluationType, name=name) -def create_task_solution(miner_answer: dict, judgement_id, challenge_id: int, created_at: datetime): - return create_record(session, TaskSolution, miner_answer=miner_answer, judgement_id=judgement_id, challenge_id=challenge_id, created_at=created_at) +def create_task_solution(miner_answer: dict, challenge_id: int): + return create_record(session, TaskSolution, miner_answer=miner_answer, challenge_id=challenge_id) def create_solution_evaluation(solution_id: int, score_type_id: int, judgement_id: int, value: float): return create_record(session, SolutionEvaluation, solution_id=solution_id, score_type_id=score_type_id, judgement_id=judgement_id, value=value) @@ -116,18 +116,12 @@ def store_results_to_database(results: dict): neuron_validator_id = add_neuron(vali_coldkey, vali_hotkey) neuron_miner_id = add_neuron(coldkey, hotkey) judgement_id = create_judgement(neuron_validator_id, neuron_miner_id) - solution_id = create_task_solution(miner_answer, judgement_id, challenge_id) - # for type in evaluation_types: - # # Check if the evaluation type already exists - # evaluation_type_id = create_evaluation_type(type) - # create_solution_evaluation(solution_id, evaluation_type_id, judgement_id, score[type]) + solution_id = create_task_solution(miner_answer, challenge_id) # Collect evaluation types and their scores for eval_type, score_value in score.items(): evaluation_type_id = create_evaluation_type(eval_type) create_solution_evaluation(solution_id, evaluation_type_id, judgement_id, score_value) - pass - if __name__ == "__main__": neuron_id = add_neuron("5GKH9FPPnWSUoeeTJp19wVtd84XqFW4pyK2ijV2GsFbhTrP1", "5F4tQyWrhfGVcNhoqeiNsR6KjD4wMZ2kfhLj4oHYuyHbZAc3") logging.info(f"neuron_id: {neuron_id}") From e13a631caee53fba21a8981eafc02979decd64e1 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 05:14:17 +0000 Subject: [PATCH 237/554] chore: remove text2html feature --- neurons/miners/hf_miner.py | 8 ++------ neurons/miners/miner.py | 1 + neurons/miners/openai_miner.py | 1 + 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/neurons/miners/hf_miner.py b/neurons/miners/hf_miner.py index adc62b1f..827d96f5 100644 --- a/neurons/miners/hf_miner.py +++ b/neurons/miners/hf_miner.py @@ -12,20 +12,16 @@ bt.logging.info(f"Total memory: {total_memory_mb}") -if total_memory_mb < 1024 * 23: +if total_memory_mb < 1024 * 25: raise ValueError("Insufficient GPU memory. HfMiner requires at least 25GB of GPU memory.") -from neurons.miners.hf_models.websight_finetuned import generate_html_from_image - -if total_memory_mb > 1024 * 50: - from neurons.miners.hf_models.falcon7b import generate_html_from_text - class HfMiner: def __init__(self, neuron: BaseNeuron): self.neuron = neuron async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynapse: + raise Exception("Not Supported yet.") try: if total_memory_mb > 1024 * 50: synapse.html = generate_html_from_text(synapse.prompt) diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index a46435fc..ca79611d 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -62,6 +62,7 @@ def __init__(self, config=None): async def forward_text( self, synapse: WebgenieTextSynapse ) -> WebgenieTextSynapse: + raise Exception("Not Supported yet.") bt.logging.debug(f"Miner text forward called with prompt: {synapse.prompt}") return await self.genie_miner.forward_text(synapse) diff --git a/neurons/miners/openai_miner.py b/neurons/miners/openai_miner.py index 93498f9e..3cd4c6f8 100644 --- a/neurons/miners/openai_miner.py +++ b/neurons/miners/openai_miner.py @@ -15,6 +15,7 @@ def __init__(self, neuron: BaseNeuron): self.neuron = neuron async def forward_text(self, synapse: WebgenieTextSynapse) -> WebgenieTextSynapse: + raise Exception("Not Supported yet.") try: html_response = await openai_call( messages = [ From fdb89a26e9388d07691e9cfcd488e9e5b8a03f3c Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 05:16:05 +0000 Subject: [PATCH 238/554] chore: refactor hf_miner --- neurons/miners/hf_miner.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/neurons/miners/hf_miner.py b/neurons/miners/hf_miner.py index 827d96f5..5155c104 100644 --- a/neurons/miners/hf_miner.py +++ b/neurons/miners/hf_miner.py @@ -5,16 +5,19 @@ from webgenie.helpers.images import base64_to_image from webgenie.utils.gpus import get_gpu_info -total_memory_mb, _, _ = get_gpu_info() -if total_memory_mb is None: - raise ValueError("No GPU detected. HfMiner requires a GPU.") +def check_requirements(): + total_memory_mb, _, _ = get_gpu_info() -bt.logging.info(f"Total memory: {total_memory_mb}") + if total_memory_mb is None: + raise ValueError("No GPU detected. HfMiner requires a GPU.") -if total_memory_mb < 1024 * 25: - raise ValueError("Insufficient GPU memory. HfMiner requires at least 25GB of GPU memory.") + bt.logging.info(f"Total memory: {total_memory_mb}") + if total_memory_mb < 1024 * 25: + raise ValueError("Insufficient GPU memory. HfMiner requires at least 25GB of GPU memory.") + +check_requirements() class HfMiner: def __init__(self, neuron: BaseNeuron): From 776be0ebc62d25a4bac5683576f2b457dd692b3b Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 05:25:06 +0000 Subject: [PATCH 239/554] fix: fix bugs in lighthouse score --- neurons/validators/genie_validator.py | 2 -- neurons/validators/validator.py | 5 ++++- .../lighthouse_reward/lighthouse_reward.py | 8 +++----- .../lighthouse_reward/lighthouse_server.py | 18 ++++++++++++++---- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 8876ed84..cf85f3c6 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -21,7 +21,6 @@ from webgenie.helpers.images import image_debug_str from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.storage import store_results_to_database -from webgenie.rewards.lighthouse_reward import start_lighthouse_server_thread from webgenie.tasks import Solution, ImageTaskGenerator from webgenie.utils.uids import get_all_available_uids @@ -38,7 +37,6 @@ def __init__(self, neuron: BaseNeuron): ] self.make_work_dir() - start_lighthouse_server_thread() def make_work_dir(self): if not os.path.exists(WORK_DIR): diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index e3a34ed9..178c6480 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -17,6 +17,7 @@ from webgenie.base.validator import BaseValidatorNeuron from webgenie.constants import API_HOTKEY from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse +from webgenie.rewards.lighthouse_reward import start_lighthouse_server_thread, stop_lighthouse_server from webgenie.utils.uids import get_validator_index from neurons.validators.genie_validator import GenieValidator @@ -301,7 +302,8 @@ def run_background_threads(self): self.synthensize_task_thread.start() self.query_miners_thread.start() self.score_thread.start() - self.set_weights_thread.start() + self.set_weights_thread.start() + start_lighthouse_server_thread() bt.logging.info("Started background threads") bt.logging.info("=" * 40) @@ -320,6 +322,7 @@ def stop_background_threads(self): self.query_miners_thread = None self.score_thread = None self.set_weights_thread = None + stop_lighthouse_server() bt.logging.info("Stopped background threads") def __enter__(self): diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py index 42a1438c..f2497abd 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py @@ -19,9 +19,9 @@ class LighthouseReward(Reward): def __init__(self): pass - def sync_reward_worker(self, htmls: List[str], port: int = LIGHTHOUSE_SERVER_PORT) -> List[float]: + def sync_reward_worker(self, htmls: List[str]) -> List[float]: try: - scores_dict = get_lighthouse_score(htmls, port) + scores_dict = get_lighthouse_score(htmls) scores = [] weights = [0, 0.25, 0.25, 0.5] for score_dict in scores_dict: @@ -49,11 +49,9 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: # Create partial tasks for each chunk futures = [] - port = LIGHTHOUSE_SERVER_PORT for chunk in html_chunks: - future = pool.apply_async(self.sync_reward_worker, args=(chunk, port)) + future = pool.apply_async(self.sync_reward_worker, args=(chunk)) futures.append(future) - port += 1 # Gather all results scores = [] diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_server.py b/webgenie/rewards/lighthouse_reward/lighthouse_server.py index 74f6f889..20385a33 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_server.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_server.py @@ -24,8 +24,8 @@ def start_lighthouse_server(): httpd = HTTPServer(('localhost', LIGHTHOUSE_SERVER_PORT), CustomHTTPRequestHandler) httpd.serve_forever() bt.logging.success(f"Lighthouse server started on port {LIGHTHOUSE_SERVER_PORT}") - except KeyboardInterrupt: - bt.logging.info("Keyboard interrupt detected, stopping server") + except Exception as e: + bt.logging.error(f"Error starting lighthouse server: {e}") httpd.shutdown() httpd.server_close() httpd = None @@ -37,8 +37,18 @@ def start_lighthouse_server_thread(): try: lighthouse_server_thread = threading.Thread(target=start_lighthouse_server, daemon=True) lighthouse_server_thread.start() - except KeyboardInterrupt: - bt.logging.info("Keyboard interrupt detected, stopping server") + bt.logging.info("Lighthouse server started") + except Exception as e: + bt.logging.error(f"Error starting lighthouse server: {e}") httpd.shutdown() httpd.server_close() bt.logging.info("Server stopped") + + +def stop_lighthouse_server(): + global httpd + if httpd: + httpd.shutdown() + httpd.server_close() + httpd = None + bt.logging.info("Server stopped") From 83f3159be0b5a95bc4b5858d0d3bc71b589d2541 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 05:29:07 +0000 Subject: [PATCH 240/554] chore: fix bugs --- neurons/validators/genie_validator.py | 3 ++- webgenie/rewards/lighthouse_reward/__init__.py | 5 ++++- webgenie/tasks/__init__.py | 3 --- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index cf85f3c6..7225f555 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -21,7 +21,8 @@ from webgenie.helpers.images import image_debug_str from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse from webgenie.storage import store_results_to_database -from webgenie.tasks import Solution, ImageTaskGenerator +from webgenie.tasks import Solution +from webgenie.tasks.image_task_generator import ImageTaskGenerator from webgenie.utils.uids import get_all_available_uids diff --git a/webgenie/rewards/lighthouse_reward/__init__.py b/webgenie/rewards/lighthouse_reward/__init__.py index a61db9e3..98a629aa 100644 --- a/webgenie/rewards/lighthouse_reward/__init__.py +++ b/webgenie/rewards/lighthouse_reward/__init__.py @@ -1,2 +1,5 @@ from .lighthouse_reward import LighthouseReward -from .lighthouse_server import start_lighthouse_server_thread \ No newline at end of file +from .lighthouse_server import ( + start_lighthouse_server_thread, + stop_lighthouse_server, +) \ No newline at end of file diff --git a/webgenie/tasks/__init__.py b/webgenie/tasks/__init__.py index 1cc72146..86a8eb18 100644 --- a/webgenie/tasks/__init__.py +++ b/webgenie/tasks/__init__.py @@ -1,5 +1,2 @@ from .solution import Solution from .task import Task, ImageTask, TextTask -from .task_generator import TaskGenerator -from .image_task_generator import ImageTaskGenerator -from .text_task_generator import TextTaskGenerator From 7e93c7150ab7809e56148d91dde8162fa6c1765b Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sun, 19 Jan 2025 23:36:26 -0600 Subject: [PATCH 241/554] fix: score result payload --- storage/utils.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/storage/utils.py b/storage/utils.py index 6fdbf8ca..fb32d23b 100644 --- a/storage/utils.py +++ b/storage/utils.py @@ -94,24 +94,29 @@ def create_solution_evaluation(solution_id: int, score_type_id: int, judgement_i return create_record(session, SolutionEvaluation, solution_id=solution_id, score_type_id=score_type_id, judgement_id=judgement_id, value=value) def store_results_to_database(results: dict): - neuron = results["neuron"] - vali_coldkey, vali_hotkey = results["validator"] - miner_uids = results["miner_uids"] + # Extracting validator keys correctly + vali_coldkey = results["validator"]["coldkey"] + vali_hotkey = results["validator"]["hotkey"] + + # Extracting miners, solutions, scores, and challenge details + miners = results["miners"] solutions = results["solutions"] scores = results["scores"] challenge = results["challenge"] + session_number = challenge["session_number"] - block_start_datetime = results["block_start_datetime"] + session_start_datetime = results["session_start_datetime"] ground_truth_html = challenge["task"] competition_type = challenge["competition_type"] + competition_id = create_competition(competition_type) - session_id = create_leaderboard_session(session_number, block_start_datetime, competition_id) + session_id = create_leaderboard_session(session_number, session_start_datetime, competition_id) challenge_id = create_challenge(session_id, ground_truth_html) # Iterate over miner_uids to store TaskSolution data - for miner_uid, solution, score in zip(miner_uids, solutions, scores): - coldkey = miner_uid["coldkey"] - hotkey = miner_uid["hotkey"] + for miner, solution, score in zip(miners, solutions, scores): + coldkey = miner["coldkey"] + hotkey = miner["hotkey"] miner_answer = solution['miner_answer'] neuron_validator_id = add_neuron(vali_coldkey, vali_hotkey) neuron_miner_id = add_neuron(coldkey, hotkey) From 51208da76fdf03e0f0fa1245eccf70f9447436ea Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 00:59:30 -0600 Subject: [PATCH 242/554] fix: send the challenge data to the stats collector --- storage/utils.py | 130 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/storage/utils.py b/storage/utils.py index fb32d23b..b7cdcdb0 100644 --- a/storage/utils.py +++ b/storage/utils.py @@ -1,3 +1,5 @@ +from bittensor import Wallet +from io import BufferedReader from database import Session as DBSession from models import Neuron, LeaderboardSession, Competition, Challenge, Judgement, EvaluationType, TaskSolution, SolutionEvaluation from datetime import datetime @@ -5,6 +7,10 @@ from sqlalchemy import and_ from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session +import json +import time +import requests + # Setup basic configuration for logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" @@ -127,6 +133,130 @@ def store_results_to_database(results: dict): evaluation_type_id = create_evaluation_type(eval_type) create_solution_evaluation(solution_id, evaluation_type_id, judgement_id, score_value) +def get_session_data(session_number: int): + try: + competition = session.query(Competition).join(LeaderboardSession).filter( + LeaderboardSession.id == session_number + ).first() + + if not competition: + return {} + + # Constructing the payload + payload = { + "external_id": competition.id, + "name": competition.name, + "leaderboard_sessions": [] + } + + for leaderboard_session in competition.sessions: + session_data = { + "external_id": leaderboard_session.id, + "created_at": leaderboard_session.created_at.isoformat(), + "challenges": [] + } + + for challenge in leaderboard_session.challenges: + challenge_data = { + "external_id": challenge.id, + "ground_truth_html": challenge.ground_truth_html, + "task_solutions": [] + } + + for task_solution in challenge.solutions: + solution_data = { + "external_id": task_solution.id, + "created_at": task_solution.created_at.isoformat(), + "miner_answer": task_solution.miner_answer, + "solution_evaluations": [] + } + + # Retrieve evaluations for the task solution + for evaluation in task_solution.solution_scores: + judgement = session.query(Judgement).get(evaluation.judgement_id) + miner_neuron = session.query(Neuron).get(judgement.miner_id) + validator_neuron = session.query(Neuron).get(judgement.validator_id) + + evaluation_data = { + "external_id": evaluation.id, + "judgement": { + "external_id": judgement.id, + "miner": miner_neuron.hotkey, # Assuming hotkey is used as identifier + "validator": validator_neuron.hotkey + }, + "evaluation_type": { + "external_id": evaluation.score_type_id, + "name": session.query(EvaluationType).get(evaluation.score_type_id).name + }, + "value": evaluation.value + } + + solution_data["solution_evaluations"].append(evaluation_data) + + challenge_data["task_solutions"].append(solution_data) + + session_data["challenges"].append(challenge_data) + + payload["leaderboard_sessions"].append(session_data) + + return payload + except SQLAlchemyError as e: + logging.error(f"An error occurred while fetching neuron: {e}") + return None + finally: + session.close() + +def make_signed_request( + wallet: Wallet, + url: str, + subnet_id: int, + payload: dict, + method: str = 'POST', + file_path: str | None = None, + subnet_chain: str = 'mainnet', +) -> requests.Response: + headers = { + 'Realm': subnet_chain, + 'SubnetID': str(subnet_id), + 'Nonce': str(time.time()), + 'Hotkey': wallet.hotkey.ss58_address, + } + + file_content = b"" + files = None + if file_path: + # TODO: start context for opening file + opened_file = open(file_path, "rb") + files = {"file": opened_file} + file = files.get("file") + + if isinstance(file, BufferedReader): + file_content = file.read() + file.seek(0) + + headers_str = json.dumps(headers, sort_keys=True) + data_to_sign = f"{method}{url}{headers_str}{file_content.decode(errors='ignore')}".encode() + signature = wallet.hotkey.sign( + data_to_sign, + ).hex() + headers["Signature"] = signature + + response = requests.request(method, url, headers=headers, files=files, json=payload, timeout=5) + return response + +def test_send_payload(session_number: int) -> None: + wallet = Wallet() + session_data = get_session_data(session_number) + response = make_signed_request( + wallet=wallet, + url="https://webgenie-collector.bactensor.io/api/competitions/", + subnet_id=12, + method="POST", + payload=session_data, + ) + if not response.ok: + print(response.json()) + if __name__ == "__main__": neuron_id = add_neuron("5GKH9FPPnWSUoeeTJp19wVtd84XqFW4pyK2ijV2GsFbhTrP1", "5F4tQyWrhfGVcNhoqeiNsR6KjD4wMZ2kfhLj4oHYuyHbZAc3") logging.info(f"neuron_id: {neuron_id}") From 9484c47950eafd72dc2a23455be0f82f92a72e69 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 07:31:53 +0000 Subject: [PATCH 243/554] fix: fix bug in lighthouse score --- neurons/miners/miner.py | 47 ++++++++++------ neurons/validators/genie_validator.py | 54 +++++++++++++++---- tests/rewards/test_lighthouse.py | 7 ++- webgenie/constants.py | 8 ++- webgenie/protocol.py | 33 ++++++++++++ .../lighthouse_reward/get_lighthouse_score.py | 9 +++- .../lighthouse_reward/lighthouse_reward.py | 6 +-- .../lighthouse_reward/lighthouse_server.py | 30 +++++------ webgenie/tasks/task.py | 1 + 9 files changed, 144 insertions(+), 51 deletions(-) diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index ca79611d..62244df0 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -24,6 +24,7 @@ import bittensor as bt from webgenie.base.miner import BaseMinerNeuron +from webgenie.constants import TASK_REVEAL_TIME from webgenie.helpers.images import image_debug_str from webgenie.helpers.weights import init_wandb from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse @@ -46,10 +47,6 @@ def __init__(self, config=None): # Attach determiners which functions are called when servicing a request. bt.logging.info(f"Attaching forward function to miner axon.") self.axon.attach( - forward_fn=self.forward_text, - blacklist_fn=self.blacklist_text, - priority_fn=self.priority_text, - ).attach( forward_fn = self.forward_image, blacklist_fn=self.blacklist_image, priority_fn=self.priority_image, @@ -57,30 +54,46 @@ def __init__(self, config=None): self.genie_miner = OpenaiMiner(self) + self.task_state: typing.Dict[str, typing.Dict[str, typing.Any]] = {} + init_wandb(self) - async def forward_text( - self, synapse: WebgenieTextSynapse - ) -> WebgenieTextSynapse: - raise Exception("Not Supported yet.") - bt.logging.debug(f"Miner text forward called with prompt: {synapse.prompt}") - return await self.genie_miner.forward_text(synapse) - async def forward_image( self, synapse: WebgenieImageSynapse ) -> WebgenieImageSynapse: bt.logging.debug(f"Miner image forward called with image: {image_debug_str(synapse.base64_image)}...") - return await self.genie_miner.forward_image(synapse) + + if synapse.task_id not in self.task_state: + bt.logging.debug(f"Task {synapse.task_id} is not calculated yet.") + create_time = time.time() + synapse = await self.genie_miner.forward_image(synapse) + + nonce = synapse.add_answer_hash(synapse.html) + self.task_state[synapse.task_id] = { + "html": synapse.html, + "nonce": nonce, + "create_time": create_time + } + + synapse.hide_secret_info() + return synapse + else: + DELTA = 2 + create_time = self.task_state[synapse.task_id]["create_time"] + if time.time() - create_time >= TASK_REVEAL_TIME + IMAGE_TASK_TIMEOUT - DELTA: + bt.logging.debug(f"Task {synapse.task_id} is ready to reveal.") + synapse.html = self.task_state[synapse.task_id]["html"] + synapse.nonce = self.task_state[synapse.task_id]["nonce"] + del self.task_state[synapse.task_id] + return synapse + else: + bt.logging.warning(f"Task {synapse.task_id} is not ready to reveal yet.") + return synapse - async def blacklist_text(self, synapse: WebgenieTextSynapse) -> typing.Tuple[bool, str]: - return await self.blacklist(synapse) async def blacklist_image(self, synapse: WebgenieImageSynapse) -> typing.Tuple[bool, str]: return await self.blacklist(synapse) - async def priority_text(self, synapse: WebgenieTextSynapse) -> float: - return await self.priority(synapse) - async def priority_image(self, synapse: WebgenieImageSynapse) -> float: return await self.priority(synapse) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 7225f555..c1f4b449 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -78,26 +78,39 @@ async def query_miners(self): challenge_class = available_challenges_classes[session_number % len(available_challenges_classes)] challenge = challenge_class(task=task, session_number=session_number) + + synapse.task_id = task.task_id synapse.competition_type = challenge.competition_type bt.logging.debug(f"Querying {len(miner_uids)} miners") + + query_time = time.time() async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: all_synapse_results = await dendrite( axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], synapse=synapse, timeout=task.timeout, ) - bt.logging.debug(f"Received {len(all_synapse_results)} synapse results") + + elapsed_time = time.time() - query_time + sleep_time_before_reveal = max(0, task.timeout - elapsed_time) + TASK_REVEAL_TIME + time.sleep(sleep_time_before_reveal) + async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: + all_synapse_results = await dendrite( + axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], + synapse=synapse, + timeout=TASK_REVEAL_TIMEOUT, + ) + solutions = [] for synapse, miner_uid in zip(all_synapse_results, miner_uids): - processed_synapse = await self.process_synapse(synapse) - if processed_synapse is not None: + checked_synapse = await self.checked_synapse(synapse) + if checked_synapse is not None: solutions.append( Solution( - html = processed_synapse.html, + html = checked_synapse.html, miner_uid = miner_uid, - process_time = processed_synapse.dendrite.process_time, ) ) challenge.solutions = solutions @@ -178,6 +191,9 @@ async def synthensize_task(self): async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImageSynapse]): if isinstance(synapse, WebgenieTextSynapse): bt.logging.debug(f"Organic text forward: {synapse.prompt}") + bt.logging.info("Not supported yet.") + synapse.html = "Not supported yet." + return synapse else: bt.logging.debug(f"Organic image forward: {image_debug_str(synapse.base64_image)}...") @@ -186,35 +202,51 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag if not all_miner_uids: raise Exception("No miners available") + query_time = time.time() async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: responses = await dendrite( axons=[self.neuron.metagraph.axons[uid] for uid in all_miner_uids], synapse=synapse, timeout=synapse.timeout, ) + + elapsed_time = time.time() - query_time + sleep_time_before_reveal = max(0, synapse.timeout - elapsed_time) + TASK_REVEAL_TIME + time.sleep(sleep_time_before_reveal) + + async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: + responses = await dendrite( + axons=[self.neuron.metagraph.axons[uid] for uid in all_miner_uids], + synapse=synapse, + timeout=TASK_REVEAL_TIMEOUT, + ) + # Sort miner UIDs and responses by incentive scores incentives = self.neuron.metagraph.I[all_miner_uids] sorted_indices = np.argsort(-incentives) # Negative for descending order all_miner_uids = [all_miner_uids[i] for i in sorted_indices] + responses = [responses[i] for i in sorted_indices] for response in responses: - processed_synapse = await self.process_synapse(response) - if processed_synapse is None: + checked_synapse = await self.checked_synapse(response) + if checked_synapse is None: continue - return processed_synapse + return checked_synapse + raise Exception(f"No valid solution received") except Exception as e: bt.logging.error(f"[forward_organic_synapse] Error querying dendrite: {e}") synapse.html = f"Error: {e}" return synapse - async def process_synapse(self, synapse: bt.Synapse) -> bt.Synapse: + async def checked_synapse(self, synapse: bt.Synapse) -> bt.Synapse: if synapse.dendrite.status_code == 200: - html = preprocess_html(synapse.html) - if not html: + if not synapse.verify_answer_hash(): return None + if not is_valid_resources(html): return None + synapse.html = html return synapse return None diff --git a/tests/rewards/test_lighthouse.py b/tests/rewards/test_lighthouse.py index 1011196d..b5648275 100644 --- a/tests/rewards/test_lighthouse.py +++ b/tests/rewards/test_lighthouse.py @@ -2,16 +2,19 @@ import sys import os -parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(parent_dir) from dotenv import load_dotenv, find_dotenv load_dotenv(find_dotenv(filename=".env.validator")) +from webgenie.rewards.lighthouse_reward.lighthouse_server import start_lighthouse_server_thread, stop_lighthouse_server from webgenie.rewards.lighthouse_reward import LighthouseReward from webgenie.tasks import Task, Solution + async def test_lighthouse_reward(): + start_lighthouse_server_thread() reward = LighthouseReward() task = Task( prompt="Create a website that displays a list of 10 random numbers.", @@ -21,6 +24,8 @@ async def test_lighthouse_reward(): html2 = "

Hello, World!

" solutions = [Solution(html=html) for _ in range(5)] + [Solution(html=html2) for _ in range(5)] print(await reward.reward(task, solutions)) + stop_lighthouse_server() + if __name__ == "__main__": asyncio.run(test_lighthouse_reward()) diff --git a/webgenie/constants.py b/webgenie/constants.py index d915897c..ea3669eb 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -10,8 +10,14 @@ # text task timeout TEXT_TASK_TIMEOUT = 100 +# reveal time +TASK_REVEAL_TIME = 20 + +# reveal time out +TASK_REVEAL_TIMEOUT = 20 + # lighthouse server port -LIGHTHOUSE_SERVER_PORT = 5000 +LIGHTHOUSE_SERVER_PORT = 5002 # max competition history size MAX_COMPETETION_HISTORY_SIZE = 30 diff --git a/webgenie/protocol.py b/webgenie/protocol.py index 06e37abb..7d623511 100644 --- a/webgenie/protocol.py +++ b/webgenie/protocol.py @@ -32,6 +32,12 @@ class WebgenieImageSynapse(bt.Synapse): """ A protocol for the webgenie image task. """ + task_id: str = pydantic.Field( + factory=lambda: str(uuid.uuid4()), + title="Task ID", + description="The task ID.", + ) + base64_image: str = pydantic.Field( "", title="Base64 Image", @@ -49,3 +55,30 @@ class WebgenieImageSynapse(bt.Synapse): title="HTML", description="The HTML received from miners.", ) + + html_hash: str = pydantic.Field( + "", + title="HTML Hash", + description="The hash of the HTML.", + ) + + nonce: int = pydantic.Field( + "", + title="Nonce", + description="The nonce.", + ) + + def add_answer_hash(self, html: str): + nonce = random.randint(0, 1000000) + hash_input = html + str(nonce) + self.html_hash = hashlib.sha256(hash_input.encode()).hexdigest() + self.nonce = nonce + return nonce + + def verify_answer_hash(self): + hash_input = self.html + str(self.nonce) + return hashlib.sha256(hash_input.encode()).hexdigest() == self.html_hash + + def hide_secret_info(self): + self.html = "" + self.nonce = 0 \ No newline at end of file diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 453bb863..67bf38c9 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -3,11 +3,16 @@ import subprocess import threading import time +import uuid +import os from http.server import SimpleHTTPRequestHandler, HTTPServer from typing import List, Dict -from webgenie.constants import LIGHTHOUSE_SERVER_PORT +from webgenie.constants import ( + LIGHTHOUSE_SERVER_PORT, + LIGHTHOUSE_SERVER_WORK_DIR, +) from webgenie.rewards.lighthouse_reward.lighthouse_server import httpd def get_lighthouse_score(htmls: List[str]) -> List[Dict[str, float]]: @@ -45,7 +50,7 @@ def get_lighthouse_score_from_subprocess(url): with open(f"{LIGHTHOUSE_SERVER_WORK_DIR}/{file_name}", "w") as f: f.write(htmls[i]) - url = f"http://localhost:{LIGHTHOUSE_SERVER_PORT}/lighthouse_score/{file_name}" + url = f"http://localhost:{LIGHTHOUSE_SERVER_PORT}/{file_name}" scores.append(get_lighthouse_score_from_subprocess(url)) os.remove(f"{LIGHTHOUSE_SERVER_WORK_DIR}/{file_name}") diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py index f2497abd..adfe80f9 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py @@ -7,11 +7,9 @@ import numpy as np from typing import List -from webgenie.constants import LIGHTHOUSE_SERVER_PORT from webgenie.rewards.reward import Reward from webgenie.tasks import Task, Solution - from .get_lighthouse_score import get_lighthouse_score @@ -45,12 +43,13 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: with multiprocessing.Pool(processes=os.cpu_count()) as pool: # Convert solutions into chunks for parallel processing chunk_size = max(1, len(htmls) // os.cpu_count()) + html_chunks = [htmls[i:i + chunk_size] for i in range(0, len(htmls), chunk_size)] # Create partial tasks for each chunk futures = [] for chunk in html_chunks: - future = pool.apply_async(self.sync_reward_worker, args=(chunk)) + future = pool.apply_async(self.sync_reward_worker, args=(chunk,)) futures.append(future) # Gather all results @@ -58,3 +57,4 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: for future in futures: scores.extend(future.get()) return np.array(scores) + \ No newline at end of file diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_server.py b/webgenie/rewards/lighthouse_reward/lighthouse_server.py index 20385a33..2be43e8b 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_server.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_server.py @@ -18,6 +18,15 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) +def stop_lighthouse_server(): + global httpd + if httpd: + httpd.shutdown() + httpd.server_close() + httpd = None + bt.logging.info("Lighthouse server stopped") + + def start_lighthouse_server(): global httpd try: @@ -26,10 +35,9 @@ def start_lighthouse_server(): bt.logging.success(f"Lighthouse server started on port {LIGHTHOUSE_SERVER_PORT}") except Exception as e: bt.logging.error(f"Error starting lighthouse server: {e}") - httpd.shutdown() - httpd.server_close() - httpd = None - bt.logging.info("Server stopped") + stop_lighthouse_server() + raise e + def start_lighthouse_server_thread(): @@ -40,15 +48,5 @@ def start_lighthouse_server_thread(): bt.logging.info("Lighthouse server started") except Exception as e: bt.logging.error(f"Error starting lighthouse server: {e}") - httpd.shutdown() - httpd.server_close() - bt.logging.info("Server stopped") - - -def stop_lighthouse_server(): - global httpd - if httpd: - httpd.shutdown() - httpd.server_close() - httpd = None - bt.logging.info("Server stopped") + stop_lighthouse_server() + raise e diff --git a/webgenie/tasks/task.py b/webgenie/tasks/task.py index d588bc15..c6800db7 100644 --- a/webgenie/tasks/task.py +++ b/webgenie/tasks/task.py @@ -9,6 +9,7 @@ class Task(BaseModel): generator: Any = Field(default=None) src: str = Field(default="Unknown", description="The source of the task") + class ImageTask(Task): base64_image: str = Field(default="", description="The base64 encoded image") ground_truth_html: str = Field(default="", description="The ground truth html") From 5ecfb48d1ebf6944ce5159d82692d41de41bd8f7 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 02:38:02 -0600 Subject: [PATCH 244/554] fix: logic to retrieve the payload for the stats collector --- storage/utils.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/storage/utils.py b/storage/utils.py index b7cdcdb0..dfc0a92f 100644 --- a/storage/utils.py +++ b/storage/utils.py @@ -150,6 +150,9 @@ def get_session_data(session_number: int): } for leaderboard_session in competition.sessions: + if leaderboard_session.id != session_number: + continue # Skip sessions that do not match the session number + session_data = { "external_id": leaderboard_session.id, "created_at": leaderboard_session.created_at.isoformat(), @@ -256,16 +259,3 @@ def test_send_payload(session_number: int) -> None: ) if not response.ok: print(response.json()) - -if __name__ == "__main__": - neuron_id = add_neuron("5GKH9FPPnWSUoeeTJp19wVtd84XqFW4pyK2ijV2GsFbhTrP1", "5F4tQyWrhfGVcNhoqeiNsR6KjD4wMZ2kfhLj4oHYuyHbZAc3") - logging.info(f"neuron_id: {neuron_id}") - - html = "
test
" - - create_competition("Accuracy") - create_competition("SEO") - create_competition("CODE_QUALITY") - create_competition("WEIGHTED_SCORE") - session_id = create_leaderboard_session(datetime.now(), 1) - challenge = create_challenge(session_id, html) From 24911e8157a1190f90dbf9b856f90dc262db0b91 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 08:55:42 +0000 Subject: [PATCH 245/554] wip: lighthouse --- pyproject.toml | 2 + test.html | 5 ++ tests/rewards/test_lighthouse.py | 9 ++- uv.lock | 4 ++ .../lighthouse_reward/get_lighthouse_score.py | 10 +++- .../lighthouse_reward/lighthouse_server.py | 13 ++--- .../lighthouse_server_fastapi.py | 57 +++++++++++++++++++ 7 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 test.html create mode 100644 webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py diff --git a/pyproject.toml b/pyproject.toml index a4b019cc..9398f990 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "duckduckgo_search", "datasets", "einops", + "fastapi", "lxml==5.3.0", "matplotlib-inline==0.1.7", "nltk", @@ -36,6 +37,7 @@ dependencies = [ "sentence-transformers", "tensorflow>=2.18.0", "tf-keras", + "uvicorn", ] [project.urls] diff --git a/test.html b/test.html new file mode 100644 index 00000000..47d4d9a1 --- /dev/null +++ b/test.html @@ -0,0 +1,5 @@ + + +

Hello World

+ + \ No newline at end of file diff --git a/tests/rewards/test_lighthouse.py b/tests/rewards/test_lighthouse.py index b5648275..9c4eb263 100644 --- a/tests/rewards/test_lighthouse.py +++ b/tests/rewards/test_lighthouse.py @@ -8,7 +8,7 @@ from dotenv import load_dotenv, find_dotenv load_dotenv(find_dotenv(filename=".env.validator")) -from webgenie.rewards.lighthouse_reward.lighthouse_server import start_lighthouse_server_thread, stop_lighthouse_server +from webgenie.rewards.lighthouse_reward.lighthouse_server_fastapi import start_lighthouse_server_thread, stop_lighthouse_server from webgenie.rewards.lighthouse_reward import LighthouseReward from webgenie.tasks import Task, Solution @@ -22,8 +22,13 @@ async def test_lighthouse_reward(): ) html = "

Hello, World!

" html2 = "

Hello, World!

Hello, World!
" - solutions = [Solution(html=html) for _ in range(5)] + [Solution(html=html2) for _ in range(5)] + solutions = [Solution(html=html) for _ in range(1)] + [Solution(html=html2) for _ in range(1)] print(await reward.reward(task, solutions)) + while True: + try: + pass + except KeyboardInterrupt: + break stop_lighthouse_server() diff --git a/uv.lock b/uv.lock index 10de647e..8e572589 100644 --- a/uv.lock +++ b/uv.lock @@ -3282,6 +3282,7 @@ dependencies = [ { name = "ddt" }, { name = "duckduckgo-search" }, { name = "einops" }, + { name = "fastapi" }, { name = "lxml" }, { name = "matplotlib-inline" }, { name = "nltk" }, @@ -3300,6 +3301,7 @@ dependencies = [ { name = "tensorflow" }, { name = "tf-keras" }, { name = "tinycss2" }, + { name = "uvicorn" }, { name = "wandb" }, ] @@ -3317,6 +3319,7 @@ requires-dist = [ { name = "ddt", specifier = "==1.6.0" }, { name = "duckduckgo-search" }, { name = "einops" }, + { name = "fastapi" }, { name = "lxml", specifier = "==5.3.0" }, { name = "matplotlib-inline", specifier = "==0.1.7" }, { name = "nltk" }, @@ -3335,6 +3338,7 @@ requires-dist = [ { name = "tensorflow", specifier = ">=2.18.0" }, { name = "tf-keras" }, { name = "tinycss2", specifier = "==1.4.0" }, + { name = "uvicorn" }, { name = "wandb", specifier = "==0.19.0" }, ] diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 67bf38c9..0520d6ae 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -45,14 +45,18 @@ def get_lighthouse_score_from_subprocess(url): bt.logging.info(f"Getting lighthouse scores from localhost:{LIGHTHOUSE_SERVER_PORT}...") scores = [] + for i in range(len(htmls)): + file_name = f"{uuid.uuid4()}.html" + file_name = file_name.replace("-", "") + with open(f"{LIGHTHOUSE_SERVER_WORK_DIR}/{file_name}", "w") as f: f.write(htmls[i]) - url = f"http://localhost:{LIGHTHOUSE_SERVER_PORT}/{file_name}" + url = f"http://localhost:{LIGHTHOUSE_SERVER_PORT}/test.html" scores.append(get_lighthouse_score_from_subprocess(url)) - - os.remove(f"{LIGHTHOUSE_SERVER_WORK_DIR}/{file_name}") + + #os.remove(f"{LIGHTHOUSE_SERVER_WORK_DIR}/{file_name}") return scores \ No newline at end of file diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_server.py b/webgenie/rewards/lighthouse_reward/lighthouse_server.py index 2be43e8b..69c2e2a2 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_server.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_server.py @@ -1,6 +1,7 @@ import bittensor as bt import os import threading + from http.server import SimpleHTTPRequestHandler, HTTPServer from webgenie.constants import ( @@ -10,12 +11,8 @@ httpd = None - - -class CustomHTTPRequestHandler(SimpleHTTPRequestHandler): - def __init__(self, *args, **kwargs): - self.directory = LIGHTHOUSE_SERVER_WORK_DIR - super().__init__(*args, **kwargs) +handler = SimpleHTTPRequestHandler +handler.directory = f"/{LIGHTHOUSE_SERVER_WORK_DIR}" def stop_lighthouse_server(): @@ -28,9 +25,9 @@ def stop_lighthouse_server(): def start_lighthouse_server(): - global httpd + global httpd, handler try: - httpd = HTTPServer(('localhost', LIGHTHOUSE_SERVER_PORT), CustomHTTPRequestHandler) + httpd = HTTPServer(('localhost', LIGHTHOUSE_SERVER_PORT), handler) httpd.serve_forever() bt.logging.success(f"Lighthouse server started on port {LIGHTHOUSE_SERVER_PORT}") except Exception as e: diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py new file mode 100644 index 00000000..9828efaf --- /dev/null +++ b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py @@ -0,0 +1,57 @@ +import bittensor as bt +import os +import threading +import uvicorn + +from fastapi import FastAPI +from fastapi.responses import FileResponse + +from webgenie.constants import ( + LIGHTHOUSE_SERVER_WORK_DIR, + LIGHTHOUSE_SERVER_PORT, +) + + +app = FastAPI() +static_folder = f"/{LIGHTHOUSE_SERVER_WORK_DIR}" +lighthouse_server_thread = None + + +@app.get("/{file_path:path}") +async def serve_file(file_path: str): + try: + print("serving file", file_path) + return FileResponse(os.path.join(static_folder, file_path)) + except Exception as e: + return FileResponse(status_code=404) + + +def stop_lighthouse_server(): + global lighthouse_server_thread + if lighthouse_server_thread: + lighthouse_server_thread.join(10) + lighthouse_server_thread = None + bt.logging.info("Lighthouse server stopped") + + +def start_lighthouse_server(): + try: + uvicorn.run(app, host="0.0.0.0", port=LIGHTHOUSE_SERVER_PORT) + bt.logging.success(f"Lighthouse server started on port {LIGHTHOUSE_SERVER_PORT}") + except Exception as e: + bt.logging.error(f"Error starting lighthouse server: {e}") + stop_lighthouse_server() + raise e + + + +def start_lighthouse_server_thread(): + global lighthouse_server_thread + try: + lighthouse_server_thread = threading.Thread(target=start_lighthouse_server, daemon=True) + lighthouse_server_thread.start() + bt.logging.info("Lighthouse server started") + except Exception as e: + bt.logging.error(f"Error starting lighthouse server: {e}") + stop_lighthouse_server() + raise e From fa8e12e078e2f63282823f14e8c06e579d00ce13 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 09:34:42 +0000 Subject: [PATCH 246/554] fix: fix bugs in lighthouse scoring --- neurons/validators/genie_validator.py | 4 +++- .../lighthouse_reward/get_lighthouse_score.py | 3 +-- .../lighthouse_server_fastapi.py | 12 +++--------- webgenie/tasks/image_task_generator.py | 17 +++++++++-------- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index c1f4b449..accb1759 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -3,6 +3,7 @@ import numpy as np import random import threading +import time from typing import Union from webgenie.base.neuron import BaseNeuron @@ -11,6 +12,8 @@ MAX_SYNTHETIC_TASK_SIZE, WORK_DIR, LIGHTHOUSE_SERVER_WORK_DIR, + TASK_REVEAL_TIME, + TASK_REVEAL_TIMEOUT, ) from webgenie.challenges import ( AccuracyChallenge, @@ -79,7 +82,6 @@ async def query_miners(self): challenge_class = available_challenges_classes[session_number % len(available_challenges_classes)] challenge = challenge_class(task=task, session_number=session_number) - synapse.task_id = task.task_id synapse.competition_type = challenge.competition_type bt.logging.debug(f"Querying {len(miner_uids)} miners") diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 0520d6ae..a4a6660d 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -54,9 +54,8 @@ def get_lighthouse_score_from_subprocess(url): with open(f"{LIGHTHOUSE_SERVER_WORK_DIR}/{file_name}", "w") as f: f.write(htmls[i]) - url = f"http://localhost:{LIGHTHOUSE_SERVER_PORT}/test.html" scores.append(get_lighthouse_score_from_subprocess(url)) - #os.remove(f"{LIGHTHOUSE_SERVER_WORK_DIR}/{file_name}") + os.remove(f"{LIGHTHOUSE_SERVER_WORK_DIR}/{file_name}") return scores \ No newline at end of file diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py index 9828efaf..ac58f377 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py @@ -2,9 +2,9 @@ import os import threading import uvicorn - from fastapi import FastAPI from fastapi.responses import FileResponse +from fastapi.staticfiles import StaticFiles from webgenie.constants import ( LIGHTHOUSE_SERVER_WORK_DIR, @@ -13,17 +13,11 @@ app = FastAPI() + static_folder = f"/{LIGHTHOUSE_SERVER_WORK_DIR}" lighthouse_server_thread = None - -@app.get("/{file_path:path}") -async def serve_file(file_path: str): - try: - print("serving file", file_path) - return FileResponse(os.path.join(static_folder, file_path)) - except Exception as e: - return FileResponse(status_code=404) +app.mount("/", StaticFiles(directory=f"{LIGHTHOUSE_SERVER_WORK_DIR}"), name="static") def stop_lighthouse_server(): diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 6642aa94..c4e2aa4c 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -64,13 +64,14 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: base64_image = await html_to_screenshot(ground_truth_html, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) bt.logging.debug(f"Screenshot generated for {dataset_entry.src}") + image_task = ImageTask( + base64_image=base64_image, + ground_truth_html=ground_truth_html, + timeout=IMAGE_TASK_TIMEOUT, + generator=self, + src=dataset_entry.src, + ) return ( - ImageTask( - base64_image=base64_image, - ground_truth_html=ground_truth_html, - timeout=IMAGE_TASK_TIMEOUT, - generator=self, - src=dataset_entry.src, - ), - WebgenieImageSynapse(base64_image=base64_image), + image_task, + WebgenieImageSynapse(base64_image=base64_image, task_id=image_task.task_id), ) From feeb0cb036620d6b0368d29bb2a1cb5c1a8fefa2 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 03:41:01 -0600 Subject: [PATCH 247/554] feat: merge conflicts --- webgenie/constants.py | 3 --- webgenie/storage/utils.py | 10 ---------- 2 files changed, 13 deletions(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index fd8df5a2..ef4cbf06 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -61,7 +61,6 @@ # image extension IMAGE_EXTENSION = ".png" -<<<<<<< HEAD MAX_COUNT_VALIDATORS = 1 @@ -71,7 +70,5 @@ QUERING_WINDOW_BLOCKS = 10 WEIGHT_SETTING_WINDOW_BLOCKS = 50 # 50 blocks = 10 minutes -======= # image extension IMAGE_EXTENSION = ".png" ->>>>>>> d479540c463c84c143f3b2ec47c8399ee9de644c diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index cd1994ad..dfc0a92f 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -1,9 +1,5 @@ -<<<<<<< HEAD:storage/utils.py from bittensor import Wallet from io import BufferedReader -======= -import bittensor as bt ->>>>>>> d479540c463c84c143f3b2ec47c8399ee9de644c:webgenie/storage/utils.py from database import Session as DBSession from models import Neuron, LeaderboardSession, Competition, Challenge, Judgement, EvaluationType, TaskSolution, SolutionEvaluation from datetime import datetime @@ -103,7 +99,6 @@ def create_task_solution(miner_answer: dict, challenge_id: int): def create_solution_evaluation(solution_id: int, score_type_id: int, judgement_id: int, value: float): return create_record(session, SolutionEvaluation, solution_id=solution_id, score_type_id=score_type_id, judgement_id=judgement_id, value=value) -<<<<<<< HEAD:storage/utils.py def store_results_to_database(results: dict): # Extracting validator keys correctly vali_coldkey = results["validator"]["coldkey"] @@ -137,11 +132,6 @@ def store_results_to_database(results: dict): for eval_type, score_value in score.items(): evaluation_type_id = create_evaluation_type(eval_type) create_solution_evaluation(solution_id, evaluation_type_id, judgement_id, score_value) -======= -if __name__ == "__main__": - neuron_id = add_neuron("5GKH9FPPnWSUoeeTJp19wVtd84XqFW4pyK2ijV2GsFbhTrP1", "5F4tQyWrhfGVcNhoqeiNsR6KjD4wMZ2kfhLj4oHYuyHbZAc3") - logging.info(f"neuron_id: {neuron_id}") ->>>>>>> d479540c463c84c143f3b2ec47c8399ee9de644c:webgenie/storage/utils.py def get_session_data(session_number: int): try: From 6422ed4d79fd3296f97f741d2d359c9ab79c4952 Mon Sep 17 00:00:00 2001 From: Sangar <8627971+sangar-1028@users.noreply.github.com> Date: Mon, 20 Jan 2025 02:58:33 -0700 Subject: [PATCH 248/554] Update README.md --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ab654080..11b9bef7 100644 --- a/README.md +++ b/README.md @@ -122,19 +122,21 @@ The WebGenieAI subnet incentivizes miners and validators to ensure high-quality npm install pm2 -g git clone https://github.com/web-genie-ai/web-genie-ai.git cd web-genie-ai -conda create -name venv python=3.12 -conda activate venv -pip install -r requirements.txt +curl -LsSf https://astral.sh/uv/install.sh | sh +uv sync ``` - miner ```bash -pm2 start neurons/miners/miner.py --name "webgenie_miner" --interpreter python -- --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port] +pm2 start uv -- run neurons/miners/miner.py --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port] ``` - validator ```bash +npm install -g lighthouse +wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb +sudo dpkg -i google-chrome-stable_current_amd64.deb playwright install-deps playwright install -pm2 start neurons/validators/validator.py --name "webgenie_validator" --interpreter python -- --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port] +pm2 start uv -- run neurons/validators/validator.py --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port] ``` - running auto_update script for validators ```bash From be1d00d7fdbaee3de04c2207608231a552fa3f51 Mon Sep 17 00:00:00 2001 From: Sangar <8627971+sangar-1028@users.noreply.github.com> Date: Mon, 20 Jan 2025 03:16:25 -0700 Subject: [PATCH 249/554] Update min_compute.yml --- min_compute.yml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/min_compute.yml b/min_compute.yml index 3d8e0153..d80fe34b 100644 --- a/min_compute.yml +++ b/min_compute.yml @@ -21,13 +21,13 @@ compute_spec: architecture: "x86_64" # Architecture type (e.g., x86_64, arm64) gpu: - required: True # Does the application require a GPU? - min_vram: 8 # Minimum GPU VRAM (GB) - recommended_vram: 24 # Recommended GPU VRAM (GB) - cuda_cores: 1024 # Minimum number of CUDA cores (if applicable) - min_compute_capability: 6.0 # Minimum CUDA compute capability - recommended_compute_capability: 7.0 # Recommended CUDA compute capability - recommended_gpu: "NVIDIA A100" # provide a recommended GPU to purchase/rent + required: True # Does the application require a GPU? + min_vram: 24 # Minimum GPU VRAM (GB) + recommended_vram: 24 # Recommended GPU VRAM (GB) + cuda_cores: 1024 # Minimum number of CUDA cores (if applicable) + min_compute_capability: 6.0 # Minimum CUDA compute capability + recommended_compute_capability: 7.0 # Recommended CUDA compute capability + recommended_gpu: "NVIDIA RTX 4090" # provide a recommended GPU to purchase/rent memory: min_ram: 16 # Minimum RAM (GB) @@ -36,9 +36,9 @@ compute_spec: ram_type: "DDR4" # RAM type (e.g., DDR4, DDR3, etc.) storage: - min_space: 10 # Minimum free storage space (GB) - recommended_space: 100 # Recommended free storage space (GB) - type: "SSD" # Preferred storage type (e.g., SSD, HDD) + min_space: 320 # Minimum free storage space (GB) + recommended_space: 320 # Recommended free storage space (GB) + recommended_type: "SSD" # Preferred storage type (e.g., SSD, HDD) min_iops: 1000 # Minimum I/O operations per second (if applicable) recommended_iops: 5000 # Recommended I/O operations per second @@ -49,15 +49,15 @@ compute_spec: validator: cpu: - min_cores: 4 # Minimum number of CPU cores - min_speed: 2.5 # Minimum speed per core (GHz) - recommended_cores: 8 # Recommended number of CPU cores - recommended_speed: 3.5 # Recommended speed per core (GHz) - architecture: "x86_64" # Architecture type (e.g., x86_64, arm64) + min_cores: 24 # Minimum number of CPU cores + min_speed: 2.5 # Minimum speed per core (GHz) + recommended_cores: 24 # Recommended number of CPU cores + recommended_speed: 3.5 # Recommended speed per core (GHz) + architecture: "x86_64" # Architecture type (e.g., x86_64, arm64) gpu: - required: True # Does the application require a GPU? - min_vram: 80 # Minimum GPU VRAM (GB) + required: False # Does the application require a GPU? + min_vram: 24 # Minimum GPU VRAM (GB) recommended_vram: 100 # Recommended GPU VRAM (GB) cuda_cores: 1024 # Minimum number of CUDA cores (if applicable) min_compute_capability: 6.0 # Minimum CUDA compute capability @@ -65,7 +65,7 @@ compute_spec: recommended_gpu: "NVIDIA A100" # provide a recommended GPU to purchase/rent memory: - min_ram: 16 # Minimum RAM (GB) + min_ram: 32 # Minimum RAM (GB) min_swap: 4 # Minimum swap space (GB) recommended_swap: 8 # Recommended swap space (GB) ram_type: "DDR4" # RAM type (e.g., DDR4, DDR3, etc.) From b82d5897360b5e0217fea9c26e8b2b3a5dde19b7 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 10:52:22 +0000 Subject: [PATCH 250/554] chore: fix bugs for pydantic --- neurons/miners/miner.py | 13 +++++++---- neurons/validators/genie_validator.py | 8 +++++-- webgenie/constants.py | 16 +++++++++----- webgenie/protocol.py | 32 ++++++++++++++++----------- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index 62244df0..d28c50ee 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -27,7 +27,12 @@ from webgenie.constants import TASK_REVEAL_TIME from webgenie.helpers.images import image_debug_str from webgenie.helpers.weights import init_wandb -from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse +from webgenie.protocol import ( + WebgenieTextSynapse, + WebgenieImageSynapse, + add_answer_hash, + hide_secret_info, +) from neurons.miners.openai_miner import OpenaiMiner @@ -68,17 +73,17 @@ async def forward_image( create_time = time.time() synapse = await self.genie_miner.forward_image(synapse) - nonce = synapse.add_answer_hash(synapse.html) + nonce = add_answer_hash(synapse, synapse.html) self.task_state[synapse.task_id] = { "html": synapse.html, "nonce": nonce, "create_time": create_time } - synapse.hide_secret_info() + hide_secret_info(synapse) return synapse else: - DELTA = 2 + DELTA = 10 create_time = self.task_state[synapse.task_id]["create_time"] if time.time() - create_time >= TASK_REVEAL_TIME + IMAGE_TASK_TIMEOUT - DELTA: bt.logging.debug(f"Task {synapse.task_id} is ready to reveal.") diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index accb1759..a76ca760 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -22,7 +22,11 @@ ) from webgenie.helpers.htmls import preprocess_html, is_valid_resources from webgenie.helpers.images import image_debug_str -from webgenie.protocol import WebgenieImageSynapse, WebgenieTextSynapse +from webgenie.protocol import ( + WebgenieImageSynapse, + WebgenieTextSynapse, + verify_answer_hash, +) from webgenie.storage import store_results_to_database from webgenie.tasks import Solution from webgenie.tasks.image_task_generator import ImageTaskGenerator @@ -243,7 +247,7 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag async def checked_synapse(self, synapse: bt.Synapse) -> bt.Synapse: if synapse.dendrite.status_code == 200: - if not synapse.verify_answer_hash(): + if not verify_answer_hash(synapse): return None if not is_valid_resources(html): diff --git a/webgenie/constants.py b/webgenie/constants.py index ef4cbf06..a13f40bb 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -5,10 +5,10 @@ DEFAULT_LOAD_TIME = 1000 # image task timeout -IMAGE_TASK_TIMEOUT = 100 +IMAGE_TASK_TIMEOUT = 72 # text task timeout -TEXT_TASK_TIMEOUT = 100 +TEXT_TASK_TIMEOUT = 72 # reveal time TASK_REVEAL_TIME = 20 @@ -61,14 +61,20 @@ # image extension IMAGE_EXTENSION = ".png" - +# max count of validators MAX_COUNT_VALIDATORS = 1 +# block in seconds BLOCK_IN_SECONDS = 12 + +# tempo blocks TEMPO_BLOCKS = 360 + +# session window blocks SESSION_WINDOW_BLOCKS = TEMPO_BLOCKS * 3 +# querying window blocks QUERING_WINDOW_BLOCKS = 10 + +# weight setting window blocks WEIGHT_SETTING_WINDOW_BLOCKS = 50 # 50 blocks = 10 minutes -# image extension -IMAGE_EXTENSION = ".png" diff --git a/webgenie/protocol.py b/webgenie/protocol.py index 7d623511..4e3ec1ae 100644 --- a/webgenie/protocol.py +++ b/webgenie/protocol.py @@ -2,7 +2,10 @@ # Copyright © 2024 pycorn import bittensor as bt +import hashlib import pydantic +import random +import uuid class WebgenieTextSynapse(bt.Synapse): @@ -33,7 +36,7 @@ class WebgenieImageSynapse(bt.Synapse): A protocol for the webgenie image task. """ task_id: str = pydantic.Field( - factory=lambda: str(uuid.uuid4()), + "", title="Task ID", description="The task ID.", ) @@ -68,17 +71,20 @@ class WebgenieImageSynapse(bt.Synapse): description="The nonce.", ) - def add_answer_hash(self, html: str): - nonce = random.randint(0, 1000000) - hash_input = html + str(nonce) - self.html_hash = hashlib.sha256(hash_input.encode()).hexdigest() - self.nonce = nonce - return nonce - def verify_answer_hash(self): - hash_input = self.html + str(self.nonce) - return hashlib.sha256(hash_input.encode()).hexdigest() == self.html_hash +def add_answer_hash(self, html: str) -> int: + nonce = random.randint(0, 1000000) + hash_input = html + str(nonce) + self.html_hash = hashlib.sha256(hash_input.encode()).hexdigest() + self.nonce = nonce + return nonce + + +def verify_answer_hash(self) -> bool: + hash_input = self.html + str(self.nonce) + return hashlib.sha256(hash_input.encode()).hexdigest() == self.html_hash + - def hide_secret_info(self): - self.html = "" - self.nonce = 0 \ No newline at end of file +def hide_secret_info(self): + self.html = "" + self.nonce = 0 \ No newline at end of file From 7e67d85b62117476b579f52f332caca15a626522 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 10:57:21 +0000 Subject: [PATCH 251/554] chore: fix some unable logs --- .../lighthouse_reward/lighthouse_server.py | 49 ------------------- .../lighthouse_server_fastapi.py | 2 +- 2 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 webgenie/rewards/lighthouse_reward/lighthouse_server.py diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_server.py b/webgenie/rewards/lighthouse_reward/lighthouse_server.py deleted file mode 100644 index 69c2e2a2..00000000 --- a/webgenie/rewards/lighthouse_reward/lighthouse_server.py +++ /dev/null @@ -1,49 +0,0 @@ -import bittensor as bt -import os -import threading - -from http.server import SimpleHTTPRequestHandler, HTTPServer - -from webgenie.constants import ( - LIGHTHOUSE_SERVER_WORK_DIR, - LIGHTHOUSE_SERVER_PORT, -) - - -httpd = None -handler = SimpleHTTPRequestHandler -handler.directory = f"/{LIGHTHOUSE_SERVER_WORK_DIR}" - - -def stop_lighthouse_server(): - global httpd - if httpd: - httpd.shutdown() - httpd.server_close() - httpd = None - bt.logging.info("Lighthouse server stopped") - - -def start_lighthouse_server(): - global httpd, handler - try: - httpd = HTTPServer(('localhost', LIGHTHOUSE_SERVER_PORT), handler) - httpd.serve_forever() - bt.logging.success(f"Lighthouse server started on port {LIGHTHOUSE_SERVER_PORT}") - except Exception as e: - bt.logging.error(f"Error starting lighthouse server: {e}") - stop_lighthouse_server() - raise e - - - -def start_lighthouse_server_thread(): - global httpd - try: - lighthouse_server_thread = threading.Thread(target=start_lighthouse_server, daemon=True) - lighthouse_server_thread.start() - bt.logging.info("Lighthouse server started") - except Exception as e: - bt.logging.error(f"Error starting lighthouse server: {e}") - stop_lighthouse_server() - raise e diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py index ac58f377..14a59c55 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py @@ -30,8 +30,8 @@ def stop_lighthouse_server(): def start_lighthouse_server(): try: + bt.logging.success(f"Trying to start lighthouse server on port {LIGHTHOUSE_SERVER_PORT}") uvicorn.run(app, host="0.0.0.0", port=LIGHTHOUSE_SERVER_PORT) - bt.logging.success(f"Lighthouse server started on port {LIGHTHOUSE_SERVER_PORT}") except Exception as e: bt.logging.error(f"Error starting lighthouse server: {e}") stop_lighthouse_server() From 8f8244ebf67a86bb2ec3f13831d083bd95ee7c26 Mon Sep 17 00:00:00 2001 From: Sangar <8627971+sangar-1028@users.noreply.github.com> Date: Mon, 20 Jan 2025 05:33:17 -0600 Subject: [PATCH 252/554] Add files via upload --- Web-Genie-logo.png | Bin 0 -> 9359 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Web-Genie-logo.png diff --git a/Web-Genie-logo.png b/Web-Genie-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ac16d1c23aee3e22e37b5476e000c19708a88506 GIT binary patch literal 9359 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 Date: Mon, 20 Jan 2025 11:39:38 +0000 Subject: [PATCH 253/554] fix: fix bugs in commit reveal --- neurons/miners/miner.py | 6 +++++- neurons/validators/genie_validator.py | 15 ++++++++++---- neurons/validators/validator.py | 20 +++++++++---------- test.html | 5 ----- webgenie/protocol.py | 2 +- .../rewards/lighthouse_reward/__init__.py | 2 +- .../lighthouse_reward/get_lighthouse_score.py | 1 - 7 files changed, 28 insertions(+), 23 deletions(-) delete mode 100644 test.html diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index d28c50ee..7f5c4628 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -24,7 +24,11 @@ import bittensor as bt from webgenie.base.miner import BaseMinerNeuron -from webgenie.constants import TASK_REVEAL_TIME +from webgenie.constants import ( + TASK_REVEAL_TIME, + IMAGE_TASK_TIMEOUT, + TEXT_TASK_TIMEOUT, +) from webgenie.helpers.images import image_debug_str from webgenie.helpers.weights import init_wandb from webgenie.protocol import ( diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index a76ca760..555fccf5 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -92,7 +92,7 @@ async def query_miners(self): query_time = time.time() async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: - all_synapse_results = await dendrite( + all_synapse_hash_results = await dendrite( axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], synapse=synapse, timeout=task.timeout, @@ -102,16 +102,19 @@ async def query_miners(self): sleep_time_before_reveal = max(0, task.timeout - elapsed_time) + TASK_REVEAL_TIME time.sleep(sleep_time_before_reveal) + bt.logging.debug(f"Revealing task {task.task_id}") + async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: - all_synapse_results = await dendrite( + all_synapse_reveal_results = await dendrite( axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], synapse=synapse, timeout=TASK_REVEAL_TIMEOUT, ) solutions = [] - for synapse, miner_uid in zip(all_synapse_results, miner_uids): - checked_synapse = await self.checked_synapse(synapse) + for reveal_synapse, hash_synapse, miner_uid in zip(all_synapse_reveal_results, all_synapse_hash_results, miner_uids): + reveal_synapse.html_hash = hash_synapse.html_hash + checked_synapse = await self.checked_synapse(reveal_synapse) if checked_synapse is not None: solutions.append( Solution( @@ -247,6 +250,10 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag async def checked_synapse(self, synapse: bt.Synapse) -> bt.Synapse: if synapse.dendrite.status_code == 200: + bt.logging.debug(f"Checking synapse: {synapse.html}") + bt.logging.debug(f"Checking synapse: {synapse.nonce}") + bt.logging.debug(f"Checking synapse: {synapse.html_hash}") + bt.logging.debug(f"Checking synapse: {verify_answer_hash(synapse)}") if not verify_answer_hash(synapse): return None diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index c5245903..515568e8 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -179,16 +179,16 @@ def query_miners_loop(self): f"End: {end_period_block}, " f"Current: {current_block}") # Sleep if outside query window - if current_block < start_period_block: - sleep_blocks = start_period_block - current_block - bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") - time.sleep(sleep_blocks * BLOCK_IN_SECONDS) - continue - elif current_block > end_period_block: - sleep_blocks = (start_period_block - current_block + all_validator_query_period_blocks) - bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") - time.sleep(sleep_blocks * BLOCK_IN_SECONDS) - continue + # if current_block < start_period_block: + # sleep_blocks = start_period_block - current_block + # bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") + # time.sleep(sleep_blocks * BLOCK_IN_SECONDS) + # continue + # elif current_block > end_period_block: + # sleep_blocks = (start_period_block - current_block + all_validator_query_period_blocks) + # bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") + # time.sleep(sleep_blocks * BLOCK_IN_SECONDS) + # continue QUERY_MINERS_TIMEOUT = 60 * 15 self.query_miners_event_loop.run_until_complete( diff --git a/test.html b/test.html deleted file mode 100644 index 47d4d9a1..00000000 --- a/test.html +++ /dev/null @@ -1,5 +0,0 @@ - - -

Hello World

- - \ No newline at end of file diff --git a/webgenie/protocol.py b/webgenie/protocol.py index 4e3ec1ae..9620e76b 100644 --- a/webgenie/protocol.py +++ b/webgenie/protocol.py @@ -66,7 +66,7 @@ class WebgenieImageSynapse(bt.Synapse): ) nonce: int = pydantic.Field( - "", + 0, title="Nonce", description="The nonce.", ) diff --git a/webgenie/rewards/lighthouse_reward/__init__.py b/webgenie/rewards/lighthouse_reward/__init__.py index 98a629aa..663ee400 100644 --- a/webgenie/rewards/lighthouse_reward/__init__.py +++ b/webgenie/rewards/lighthouse_reward/__init__.py @@ -1,5 +1,5 @@ from .lighthouse_reward import LighthouseReward -from .lighthouse_server import ( +from .lighthouse_server_fastapi import ( start_lighthouse_server_thread, stop_lighthouse_server, ) \ No newline at end of file diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index a4a6660d..3ce02bc1 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -13,7 +13,6 @@ LIGHTHOUSE_SERVER_PORT, LIGHTHOUSE_SERVER_WORK_DIR, ) -from webgenie.rewards.lighthouse_reward.lighthouse_server import httpd def get_lighthouse_score(htmls: List[str]) -> List[Dict[str, float]]: def get_lighthouse_score_from_subprocess(url): From 655ee18f63029c2c3cdba9a56107502ddbe16cae Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 11:43:13 +0000 Subject: [PATCH 254/554] fix: fix bugs --- neurons/validators/genie_validator.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 555fccf5..79e9e360 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -250,14 +250,13 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag async def checked_synapse(self, synapse: bt.Synapse) -> bt.Synapse: if synapse.dendrite.status_code == 200: - bt.logging.debug(f"Checking synapse: {synapse.html}") - bt.logging.debug(f"Checking synapse: {synapse.nonce}") - bt.logging.debug(f"Checking synapse: {synapse.html_hash}") - bt.logging.debug(f"Checking synapse: {verify_answer_hash(synapse)}") if not verify_answer_hash(synapse): + bt.logging.warning(f"Invalid answer hash: {synapse.html_hash}") return None + html = preprocess_html(synapse.html) if not is_valid_resources(html): + bt.logging.warning(f"Invalid resources: {html}") return None synapse.html = html From 9a1ef96182cd5480e6d2a00d6a18ddc19479e982 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 11:58:42 +0000 Subject: [PATCH 255/554] chore: fix bugs --- webgenie/rewards/lighthouse_reward/get_lighthouse_score.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 3ce02bc1..625d5245 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -48,11 +48,11 @@ def get_lighthouse_score_from_subprocess(url): for i in range(len(htmls)): file_name = f"{uuid.uuid4()}.html" - file_name = file_name.replace("-", "") with open(f"{LIGHTHOUSE_SERVER_WORK_DIR}/{file_name}", "w") as f: f.write(htmls[i]) + url = f"http://localhost:{LIGHTHOUSE_SERVER_PORT}/{file_name}" scores.append(get_lighthouse_score_from_subprocess(url)) os.remove(f"{LIGHTHOUSE_SERVER_WORK_DIR}/{file_name}") From 2079bebc13c1162e6c1c0a4023964c6128d814ea Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 13:27:33 +0000 Subject: [PATCH 256/554] fix: fix bugs in datetime format --- .gitignore | 2 +- neurons/validators/genie_validator.py | 63 +++++++++++++++--- webgenie.db | Bin 20480 -> 0 bytes .../lighthouse_reward/get_lighthouse_score.py | 2 +- webgenie/storage/__init__.py | 5 +- webgenie/storage/models.py | 2 +- webgenie/storage/utils.py | 33 +++++---- 7 files changed, 72 insertions(+), 35 deletions(-) delete mode 100644 webgenie.db diff --git a/.gitignore b/.gitignore index 7819271b..ceef3254 100644 --- a/.gitignore +++ b/.gitignore @@ -50,7 +50,7 @@ coverage.xml .hypothesis/ .pytest_cache/ cover/ - +*.db # Translations *.mo *.pot diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 79e9e360..05052531 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -4,6 +4,8 @@ import random import threading import time + +from datetime import datetime, timedelta from typing import Union from webgenie.base.neuron import BaseNeuron @@ -14,6 +16,8 @@ LIGHTHOUSE_SERVER_WORK_DIR, TASK_REVEAL_TIME, TASK_REVEAL_TIMEOUT, + SESSION_WINDOW_BLOCKS, + BLOCK_IN_SECONDS, ) from webgenie.challenges import ( AccuracyChallenge, @@ -29,6 +33,11 @@ ) from webgenie.storage import store_results_to_database from webgenie.tasks import Solution +from webgenie.tasks.metric_types import ( + ACCURACY_METRIC_NAME, + QUALITY_METRIC_NAME, + SEO_METRIC_NAME, +) from webgenie.tasks.image_task_generator import ImageTaskGenerator from webgenie.utils.uids import get_all_available_uids @@ -156,17 +165,51 @@ async def score(self): bt.logging.success(f"Competition Type: {challenge.competition_type}") bt.logging.success(f"Scores: {scores}") bt.logging.success(f"Final scores for {miner_uids}: {aggregated_scores}") - - store_results_to_database( - { - "neuron": self.neuron, - "miner_uids": miner_uids, - "solutions": solutions, - "scores": scores, - "aggregated_scores": aggregated_scores, - "challenge": challenge, + + with self.neuron.lock: + current_block = self.neuron.block + session_number = self.neuron.session_number + session_start_block = session_number * SESSION_WINDOW_BLOCKS + session_start_datetime = ( + datetime.now() - + timedelta( + seconds=(current_block - session_start_block) * BLOCK_IN_SECONDS + ) + ) + payload = { + "validator": { + "hotkey": self.neuron.metagraph.axons[self.neuron.uid].hotkey, + "coldkey": self.neuron.metagraph.axons[self.neuron.uid].coldkey, + }, + "miners": [ + { + "coldkey": self.neuron.metagraph.axons[miner_uids[i]].coldkey, + "hotkey": self.neuron.metagraph.axons[miner_uids[i]].hotkey, + } for i in range(len(miner_uids)) + ], + "solutions": [ + { + "miner_answer": { "html": solution.html }, + } for solution in solutions + ], + "scores": [ + { + "aggregated_score": aggregated_scores[i], + "accuracy": scores[ACCURACY_METRIC_NAME][i], + "seo": scores[SEO_METRIC_NAME][i], + "code_quality": scores[QUALITY_METRIC_NAME][i], + } for i in range(len(miner_uids)) + ], + "challenge": { + "task": challenge.task.ground_truth_html, + "competition_type": challenge.competition_type, + "session_number": challenge.session_number, + }, + "session_start_datetime": session_start_datetime, } - ) + + bt.logging.info(f"Storing results to database: {payload}") + store_results_to_database(payload) self.neuron.score_manager.update_scores( aggregated_scores, diff --git a/webgenie.db b/webgenie.db deleted file mode 100644 index 7f82c3a3e4dc91fff10bde2320b0d5ab89c3ab57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmeI%?{AYp7zc1zJL?*^^0L=P+Uc^_^-BWW4+{5uo+l)v!wV;toP5mkX(-7{qh*+;@tP3BFluVwRr_kw z{rak-&Ss_kuV&47{&lv0_}i#IDU5?(hd&Qms(}px5P$##AOHafKmY;|_PNow z2j{kbN#4>+(v2fakM%vD4$fRX*4^O=Z2HN?j90_F%CjPkn9K_~VG}t`s*?{kP%%ie(e8B2o8B^prK%{8 zJ8SDM$L?G4dI9~dJd$Xmnqt9neiaMJbCscl)a0)27MbBBR%tJ)?-8qW)^ zi+NR_BX@8yqAF)`bWF-csnk8&dDMQ@F^xEl_!lu(HOE<4$ZXkXWvuK)dDh?ZkM#>@ zUmsfffDHl=fB*y_009U<00Izz00bZafo&CN>cH^*zpd#-To8Z&1Rwwb2tWV=5P$## zAOL}-0RI0+AwU2E5P$##AOHafKmY;|fB*!xUjX0#+n-}Z2muH{00Izz00bZa0SG_< H0ucBEHhCdm diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 625d5245..eeec1815 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -20,7 +20,7 @@ def get_lighthouse_score_from_subprocess(url): try: result = subprocess.run( ['lighthouse', url, '--output=json', '--quiet', '--chrome-flags="--headless --no-sandbox"'], - capture_output=True, text=True, timeout=60 + capture_output=True, text=True, timeout=180 ) if result.returncode == 0: lighthouse_report = json.loads(result.stdout) diff --git a/webgenie/storage/__init__.py b/webgenie/storage/__init__.py index f3d1ef85..95a116b2 100644 --- a/webgenie/storage/__init__.py +++ b/webgenie/storage/__init__.py @@ -1,4 +1 @@ -import bittensor as bt - -def store_results_to_database(results: dict): - bt.logging.info(results) +from .utils import store_results_to_database diff --git a/webgenie/storage/models.py b/webgenie/storage/models.py index e01fd13f..3f486cd2 100644 --- a/webgenie/storage/models.py +++ b/webgenie/storage/models.py @@ -1,7 +1,7 @@ from sqlalchemy import Column, DateTime, ForeignKey, JSON from sqlalchemy.orm import relationship, Mapped, mapped_column from datetime import datetime -from database import Base, engine +from .database import Base, engine class Neuron(Base): __tablename__ = "neurons" diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index dfc0a92f..a091bbcc 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -1,20 +1,17 @@ from bittensor import Wallet +import bittensor as bt from io import BufferedReader -from database import Session as DBSession -from models import Neuron, LeaderboardSession, Competition, Challenge, Judgement, EvaluationType, TaskSolution, SolutionEvaluation +from .database import Session as DBSession +from .models import Neuron, LeaderboardSession, Competition, Challenge, Judgement, EvaluationType, TaskSolution, SolutionEvaluation from datetime import datetime -import logging from sqlalchemy import and_ from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session import json import time import requests +from typing import Any -# Setup basic configuration for logging -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" -) # session period tempos SESSION_PERIOD = 2 @@ -29,7 +26,7 @@ def create_record(session: Session, model_class, **kwargs): return new_record.id # Return the new record's ID except Exception as e: session.rollback() # Rollback in case of error - logging.error(f"An error occurred: {e}") + bt.logging.error(f"An error occurred: {e}") return None # Return None to indicate failure finally: session.close() # Close the session @@ -39,7 +36,7 @@ def add_neuron(coldkey: str, hotkey: str): existing_neuron = session.query(Neuron).filter_by(hotkey=hotkey).first() if existing_neuron: - logging.info(f"neuron with hotkey {hotkey} already exists. Skipping creation.") + bt.logging.info(f"neuron with hotkey {hotkey} already exists. Skipping creation.") return existing_neuron.id # Return the existing session id return create_record(session, Neuron, coldkey=coldkey, hotkey=hotkey) @@ -52,7 +49,7 @@ def get_neuron_id(hotkey: str): else: return None # Return None if no matching neuron is found except SQLAlchemyError as e: - logging.error(f"An error occurred while fetching neuron: {e}") + bt.logging.error(f"An error occurred while fetching neuron: {e}") return None finally: session.close() # Ensure the session is closed @@ -62,7 +59,7 @@ def create_leaderboard_session(session_number: int, created_at: datetime, compet existing_session = session.query(LeaderboardSession).filter_by(id=session_number).first() if existing_session: - logging.info(f"Session with id {session_number} already exists. Skipping creation.") + bt.logging.info(f"Session with id {session_number} already exists. Skipping creation.") return existing_session.id # Return the existing session id return create_record(session, LeaderboardSession, @@ -73,7 +70,7 @@ def create_competition(name: str): # Check if the competition with the given name already exists existing_competition = session.query(Competition).filter_by(name=name).first() if existing_competition: - logging.info(f"Competition with name {name} already exists. Skipping creation.") + bt.logging.info(f"Competition with name {name} already exists. Skipping creation.") return existing_competition.id # Return the existing competition id return create_record(session, Competition, name=name) @@ -88,7 +85,7 @@ def create_evaluation_type(name: str): # Check if the competition with the given name already exists existing_evaluation_type = session.query(EvaluationType).filter_by(name=name).first() if existing_evaluation_type: - logging.info(f"Evaluation type with name {name} already exists. Skipping creation.") + bt.logging.info(f"Evaluation type with name {name} already exists. Skipping creation.") return existing_evaluation_type.id # Return the existing evaluation type id return create_record(session, EvaluationType, name=name) @@ -109,7 +106,7 @@ def store_results_to_database(results: dict): solutions = results["solutions"] scores = results["scores"] challenge = results["challenge"] - + session_number = challenge["session_number"] session_start_datetime = results["session_start_datetime"] ground_truth_html = challenge["task"] @@ -204,13 +201,13 @@ def get_session_data(session_number: int): return payload except SQLAlchemyError as e: - logging.error(f"An error occurred while fetching neuron: {e}") + bt.logging.error(f"An error occurred while fetching neuron: {e}") return None finally: session.close() def make_signed_request( - wallet: Wallet, + wallet: Any, url: str, subnet_id: int, payload: dict, @@ -247,13 +244,13 @@ def make_signed_request( response = requests.request(method, url, headers=headers, files=files, json=payload, timeout=5) return response -def test_send_payload(session_number: int) -> None: +def send_challenge_to_stats_collector(session_number: int) -> None: wallet = Wallet() session_data = get_session_data(session_number) response = make_signed_request( wallet=wallet, url="https://webgenie-collector.bactensor.io/api/competitions/", - subnet_id=12, + subnet_id=54, method="POST", payload=session_data, ) From c3996be547ee165b3fc8894eb9e9a015b707049f Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 07:32:47 -0600 Subject: [PATCH 257/554] fix: judgement storing issue --- storage/models.py | 78 --------------------------------------- webgenie/storage/utils.py | 6 +++ 2 files changed, 6 insertions(+), 78 deletions(-) delete mode 100644 storage/models.py diff --git a/storage/models.py b/storage/models.py deleted file mode 100644 index dfe1da62..00000000 --- a/storage/models.py +++ /dev/null @@ -1,78 +0,0 @@ -from sqlalchemy import Column, DateTime, ForeignKey, JSON -from sqlalchemy.orm import relationship, Mapped, mapped_column -from datetime import datetime -from database import Base, engine - -class Neuron(Base): - __tablename__ = "neurons" - id: Mapped[int] = mapped_column(primary_key=True) - coldkey: Mapped[str] - hotkey: Mapped[str] = mapped_column(index=True) - -class Competition(Base): - __tablename__ = "competitions" - id: Mapped[int] = mapped_column(primary_key=True) - name: Mapped[str] = mapped_column(index=True) - - # Relationships - sessions: Mapped[list["LeaderboardSession"]] = relationship(back_populates="competition") - -class LeaderboardSession(Base): - __tablename__ = "leaderboard_sessions" - id: Mapped[int] = mapped_column(primary_key=True) - created_at = Column(DateTime, default=datetime.utcnow, index=True) - competition_id: Mapped[int] = mapped_column(ForeignKey("competitions.id"), index=True) - - # Relationships - competition: Mapped["Competition"] = relationship(back_populates="sessions") - challenges: Mapped[list["Challenge"]] = relationship(back_populates="session") - -class Challenge(Base): - __tablename__ = "challenges" - id: Mapped[int] = mapped_column(primary_key=True) - session_id: Mapped[int] = mapped_column(ForeignKey("leaderboard_sessions.id"), index=True) - ground_truth_html: Mapped[str] - - # Relationships - session: Mapped["LeaderboardSession"] = relationship(back_populates="challenges") - solutions: Mapped[list["TaskSolution"]] = relationship(back_populates="challenge") - - -class Judgement(Base): - __tablename__ = "judgements" - id: Mapped[int] = mapped_column(primary_key=True) - validator_id: Mapped[int] = mapped_column(ForeignKey("neurons.id"), index=True) - miner_id: Mapped[int] = mapped_column(ForeignKey("neurons.id"), index=True) - -class EvaluationType(Base): - __tablename__ = "evaluation_types" - id: Mapped[int] = mapped_column(primary_key=True) - name: Mapped[str] = mapped_column(index=True) - - # Relationship - solution_scores: Mapped[list["SolutionEvaluation"]] = relationship(back_populates="score_type") - -class TaskSolution(Base): - __tablename__ = "task_solutions" - id: Mapped[int] = mapped_column(primary_key=True) - created_at = Column(DateTime, default=datetime.utcnow) - challenge_id: Mapped[int] = mapped_column(ForeignKey("challenges.id"), index=True) - miner_answer: Mapped[dict] = mapped_column(JSON) - - # Relationship - challenge: Mapped["Challenge"] = relationship(back_populates="solutions") - solution_scores: Mapped[list["SolutionEvaluation"]] = relationship(back_populates="solution") - -class SolutionEvaluation(Base): - __tablename__ = "solution_evaluations" - id: Mapped[int] = mapped_column(primary_key=True) - solution_id: Mapped[int] = mapped_column(ForeignKey("task_solutions.id"), index=True) - judgement_id: Mapped[int] = mapped_column(ForeignKey("judgements.id"), index=True) - score_type_id: Mapped[int] = mapped_column(ForeignKey("evaluation_types.id"), index=True) - value: Mapped[float] - - # Relationships - score_type: Mapped["EvaluationType"] = relationship(back_populates="solution_scores") - solution: Mapped["TaskSolution"] = relationship(back_populates="solution_scores") - -Base.metadata.create_all(engine) diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index a091bbcc..fb6daa82 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -79,6 +79,12 @@ def create_challenge(session_id: int, ground_truth_html: str): return create_record(session, Challenge, session_id=session_id, ground_truth_html=ground_truth_html) def create_judgement(validator_id: int, miner_id: int): + # Check if the judgement with the given validator and miner id already exists + existing_judgement = session.query(Judgement).filter_by(validator_id=validator_id, miner_id=miner_id).first() + if existing_judgement: + bt.logging.info(f"judgement with given {validator_id} and {miner_id} already exists. Skipping creation.") + return existing_judgement.id # Return the existing competition id + return create_record(session, Judgement, validator_id=validator_id, miner_id=miner_id) def create_evaluation_type(name: str): From 397732c010ae1595c77a63bb407020be8596b0c2 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 13:47:03 +0000 Subject: [PATCH 258/554] fix: fix bugs --- webgenie/constants.py | 2 +- .../rewards/lighthouse_reward/lighthouse_server_fastapi.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index a13f40bb..5b0aaf21 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -17,7 +17,7 @@ TASK_REVEAL_TIMEOUT = 20 # lighthouse server port -LIGHTHOUSE_SERVER_PORT = 5002 +LIGHTHOUSE_SERVER_PORT = 5003 # max competition history size MAX_COMPETETION_HISTORY_SIZE = 30 diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py index 14a59c55..69399757 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py @@ -1,5 +1,6 @@ import bittensor as bt import os +import sys import threading import uvicorn from fastapi import FastAPI @@ -35,7 +36,7 @@ def start_lighthouse_server(): except Exception as e: bt.logging.error(f"Error starting lighthouse server: {e}") stop_lighthouse_server() - raise e + sys.exit(1) @@ -48,4 +49,4 @@ def start_lighthouse_server_thread(): except Exception as e: bt.logging.error(f"Error starting lighthouse server: {e}") stop_lighthouse_server() - raise e + sys.exit(1) From 1c4a02b09c86253f9c911d2eda9e5637989376a9 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 13:58:47 +0000 Subject: [PATCH 259/554] chore: add consts --- webgenie/constants.py | 21 +++++++++++++++++++++ webgenie/helpers/llms.py | 19 ++++++++++--------- webgenie/helpers/weights.py | 10 ++++++++-- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index 5b0aaf21..b3a14b31 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -1,3 +1,5 @@ +import os + # backend api hotkey API_HOTKEY = "5DXDCYTuPfLqQXbxfvvnarG31SdTDtaubqpQrzjrcMgoP9dp" @@ -78,3 +80,22 @@ # weight setting window blocks WEIGHT_SETTING_WINDOW_BLOCKS = 50 # 50 blocks = 10 minutes + +# llm model id +LLM_MODEL_ID = os.getenv("LLM_MODEL_ID") + +# llm api key +LLM_API_KEY = os.getenv("LLM_API_KEY") + +# llm model url +LLM_MODEL_URL = os.getenv("LLM_MODEL_URL") + +# wandb api key +WANDB_API_KEY = os.getenv("WANDB_API_KEY") + +# wandb project name +WANDB_PROJECT_NAME = os.getenv("WANDB_PROJECT_NAME") + +# wandb entity name +WANDB_ENTITY_NAME = os.getenv("WANDB_ENTITY_NAME") + diff --git a/webgenie/helpers/llms.py b/webgenie/helpers/llms.py index bf8d755b..5f9a6958 100644 --- a/webgenie/helpers/llms.py +++ b/webgenie/helpers/llms.py @@ -1,18 +1,19 @@ -import os import bittensor as bt from openai import AsyncOpenAI -model = os.getenv("LLM_MODEL_ID") -api_key = os.getenv("LLM_API_KEY") -base_url = os.getenv("LLM_MODEL_URL") +from webgenie.constants import ( + LLM_MODEL_ID, + LLM_API_KEY, + LLM_MODEL_URL +) -if not api_key or not base_url or not model: +if not LLM_API_KEY or not LLM_MODEL_URL or not LLM_MODEL_ID: raise Exception("LLM_API_KEY, LLM_MODEL_URL, and LLM_MODEL_ID must be set") client = AsyncOpenAI( - api_key=api_key, - base_url=base_url, + api_key=LLM_API_KEY, + base_url=LLM_MODEL_URL, ) async def openai_call(messages, response_format, deterministic=False, retries=3): @@ -20,14 +21,14 @@ async def openai_call(messages, response_format, deterministic=False, retries=3) try: if deterministic: completion = await client.beta.chat.completions.parse( - model=model, + model=LLM_MODEL_ID, messages= messages, response_format=response_format, temperature=0, ) else: completion = await client.beta.chat.completions.parse( - model=model, + model=LLM_MODEL_ID, messages= messages, response_format=response_format, temperature=0.7, diff --git a/webgenie/helpers/weights.py b/webgenie/helpers/weights.py index 9c39a40b..90899535 100644 --- a/webgenie/helpers/weights.py +++ b/webgenie/helpers/weights.py @@ -4,6 +4,12 @@ import webgenie +from webgenie.constants import ( + WANDB_API_KEY, + WANDB_PROJECT_NAME, + WANDB_ENTITY_NAME, +) + wandb_on = False @@ -16,12 +22,12 @@ def init_wandb(self): return wandb_on = True - wandb.login(key=os.getenv("WANDB_API_KEY")) + wandb.login(key=WANDB_API_KEY) run_name = f"{self.config.neuron.name}-{self.uid}" run = wandb.init( project=webgenie.PROJECT_NAME, - entity=os.getenv("WANDB_ENTITY_NAME"), + entity=WANDB_ENTITY_NAME, name=run_name, config=self.config, reinit=True, From 7418d8e50adbc6a4b575641cc1336be1f162572c Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 14:27:58 +0000 Subject: [PATCH 260/554] fix: fix bugs --- neurons/validators/score_manager.py | 10 ++++++++++ neurons/validators/validator.py | 30 ++++++++++++----------------- webgenie/storage/__init__.py | 6 +++++- webgenie/storage/utils.py | 5 ++--- webgenie/utils/uids.py | 20 +++++++++---------- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 456e3dc8..3f4e1ddd 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -11,6 +11,8 @@ ) from webgenie.base.neuron import BaseNeuron +from webgenie.storage import send_challenge_to_stats_collector + class ScoreManager: def __init__(self, neuron: BaseNeuron): @@ -20,6 +22,7 @@ def __init__(self, neuron: BaseNeuron): self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.session_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.should_save = False + self.last_send_stats_collector_session_number = -1 def load_scores(self): try: @@ -95,6 +98,13 @@ def set_weights(self): """ if not self.neuron.should_set_weights(): return + + with self.neuron.lock: + current_session_number = self.neuron.session_number + + if current_session_number != self.last_send_stats_collector_session_number: + send_challenge_to_stats_collector(self.neuron.wallet, current_session_number) + self.last_send_stats_collector_session_number = current_session_number self.scores = np.zeros_like(self.scores) best_index = np.argmax(self.session_accumulated_scores) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 515568e8..d4a76186 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -17,7 +17,6 @@ from webgenie.base.validator import BaseValidatorNeuron from webgenie.constants import ( API_HOTKEY, - MAX_COUNT_VALIDATORS, BLOCK_IN_SECONDS, SESSION_WINDOW_BLOCKS, QUERING_WINDOW_BLOCKS, @@ -153,20 +152,15 @@ def query_miners_loop(self): try: with self.lock: self.sync() - validator_index = get_validator_index(self, self.uid) + validator_index, max_validator_count = get_validator_index(self, self.uid) if validator_index == -1: continue - validator_index = 0 - - # Only allow first N validators to query miners - # if validator_index > MAX_VALIDATORS: - # continue # Calculate query period blocks with self.lock: current_block = self.block - all_validator_query_period_blocks = MAX_COUNT_VALIDATORS * QUERING_WINDOW_BLOCKS + all_validator_query_period_blocks = max_validator_count * QUERING_WINDOW_BLOCKS # Calculate query period blocks start_period_block = ( (current_block // all_validator_query_period_blocks) * @@ -179,16 +173,16 @@ def query_miners_loop(self): f"End: {end_period_block}, " f"Current: {current_block}") # Sleep if outside query window - # if current_block < start_period_block: - # sleep_blocks = start_period_block - current_block - # bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") - # time.sleep(sleep_blocks * BLOCK_IN_SECONDS) - # continue - # elif current_block > end_period_block: - # sleep_blocks = (start_period_block - current_block + all_validator_query_period_blocks) - # bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") - # time.sleep(sleep_blocks * BLOCK_IN_SECONDS) - # continue + if current_block < start_period_block: + sleep_blocks = start_period_block - current_block + bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") + time.sleep(sleep_blocks * BLOCK_IN_SECONDS) + continue + elif current_block > end_period_block: + sleep_blocks = (start_period_block - current_block + all_validator_query_period_blocks) + bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") + time.sleep(sleep_blocks * BLOCK_IN_SECONDS) + continue QUERY_MINERS_TIMEOUT = 60 * 15 self.query_miners_event_loop.run_until_complete( diff --git a/webgenie/storage/__init__.py b/webgenie/storage/__init__.py index 95a116b2..3967d0a3 100644 --- a/webgenie/storage/__init__.py +++ b/webgenie/storage/__init__.py @@ -1 +1,5 @@ -from .utils import store_results_to_database +from .utils import ( + store_results_to_database, + send_challenge_to_stats_collector, +) + diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index fb6daa82..485ae76e 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -213,7 +213,7 @@ def get_session_data(session_number: int): session.close() def make_signed_request( - wallet: Any, + wallet: "bt.Wallet", url: str, subnet_id: int, payload: dict, @@ -250,8 +250,7 @@ def make_signed_request( response = requests.request(method, url, headers=headers, files=files, json=payload, timeout=5) return response -def send_challenge_to_stats_collector(session_number: int) -> None: - wallet = Wallet() +def send_challenge_to_stats_collector(wallet: "bt.Wallet", session_number: int) -> None: session_data = get_session_data(session_number) response = make_signed_request( wallet=wallet, diff --git a/webgenie/utils/uids.py b/webgenie/utils/uids.py index ee12fe15..08535649 100644 --- a/webgenie/utils/uids.py +++ b/webgenie/utils/uids.py @@ -1,24 +1,23 @@ import random import bittensor as bt import numpy as np -from typing import List +from typing import List, Tuple def is_validator(metagraph: "bt.metagraph.Metagraph", uid: int, vpermit_tao_limit: int) -> bool: - return True - #return metagraph.S[uid] >= vpermit_tao_limit + return metagraph.S[uid] >= vpermit_tao_limit -def get_validator_index(self, uid: int) -> int: +def get_validator_index(self, uid: int) -> Tuple[int, int]: validator_uids = [] for uid in range(self.metagraph.n.item()): if is_validator(self.metagraph, uid, self.config.neuron.vpermit_tao_limit): validator_uids.append(uid) validator_uids.sort(key=lambda uid: self.metagraph.S[uid], reverse=True) try: - return validator_uids.index(uid) + return validator_uids.index(uid), len(validator_uids) except ValueError: - return -1 + return -1, len(validator_uids) def check_uid_availability( @@ -36,12 +35,11 @@ def check_uid_availability( if not metagraph.axons[uid].is_serving: return False # Filter validator permit > 1024 stake. + if metagraph.validator_permit[uid]: + if metagraph.S[uid] > vpermit_tao_limit: + return False + # Available otherwise. return True - # if metagraph.validator_permit[uid]: - # if metagraph.S[uid] > vpermit_tao_limit: - # return False - # # Available otherwise. - # return True def get_most_available_uid(self, exclude: List[int] = None) -> int: From 7ce0af494951ecb84ab3487dadab174c404202d8 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 15:01:28 +0000 Subject: [PATCH 261/554] chore: update the doc related to the pm2 cli --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 11b9bef7..fe352c65 100644 --- a/README.md +++ b/README.md @@ -127,16 +127,17 @@ uv sync ``` - miner ```bash -pm2 start uv -- run neurons/miners/miner.py --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port] +pm2 start "uv run neurons/miners/miner.py --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port]" --name webgenie_miner ``` - validator ```bash npm install -g lighthouse wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo dpkg -i google-chrome-stable_current_amd64.deb +sudo apt-get install -f playwright install-deps playwright install -pm2 start uv -- run neurons/validators/validator.py --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port] +pm2 start "uv run neurons/validators/validator.py --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port]" --name webgenie_validator ``` - running auto_update script for validators ```bash From f2c5480bffb26092c157d785003cc69df3503dc0 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 09:11:59 -0600 Subject: [PATCH 262/554] fix: remove unnecessary files --- webgenie/storage/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index 485ae76e..65e6c39a 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -1,4 +1,3 @@ -from bittensor import Wallet import bittensor as bt from io import BufferedReader from .database import Session as DBSession @@ -10,7 +9,6 @@ import json import time import requests -from typing import Any # session period tempos SESSION_PERIOD = 2 From 9dd5cc8b7ea127b8d58a5cf3a2bea8a97f5240be Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 15:29:21 +0000 Subject: [PATCH 263/554] chore: update the readme for both staging and mainnet --- docs/running_on_mainnet.md | 12 +++++++----- docs/running_on_staging.md | 7 ++++--- docs/running_on_testnet.md | 11 ++++++----- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/docs/running_on_mainnet.md b/docs/running_on_mainnet.md index f95f363c..3901ab6a 100644 --- a/docs/running_on_mainnet.md +++ b/docs/running_on_mainnet.md @@ -31,6 +31,7 @@ After installing `bittensor`, proceed as below: In your project directory: ```bash +npm install pm2 -g git clone https://github.com/web-genie-ai/web-genie-ai.git ``` @@ -43,7 +44,8 @@ cd web-genie-ai Install the Bittensor subnet template package: ```bash -python -m pip install -e . # Install your subnet template package +curl -LsSf https://astral.sh/uv/install.sh | sh +uv sync ``` ## 2. Create wallets @@ -202,25 +204,25 @@ miner default 1 True 0.00000 0.00000 0.00000 0.00000 0.00000 Run the subnet miner: ```bash -python neurons/miner.py --netuid [mainnet_netuid] --wallet.name miner --wallet.hotkey default --logging.debug +pm2 start "uv run neurons/miners/miner.py --netuid 54 --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port]" --name webgenie_miner ``` You will see the below terminal output: ```bash ->> 2023-08-08 16:58:11.223 | INFO | Running miner for subnet: 1 on network: wss://entrypoint-finney.opentensor.ai:443 with config: ... +>> 2023-08-08 16:58:11.223 | INFO | Running miner for subnet: 54 on network: wss://entrypoint-finney.opentensor.ai:443 with config: ... ``` Run the subnet validator: ```bash -python neurons/validator.py --netuid [mainnet_netuid] --wallet.name validator --wallet.hotkey default --logging.debug +pm2 start "uv run neurons/validators/validator.py --netuid 54 --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port]" --name webgenie_validator ``` You will see the below terminal output: ```bash ->> 2023-08-08 16:58:11.223 | INFO | Running validator for subnet: 1 on network: wss://entrypoint-finney.opentensor.ai:443 with config: ... +>> 2023-08-08 16:58:11.223 | INFO | Running validator for subnet: 54 on network: wss://entrypoint-finney.opentensor.ai:443 with config: ... ``` ## 8. Get emissions flowing diff --git a/docs/running_on_staging.md b/docs/running_on_staging.md index d93c9986..16bf028b 100644 --- a/docs/running_on_staging.md +++ b/docs/running_on_staging.md @@ -109,7 +109,8 @@ cd web-genie-ai Install the web-genie-ai Python package: ```bash -python -m pip install -e . +curl -LsSf https://astral.sh/uv/install.sh | sh +uv sync ``` ## 7. Set up wallets @@ -298,13 +299,13 @@ Run the subnet miner and subnet validator. Make sure to specify your subnet para Run the subnet miner: ```bash -python neurons/miner.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name miner --wallet.hotkey default --logging.debug +uv run neurons/miners/miner.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name miner --wallet.hotkey default --logging.debug ``` Run the subnet validator: ```bash -python neurons/validator.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name validator --wallet.hotkey default --logging.debug +uv run neurons/validators/validator.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name validator --wallet.hotkey default --logging.debug ``` ## 14. Set weights for your subnet diff --git a/docs/running_on_testnet.md b/docs/running_on_testnet.md index be4e8553..4c5ea266 100644 --- a/docs/running_on_testnet.md +++ b/docs/running_on_testnet.md @@ -37,7 +37,8 @@ cd web-genie-ai # Enter the Install the web-genie-ai package: ```bash -python -m pip install -e . +curl -LsSf https://astral.sh/uv/install.sh | sh +uv sync ``` ## 2. Create wallets @@ -201,25 +202,25 @@ miner default 1 True 0.00000 0.00000 0.00000 0.00000 0.00000 Run the subnet miner: ```bash -python neurons/miner.py --netuid 214 --subtensor.network test --wallet.name miner --wallet.hotkey default --logging.debug +uv run neurons/miners/miner.py --netuid 214 --subtensor.network test --wallet.name miner --wallet.hotkey default --logging.debug ``` You will see the below terminal output: ```bash ->> 2023-08-08 16:58:11.223 | INFO | Running miner for subnet: 1 on network: ws://127.0.0.1:9946 with config: ... +>> 2023-08-08 16:58:11.223 | INFO | Running miner for subnet: 214 on network: ws://127.0.0.1:9946 with config: ... ``` Next, run the subnet validator: ```bash -python neurons/validator.py --netuid 214 --subtensor.network test --wallet.name validator --wallet.hotkey default --logging.debug +uv run neurons/validators/validator.py --netuid 214 --subtensor.network test --wallet.name validator --wallet.hotkey default --logging.debug ``` You will see the below terminal output: ```bash ->> 2023-08-08 16:58:11.223 | INFO | Running validator for subnet: 1 on network: ws://127.0.0.1:9946 with config: ... +>> 2023-08-08 16:58:11.223 | INFO | Running validator for subnet: 214 on network: ws://127.0.0.1:9946 with config: ... ``` From c2d1540327a6e3a7ee7bb755a5a9b32c4349da96 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 10:18:01 -0600 Subject: [PATCH 264/554] fix: update the uv sync description --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index fe352c65..c1612a73 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,9 @@ npm install pm2 -g git clone https://github.com/web-genie-ai/web-genie-ai.git cd web-genie-ai curl -LsSf https://astral.sh/uv/install.sh | sh +``` +Install the packages in the new terminal: +```bash uv sync ``` - miner From e4bbb41b228d843590ce1441c58d2b405402f463 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 10:19:32 -0600 Subject: [PATCH 265/554] chore: typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c1612a73..25848431 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ git clone https://github.com/web-genie-ai/web-genie-ai.git cd web-genie-ai curl -LsSf https://astral.sh/uv/install.sh | sh ``` -Install the packages in the new terminal: +Install the packages in a new terminal: ```bash uv sync ``` From 4d6e88bac028f865dcaf038f880bf7d275dd7a5b Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 10:38:10 -0600 Subject: [PATCH 266/554] fix: update the playwright install bash --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 25848431..5f245274 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ npm install -g lighthouse wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo dpkg -i google-chrome-stable_current_amd64.deb sudo apt-get install -f +source .venv/bin/activate playwright install-deps playwright install pm2 start "uv run neurons/validators/validator.py --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port]" --name webgenie_validator From 43f4b04bffc2888cc6a5a687db845187669574ad Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 16:55:29 +0000 Subject: [PATCH 267/554] hotfix: fix bugs --- .env.miner.example | 4 ++- .env.validator.example | 9 +++++- neurons/validators/validator.py | 20 +++++++------ webgenie/constants.py | 9 +++++- .../rewards/visual_reward/visual_reward.py | 2 +- webgenie/utils/config.py | 2 +- webgenie/utils/uids.py | 29 +++++++++++-------- 7 files changed, 49 insertions(+), 26 deletions(-) diff --git a/.env.miner.example b/.env.miner.example index e9fd95e7..30e18d98 100644 --- a/.env.miner.example +++ b/.env.miner.example @@ -5,4 +5,6 @@ HF_TOKEN = your_huggingface_token LLM_API_KEY = your_openai_api_key LLM_MODEL_ID = your_openai_model_id -LLM_MODEL_URL = your_openai_model_url \ No newline at end of file +LLM_MODEL_URL = your_openai_model_url + +VPERMIT_TAO_LIMIT = 6000 diff --git a/.env.validator.example b/.env.validator.example index 3e0b781f..f3fade9e 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -3,4 +3,11 @@ WANDB_ENTITY_NAME = your_wandb_entity_name LLM_API_KEY = your_openai_api_key LLM_MODEL_ID = your_openai_model_id -LLM_MODEL_URL = your_openai_model_url \ No newline at end of file +LLM_MODEL_URL = your_openai_model_url + +LIGHTHOUSE_SERVER_PORT = 5000 + +VPERMIT_TAO_LIMIT = 6000 + +AXON_OFF = True + diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index d4a76186..5e8be16f 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -20,7 +20,8 @@ BLOCK_IN_SECONDS, SESSION_WINDOW_BLOCKS, QUERING_WINDOW_BLOCKS, - WEIGHT_SETTING_WINDOW_BLOCKS + WEIGHT_SETTING_WINDOW_BLOCKS, + AXON_OFF, ) from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from webgenie.rewards.lighthouse_reward import start_lighthouse_server_thread, stop_lighthouse_server @@ -28,10 +29,8 @@ from neurons.validators.genie_validator import GenieValidator from neurons.validators.score_manager import ScoreManager - - class Validator(BaseValidatorNeuron): """ Your validator neuron class. You should use this class to define your validator's behavior. In particular, you should replace the forward function with your own logic. @@ -71,7 +70,7 @@ def __init__(self, config=None): self.sync() - if not self.config.axon_off: + if not AXON_OFF: self.serve_axon() def resync_metagraph(self): @@ -152,15 +151,18 @@ def query_miners_loop(self): try: with self.lock: self.sync() - validator_index, max_validator_count = get_validator_index(self, self.uid) - if validator_index == -1: - continue + validator_index, validator_count = get_validator_index(self, self.uid) + if validator_index == -1: + bt.logging.error(f"Validator index {validator_index} is not valid") + continue + + bt.logging.info(f"Validator index: {validator_index}, Validator count: {validator_count}") # Calculate query period blocks with self.lock: current_block = self.block - all_validator_query_period_blocks = max_validator_count * QUERING_WINDOW_BLOCKS + all_validator_query_period_blocks = validator_count * QUERING_WINDOW_BLOCKS # Calculate query period blocks start_period_block = ( (current_block // all_validator_query_period_blocks) * @@ -204,7 +206,7 @@ def score_loop(self): with self.lock: self.sync() - SCORE_TIMEOUT = 60 * 60 + SCORE_TIMEOUT = 60 * 60 * 2 self.score_event_loop.run_until_complete( asyncio.wait_for( self.genie_validator.score(), diff --git a/webgenie/constants.py b/webgenie/constants.py index b3a14b31..e2eecd5b 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -1,3 +1,4 @@ +import bittensor as bt import os # backend api hotkey @@ -19,7 +20,7 @@ TASK_REVEAL_TIMEOUT = 20 # lighthouse server port -LIGHTHOUSE_SERVER_PORT = 5003 +LIGHTHOUSE_SERVER_PORT = int(os.getenv("LIGHTHOUSE_SERVER_PORT",5000)) # max competition history size MAX_COMPETETION_HISTORY_SIZE = 30 @@ -99,3 +100,9 @@ # wandb entity name WANDB_ENTITY_NAME = os.getenv("WANDB_ENTITY_NAME") +# vpermit tao limit +VPERMIT_TAO_LIMIT = bt.Balance(float(os.getenv("VPERMIT_TAO_LIMIT", 4096))) + +# axon off +AXON_OFF = os.getenv("AXON_OFF", "False").lower() == "true" + diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index 0c364c25..1c7295f8 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -57,7 +57,7 @@ async def reward_worker(self, task: Task, solutions: List[Solution], current_wor def sync_reward_worker(self, task: Task, solutions: List[Solution], current_work_dir: str) -> np.ndarray: try: # Timeout of 1 hour for visual reward processing - VISUAL_REWARD_TIMEOUT = 60 * 60 # seconds + VISUAL_REWARD_TIMEOUT = 60 * 60 * 2# seconds # Run the async reward worker with timeout return asyncio.run( diff --git a/webgenie/utils/config.py b/webgenie/utils/config.py index c57fb08a..c13ec43f 100644 --- a/webgenie/utils/config.py +++ b/webgenie/utils/config.py @@ -239,7 +239,7 @@ def add_validator_args(cls, parser): "--neuron.vpermit_tao_limit", type=int, help="The maximum number of TAO allowed to query a validator with a vpermit.", - default=5096, + default=4096, ) parser.add_argument( diff --git a/webgenie/utils/uids.py b/webgenie/utils/uids.py index 08535649..3d8d8e73 100644 --- a/webgenie/utils/uids.py +++ b/webgenie/utils/uids.py @@ -1,18 +1,22 @@ -import random import bittensor as bt +import random +import re import numpy as np from typing import List, Tuple +from webgenie.constants import ( + VPERMIT_TAO_LIMIT, +) -def is_validator(metagraph: "bt.metagraph.Metagraph", uid: int, vpermit_tao_limit: int) -> bool: - return metagraph.S[uid] >= vpermit_tao_limit +def is_validator(metagraph: "bt.metagraph.Metagraph", uid: int) -> bool: + return metagraph.S[uid] > VPERMIT_TAO_LIMIT def get_validator_index(self, uid: int) -> Tuple[int, int]: validator_uids = [] - for uid in range(self.metagraph.n.item()): - if is_validator(self.metagraph, uid, self.config.neuron.vpermit_tao_limit): - validator_uids.append(uid) + for each_uid in range(self.metagraph.n.item()): + if is_validator(self.metagraph, each_uid): + validator_uids.append(each_uid) validator_uids.sort(key=lambda uid: self.metagraph.S[uid], reverse=True) try: return validator_uids.index(uid), len(validator_uids) @@ -21,13 +25,12 @@ def get_validator_index(self, uid: int) -> Tuple[int, int]: def check_uid_availability( - metagraph: "bt.metagraph.Metagraph", uid: int, vpermit_tao_limit: int + metagraph: "bt.metagraph.Metagraph", uid: int ) -> bool: """Check if uid is available. The UID should be available if it is serving and has less than vpermit_tao_limit stake Args: metagraph (:obj: bt.metagraph.Metagraph): Metagraph object uid (int): uid to be checked - vpermit_tao_limit (int): Validator permit tao limit Returns: bool: True if uid is available, False otherwise """ @@ -36,7 +39,7 @@ def check_uid_availability( return False # Filter validator permit > 1024 stake. if metagraph.validator_permit[uid]: - if metagraph.S[uid] > vpermit_tao_limit: + if metagraph.S[uid] > VPERMIT_TAO_LIMIT: return False # Available otherwise. return True @@ -52,7 +55,7 @@ def get_most_available_uid(self, exclude: List[int] = None) -> int: for uid in range(self.metagraph.n.item()): uid_is_available = check_uid_availability( - self.metagraph, uid, self.config.neuron.vpermit_tao_limit + self.metagraph, uid ) uid_is_not_excluded = exclude is None or uid not in exclude @@ -63,17 +66,19 @@ def get_most_available_uid(self, exclude: List[int] = None) -> int: return candidate_uids[np.argmax(self.metagraph.I[candidate_uids])] + def get_all_available_uids( self, exclude: List[int] = None ) -> np.ndarray: avail_uids = [] for uid in range(self.metagraph.n.item()): - uid_is_available = check_uid_availability(self.metagraph, uid, self.config.neuron.vpermit_tao_limit) + uid_is_available = check_uid_availability(self.metagraph, uid) uid_is_not_excluded = exclude is None or uid not in exclude if uid_is_available and uid_is_not_excluded: avail_uids.append(uid) return np.array(avail_uids) + def get_random_uids( self, k: int, exclude: List[int] = None ) -> np.ndarray: @@ -91,7 +96,7 @@ def get_random_uids( for uid in range(self.metagraph.n.item()): uid_is_available = check_uid_availability( - self.metagraph, uid, self.config.neuron.vpermit_tao_limit + self.metagraph, uid ) uid_is_not_excluded = exclude is None or uid not in exclude From 4f32f3cfd1b8a800a76e59e3e96b5f525e673def Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 20 Jan 2025 18:05:20 +0000 Subject: [PATCH 268/554] hotfix: make workdir --- neurons/validators/genie_validator.py | 11 ----------- .../lighthouse_reward/lighthouse_server_fastapi.py | 12 +++++++++++- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 05052531..fa9f0d40 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -52,17 +52,6 @@ def __init__(self, neuron: BaseNeuron): self.task_generators = [ (ImageTaskGenerator(), 1.0), ] - - self.make_work_dir() - - def make_work_dir(self): - if not os.path.exists(WORK_DIR): - os.makedirs(WORK_DIR) - bt.logging.info(f"Created work directory at {WORK_DIR}") - - if not os.path.exists(LIGHTHOUSE_SERVER_WORK_DIR): - os.makedirs(LIGHTHOUSE_SERVER_WORK_DIR) - bt.logging.info(f"Created lighthouse server work directory at {LIGHTHOUSE_SERVER_WORK_DIR}") async def query_miners(self): try: diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py index 69399757..8dff0fe8 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py @@ -8,16 +8,26 @@ from fastapi.staticfiles import StaticFiles from webgenie.constants import ( + WORK_DIR, LIGHTHOUSE_SERVER_WORK_DIR, LIGHTHOUSE_SERVER_PORT, ) app = FastAPI() - static_folder = f"/{LIGHTHOUSE_SERVER_WORK_DIR}" lighthouse_server_thread = None +def make_work_dir(self): + if not os.path.exists(WORK_DIR): + os.makedirs(WORK_DIR) + bt.logging.info(f"Created work directory at {WORK_DIR}") + + if not os.path.exists(LIGHTHOUSE_SERVER_WORK_DIR): + os.makedirs(LIGHTHOUSE_SERVER_WORK_DIR) + bt.logging.info(f"Created lighthouse server work directory at {LIGHTHOUSE_SERVER_WORK_DIR}") + +make_work_dir() app.mount("/", StaticFiles(directory=f"{LIGHTHOUSE_SERVER_WORK_DIR}"), name="static") From 0affcb1b06a93370b40c897a2dd4b2e7de0d7e92 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 12:22:56 -0600 Subject: [PATCH 269/554] fix: update readme and fastapi issue --- .gitignore | 2 +- README.md | 1 + webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index ceef3254..c024c3f4 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST - +*.deb # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. diff --git a/README.md b/README.md index 5f245274..5b3a56ea 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,7 @@ sudo apt-get install -f source .venv/bin/activate playwright install-deps playwright install +export PYTHONPATH="." pm2 start "uv run neurons/validators/validator.py --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port]" --name webgenie_validator ``` - running auto_update script for validators diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py index 8dff0fe8..33434c76 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py @@ -18,7 +18,7 @@ static_folder = f"/{LIGHTHOUSE_SERVER_WORK_DIR}" lighthouse_server_thread = None -def make_work_dir(self): +def make_work_dir(): if not os.path.exists(WORK_DIR): os.makedirs(WORK_DIR) bt.logging.info(f"Created work directory at {WORK_DIR}") From 22c1b0e755475d9d4456b44346859181ad40b702 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 12:39:34 -0600 Subject: [PATCH 270/554] feat: update the bittensor version --- pyproject.toml | 3 +-- uv.lock | 61 ++++++++++++++++++++++++++++---------------------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9398f990..9f2a9edd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,8 +8,7 @@ dependencies = [ "ansible-vault==2.1.0", "beautifulsoup4==4.12.3", "bert-score==0.3.13", - "bittensor-cli==8.2.0rc8", - "bittensor==8.5.1rc5", + "bittensor==8.5.2", "clip", "datasets==3.2.0", "ddt==1.6.0", diff --git a/uv.lock b/uv.lock index 8e572589..0e746371 100644 --- a/uv.lock +++ b/uv.lock @@ -185,6 +185,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/80/9f608d13b4b3afcebd1dd13baf9551c95fc424d6390e4b1cfd7b1810cd06/async_property-0.2.2-py2.py3-none-any.whl", hash = "sha256:8924d792b5843994537f8ed411165700b27b2bd966cefc4daeefc1253442a9d7", size = 9546 }, ] +[[package]] +name = "asyncstdlib" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/5e/15e50af11110c3256f8d328f8681dcf404f2803e89a3d1dd3ab1b9be2427/asyncstdlib-3.13.0.tar.gz", hash = "sha256:f2a6ffb44f118233bb99bef50861d6f64c432decbdcc4c2cb93b3fff40d1b533", size = 49688 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/59/6b16492aef217e0fa1fae0ad49cf65ed4aa56b3691919cceb21a2c901fc1/asyncstdlib-3.13.0-py3-none-any.whl", hash = "sha256:60e097c19e815f3c419a77426cf6c3653aebcb766544d631d5ce6128d0851ae8", size = 43928 }, +] + [[package]] name = "attrs" version = "24.3.0" @@ -245,11 +254,12 @@ wheels = [ [[package]] name = "bittensor" -version = "8.5.1rc5" +version = "8.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "async-property" }, + { name = "asyncstdlib" }, { name = "bittensor-cli" }, { name = "bittensor-commit-reveal" }, { name = "bittensor-wallet" }, @@ -277,9 +287,9 @@ dependencies = [ { name = "websockets" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/03/b86759459b09456a2eb6a5e84713cf0bd0db10558c0f724ce6ca7940c346/bittensor-8.5.1rc5.tar.gz", hash = "sha256:b4f26b237b45964666fde21d77b5d71fe26270ce9f9450e724b58b8d8bf705e9", size = 217653 } +sdist = { url = "https://files.pythonhosted.org/packages/c5/3b/430a897d6045f430bcb7eeadbadf6c60653a1023c495afb24d50a4a98943/bittensor-8.5.2.tar.gz", hash = "sha256:2adcfd47b016ce499cd9f378f0d5562ca55d40801128ca12418abf6ada14898b", size = 211979 } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/11/e1b42d98cb955a090df195d004759e4f2a6563513b6d5e1f7dffcd9e2b4e/bittensor-8.5.1rc5-py3-none-any.whl", hash = "sha256:60904f4e8a296ab1a45bfbbba5b39cc62812709562a82f6b4ee16cb38b873a46", size = 265253 }, + { url = "https://files.pythonhosted.org/packages/6e/23/84fa9b12eb3dca3a1caf7654123b08c6d926d71d9863a2d05d6dcb276b86/bittensor-8.5.2-py3-none-any.whl", hash = "sha256:ccee3e48c6d0747e7194eb148980c485f9ed857d97b50fed3ae933b9344767ef", size = 260629 }, ] [[package]] @@ -315,23 +325,23 @@ wheels = [ [[package]] name = "bittensor-commit-reveal" -version = "0.1.0" +version = "0.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/93/f6361d6d617f1620f1b642308384d7f22c7917c169b821ddb3a90856a0c9/bittensor_commit_reveal-0.1.0.tar.gz", hash = "sha256:1c8bb8d77f6279988902c5c28361cc460167829c63ffa8d788209f8810933211", size = 23249 } +sdist = { url = "https://files.pythonhosted.org/packages/a7/97/2bb9e34f807b06d6faaaf765285eddedb5bd969ad97bd5f5f99d53f81934/bittensor_commit_reveal-0.2.0.tar.gz", hash = "sha256:d67bc49cb93b94136ae10af25a98ec29fe9a88b4ebefadd4f8504eebf63643c0", size = 23303 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/3a/7705ea18c3d61c8affc4696b8ab483bdb7e3d0bfdfb61ca1583a787ef1e0/bittensor_commit_reveal-0.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8509250549b6f5c475a9150e941b28fc66e82f30b27fe078fd80fa840943bb7b", size = 491259 }, - { url = "https://files.pythonhosted.org/packages/80/21/02b400750c7d1d5ed081dc22c740e21e22fd72fbb18b72517d5687eca8bd/bittensor_commit_reveal-0.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bed04f82f162121747cfd7f51bb5d625dda0bf763a0699054565f255d219a9c2", size = 492612 }, - { url = "https://files.pythonhosted.org/packages/9a/82/bf02fda4c7bfbe6830709476cf1893ad4e7b591c4e1f62eab2abbfcd0106/bittensor_commit_reveal-0.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25af2d9c82cacc4278095460493430d36070cb2843c0aa54b1c563788d0742eb", size = 712159 }, - { url = "https://files.pythonhosted.org/packages/31/d1/7e41e52251c277bf0bebe0fcb3f700e6faf6a488c9cefa8b8fb2bae42cee/bittensor_commit_reveal-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8f530793274698aaf4ac7cc8f24e915749d8156df8302c9e1e16446177b429d", size = 551180 }, - { url = "https://files.pythonhosted.org/packages/fa/20/272b35206c52db8b385ff7f2a6579ca700fa996c147e4533cd4d323446a7/bittensor_commit_reveal-0.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e52955d652b61f091310e2b9b232d26b9e586a928e3b414a09a1b6615e9cc7a0", size = 491231 }, - { url = "https://files.pythonhosted.org/packages/ee/05/02329c66db0970569a31779c0effcee67a1f6bb20a12ccbd667123d89f3f/bittensor_commit_reveal-0.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7be8c8f79dea2e137f5add6ee4711447c4f5d43668be26616ab7c1cacf317e07", size = 492469 }, - { url = "https://files.pythonhosted.org/packages/f4/47/ca9a347273e6993b8775a2a04e9d3df5569aaab46dc95247bf0c1f1b5ea1/bittensor_commit_reveal-0.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b88ecb6a0989c2200486e29419a8e7d3b3f7918bdbde4ec04dbb4464abdee08f", size = 711920 }, - { url = "https://files.pythonhosted.org/packages/fe/87/cbef0fa4b4d3159030d61d09da5a09181c0ca8f25bbb451437cb50627ac7/bittensor_commit_reveal-0.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac015f9eefa9dbddd2875cd7214e3a0bc2e394a2915772e655bdcc5c0af67de", size = 551137 }, + { url = "https://files.pythonhosted.org/packages/76/10/12fc321dc44eb0f6c305993d1644698baf2b4a8fe0e70490128fcd26fa14/bittensor_commit_reveal-0.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e6546b9b22fd392dfc569b58e6b3e86899427fc1c07ae52cb60e90c0a520796", size = 491647 }, + { url = "https://files.pythonhosted.org/packages/46/27/0025957757c40da992263d8de8d93cc3dc7ecf512071a8b3cbd42419acff/bittensor_commit_reveal-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76218ffdba8b08248ea02b6f349d22171b12300126cb9a24d48241d746ad444f", size = 492798 }, + { url = "https://files.pythonhosted.org/packages/9a/b3/789aa75457705cbb244ef28cb34e4361e692c01eb035ee66943a75ee0b54/bittensor_commit_reveal-0.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44773448eef88d28c0cc2953d06a4a7137f83b90ff4a246abd41c9d275601b4a", size = 723463 }, + { url = "https://files.pythonhosted.org/packages/8a/57/e7a808ccb4e3a4728e8ab0960e84219707bd1774a5152a6b07fe863ffca2/bittensor_commit_reveal-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81979fd35f95456a60da90e872d1c2d2e68babc8a745778ab5d4025659fef2d7", size = 566260 }, + { url = "https://files.pythonhosted.org/packages/95/7e/b902231bee489162ac4e59c787dd93f5e75e71d13c2bb88ebb6459be1aa8/bittensor_commit_reveal-0.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:78b6041b783735788af75d1994bf11a3fb610caa40547b0e55e494dbb35a7947", size = 491623 }, + { url = "https://files.pythonhosted.org/packages/9e/f9/21c45618d67c220a95058ac95b44dcd2bf1091029c8a5c3314310d90ed68/bittensor_commit_reveal-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:999a50cc3be74ecaf23d4e6cfc9eac8640733745d5c88ac80bed8733c39908b6", size = 492654 }, + { url = "https://files.pythonhosted.org/packages/07/4b/e9633a718b86e6ef82dcc817bd7373fd3085f9d2934a0fefd32160ec47d9/bittensor_commit_reveal-0.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c708ac43c37514c979a447dc7f01d567a4c4caac33d47b946876d30d30ce7807", size = 723252 }, + { url = "https://files.pythonhosted.org/packages/4d/ce/e7fc2d1c3ccf1115f1793aa7cec2185699c7ac3fd467724d2b7766204575/bittensor_commit_reveal-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f690c5cdb00dd340fa948171f586c5daaccb9429bf898ebf21cff86e5767c99", size = 566030 }, ] [[package]] name = "bittensor-wallet" -version = "2.1.3" +version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, @@ -340,19 +350,18 @@ dependencies = [ { name = "password-strength" }, { name = "py-bip39-bindings" }, { name = "rich" }, - { name = "substrate-interface" }, { name = "termcolor" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a4/17/38a9ec85be2167dd2c1aa2e75f0ac7c25ccf7c31859fe9b0d325b474fbbb/bittensor_wallet-2.1.3.tar.gz", hash = "sha256:41927d7e5d68fff1494cef5abd861ede0afc684dff366824b0806cfa3ce13af0", size = 70285 } +sdist = { url = "https://files.pythonhosted.org/packages/51/79/324672b28f7b3ac58baadc92f9a1b9fa74b32c978a7adf3430233103b06f/bittensor_wallet-3.0.0.tar.gz", hash = "sha256:045561e1be2546965a4adecb1515f61c7953b262328809c71d1acdb3aeddc20f", size = 72409 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/b0/a803fb7abe4b004464d67f6812f5067ee0346e7ba0bfb1e3012f569261cd/bittensor_wallet-2.1.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e2f0d03a21a0c54b1f8cd59f34941d7a60df490e9aab7d7776b03f290de6074", size = 797657 }, - { url = "https://files.pythonhosted.org/packages/24/35/506d88aed623872fe4ecbcc2d6484ac864dc2c639ef8810141628fd28763/bittensor_wallet-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24c446b0af4c9ffc3ac122f97a1de25b283c877aa49892748ad06a8a61a74e13", size = 752425 }, - { url = "https://files.pythonhosted.org/packages/eb/37/c6feb7d6ac75c24bfe170ffabbd42f2d91bc34cc75b99575f2417ec486b1/bittensor_wallet-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eafd9c82720644b3eeac2f68deaa9cec4cf175836b16c89206d98ce22590e8e", size = 3146851 }, - { url = "https://files.pythonhosted.org/packages/8e/63/0dfe52c8c4c7d943d3ca2f52530039e1ee0dbdbffb3d16a90d770725b9bd/bittensor_wallet-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f5122b05d8eede2bfc2eb242214b75ecab08f0da5d4f7547ed01ad253349e019", size = 2954118 }, - { url = "https://files.pythonhosted.org/packages/ad/81/670424362f512f96760694839cd44a1d4aa6401d5e1c93ff1bf37f3a3653/bittensor_wallet-2.1.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:88020b18aa2f91b336a6f04354b7acb124701f9678d74e41f5ffb64a7e1e5731", size = 797707 }, - { url = "https://files.pythonhosted.org/packages/e8/de/81744fd99af5339aa196c4c5e559ae3d2dd773d8fc1e39059fd651982b4b/bittensor_wallet-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb7dd2ed4c12e617574b7302a6c20fb8e915477ce2942627f624293b5de9a003", size = 752028 }, - { url = "https://files.pythonhosted.org/packages/41/3c/309505722c2390337d417c17cc50040ddcbdaee03cc8fc664a34320f777a/bittensor_wallet-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de47dea7d283e83449465f9780d4dde608fe09da45d6ef8c795806e49ccf4fd2", size = 3145919 }, - { url = "https://files.pythonhosted.org/packages/bc/3f/e973420941b0d0b23d944fd60cd95c3bbbca38f5c582d83409f6243880fa/bittensor_wallet-2.1.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e35adc5303b2186df889e07c79bf0bc074df382df49e6c216a8feb27f00453a4", size = 2953541 }, + { url = "https://files.pythonhosted.org/packages/87/35/c3bf16166c596dcc3b1498e336163cb7622dcdbfa6734c9c6b59c69642c0/bittensor_wallet-3.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7f1744f87d2a859700409d6419448f12475147f8c213b4d1bcc073e11fb8e12d", size = 805856 }, + { url = "https://files.pythonhosted.org/packages/f2/7a/4efc2f566fbecc3a2b2c6b96794620d4f5ce322f6bafe58f82b0ae05ab37/bittensor_wallet-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b5e32c40f1dfa9f6dc30c9525aedb02975fb25ea70692efd31890d02789225", size = 771198 }, + { url = "https://files.pythonhosted.org/packages/56/e5/8235041c2fa655f42d4a3a45720863c5e810aa310a401f081db68c816a85/bittensor_wallet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5beae01cffc7557dc6a68c3ceaf0568f105b25b8b53a4f02db8b39aa364fed4", size = 3165442 }, + { url = "https://files.pythonhosted.org/packages/f7/a3/14a9beec68caffda7660d92835c19fb1bf4d3bf07bc2c805b9682f8caab4/bittensor_wallet-3.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a69271528f81386dd644ca6c03ea902f60b19c5fd9520d86340137cb67241368", size = 2968740 }, + { url = "https://files.pythonhosted.org/packages/92/1f/c0e016333f84bd5cbce6a5855cfb50564f6fcf4d13662f7a5a6ad7c721fc/bittensor_wallet-3.0.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ef66c0b08386c0437fc1ee45c33cc59e5ce713a4f08fd40285c70ccec3129ab5", size = 805237 }, + { url = "https://files.pythonhosted.org/packages/cd/a6/876548ed4833c7340c9880bfe017b49d68e1b2d6ce053a102815678a1514/bittensor_wallet-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f410a21382b128a90b403dd0c0a9b3751c5d76cbd5eb4c12e3629a5a7e6db898", size = 770657 }, + { url = "https://files.pythonhosted.org/packages/2e/02/da36c134dc5b575d606b6d180603fef66a48a8684417bc61f045206a83df/bittensor_wallet-3.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3998ba3f0c2a563dae5605a01f971c421ed4291a0f57f9db5fe6ff4f5acea3eb", size = 3164911 }, + { url = "https://files.pythonhosted.org/packages/2f/fc/9a69465dfdfec846d92b9de5569f4eb62669c45951aceaeb37ca0b4f4385/bittensor_wallet-3.0.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ded68ced27dcaf4473219eef27709c8bd30f2dbc6bbec7656329ab550af234a6", size = 2968063 }, ] [[package]] @@ -3275,7 +3284,6 @@ dependencies = [ { name = "beautifulsoup4" }, { name = "bert-score" }, { name = "bittensor" }, - { name = "bittensor-cli" }, { name = "clip" }, { name = "colormath" }, { name = "datasets" }, @@ -3310,8 +3318,7 @@ requires-dist = [ { name = "ansible-vault", specifier = "==2.1.0" }, { name = "beautifulsoup4", specifier = "==4.12.3" }, { name = "bert-score", specifier = "==0.3.13" }, - { name = "bittensor", specifier = "==8.5.1rc5" }, - { name = "bittensor-cli", specifier = "==8.2.0rc8" }, + { name = "bittensor", specifier = "==8.5.2" }, { name = "clip", git = "https://github.com/openai/CLIP.git" }, { name = "colormath", specifier = ">=3.0.0" }, { name = "datasets" }, From 4162cb04dfab5cca7f42be13f69ffc506fa25c18 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 12:50:05 -0600 Subject: [PATCH 271/554] fix: enough validator stake warning --- neurons/validators/validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 5e8be16f..8ae0f892 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -154,7 +154,7 @@ def query_miners_loop(self): validator_index, validator_count = get_validator_index(self, self.uid) if validator_index == -1: - bt.logging.error(f"Validator index {validator_index} is not valid") + bt.logging.error(f"No enough stake for the validator.") continue bt.logging.info(f"Validator index: {validator_index}, Validator count: {validator_count}") From 06fc06c1ec20de8001b72d9d9301d3de27dfba0e Mon Sep 17 00:00:00 2001 From: Sangar <8627971+sangar-1028@users.noreply.github.com> Date: Mon, 20 Jan 2025 12:43:59 -0700 Subject: [PATCH 272/554] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5b3a56ea..6b64a435 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Welcome to WebGenieAI Subnet, a pioneering Bittensor-based subnet designed to re - [Features](#features) - [Incentive Mechanism](#incentive-mechanism-v1) - [Roadmap](#roadmap) +- [Installation](#installation) - [References](#references) ## Overview @@ -130,7 +131,8 @@ uv sync ``` - miner ```bash -pm2 start "uv run neurons/miners/miner.py --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port]" --name webgenie_miner +export PYTHONPATH="." +pm2 start "uv run neurons/miners/miner.py --netuid [54 | 214] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port]" --name webgenie_miner ``` - validator ```bash @@ -142,7 +144,7 @@ source .venv/bin/activate playwright install-deps playwright install export PYTHONPATH="." -pm2 start "uv run neurons/validators/validator.py --netuid [NET_UID] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port]" --name webgenie_validator +pm2 start "uv run neurons/validators/validator.py --netuid [54 | 214] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port]" --name webgenie_validator ``` - running auto_update script for validators ```bash @@ -160,7 +162,7 @@ pm2 start --name auto_update auto_update.sh - [x] Begin marketing for brand awareness and interest ### Phase 2: Upgrade (Q1 2025) -- [ ] Launch on mainnet +- [x] Launch on mainnet - [ ] Build a leaderboard to track miner performance and progress - [ ] Upgrade front-end application to v2 - Online IDE like code sandbox and auto-deployment with one click From bdd65e9af97bc2203571dd39d2569c42e3793525 Mon Sep 17 00:00:00 2001 From: Sangar <8627971+sangar-1028@users.noreply.github.com> Date: Mon, 20 Jan 2025 12:48:53 -0700 Subject: [PATCH 273/554] Update running_on_mainnet.md --- docs/running_on_mainnet.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/running_on_mainnet.md b/docs/running_on_mainnet.md index 3901ab6a..2d3ef37a 100644 --- a/docs/running_on_mainnet.md +++ b/docs/running_on_mainnet.md @@ -204,6 +204,7 @@ miner default 1 True 0.00000 0.00000 0.00000 0.00000 0.00000 Run the subnet miner: ```bash +export PYTHONPATH="." pm2 start "uv run neurons/miners/miner.py --netuid 54 --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port]" --name webgenie_miner ``` @@ -216,6 +217,7 @@ You will see the below terminal output: Run the subnet validator: ```bash +export PYTHONPATH="." pm2 start "uv run neurons/validators/validator.py --netuid 54 --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port]" --name webgenie_validator ``` @@ -243,4 +245,4 @@ btcli root weights To stop your nodes, press CTRL + C in the terminal where the nodes are running. ---- \ No newline at end of file +--- From ac5414609245913322b701e23604a1cc1e3af710 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 19:00:28 -0600 Subject: [PATCH 274/554] fix: add the description to the env variables --- .env.validator.example | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.validator.example b/.env.validator.example index f3fade9e..35584e2d 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -2,10 +2,10 @@ WANDB_API_KEY = your_wandb_api_key WANDB_ENTITY_NAME = your_wandb_entity_name LLM_API_KEY = your_openai_api_key -LLM_MODEL_ID = your_openai_model_id -LLM_MODEL_URL = your_openai_model_url +LLM_MODEL_ID = your_openai_model_id # minimun model_id: gpt-4o +LLM_MODEL_URL = your_openai_model_url # https://api.openai.com/v1/ -LIGHTHOUSE_SERVER_PORT = 5000 +LIGHTHOUSE_SERVER_PORT = 5000 # fast api server port to get the lighthouse score VPERMIT_TAO_LIMIT = 6000 From 72701505bc8f5d75c1084eb280d9ed261fba5a5f Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 19:42:49 -0600 Subject: [PATCH 275/554] fix: memory leak issue --- neurons/validators/score_manager.py | 9 +++++++-- .../common/extract_html_elements.py | 16 +++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 3f4e1ddd..d4f3829a 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -17,11 +17,13 @@ class ScoreManager: def __init__(self, neuron: BaseNeuron): self.neuron = neuron - self.scoring_session_number = 0 + + self.should_save = False + self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + self.scoring_session_number = 0 self.session_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - self.should_save = False self.last_send_stats_collector_session_number = -1 def load_scores(self): @@ -32,12 +34,14 @@ def load_scores(self): self.hotkeys = state["hotkeys"] self.scoring_session_number = state["scoring_session_number"] self.session_accumulated_scores = state["tempo_accumulated_scores"] + self.last_send_stats_collector_session_number = state["last_send_stats_collector_session_number"] except Exception as e: bt.logging.warning(f"Error loading scores: {e}") self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.scoring_session_number = 0 self.session_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + self.last_send_stats_collector_session_number = -1 def save_scores(self): if not self.should_save: @@ -51,6 +55,7 @@ def save_scores(self): hotkeys=self.hotkeys, scoring_session_number=self.scoring_session_number, tempo_accumulated_scores=self.session_accumulated_scores, + last_send_stats_collector_session_number=self.last_send_stats_collector_session_number, ) def set_new_hotkeys(self, new_hotkeys: List[str]): diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index f2c72856..1c106b34 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -133,13 +133,15 @@ async def add_element(node, has_children): ) async def traverse(node): - children = await node.query_selector_all(':scope > *') - has_children = False - for child in children: - await traverse(child) - has_children = True - - await add_element(node, has_children) + stack = [node] + while stack: + current_node = stack.pop() + children = await current_node.query_selector_all(':scope > *') + for child in children: + stack.append(child) + await add_element(current_node, bool(children)) + # Dispose the node when done + await current_node.dispose() await traverse(await page.query_selector('body')) await page.close() From 88d488dc9067f062b107947db943fcac51daa02f Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 20:20:00 -0600 Subject: [PATCH 276/554] fix: validator stake display issue --- webgenie/constants.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index e2eecd5b..8760a2fa 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -101,7 +101,8 @@ WANDB_ENTITY_NAME = os.getenv("WANDB_ENTITY_NAME") # vpermit tao limit -VPERMIT_TAO_LIMIT = bt.Balance(float(os.getenv("VPERMIT_TAO_LIMIT", 4096))) +#VPERMIT_TAO_LIMIT = bt.Balance(float(os.getenv("VPERMIT_TAO_LIMIT", 4096))) +VPERMIT_TAO_LIMIT = float(os.getenv("VPERMIT_TAO_LIMIT", 4096)) # axon off AXON_OFF = os.getenv("AXON_OFF", "False").lower() == "true" From e468275d755e90e64a3ceb0fec62245a2ae4dff8 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 23:46:23 -0600 Subject: [PATCH 277/554] feat: update logic to extract elements from html --- .../common/extract_html_elements.py | 130 +++++++++--------- 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index 1c106b34..8130ca50 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -1,3 +1,4 @@ +import bittensor as bt import os import asyncio import numpy as np @@ -18,7 +19,6 @@ class HTMLElement(BaseModel): color: tuple[int, int, int] = Field(default=(0, 0, 0)) input_type: str = Field(default="") input_placeholder: str = Field(default="") - rendered_style: dict = Field(default={}) keypoints: Any = Field(default=None) descriptors: Any = Field(default=None) @@ -27,18 +27,21 @@ class HTMLElement(BaseModel): def parse_rgb_string(rgb_str: str) -> tuple[int, int, int]: """Convert RGB/RGBA color string like 'rgb(23, 34, 45)' or 'rgba(23, 34, 45, 0.5)' to (23, 34, 45) tuple.""" - # Handle both rgb and rgba formats - rgb_str = rgb_str.strip() - if rgb_str.startswith('rgba'): - rgb_str = rgb_str.removeprefix('rgba(') - else: - rgb_str = rgb_str.removeprefix('rgb(') - rgb_str = rgb_str.removesuffix(')') - - # Split and take only the RGB values, ignoring alpha if present - values = rgb_str.split(',')[:3] - return tuple(int(v.strip()) for v in values) - + try: + # Handle both rgb and rgba formats + rgb_str = rgb_str.strip() + if rgb_str.startswith('rgba'): + rgb_str = rgb_str.removeprefix('rgba(') + else: + rgb_str = rgb_str.removeprefix('rgb(') + rgb_str = rgb_str.removesuffix(')') + + # Split and take only the RGB values, ignoring alpha if present + values = rgb_str.split(',')[:3] + return tuple(int(v.strip()) for v in values) + except Exception as e: + bt.logging.error(f"Error parsing rgb string: {e}") + return (0, 0, 0) async def extract_html_elements(file_path, load_time = DEFAULT_LOAD_TIME): if os.path.exists(file_path): @@ -52,35 +55,44 @@ async def extract_html_elements(file_path, load_time = DEFAULT_LOAD_TIME): anchor_elements = [] try: page = await web_player["browser"].new_page() - + await page.goto(url, timeout=CHROME_HTML_LOAD_TIME) - await page.wait_for_timeout(load_time) + await page.wait_for_load_state("networkidle") + await page.wait_for_timeout(1000) await page.screenshot( path=screenshot_path, full_page=True, animations="disabled", timeout=CHROME_HTML_LOAD_TIME, ) - await asyncio.sleep(10) + bt.logging.info(f"Extracting html elements from {file_path}") with open(screenshot_path, "rb") as f: screenshot = Image.open(f) W, H = screenshot.size - + bt.logging.info(f"Extracted screenshot from {file_path}") async def add_element(node, has_children): + # Combine all necessary evaluations into one to reduce overhead + rendered_data = await node.evaluate(""" + (el) => { + const styles = window.getComputedStyle(el); + const type = el.getAttribute('type') || 'text'; + const placeholder = el.getAttribute('placeholder') || ''; + return { + tagName: el.tagName.toLowerCase(), + color: styles.color || 'rgb(0, 0, 0)', + type: type, + placeholder: placeholder + }; + } + """) + + # Extract all relevant data from the evaluated result text = await node.inner_text() bounding_box = await node.bounding_box() - rendered_style = await node.evaluate( - """(el) => { - const styles = window.getComputedStyle(el); - return styles; - }""" - ) - tag_name = await node.evaluate("(node) => node.tagName.toLowerCase()") - if bounding_box is None: - return - if bounding_box["width"] <= 0 or bounding_box["height"] <= 0: + # Early return if no bounding box or invalid dimensions + if bounding_box is None or bounding_box["width"] <= 0 or bounding_box["height"] <= 0: return scaled_bounding_box = { @@ -89,53 +101,41 @@ async def add_element(node, has_children): "width": bounding_box["width"] / W, "height": bounding_box["height"] / H } - if tag_name == "button": - button_elements.append( - HTMLElement( - text=text, - bounding_box=bounding_box, - scaled_bounding_box=scaled_bounding_box, - rendered_style=rendered_style, - ) - ) - elif tag_name == "input": - input_type = await node.evaluate("(node) => node.getAttribute('type') || 'text'") - input_placeholder = await node.evaluate("(node) => node.getAttribute('placeholder') || ''") - input_elements.append( - HTMLElement( - text=text, - bounding_box=bounding_box, - scaled_bounding_box=scaled_bounding_box, - rendered_style=rendered_style, - input_type=input_type, - input_placeholder=input_placeholder, - ) - ) - elif tag_name == "a": - anchor_elements.append( - HTMLElement( - text=text, - bounding_box=bounding_box, - scaled_bounding_box=scaled_bounding_box, - rendered_style=rendered_style, - ) - ) + # Create the HTMLElement object with the extracted data + element_data = HTMLElement( + text=text, + bounding_box=bounding_box, + scaled_bounding_box=scaled_bounding_box, + ) + + # Add the element based on its tag name + if rendered_data['tagName'] == "button": + button_elements.append(element_data) + elif rendered_data['tagName'] == "input": + # Additional input-specific properties + element_data.input_type = rendered_data['type'] + element_data.input_placeholder = rendered_data['placeholder'] + input_elements.append(element_data) + elif rendered_data['tagName'] == "a": + anchor_elements.append(element_data) + + # Add to text elements only if no children if not has_children: text_elements.append( HTMLElement( text=text, bounding_box=bounding_box, scaled_bounding_box=scaled_bounding_box, - color=parse_rgb_string(rendered_style["color"]), - rendered_style=rendered_style, + color=parse_rgb_string(rendered_data['color']), ) ) - + async def traverse(node): stack = [node] while stack: current_node = stack.pop() + bt.logging.info(f"Traversing node: {current_node}") children = await current_node.query_selector_all(':scope > *') for child in children: stack.append(child) @@ -145,11 +145,13 @@ async def traverse(node): await traverse(await page.query_selector('body')) await page.close() + bt.logging.info(f"Extracted html elements from {file_path}") preprocess_html_elements(file_path, button_elements) preprocess_html_elements(file_path, input_elements) preprocess_html_elements(file_path, anchor_elements) + bt.logging.info(f"Preprocessed html elements from {file_path}") except Exception as e: - print(e) + bt.logging.error(f"Error extracting html elements from {file_path}: {e}") return text_elements, button_elements, input_elements, anchor_elements @@ -162,7 +164,7 @@ def preprocess_html_elements(html_path, html_elements): try: element.avg_color = np.mean(color_image[y:y+h, x:x+w], axis=(0, 1)) except Exception as e: - print(e) + bt.logging.error(f"Error extracting html elements from {html_path}: {e}") element.avg_color = (0, 0, 0) gray_image = color.rgb2gray(color_image) @@ -172,6 +174,6 @@ def preprocess_html_elements(html_path, html_elements): try: element.keypoints, element.descriptors = extract_sift_from_roi(gray_image, (x, y, w, h)) except Exception as e: - print(e) + bt.logging.error(f"Error extracting html elements from {html_path}: {e}") element.keypoints = None element.descriptors = None From e18a45b36037c1d598af3d826d0b791913fffd93 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 20 Jan 2025 23:53:02 -0600 Subject: [PATCH 278/554] fix: change the max competition history size --- webgenie/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index 8760a2fa..2e963237 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -23,10 +23,10 @@ LIGHTHOUSE_SERVER_PORT = int(os.getenv("LIGHTHOUSE_SERVER_PORT",5000)) # max competition history size -MAX_COMPETETION_HISTORY_SIZE = 30 +MAX_COMPETETION_HISTORY_SIZE = 10 # max synthetic task size -MAX_SYNTHETIC_TASK_SIZE = 30 +MAX_SYNTHETIC_TASK_SIZE = 10 # max debug image string length MAX_DEBUG_IMAGE_STRING_LENGTH = 20 From 506adfbc67eb282ec6fc7d3238404e40556906b8 Mon Sep 17 00:00:00 2001 From: pycorn Date: Tue, 21 Jan 2025 06:26:01 +0000 Subject: [PATCH 279/554] hotfix: try catch error --- .../rewards/visual_reward/common/extract_html_elements.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index 8130ca50..f3a2d8bc 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -70,6 +70,7 @@ async def extract_html_elements(file_path, load_time = DEFAULT_LOAD_TIME): with open(screenshot_path, "rb") as f: screenshot = Image.open(f) W, H = screenshot.size + bt.logging.info(f"Extracted screenshot from {file_path}") async def add_element(node, has_children): # Combine all necessary evaluations into one to reduce overhead @@ -139,7 +140,10 @@ async def traverse(node): children = await current_node.query_selector_all(':scope > *') for child in children: stack.append(child) - await add_element(current_node, bool(children)) + try: + await add_element(current_node, bool(children)) + except Exception as e: + bt.logging.error(f"Error adding element: {e}") # Dispose the node when done await current_node.dispose() From 4e0fccef20ded888afa370f2dfef406e9372b6d0 Mon Sep 17 00:00:00 2001 From: pycorn Date: Tue, 21 Jan 2025 06:27:33 +0000 Subject: [PATCH 280/554] style: add empty line --- webgenie/rewards/visual_reward/common/extract_html_elements.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index f3a2d8bc..0adb966a 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -43,6 +43,7 @@ def parse_rgb_string(rgb_str: str) -> tuple[int, int, int]: bt.logging.error(f"Error parsing rgb string: {e}") return (0, 0, 0) + async def extract_html_elements(file_path, load_time = DEFAULT_LOAD_TIME): if os.path.exists(file_path): url = f"file:///{os.path.abspath(file_path)}" From 8c7d29f747ca478c7c9bc4eb71dc33f9cbd1754d Mon Sep 17 00:00:00 2001 From: pycorn Date: Tue, 21 Jan 2025 06:30:51 +0000 Subject: [PATCH 281/554] chore: remove temp files --- webgenie/rewards/visual_reward/visual_reward.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index 1c7295f8..3fbb43a3 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -52,6 +52,11 @@ async def reward_worker(self, task: Task, solutions: List[Solution], current_wor scores = high_level_scores * 0.3 + low_level_scores * 0.7 await stop_browser() + + for html_path in miner_html_paths: + os.remove(html_path) + os.remove(original_html_path) + return scores def sync_reward_worker(self, task: Task, solutions: List[Solution], current_work_dir: str) -> np.ndarray: From 1fec6ac1a3f18b92b2ffbc81c58d08546672edf1 Mon Sep 17 00:00:00 2001 From: pycorn Date: Tue, 21 Jan 2025 06:38:02 +0000 Subject: [PATCH 282/554] fix: mutex issue --- neurons/validators/genie_validator.py | 13 +++++++------ neurons/validators/score_manager.py | 9 +++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index fa9f0d40..a2ab3f8c 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -199,12 +199,13 @@ async def score(self): bt.logging.info(f"Storing results to database: {payload}") store_results_to_database(payload) - - self.neuron.score_manager.update_scores( - aggregated_scores, - miner_uids, - challenge.session_number, - ) + + with self.neuron.lock: + self.neuron.score_manager.update_scores( + aggregated_scores, + miner_uids, + challenge.session_number, + ) async def synthensize_task(self): try: diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index d4f3829a..f9767749 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -110,10 +110,11 @@ def set_weights(self): if current_session_number != self.last_send_stats_collector_session_number: send_challenge_to_stats_collector(self.neuron.wallet, current_session_number) self.last_send_stats_collector_session_number = current_session_number - - self.scores = np.zeros_like(self.scores) - best_index = np.argmax(self.session_accumulated_scores) - self.scores[best_index] = 1 + + with self.neuron.lock: + self.scores = np.zeros_like(self.scores) + best_index = np.argmax(self.session_accumulated_scores) + self.scores[best_index] = 1 # Calculate the average reward for each uid across non-zero values. # Replace any NaN values with 0. From f0cfe60653c29be0e7533bb20768d6d1e0418f22 Mon Sep 17 00:00:00 2001 From: pycorn Date: Tue, 21 Jan 2025 06:39:22 +0000 Subject: [PATCH 283/554] fix: mutex issue --- neurons/validators/score_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index f9767749..ae6a6014 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -101,10 +101,10 @@ def set_weights(self): """ Sets the validator weights to the metagraph hotkeys based on the scores it has received from the miners. The weights determine the trust and incentive level the validator assigns to miner nodes on the network. """ - if not self.neuron.should_set_weights(): - return with self.neuron.lock: + if not self.neuron.should_set_weights(): + return current_session_number = self.neuron.session_number if current_session_number != self.last_send_stats_collector_session_number: From a0e2f742d6cef7d03f167fbfaf53fb57fd4337cb Mon Sep 17 00:00:00 2001 From: pycorn Date: Tue, 21 Jan 2025 06:41:21 +0000 Subject: [PATCH 284/554] chore: update logging string --- .../rewards/visual_reward/common/extract_html_elements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index 0adb966a..290e7632 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -169,7 +169,7 @@ def preprocess_html_elements(html_path, html_elements): try: element.avg_color = np.mean(color_image[y:y+h, x:x+w], axis=(0, 1)) except Exception as e: - bt.logging.error(f"Error extracting html elements from {html_path}: {e}") + bt.logging.error(f"Error calculating avg color of html elements from {html_path}: {e}") element.avg_color = (0, 0, 0) gray_image = color.rgb2gray(color_image) @@ -179,6 +179,6 @@ def preprocess_html_elements(html_path, html_elements): try: element.keypoints, element.descriptors = extract_sift_from_roi(gray_image, (x, y, w, h)) except Exception as e: - bt.logging.error(f"Error extracting html elements from {html_path}: {e}") + bt.logging.error(f"Error extracting sift from html elements from {html_path}: {e}") element.keypoints = None element.descriptors = None From 04b8226000badabde7a1b7705837de2972d2d9da Mon Sep 17 00:00:00 2001 From: pycorn Date: Tue, 21 Jan 2025 06:46:12 +0000 Subject: [PATCH 285/554] chore: refactor html load logic --- webgenie/constants.py | 2 ++ webgenie/datasets/random_website_dataset.py | 8 ++++++-- webgenie/helpers/htmls.py | 6 +++++- .../visual_reward/common/extract_html_elements.py | 10 ++++++++-- .../rewards/visual_reward/common/take_screenshot.py | 11 +++++++++-- 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index 2e963237..d3873f5d 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -82,6 +82,8 @@ # weight setting window blocks WEIGHT_SETTING_WINDOW_BLOCKS = 50 # 50 blocks = 10 minutes +JAVASCRIPT_RUNNING_TIME = 1000 + # llm model id LLM_MODEL_ID = os.getenv("LLM_MODEL_ID") diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index 15a9619f..a797882f 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -11,7 +11,11 @@ from typing import Optional from webgenie.datasets.dataset import Dataset, DatasetEntry -from webgenie.constants import GROUND_TRUTH_HTML_LOAD_TIME, CHROME_HTML_LOAD_TIME +from webgenie.constants import ( + GROUND_TRUTH_HTML_LOAD_TIME, + CHROME_HTML_LOAD_TIME, + JAVASCRIPT_RUNNING_TIME, +) class RandomWebsiteDataset(Dataset): def __init__(self , **kwargs): @@ -47,7 +51,7 @@ async def get_rendered_html(self, url): # await page.wait_for_timeout(GROUND_TRUTH_HTML_LOAD_TIME) await page.wait_for_load_state('networkidle') - await page.wait_for_timeout(1000) + await page.wait_for_timeout(JAVASCRIPT_RUNNING_TIME) rendered_html = await page.content() # Get the rendered HTML diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index bf266e1c..1d3a83bb 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -13,6 +13,7 @@ from webgenie.constants import ( WORK_DIR, CHROME_HTML_LOAD_TIME, + JAVASCRIPT_RUNNING_TIME, PLACE_HOLDER_IMAGE_URL, ) from webgenie.helpers.images import image_to_base64 @@ -99,7 +100,10 @@ async def html_to_screenshot(html_content: str, page_load_time: int = 1000) -> s # Navigate to the URL await page.goto(url, timeout=CHROME_HTML_LOAD_TIME) - await page.wait_for_timeout(page_load_time) + + await page.wait_for_load_state('networkidle') + await page.wait_for_timeout(JAVASCRIPT_RUNNING_TIME) + # Take the screenshot await page.screenshot( path=png_path, diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index 290e7632..12095295 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -7,7 +7,11 @@ from typing import Any from skimage import io, color -from webgenie.constants import DEFAULT_LOAD_TIME, CHROME_HTML_LOAD_TIME +from webgenie.constants import ( + DEFAULT_LOAD_TIME, + CHROME_HTML_LOAD_TIME, + JAVASCRIPT_RUNNING_TIME, +) from webgenie.rewards.visual_reward.common.browser import web_player from webgenie.rewards.visual_reward.common.sift import extract_sift_from_roi @@ -58,8 +62,10 @@ async def extract_html_elements(file_path, load_time = DEFAULT_LOAD_TIME): page = await web_player["browser"].new_page() await page.goto(url, timeout=CHROME_HTML_LOAD_TIME) + await page.wait_for_load_state("networkidle") - await page.wait_for_timeout(1000) + await page.wait_for_timeout(JAVASCRIPT_RUNNING_TIME) + await page.screenshot( path=screenshot_path, full_page=True, diff --git a/webgenie/rewards/visual_reward/common/take_screenshot.py b/webgenie/rewards/visual_reward/common/take_screenshot.py index c47c99a3..a69e3982 100644 --- a/webgenie/rewards/visual_reward/common/take_screenshot.py +++ b/webgenie/rewards/visual_reward/common/take_screenshot.py @@ -2,7 +2,11 @@ import os from PIL import Image -from webgenie.constants import DEFAULT_LOAD_TIME, CHROME_HTML_LOAD_TIME +from webgenie.constants import ( + DEFAULT_LOAD_TIME, + CHROME_HTML_LOAD_TIME, + JAVASCRIPT_RUNNING_TIME, +) from webgenie.rewards.visual_reward.common.browser import web_player @@ -20,7 +24,10 @@ async def take_screenshot(url, output_file_path, load_time = DEFAULT_LOAD_TIME, try: page = await web_player["browser"].new_page() await page.goto(url, timeout=CHROME_HTML_LOAD_TIME) - await page.wait_for_timeout(load_time) + + await page.wait_for_load_state('networkidle') + await page.wait_for_timeout(JAVASCRIPT_RUNNING_TIME) + await page.screenshot( path=output_file_path, full_page=True, From 056998988fe7f6b410db71f4bffda470ace046b1 Mon Sep 17 00:00:00 2001 From: pycorn Date: Tue, 21 Jan 2025 11:59:10 +0000 Subject: [PATCH 286/554] chore: do not screenshot if exists --- .../common/extract_html_elements.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index 12095295..1a93e81c 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -66,13 +66,16 @@ async def extract_html_elements(file_path, load_time = DEFAULT_LOAD_TIME): await page.wait_for_load_state("networkidle") await page.wait_for_timeout(JAVASCRIPT_RUNNING_TIME) - await page.screenshot( - path=screenshot_path, - full_page=True, - animations="disabled", - timeout=CHROME_HTML_LOAD_TIME, - ) - + if not os.path.exists(screenshot_path): + await page.screenshot( + path=screenshot_path, + full_page=True, + animations="disabled", + timeout=CHROME_HTML_LOAD_TIME, + ) + else: + bt.logging.info(f"Screenshot already exists for {file_path}") + bt.logging.info(f"Extracting html elements from {file_path}") with open(screenshot_path, "rb") as f: screenshot = Image.open(f) From 942c7c15624d356457ed8f502577de88c72ccc1a Mon Sep 17 00:00:00 2001 From: pycorn Date: Tue, 21 Jan 2025 12:09:06 +0000 Subject: [PATCH 287/554] fix: prevent one miner's exception's affection on other miners scoring --- .../clip_matching_score.py | 16 +++++++---- .../high_level_matching_score/histogram.py | 14 ++++++---- .../element_matching_score.py | 25 ++++++++++------- .../input_matching_score.py | 28 +++++++++++-------- .../low_level_matching_score.py | 28 +++++++++++-------- 5 files changed, 66 insertions(+), 45 deletions(-) diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py b/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py index 577693be..7b5c5ba0 100644 --- a/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py +++ b/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py @@ -65,11 +65,15 @@ async def calculate_clip_score(predict_html_path_list, original_html_path): results = [] for predict_html_path in predict_html_path_list: - predict_img_path = predict_html_path.replace(HTML_EXTENSION, f"_inpainted{IMAGE_EXTENSION}") - await inpaint_image(predict_html_path, predict_img_path) - predict_embedding_vector = calculate_embedding_vector(predict_img_path, model, preprocess, device) + try: + predict_img_path = predict_html_path.replace(HTML_EXTENSION, f"_inpainted{IMAGE_EXTENSION}") + await inpaint_image(predict_html_path, predict_img_path) + predict_embedding_vector = calculate_embedding_vector(predict_img_path, model, preprocess, device) + + score = (original_embedding_vector @ predict_embedding_vector.T).item() + results.append(score) + except Exception as e: + bt.logging.error(f"Error calculating clip score for {predict_html_path}: {e}") + results.append(0) - score = (original_embedding_vector @ predict_embedding_vector.T).item() - results.append(score) - return results \ No newline at end of file diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py b/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py index f3c1ba81..8f3a81d0 100644 --- a/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py +++ b/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py @@ -41,10 +41,14 @@ async def histogram_matching_score(predict_html_path_list, original_html_path): results = [] for predict_html_path in predict_html_path_list: - predict_img_path = predict_html_path.replace(HTML_EXTENSION, IMAGE_EXTENSION) - await take_screenshot(predict_html_path, predict_img_path) - predict_hist = compute_grayscale_histogram(predict_img_path) - similarity = compare_histograms(original_hist, predict_hist) - results.append(similarity) + try: + predict_img_path = predict_html_path.replace(HTML_EXTENSION, IMAGE_EXTENSION) + await take_screenshot(predict_html_path, predict_img_path) + predict_hist = compute_grayscale_histogram(predict_img_path) + similarity = compare_histograms(original_hist, predict_hist) + results.append(similarity) + except Exception as e: + bt.logging.error(f"Error calculating histogram score for {predict_html_path}: {e}") + results.append(0) return results \ No newline at end of file diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/element_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/element_matching_score.py index 04305b00..64822709 100644 --- a/webgenie/rewards/visual_reward/low_level_matching_score/element_matching_score.py +++ b/webgenie/rewards/visual_reward/low_level_matching_score/element_matching_score.py @@ -1,3 +1,4 @@ +import bittensor as bt import asyncio import numpy as np @@ -31,15 +32,19 @@ def create_cost_matrix(predicted_elements, original_elements): def calculate_element_matching_similarity(predicted_elements, original_elements): - cost_matrix = create_cost_matrix(predicted_elements, original_elements) - row_ind, col_ind = linear_sum_assignment(cost_matrix) - similarity_sum = 0 - for i , j in zip(row_ind, col_ind): - similarity_sum += calculate_cost(predicted_elements[i], original_elements[j]) + try: + cost_matrix = create_cost_matrix(predicted_elements, original_elements) + row_ind, col_ind = linear_sum_assignment(cost_matrix) + similarity_sum = 0 + for i , j in zip(row_ind, col_ind): + similarity_sum += calculate_cost(predicted_elements[i], original_elements[j]) - total_count = max(len(predicted_elements), len(original_elements)) - if total_count == 0: - return 1 - - return similarity_sum / total_count + total_count = max(len(predicted_elements), len(original_elements)) + if total_count == 0: + return 1 + + return similarity_sum / total_count + except Exception as e: + bt.logging.error(f"Error calculating element matching score: {e}") + return 0 diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/input_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/input_matching_score.py index 5f505bde..8285b97f 100644 --- a/webgenie/rewards/visual_reward/low_level_matching_score/input_matching_score.py +++ b/webgenie/rewards/visual_reward/low_level_matching_score/input_matching_score.py @@ -1,3 +1,4 @@ +import bittensor as bt import asyncio import numpy as np from math import sqrt @@ -33,16 +34,19 @@ def create_cost_matrix(predicted_elements, original_elements): def calculate_input_matching_similarity(predicted_elements, original_elements): - cost_matrix = create_cost_matrix(predicted_elements, original_elements) - row_ind, col_ind = linear_sum_assignment(cost_matrix) - similarity_sum = 0 - for i , j in zip(row_ind, col_ind): - similarity_sum += calculate_cost(predicted_elements[i], original_elements[j]) - - total_count = max(len(predicted_elements), len(original_elements)) - if total_count == 0: - return 1 - - return similarity_sum / total_count - + try: + cost_matrix = create_cost_matrix(predicted_elements, original_elements) + row_ind, col_ind = linear_sum_assignment(cost_matrix) + similarity_sum = 0 + for i , j in zip(row_ind, col_ind): + similarity_sum += calculate_cost(predicted_elements[i], original_elements[j]) + + total_count = max(len(predicted_elements), len(original_elements)) + if total_count == 0: + return 1 + + return similarity_sum / total_count + except Exception as e: + bt.logging.error(f"Error calculating input matching score: {e}") + return 0 diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py index 0b6b4edf..e12d98dd 100644 --- a/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py +++ b/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py @@ -20,19 +20,23 @@ async def low_level_matching_score(predict_html_path_list, original_html_path): results = [] for predict_html_path in predict_html_path_list: - ( - predicted_text_elements, - predicted_button_elements, - predicted_input_elements, - predicted_anchor_elements, - ) = await extract_html_elements(predict_html_path) + try: + ( + predicted_text_elements, + predicted_button_elements, + predicted_input_elements, + predicted_anchor_elements, + ) = await extract_html_elements(predict_html_path) - button_score = calculate_element_matching_similarity(predicted_button_elements, original_button_elements) - anchor_score = calculate_element_matching_similarity(predicted_anchor_elements, original_anchor_elements) + button_score = calculate_element_matching_similarity(predicted_button_elements, original_button_elements) + anchor_score = calculate_element_matching_similarity(predicted_anchor_elements, original_anchor_elements) - input_score = calculate_input_matching_similarity(predicted_input_elements, original_input_elements) - text_score = calculate_text_matching_similarity(predicted_text_elements, original_text_elements) - score = button_score * 0.25 + input_score * 0.25 + text_score * 0.25 + anchor_score * 0.25 - results.append(score) + input_score = calculate_input_matching_similarity(predicted_input_elements, original_input_elements) + text_score = calculate_text_matching_similarity(predicted_text_elements, original_text_elements) + score = button_score * 0.25 + input_score * 0.25 + text_score * 0.25 + anchor_score * 0.25 + results.append(score) + except Exception as e: + bt.logging.error(f"Error calculating low level matching score for {predict_html_path}: {e}") + results.append(0) return np.array(results) From 8fbc5000dd66c3daca80a726429965a2835061c2 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Tue, 21 Jan 2025 08:41:13 -0600 Subject: [PATCH 288/554] hotfix: update the auto update script --- .env.miner.example | 2 +- auto_update.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.miner.example b/.env.miner.example index 30e18d98..aa88798c 100644 --- a/.env.miner.example +++ b/.env.miner.example @@ -5,6 +5,6 @@ HF_TOKEN = your_huggingface_token LLM_API_KEY = your_openai_api_key LLM_MODEL_ID = your_openai_model_id -LLM_MODEL_URL = your_openai_model_url +LLM_MODEL_URL = your_openai_model_url # https://api.openai.com/v1/ VPERMIT_TAO_LIMIT = 6000 diff --git a/auto_update.sh b/auto_update.sh index 99968193..ad351e69 100644 --- a/auto_update.sh +++ b/auto_update.sh @@ -20,7 +20,7 @@ while true; do if [ "$current_head" != "$new_head" ]; then # The HEAD has changed, meaning there's a new version echo "$(date): New version detected, installing requirements and restarting the validator." - pip install -e . + uv sync pm2 restart webgenie_validator else # No new version, no action needed From 3cd84107bb209532e2fbcc25b52371631fefd084 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Tue, 21 Jan 2025 07:51:04 -0700 Subject: [PATCH 289/554] feat: update scripts --- auto_update.sh => scripts/auto_update.sh | 0 scripts/install_requirements.sh | 98 ++++++++++++++++++++++++ scripts/start.sh | 55 +++++++++++++ 3 files changed, 153 insertions(+) rename auto_update.sh => scripts/auto_update.sh (100%) create mode 100644 scripts/install_requirements.sh create mode 100644 scripts/start.sh diff --git a/auto_update.sh b/scripts/auto_update.sh similarity index 100% rename from auto_update.sh rename to scripts/auto_update.sh diff --git a/scripts/install_requirements.sh b/scripts/install_requirements.sh new file mode 100644 index 00000000..ee00ccd0 --- /dev/null +++ b/scripts/install_requirements.sh @@ -0,0 +1,98 @@ +# Section 1: Build/Install +# This section is for first-time setup and installations. + +install_dependencies() { + # Function to install packages on macOS + install_mac() { + which brew > /dev/null + if [ $? -ne 0 ]; then + echo "Installing Homebrew..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + fi + echo "Updating Homebrew packages..." + brew update + echo "Installing required packages..." + brew install git curl make node python + # Verify installations + echo "Node.js version: $(node --version)" + echo "npm version: $(npm --version)" + # Install PM2 globally + npm install pm2 -g + git clone https://github.com/web-genie-ai/web-genie-ai.git + cd web-genie-ai + + # Create and activate virtual environment + python3 -m venv .venv + source .venv/bin/activate + + # Install uv package manager + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "Installing dependencies..." + uv sync + + # Install Chrome and Playwright dependencies + npm install -g lighthouse + brew install --cask google-chrome + + # Install Playwright and its dependencies + playwright install-deps + playwright install + } + + # Function to install packages on Ubuntu/Debian + install_ubuntu() { + echo "Updating system packages..." + sudo apt update + echo "Installing required packages..." + sudo apt install --assume-yes make git curl python3-pip python3-venv + # Install Node.js and npm + curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + sudo apt install --assume-yes nodejs + # Verify installations + echo "Node.js version: $(node --version)" + echo "npm version: $(npm --version)" + # Install PM2 globally + npm install pm2 -g + git clone https://github.com/web-genie-ai/web-genie-ai.git + cd web-genie-ai + + # Create and activate virtual environment + python3 -m venv .venv + source .venv/bin/activate + + # Install uv package manager + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "Installing dependencies..." + uv sync + + # Install Chrome and Playwright dependencies + npm install -g lighthouse + wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb + sudo apt install --assume-yes gdebi-core + sudo gdebi -n google-chrome-stable_current_amd64.deb + rm google-chrome-stable_current_amd64.deb + + # Install Playwright and its dependencies + playwright install-deps + playwright install + } + + # Detect OS and call the appropriate function + if [[ "$OSTYPE" == "darwin"* ]]; then + install_mac + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + install_ubuntu + else + echo "Unsupported operating system." + exit 1 + fi + + # Update your shell's source to include Cargo's path + source "$HOME/.cargo/env" +} + +# Call install_dependencies only if it's the first time running the script +if [ ! -f ".dependencies_installed" ]; then + install_dependencies + touch .dependencies_installed +fi \ No newline at end of file diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100644 index 00000000..dd1c65fd --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Prompt user for required information +echo "Please enter your neuron details:" +read -p "Coldkey name: " COLDKEY +read -p "Hotkey name: " HOTKEY +read -p "Axon port: " AXON_PORT +read -p "PM2 process name: " PROCESS_NAME + +# Prompt for neuron type with validation +while true; do + read -p "Neuron type (validator/miner): " NEURON_TYPE + if [[ "$NEURON_TYPE" == "validator" || "$NEURON_TYPE" == "miner" ]]; then + break + else + echo "Invalid neuron type. Please enter either 'validator' or 'miner'" + fi +done + +# Prompt for network type with validation +while true; do + read -p "Network type (finney/test): " NETWORK + if [[ "$NETWORK" == "finney" || "$NETWORK" == "test" ]]; then + break + else + echo "Invalid network. Please enter either 'finney' or 'test'" + fi +done + +# Confirm the entered values +echo -e "\nYou entered:" +echo "Coldkey: $COLDKEY" +echo "Hotkey: $HOTKEY" +echo "Axon port: $AXON_PORT" +echo "PM2 process name: $PROCESS_NAME" +echo "Neuron type: $NEURON_TYPE" +echo "Network: $NETWORK" + +# Ask for confirmation +read -p "Is this correct? (y/n): " CONFIRM + +if [[ $CONFIRM != [yY] ]]; then + echo "Aborted. Please run the script again." + exit 1 +fi + +export PYTHONPATH="." +# Set netuid based on network type +NETUID=$([ "$NETWORK" == "finney" ] && echo "54" || echo "214") +if [[ "$NEURON_TYPE" == "validator" ]]; then + pm2 start "uv run neurons/validators/validator.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.debug --axon.port $AXON_PORT" --name $PROCESS_NAME + pm2 start --name auto_update auto_update.sh +else + pm2 start "uv run neurons/miners/miner.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.debug --axon.port $AXON_PORT" --name $PROCESS_NAME +fi \ No newline at end of file From 199d2efcd046fbc6a514e5300e31d83201964154 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Tue, 21 Jan 2025 08:23:16 -0700 Subject: [PATCH 290/554] feat: update readme file --- README.md | 9 ++++++++- scripts/start.sh | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6b64a435..0258012a 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,14 @@ The WebGenieAI subnet incentivizes miners and validators to ensure high-quality - See [Running on Testnet](docs/running_on_testnet.md) for instructions on how to run the subnet on testnet. - See [Running on Mainnet](docs/running_on_mainnet.md) for instructions on how to run the subnet on mainnet. -#### Scripts for running miners and validators +#### 1) Running miners and validators with script files + +```bash +bash scripts/requirements.sh +bash scripts/start.sh +``` + +#### 2) Scripts for running miners and validators manually ```bash npm install pm2 -g git clone https://github.com/web-genie-ai/web-genie-ai.git diff --git a/scripts/start.sh b/scripts/start.sh index dd1c65fd..68996c42 100644 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -5,6 +5,7 @@ echo "Please enter your neuron details:" read -p "Coldkey name: " COLDKEY read -p "Hotkey name: " HOTKEY read -p "Axon port: " AXON_PORT +echo "If you are going to run validator, PM2 process name will be [webgenie_validator] automatically. If you are going to run miner, please enter the PM2 process name." read -p "PM2 process name: " PROCESS_NAME # Prompt for neuron type with validation @@ -27,6 +28,10 @@ while true; do fi done +if [[ "$NEURON_TYPE" == "validator" ]]; then + PROCESS_NAME="webgenie_validator" +fi + # Confirm the entered values echo -e "\nYou entered:" echo "Coldkey: $COLDKEY" @@ -48,7 +53,7 @@ export PYTHONPATH="." # Set netuid based on network type NETUID=$([ "$NETWORK" == "finney" ] && echo "54" || echo "214") if [[ "$NEURON_TYPE" == "validator" ]]; then - pm2 start "uv run neurons/validators/validator.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.debug --axon.port $AXON_PORT" --name $PROCESS_NAME + pm2 start "uv run neurons/validators/validator.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.debug --axon.port $AXON_PORT" --name webgenie_validator pm2 start --name auto_update auto_update.sh else pm2 start "uv run neurons/miners/miner.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.debug --axon.port $AXON_PORT" --name $PROCESS_NAME From 521e70fcc6f80f1c83e10cc188fa3e21040a1ace Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Tue, 21 Jan 2025 10:08:19 -0700 Subject: [PATCH 291/554] feat: update script and readme --- README.md | 8 ++++++-- scripts/install_requirements.sh | 15 +++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0258012a..2e3eff97 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,12 @@ The WebGenieAI subnet incentivizes miners and validators to ensure high-quality - See [Running on Testnet](docs/running_on_testnet.md) for instructions on how to run the subnet on testnet. - See [Running on Mainnet](docs/running_on_mainnet.md) for instructions on how to run the subnet on mainnet. +Clone the web-genie-ai repository: +```bash +git clone https://github.com/web-genie-ai/web-genie-ai.git +cd web-genie-ai +``` + #### 1) Running miners and validators with script files ```bash @@ -128,8 +134,6 @@ bash scripts/start.sh #### 2) Scripts for running miners and validators manually ```bash npm install pm2 -g -git clone https://github.com/web-genie-ai/web-genie-ai.git -cd web-genie-ai curl -LsSf https://astral.sh/uv/install.sh | sh ``` Install the packages in a new terminal: diff --git a/scripts/install_requirements.sh b/scripts/install_requirements.sh index ee00ccd0..cae613ab 100644 --- a/scripts/install_requirements.sh +++ b/scripts/install_requirements.sh @@ -44,7 +44,7 @@ install_dependencies() { echo "Updating system packages..." sudo apt update echo "Installing required packages..." - sudo apt install --assume-yes make git curl python3-pip python3-venv + sudo apt install --assume-yes make curl python3-pip # Install Node.js and npm curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt install --assume-yes nodejs @@ -53,15 +53,11 @@ install_dependencies() { echo "npm version: $(npm --version)" # Install PM2 globally npm install pm2 -g - git clone https://github.com/web-genie-ai/web-genie-ai.git - cd web-genie-ai - - # Create and activate virtual environment - python3 -m venv .venv - source .venv/bin/activate - # Install uv package manager + # Install uv package manager and add to PATH curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH="$HOME/.cargo/bin:$PATH" + echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.bashrc echo "Installing dependencies..." uv sync @@ -72,6 +68,9 @@ install_dependencies() { sudo gdebi -n google-chrome-stable_current_amd64.deb rm google-chrome-stable_current_amd64.deb + # Create and activate virtual environment + source .venv/bin/activate + # Install Playwright and its dependencies playwright install-deps playwright install From 4b988c640f88b128733cc0a8de94b0fc24508a80 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Tue, 21 Jan 2025 10:11:26 -0700 Subject: [PATCH 292/554] chore: update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e3eff97..588f7c76 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ git clone https://github.com/web-genie-ai/web-genie-ai.git cd web-genie-ai ``` -#### 1) Running miners and validators with script files +#### 1) Running miners and validators using bash files ```bash bash scripts/requirements.sh From 2b24ae6e1b4163efe04f812879e93670db6f8ee8 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Tue, 21 Jan 2025 10:49:14 -0700 Subject: [PATCH 293/554] feat: update scripts --- scripts/install_requirements.sh | 5 +++-- scripts/start.sh | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/install_requirements.sh b/scripts/install_requirements.sh index cae613ab..d0b47a8e 100644 --- a/scripts/install_requirements.sh +++ b/scripts/install_requirements.sh @@ -56,8 +56,9 @@ install_dependencies() { # Install uv package manager and add to PATH curl -LsSf https://astral.sh/uv/install.sh | sh - export PATH="$HOME/.cargo/bin:$PATH" - echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.bashrc + export PATH="$HOME/.local/bin:$PATH" + echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc + source "$HOME/.local/bin/env" echo "Installing dependencies..." uv sync diff --git a/scripts/start.sh b/scripts/start.sh index 68996c42..c09b44c7 100644 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -54,7 +54,7 @@ export PYTHONPATH="." NETUID=$([ "$NETWORK" == "finney" ] && echo "54" || echo "214") if [[ "$NEURON_TYPE" == "validator" ]]; then pm2 start "uv run neurons/validators/validator.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.debug --axon.port $AXON_PORT" --name webgenie_validator - pm2 start --name auto_update auto_update.sh + pm2 start --name auto_update scripts/auto_update.sh else pm2 start "uv run neurons/miners/miner.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.debug --axon.port $AXON_PORT" --name $PROCESS_NAME fi \ No newline at end of file From f1d553394f9475ffdb747aa61e495f16f2aff360 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Tue, 21 Jan 2025 10:54:46 -0700 Subject: [PATCH 294/554] feat: update script --- scripts/start.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/start.sh b/scripts/start.sh index c09b44c7..52556927 100644 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -53,8 +53,8 @@ export PYTHONPATH="." # Set netuid based on network type NETUID=$([ "$NETWORK" == "finney" ] && echo "54" || echo "214") if [[ "$NEURON_TYPE" == "validator" ]]; then - pm2 start "uv run neurons/validators/validator.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.debug --axon.port $AXON_PORT" --name webgenie_validator + pm2 start "$HOME/.local/bin/uv run neurons/validators/validator.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.debug --axon.port $AXON_PORT" --name webgenie_validator pm2 start --name auto_update scripts/auto_update.sh else - pm2 start "uv run neurons/miners/miner.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.debug --axon.port $AXON_PORT" --name $PROCESS_NAME + pm2 start "$HOME/.local/bin/uv run neurons/miners/miner.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.debug --axon.port $AXON_PORT" --name $PROCESS_NAME fi \ No newline at end of file From a0fb7491082cf2acc891fb4a408bb3fb61918d97 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Tue, 21 Jan 2025 11:20:29 -0700 Subject: [PATCH 295/554] feat: update readme --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 588f7c76..b38faf9c 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,11 @@ The primary purpose of WebGenieAI is to: ## Incentive Mechanism v1 -The WebGenieAI subnet incentivizes miners and validators to ensure high-quality outputs. Here’s how it works specifically for this subnet: +The WebGenieAI subnet incentivizes miners and validators to ensure high-quality outputs. Here's how it works specifically for this subnet: - Task Assignment: Subnet miners are assigned tasks related to generating and improving machine learning models based on various prompts (text and image). - Evaluation: Validators evaluate the outputs produced by miners. The evaluation criteria include accuracy, code quality, and performance metrics. -- Ranking and Rewarding: Validators rank the miners according to their performance. The Bittensor blockchain’s Yuma Consensus mechanism determines the TAO rewards distribution based on these rankings. +- Ranking and Rewarding: Validators rank the miners according to their performance. The Bittensor blockchain's Yuma Consensus mechanism determines the TAO rewards distribution based on these rankings. ![Webgenie Subnet workflow](docs/webgenie-workflow.png "WebGenieAI workflow") @@ -88,9 +88,9 @@ The WebGenieAI subnet incentivizes miners and validators to ensure high-quality - ### Unsupervised Evaluation of Model by Round-Trip Correctness (Ref: [[2]](#references)) We draw inspiration from a software testing technique known as property-based testing. It allows defining properties that must hold between inputs and outputs of a program (e.g., all items in the input list must also appear in the output list) Round-trip correctness is one such property (e.g., compressing and subsequently decompressing data must yield the original data). - Consider two forms of data X and Y, such as text prompt and HTML and two (probabilistic) models whose task is to “translate” from one form of data to the other, i.e., a forward model M : X → Y and a backward model M-1: Y → X. These models could be a single LLM prompted differently. + Consider two forms of data X and Y, such as text prompt and HTML and two (probabilistic) models whose task is to "translate" from one form of data to the other, i.e., a forward model M : X → Y and a backward model M-1: Y → X. These models could be a single LLM prompted differently. - The central idea for unsupervised evaluation is the concept of round-trip correctness (RTC). Intuitively, for a “good” forward and backward model we expect ̂x =M-1 M(x) to be semantically equivalent to x. For example, we can describe the HTML code with text prompt in the forward pass and then generate back the code from the text prompt. To compute RTC we need some function sim(x, ̂x) that estimates the semantic equivalence between the original x and each predicted sample ̂x. Such functions may include discrete or continuous metrics such as exact match, BLEU and so on. + The central idea for unsupervised evaluation is the concept of round-trip correctness (RTC). Intuitively, for a "good" forward and backward model we expect ̂x =M-1 M(x) to be semantically equivalent to x. For example, we can describe the HTML code with text prompt in the forward pass and then generate back the code from the text prompt. To compute RTC we need some function sim(x, ̂x) that estimates the semantic equivalence between the original x and each predicted sample ̂x. Such functions may include discrete or continuous metrics such as exact match, BLEU and so on. - ### Supervised Evaluation of Model by CodeBERTScore Let x is prompt, y is the ground truth html, ̂y is the generated html. @@ -128,6 +128,10 @@ cd web-genie-ai ```bash bash scripts/requirements.sh +``` + +Configure your Bittensor wallets and environment variables before proceeding: +```bash bash scripts/start.sh ``` From acdad24f8cc8257b52cad9421295ed18fb2534d8 Mon Sep 17 00:00:00 2001 From: Sangar <8627971+sangar-1028@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:49:20 -0600 Subject: [PATCH 296/554] fix the bash script for the requirement --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b38faf9c..02dea79c 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ cd web-genie-ai #### 1) Running miners and validators using bash files ```bash -bash scripts/requirements.sh +bash scripts/install_requirements.sh ``` Configure your Bittensor wallets and environment variables before proceeding: From 44e5ca5095474038b5f88c694eac15b5e3bf3044 Mon Sep 17 00:00:00 2001 From: pycorn Date: Tue, 21 Jan 2025 20:41:46 +0000 Subject: [PATCH 297/554] feat: accumulate the winners count --- neurons/validators/genie_validator.py | 68 +++++++++++++-------- neurons/validators/score_manager.py | 85 +++++++++++++++------------ neurons/validators/validator.py | 8 +-- webgenie/constants.py | 19 +++--- 4 files changed, 105 insertions(+), 75 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index a2ab3f8c..038187f3 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -45,19 +45,23 @@ class GenieValidator: def __init__(self, neuron: BaseNeuron): self.neuron = neuron + self.lock = neuron.lock self.config = neuron.config self.miner_results = [] self.synthetic_tasks = [] self.task_generators = [ - (ImageTaskGenerator(), 1.0), + (ImageTaskGenerator(), 1.0), # currently only image task generator is supported ] async def query_miners(self): try: - with self.neuron.lock: + with self.lock: if len(self.miner_results) > MAX_COMPETETION_HISTORY_SIZE: - bt.logging.info(f"Competition history size {len(self.miner_results)} exceeds max size {MAX_COMPETETION_HISTORY_SIZE}, skipping") + bt.logging.info( + f"Competition history size {len(self.miner_results)} " + f"exceeds max size {MAX_COMPETETION_HISTORY_SIZE}, skipping" + ) return if not self.synthetic_tasks: @@ -78,7 +82,7 @@ async def query_miners(self): SeoChallenge, ] - with self.neuron.lock: + with self.lock: session_number = self.neuron.session_number challenge_class = available_challenges_classes[session_number % len(available_challenges_classes)] @@ -123,7 +127,7 @@ async def query_miners(self): challenge.solutions = solutions bt.logging.info(f"Received {len(solutions)} valid solutions") - with self.neuron.lock: + with self.lock: self.miner_results.append(challenge) except Exception as e: @@ -131,23 +135,33 @@ async def query_miners(self): raise e async def score(self): - with self.neuron.lock: + with self.lock: if not self.miner_results: + bt.logging.info("No miner results to score") return challenge = self.miner_results.pop(0) if not challenge.solutions: + bt.logging.info("No solutions to score") return - with self.neuron.lock: + with self.lock: if challenge.session_number != self.neuron.session_number: + bt.logging.info( + f"Session number mismatch: {challenge.session_number} != {self.neuron.session_number}" + f"This is the previous session's challenge, skipping" + ) return - bt.logging.info("Scoring") + bt.logging.info( + f"Scoring - Session number: {challenge.session_number}, " + f"Competition type: {challenge.competition_type}, " + f"Task source: {challenge.task.src}" + ) + solutions = challenge.solutions miner_uids = [solution.miner_uid for solution in solutions] - aggregated_scores, scores = await challenge.calculate_scores() bt.logging.success(f"Task Source: {challenge.task.src}") @@ -155,7 +169,14 @@ async def score(self): bt.logging.success(f"Scores: {scores}") bt.logging.success(f"Final scores for {miner_uids}: {aggregated_scores}") - with self.neuron.lock: + with self.lock: + self.neuron.score_manager.update_scores( + aggregated_scores, + miner_uids, + challenge.session_number, + ) + + with self.lock: current_block = self.neuron.block session_number = self.neuron.session_number session_start_block = session_number * SESSION_WINDOW_BLOCKS @@ -197,21 +218,20 @@ async def score(self): "session_start_datetime": session_start_datetime, } - bt.logging.info(f"Storing results to database: {payload}") - store_results_to_database(payload) - - with self.neuron.lock: - self.neuron.score_manager.update_scores( - aggregated_scores, - miner_uids, - challenge.session_number, - ) + try: + bt.logging.info(f"Storing results to database: {payload}") + store_results_to_database(payload) + except Exception as e: + bt.logging.error(f"Error storing results to database: {e}") async def synthensize_task(self): try: - with self.neuron.lock: + with self.lock: if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: - bt.logging.info(f"Synthetic task size {len(self.synthetic_tasks)} exceeds max size {MAX_SYNTHETIC_TASK_SIZE}, skipping") + bt.logging.info( + f"Synthetic task size {len(self.synthetic_tasks)} exceeds " + f"max size {MAX_SYNTHETIC_TASK_SIZE}, skipping" + ) return bt.logging.info(f"Synthensize task") @@ -222,7 +242,7 @@ async def synthensize_task(self): )[0] task, synapse = await task_generator.generate_task() - with self.neuron.lock: + with self.lock: self.synthetic_tasks.append((task, synapse)) bt.logging.success(f"Successfully generated task for {task.src}") @@ -288,8 +308,8 @@ async def checked_synapse(self, synapse: bt.Synapse) -> bt.Synapse: return None html = preprocess_html(synapse.html) - if not is_valid_resources(html): - bt.logging.warning(f"Invalid resources: {html}") + if not html or not is_valid_resources(html): + bt.logging.warning(f"Invalid html or resources: {html}") return None synapse.html = html diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index ae6a6014..444204da 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -10,38 +10,50 @@ convert_weights_and_uids_for_emit, ) from webgenie.base.neuron import BaseNeuron - +from webgenie.constants import CONSIDERING_SESSION_NUMBER from webgenie.storage import send_challenge_to_stats_collector class ScoreManager: def __init__(self, neuron: BaseNeuron): self.neuron = neuron + self.lock = neuron.lock self.should_save = False self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) - self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - self.scoring_session_number = 0 + self.scoring_session_number = -1 self.session_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.last_send_stats_collector_session_number = -1 + self.winners = [] def load_scores(self): try: bt.logging.info("Loading scores") state = np.load(self.neuron.config.neuron.full_path + "/state.npz") - self.scores = state["scores"] - self.hotkeys = state["hotkeys"] - self.scoring_session_number = state["scoring_session_number"] - self.session_accumulated_scores = state["tempo_accumulated_scores"] - self.last_send_stats_collector_session_number = state["last_send_stats_collector_session_number"] + + self.hotkeys = state.get( + "hotkeys", + copy.deepcopy(self.neuron.metagraph.hotkeys) + ) + self.scoring_session_number = state.get( + "scoring_session_number", + -1 + ) + self.session_accumulated_scores = state.get( + "tempo_accumulated_scores", + np.zeros(self.neuron.metagraph.n, dtype=np.float32) + ) + self.last_send_stats_collector_session_number = state.get( + "last_send_stats_collector_session_number", + -1 + ) + self.winners = state.get( + "winners", + [] + ) except Exception as e: bt.logging.warning(f"Error loading scores: {e}") - self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) - self.scoring_session_number = 0 - self.session_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - self.last_send_stats_collector_session_number = -1 def save_scores(self): if not self.should_save: @@ -51,11 +63,11 @@ def save_scores(self): bt.logging.info("Saving scores") np.savez( self.neuron.config.neuron.full_path + "/state.npz", - scores=self.scores, hotkeys=self.hotkeys, scoring_session_number=self.scoring_session_number, tempo_accumulated_scores=self.session_accumulated_scores, last_send_stats_collector_session_number=self.last_send_stats_collector_session_number, + winners=self.winners, ) def set_new_hotkeys(self, new_hotkeys: List[str]): @@ -66,20 +78,14 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): for uid, hotkey in enumerate(self.hotkeys): if hotkey != new_hotkeys[uid]: self.session_accumulated_scores[uid] = 0 - self.scores[uid] = 0 # hotkey has been replaced # Check to see if the metagraph has changed size. # If so, we need to add new hotkeys and moving averages. if len(self.hotkeys) < len(new_hotkeys): - new_scores = np.zeros((len(new_hotkeys))) - min_len = min(len(self.hotkeys), len(self.scores)) - new_scores[:min_len] = self.scores[:min_len] - self.scores = new_scores - - new_tempo_accumulated_scores = np.zeros((len(new_hotkeys))) + new_session_accumulated_scores = np.zeros((len(new_hotkeys))) min_len = min(len(self.hotkeys), len(self.session_accumulated_scores)) - new_tempo_accumulated_scores[:min_len] = self.session_accumulated_scores[:min_len] - self.session_accumulated_scores = new_tempo_accumulated_scores + new_session_accumulated_scores[:min_len] = self.session_accumulated_scores[:min_len] + self.session_accumulated_scores = new_session_accumulated_scores # Update the hotkeys. self.hotkeys = copy.deepcopy(new_hotkeys) @@ -87,14 +93,18 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): def update_scores(self, rewards: np.ndarray, uids: List[int], session_number: int): if self.scoring_session_number != session_number: + # This is a new session, reset the scores and winners. self.scoring_session_number = session_number - self.session_accumulated_scores = np.zeros_like(self.scores) + self.session_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + if len(self.winners) > CONSIDERING_SESSION_NUMBER: + self.winners.pop(0) + self.winners.append(-1) + + # Update accumulated scores and track best performer + self.session_accumulated_scores[uids] += rewards + self.winners[-1] = np.argmax(self.session_accumulated_scores) - scattered_rewards: np.ndarray = np.zeros_like(self.scores) - scattered_rewards[uids] = rewards - self.session_accumulated_scores: np.ndarray = scattered_rewards + self.session_accumulated_scores bt.logging.debug(f"Updated scores: {self.session_accumulated_scores}") - self.should_save = True def set_weights(self): @@ -102,33 +112,34 @@ def set_weights(self): Sets the validator weights to the metagraph hotkeys based on the scores it has received from the miners. The weights determine the trust and incentive level the validator assigns to miner nodes on the network. """ - with self.neuron.lock: + with self.lock: if not self.neuron.should_set_weights(): return current_session_number = self.neuron.session_number - + if current_session_number != self.last_send_stats_collector_session_number: send_challenge_to_stats_collector(self.neuron.wallet, current_session_number) self.last_send_stats_collector_session_number = current_session_number - with self.neuron.lock: - self.scores = np.zeros_like(self.scores) - best_index = np.argmax(self.session_accumulated_scores) - self.scores[best_index] = 1 + with self.lock: + scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + for winner in self.winners: + scores[winner] += 1 + # Calculate the average reward for each uid across non-zero values. # Replace any NaN values with 0. # Compute the norm of the scores - norm = np.linalg.norm(self.scores, ord=1, axis=0, keepdims=True) + norm = np.linalg.norm(scores, ord=1, axis=0, keepdims=True) # Check if the norm is zero or contains NaN values if np.any(norm == 0) or np.isnan(norm).any(): norm = np.ones_like(norm) # Avoid division by zero or NaN # Compute raw_weights safely - raw_weights = self.scores / norm + raw_weights = scores / norm - with self.neuron.lock: + with self.lock: # Process the raw weights to final_weights via subtensor limitations. ( processed_weight_uids, diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 8ae0f892..59fa7d48 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -186,7 +186,7 @@ def query_miners_loop(self): time.sleep(sleep_blocks * BLOCK_IN_SECONDS) continue - QUERY_MINERS_TIMEOUT = 60 * 15 + QUERY_MINERS_TIMEOUT = 60 * 15 # 15 minutes self.query_miners_event_loop.run_until_complete( asyncio.wait_for( self.genie_validator.query_miners(), @@ -206,7 +206,7 @@ def score_loop(self): with self.lock: self.sync() - SCORE_TIMEOUT = 60 * 60 * 2 + SCORE_TIMEOUT = 60 * 60 * 2 # 2 hours self.score_event_loop.run_until_complete( asyncio.wait_for( self.genie_validator.score(), @@ -226,7 +226,7 @@ def synthensize_task_loop(self): with self.lock: self.sync() - SYNTHETIC_TASK_TIMEOUT = 60 * 15 + SYNTHETIC_TASK_TIMEOUT = 60 * 15 # 15 minutes self.synthensize_task_event_loop.run_until_complete( asyncio.wait_for( self.genie_validator.synthensize_task(), @@ -312,12 +312,12 @@ def stop_background_threads(self): self.query_miners_thread.join(5) self.score_thread.join(5) self.set_weights_thread.join(5) + stop_lighthouse_server() self.synthensize_task_thread = None self.query_miners_thread = None self.score_thread = None self.set_weights_thread = None - stop_lighthouse_server() bt.logging.info("Stopped background threads") def __enter__(self): diff --git a/webgenie/constants.py b/webgenie/constants.py index d3873f5d..508962d0 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -4,9 +4,6 @@ # backend api hotkey API_HOTKEY = "5DXDCYTuPfLqQXbxfvvnarG31SdTDtaubqpQrzjrcMgoP9dp" -# default load time -DEFAULT_LOAD_TIME = 1000 - # image task timeout IMAGE_TASK_TIMEOUT = 72 @@ -34,11 +31,8 @@ # place holder image url PLACE_HOLDER_IMAGE_URL = "https://picsum.photos/seed/picsum/800/600" -# python command -PYTHON_CMD = "python" - -# screenshot script path -SCREENSHOT_SCRIPT_PATH = "webgenie/rewards/visual_reward/metrics/screenshot_single.py" +# default load time +DEFAULT_LOAD_TIME = 1000 # max page load time GROUND_TRUTH_HTML_LOAD_TIME = 20000 @@ -46,6 +40,10 @@ # miner html load time CHROME_HTML_LOAD_TIME = 60000 +# javascript running time +JAVASCRIPT_RUNNING_TIME = 1000 + + # miner html load time MINER_HTML_LOAD_TIME = 2000 @@ -76,14 +74,15 @@ # session window blocks SESSION_WINDOW_BLOCKS = TEMPO_BLOCKS * 3 +# considering session number +CONSIDERING_SESSION_NUMBER = 8 + # querying window blocks QUERING_WINDOW_BLOCKS = 10 # weight setting window blocks WEIGHT_SETTING_WINDOW_BLOCKS = 50 # 50 blocks = 10 minutes -JAVASCRIPT_RUNNING_TIME = 1000 - # llm model id LLM_MODEL_ID = os.getenv("LLM_MODEL_ID") From dd3614237ac5bbaec525d5df52d7c7fbd3e4dbef Mon Sep 17 00:00:00 2001 From: pycorn Date: Wed, 22 Jan 2025 00:08:08 +0000 Subject: [PATCH 298/554] chore: accumulate winning count --- neurons/validators/score_manager.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 444204da..c1c7cf4a 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -100,11 +100,14 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], session_number: in self.winners.pop(0) self.winners.append(-1) + if not self.winners: + self.winners.append(-1) + # Update accumulated scores and track best performer self.session_accumulated_scores[uids] += rewards + bt.logging.info(f"Updated scores: {self.session_accumulated_scores}") self.winners[-1] = np.argmax(self.session_accumulated_scores) - - bt.logging.debug(f"Updated scores: {self.session_accumulated_scores}") + bt.logging.info(f"Updated winners: {self.winners}") self.should_save = True def set_weights(self): @@ -118,15 +121,18 @@ def set_weights(self): current_session_number = self.neuron.session_number if current_session_number != self.last_send_stats_collector_session_number: - send_challenge_to_stats_collector(self.neuron.wallet, current_session_number) - self.last_send_stats_collector_session_number = current_session_number + try: + send_challenge_to_stats_collector(self.neuron.wallet, current_session_number) + self.last_send_stats_collector_session_number = current_session_number + except Exception as e: + bt.logging.error(f"Error sending challenge to stats collector: {e}") with self.lock: scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) for winner in self.winners: scores[winner] += 1 - + # Calculate the average reward for each uid across non-zero values. # Replace any NaN values with 0. # Compute the norm of the scores From 11acc4cf2df786a7987d33b21aa2f0d22082fafe Mon Sep 17 00:00:00 2001 From: pycorn Date: Wed, 22 Jan 2025 13:42:17 +0000 Subject: [PATCH 299/554] chore: make the vpermit_tao_limit constant --- .env.miner.example | 1 - .env.validator.example | 2 -- webgenie/constants.py | 3 +-- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.env.miner.example b/.env.miner.example index aa88798c..12066860 100644 --- a/.env.miner.example +++ b/.env.miner.example @@ -7,4 +7,3 @@ LLM_API_KEY = your_openai_api_key LLM_MODEL_ID = your_openai_model_id LLM_MODEL_URL = your_openai_model_url # https://api.openai.com/v1/ -VPERMIT_TAO_LIMIT = 6000 diff --git a/.env.validator.example b/.env.validator.example index 35584e2d..bf8c7ead 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -7,7 +7,5 @@ LLM_MODEL_URL = your_openai_model_url # https://api.openai.com/v1/ LIGHTHOUSE_SERVER_PORT = 5000 # fast api server port to get the lighthouse score -VPERMIT_TAO_LIMIT = 6000 - AXON_OFF = True diff --git a/webgenie/constants.py b/webgenie/constants.py index 508962d0..97e39459 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -102,8 +102,7 @@ WANDB_ENTITY_NAME = os.getenv("WANDB_ENTITY_NAME") # vpermit tao limit -#VPERMIT_TAO_LIMIT = bt.Balance(float(os.getenv("VPERMIT_TAO_LIMIT", 4096))) -VPERMIT_TAO_LIMIT = float(os.getenv("VPERMIT_TAO_LIMIT", 4096)) +VPERMIT_TAO_LIMIT = 1000 # axon off AXON_OFF = os.getenv("AXON_OFF", "False").lower() == "true" From 044c0f5f52263a245d51351d2b55a805aeabfc9d Mon Sep 17 00:00:00 2001 From: pycorn Date: Wed, 22 Jan 2025 19:34:56 +0000 Subject: [PATCH 300/554] style: rename variable name --- neurons/validators/genie_validator.py | 20 ++++++++-------- neurons/validators/score_manager.py | 34 +++++++++++++-------------- neurons/validators/validator.py | 2 +- webgenie/challenges/challenge.py | 2 +- webgenie/constants.py | 2 +- webgenie/storage/utils.py | 22 ++++++++--------- 6 files changed, 41 insertions(+), 41 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 038187f3..96758012 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -83,10 +83,10 @@ async def query_miners(self): ] with self.lock: - session_number = self.neuron.session_number + session = self.neuron.session - challenge_class = available_challenges_classes[session_number % len(available_challenges_classes)] - challenge = challenge_class(task=task, session_number=session_number) + challenge_class = available_challenges_classes[session % len(available_challenges_classes)] + challenge = challenge_class(task=task, session=session) synapse.competition_type = challenge.competition_type @@ -147,15 +147,15 @@ async def score(self): return with self.lock: - if challenge.session_number != self.neuron.session_number: + if challenge.session != self.neuron.session: bt.logging.info( - f"Session number mismatch: {challenge.session_number} != {self.neuron.session_number}" + f"Session number mismatch: {challenge.session} != {self.neuron.session}" f"This is the previous session's challenge, skipping" ) return bt.logging.info( - f"Scoring - Session number: {challenge.session_number}, " + f"Scoring - Session number: {challenge.session}, " f"Competition type: {challenge.competition_type}, " f"Task source: {challenge.task.src}" ) @@ -173,13 +173,13 @@ async def score(self): self.neuron.score_manager.update_scores( aggregated_scores, miner_uids, - challenge.session_number, + challenge.session, ) with self.lock: current_block = self.neuron.block - session_number = self.neuron.session_number - session_start_block = session_number * SESSION_WINDOW_BLOCKS + session = self.neuron.session + session_start_block = session * SESSION_WINDOW_BLOCKS session_start_datetime = ( datetime.now() - timedelta( @@ -213,7 +213,7 @@ async def score(self): "challenge": { "task": challenge.task.ground_truth_html, "competition_type": challenge.competition_type, - "session_number": challenge.session_number, + "session": challenge.session, }, "session_start_datetime": session_start_datetime, } diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index c1c7cf4a..1c1c3be4 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -10,7 +10,7 @@ convert_weights_and_uids_for_emit, ) from webgenie.base.neuron import BaseNeuron -from webgenie.constants import CONSIDERING_SESSION_NUMBER +from webgenie.constants import CONSIDERING_SESSION_COUNTS from webgenie.storage import send_challenge_to_stats_collector @@ -22,9 +22,9 @@ def __init__(self, neuron: BaseNeuron): self.should_save = False self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) - self.scoring_session_number = -1 + self.scoring_session = -1 self.session_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - self.last_send_stats_collector_session_number = -1 + self.last_send_stats_collector_session = -1 self.winners = [] def load_scores(self): @@ -36,16 +36,16 @@ def load_scores(self): "hotkeys", copy.deepcopy(self.neuron.metagraph.hotkeys) ) - self.scoring_session_number = state.get( - "scoring_session_number", + self.scoring_session = state.get( + "scoring_session", -1 ) self.session_accumulated_scores = state.get( "tempo_accumulated_scores", np.zeros(self.neuron.metagraph.n, dtype=np.float32) ) - self.last_send_stats_collector_session_number = state.get( - "last_send_stats_collector_session_number", + self.last_send_stats_collector_session = state.get( + "last_send_stats_collector_session", -1 ) self.winners = state.get( @@ -64,9 +64,9 @@ def save_scores(self): np.savez( self.neuron.config.neuron.full_path + "/state.npz", hotkeys=self.hotkeys, - scoring_session_number=self.scoring_session_number, + scoring_session=self.scoring_session, tempo_accumulated_scores=self.session_accumulated_scores, - last_send_stats_collector_session_number=self.last_send_stats_collector_session_number, + last_send_stats_collector_session=self.last_send_stats_collector_session, winners=self.winners, ) @@ -91,12 +91,12 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): self.hotkeys = copy.deepcopy(new_hotkeys) self.should_save = True - def update_scores(self, rewards: np.ndarray, uids: List[int], session_number: int): - if self.scoring_session_number != session_number: + def update_scores(self, rewards: np.ndarray, uids: List[int], session: int): + if self.scoring_session != session: # This is a new session, reset the scores and winners. - self.scoring_session_number = session_number + self.scoring_session = session self.session_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - if len(self.winners) > CONSIDERING_SESSION_NUMBER: + if len(self.winners) > CONSIDERING_SESSION_COUNTS: self.winners.pop(0) self.winners.append(-1) @@ -118,12 +118,12 @@ def set_weights(self): with self.lock: if not self.neuron.should_set_weights(): return - current_session_number = self.neuron.session_number + current_session = self.neuron.session - if current_session_number != self.last_send_stats_collector_session_number: + if current_session != self.last_send_stats_collector_session: try: - send_challenge_to_stats_collector(self.neuron.wallet, current_session_number) - self.last_send_stats_collector_session_number = current_session_number + send_challenge_to_stats_collector(self.neuron.wallet, current_session) + self.last_send_stats_collector_session = current_session except Exception as e: bt.logging.error(f"Error sending challenge to stats collector: {e}") diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 59fa7d48..2e608b05 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -41,7 +41,7 @@ class Validator(BaseValidatorNeuron): """ @property - def session_number(self): + def session(self): return self.block // SESSION_WINDOW_BLOCKS def __init__(self, config=None): diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py index 8545320b..a94a19c9 100644 --- a/webgenie/challenges/challenge.py +++ b/webgenie/challenges/challenge.py @@ -20,7 +20,7 @@ class Challenge(BaseModel): task: Optional[Task] = Field(default=None, description="The task to be solved") solutions: List[Solution] = Field(default=[], description="The solutions to the task") competition_type: str = Field(default="", description="The type of competition") - session_number: int = Field(default=0, description="The session number") + session: int = Field(default=0, description="The session number") async def calculate_scores(self) -> dict[str, np.ndarray]: pass diff --git a/webgenie/constants.py b/webgenie/constants.py index 97e39459..fdcfcb3a 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -75,7 +75,7 @@ SESSION_WINDOW_BLOCKS = TEMPO_BLOCKS * 3 # considering session number -CONSIDERING_SESSION_NUMBER = 8 +CONSIDERING_SESSION_COUNTS = 8 # querying window blocks QUERING_WINDOW_BLOCKS = 10 diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index 65e6c39a..a09aca44 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -52,16 +52,16 @@ def get_neuron_id(hotkey: str): finally: session.close() # Ensure the session is closed -def create_leaderboard_session(session_number: int, created_at: datetime, competition_id: int): +def create_leaderboard_session(session: int, created_at: datetime, competition_id: int): # Check if the session with the given id already exists - existing_session = session.query(LeaderboardSession).filter_by(id=session_number).first() + existing_session = session.query(LeaderboardSession).filter_by(id=session).first() if existing_session: - bt.logging.info(f"Session with id {session_number} already exists. Skipping creation.") + bt.logging.info(f"Session with id {session} already exists. Skipping creation.") return existing_session.id # Return the existing session id return create_record(session, LeaderboardSession, - id=session_number, + id=session, created_at=created_at, competition_id=competition_id) def create_competition(name: str): @@ -111,13 +111,13 @@ def store_results_to_database(results: dict): scores = results["scores"] challenge = results["challenge"] - session_number = challenge["session_number"] + session = challenge["session"] session_start_datetime = results["session_start_datetime"] ground_truth_html = challenge["task"] competition_type = challenge["competition_type"] competition_id = create_competition(competition_type) - session_id = create_leaderboard_session(session_number, session_start_datetime, competition_id) + session_id = create_leaderboard_session(session, session_start_datetime, competition_id) challenge_id = create_challenge(session_id, ground_truth_html) # Iterate over miner_uids to store TaskSolution data @@ -134,10 +134,10 @@ def store_results_to_database(results: dict): evaluation_type_id = create_evaluation_type(eval_type) create_solution_evaluation(solution_id, evaluation_type_id, judgement_id, score_value) -def get_session_data(session_number: int): +def get_session_data(session: int): try: competition = session.query(Competition).join(LeaderboardSession).filter( - LeaderboardSession.id == session_number + LeaderboardSession.id == session ).first() if not competition: @@ -151,7 +151,7 @@ def get_session_data(session_number: int): } for leaderboard_session in competition.sessions: - if leaderboard_session.id != session_number: + if leaderboard_session.id != session: continue # Skip sessions that do not match the session number session_data = { @@ -248,8 +248,8 @@ def make_signed_request( response = requests.request(method, url, headers=headers, files=files, json=payload, timeout=5) return response -def send_challenge_to_stats_collector(wallet: "bt.Wallet", session_number: int) -> None: - session_data = get_session_data(session_number) +def send_challenge_to_stats_collector(wallet: "bt.Wallet", session: int) -> None: + session_data = get_session_data(session) response = make_signed_request( wallet=wallet, url="https://webgenie-collector.bactensor.io/api/competitions/", From adfc347d3f4dfdba3f404b46221a70f73c639f87 Mon Sep 17 00:00:00 2001 From: pycorn Date: Wed, 22 Jan 2025 19:36:33 +0000 Subject: [PATCH 301/554] style: rename variable name --- neurons/validators/validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 2e608b05..59fa7d48 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -41,7 +41,7 @@ class Validator(BaseValidatorNeuron): """ @property - def session(self): + def session_number(self): return self.block // SESSION_WINDOW_BLOCKS def __init__(self, config=None): From 8be1b87761076caff817f431e0be67cc00558141 Mon Sep 17 00:00:00 2001 From: pycorn Date: Wed, 22 Jan 2025 19:39:36 +0000 Subject: [PATCH 302/554] style: rename variable name --- neurons/validators/validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 59fa7d48..2e608b05 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -41,7 +41,7 @@ class Validator(BaseValidatorNeuron): """ @property - def session_number(self): + def session(self): return self.block // SESSION_WINDOW_BLOCKS def __init__(self, config=None): From bbc226fdd607b5f3f64f0e3d1fa170da5728db20 Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 01:17:19 +0000 Subject: [PATCH 303/554] chore: refactor code --- neurons/validators/score_manager.py | 65 ++--------------------------- neurons/validators/validator.py | 60 +++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 62 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 1c1c3be4..61156ff3 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -4,11 +4,6 @@ from typing import List - -from webgenie.base.utils.weight_utils import ( - process_weights_for_netuid, - convert_weights_and_uids_for_emit, -) from webgenie.base.neuron import BaseNeuron from webgenie.constants import CONSIDERING_SESSION_COUNTS from webgenie.storage import send_challenge_to_stats_collector @@ -109,15 +104,9 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], session: int): self.winners[-1] = np.argmax(self.session_accumulated_scores) bt.logging.info(f"Updated winners: {self.winners}") self.should_save = True - - def set_weights(self): - """ - Sets the validator weights to the metagraph hotkeys based on the scores it has received from the miners. The weights determine the trust and incentive level the validator assigns to miner nodes on the network. - """ - + + def send_challenge_to_stats_collector(self): with self.lock: - if not self.neuron.should_set_weights(): - return current_session = self.neuron.session if current_session != self.last_send_stats_collector_session: @@ -127,56 +116,10 @@ def set_weights(self): except Exception as e: bt.logging.error(f"Error sending challenge to stats collector: {e}") + def get_scores(self): with self.lock: scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) for winner in self.winners: scores[winner] += 1 - - # Calculate the average reward for each uid across non-zero values. - # Replace any NaN values with 0. - # Compute the norm of the scores - norm = np.linalg.norm(scores, ord=1, axis=0, keepdims=True) - - # Check if the norm is zero or contains NaN values - if np.any(norm == 0) or np.isnan(norm).any(): - norm = np.ones_like(norm) # Avoid division by zero or NaN - - # Compute raw_weights safely - raw_weights = scores / norm - - with self.lock: - # Process the raw weights to final_weights via subtensor limitations. - ( - processed_weight_uids, - processed_weights, - ) = process_weights_for_netuid( - uids=self.neuron.metagraph.uids, - weights=raw_weights, - netuid=self.neuron.config.netuid, - subtensor=self.neuron.subtensor, - metagraph=self.neuron.metagraph, - ) - - # Convert to uint16 weights and uids. - ( - uint_uids, - uint_weights, - ) = convert_weights_and_uids_for_emit( - uids=processed_weight_uids, weights=processed_weights - ) - # Set the weights on chain via our subtensor connection. - result, msg = self.neuron.subtensor.set_weights( - wallet=self.neuron.wallet, - netuid=self.neuron.config.netuid, - uids=uint_uids, - weights=uint_weights, - wait_for_finalization=False, - wait_for_inclusion=False, - version_key=self.neuron.spec_version, - ) - if result is True: - bt.logging.success("set_weights on chain successfully!") - else: - bt.logging.error("set_weights failed", msg) - \ No newline at end of file + return scores \ No newline at end of file diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 2e608b05..9cf55c39 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -15,6 +15,10 @@ from typing import Tuple, Union from webgenie.base.validator import BaseValidatorNeuron +from webgenie.base.utils.weight_utils import ( + process_weights_for_netuid, + convert_weights_and_uids_for_emit, +) from webgenie.constants import ( API_HOTKEY, BLOCK_IN_SECONDS, @@ -90,6 +94,60 @@ def resync_metagraph(self): ) self.score_manager.set_new_hotkeys(self.metagraph.hotkeys) + def set_weights(self): + if not self.should_set_weights(): + return + + self.score_manager.send_challenge_to_stats_collector() + + scores = self.score_manager.get_scores() + # Calculate the average reward for each uid across non-zero values. + # Replace any NaN values with 0. + # Compute the norm of the scores + norm = np.linalg.norm(scores, ord=1, axis=0, keepdims=True) + + # Check if the norm is zero or contains NaN values + if np.any(norm == 0) or np.isnan(norm).any(): + norm = np.ones_like(norm) # Avoid division by zero or NaN + + # Compute raw_weights safely + raw_weights = scores / norm + + # Process the raw weights to final_weights via subtensor limitations. + ( + processed_weight_uids, + processed_weights, + ) = process_weights_for_netuid( + uids=self.metagraph.uids, + weights=raw_weights, + netuid=self.config.netuid, + subtensor=self.subtensor, + metagraph=self.metagraph, + ) + + # Convert to uint16 weights and uids. + ( + uint_uids, + uint_weights, + ) = convert_weights_and_uids_for_emit( + uids=processed_weight_uids, weights=processed_weights + ) + # Set the weights on chain via our subtensor connection. + result, msg = self.subtensor.set_weights( + wallet=self.wallet, + netuid=self.config.netuid, + uids=uint_uids, + weights=uint_weights, + wait_for_finalization=False, + wait_for_inclusion=False, + version_key=self.spec_version, + ) + if result is True: + bt.logging.success("set_weights on chain successfully!") + else: + bt.logging.error("set_weights failed", msg) + + def save_state(self): """Saves the state of the validator to a file.""" self.score_manager.save_scores() @@ -272,7 +330,7 @@ def set_weights_loop(self): current_block <= set_weights_end_block): bt.logging.info(f"Trying to set weights at block {current_block}") - self.score_manager.set_weights() + self.set_weights() else: # Sleep until next weight setting window sleep_blocks = set_weights_start_block - current_block From 6f8f73f7d55da527744ce0b4399eedb4c03edc43 Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 02:38:15 +0000 Subject: [PATCH 304/554] fix: session_number varialbe renamed --- webgenie/storage/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index a09aca44..d02a65e3 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -134,10 +134,10 @@ def store_results_to_database(results: dict): evaluation_type_id = create_evaluation_type(eval_type) create_solution_evaluation(solution_id, evaluation_type_id, judgement_id, score_value) -def get_session_data(session: int): +def get_session_data(session_number: int): try: competition = session.query(Competition).join(LeaderboardSession).filter( - LeaderboardSession.id == session + LeaderboardSession.id == session_number ).first() if not competition: From 61c60343eac9d0c778c51e297fa0405afacfb776 Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 02:51:46 +0000 Subject: [PATCH 305/554] chore: changed error to warning --- webgenie/helpers/llms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/helpers/llms.py b/webgenie/helpers/llms.py index 5f9a6958..7e6f5733 100644 --- a/webgenie/helpers/llms.py +++ b/webgenie/helpers/llms.py @@ -35,6 +35,6 @@ async def openai_call(messages, response_format, deterministic=False, retries=3) ) return completion.choices[0].message.parsed except Exception as e: - bt.logging.error(f"Error calling OpenAI: {e}") + bt.logging.warning(f"Error calling OpenAI: {e}") continue - raise Exception("Failed to call OpenAI") \ No newline at end of file + raise Exception("Failed to call OpenAI") From 514de8bcc2b5c56300418f713b3a573124ca1777 Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 02:55:28 +0000 Subject: [PATCH 306/554] fix: fix bugs --- webgenie/rewards/lighthouse_reward/get_lighthouse_score.py | 3 ++- webgenie/rewards/visual_reward/visual_reward.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index eeec1815..2248c756 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -32,7 +32,8 @@ def get_lighthouse_score_from_subprocess(url): } return scores else: - bt.logging.error(f"Error running Lighthouse: {result.stderr}") + bt.logging.error(f"Stderr from lighthouse: {result.stderr}") + raise Exception(f"Stderr from lighthouse: {result.stderr}") except Exception as e: bt.logging.error(f"Error running Lighthouse: {e}") return { diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index 3fbb43a3..6fc57312 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -61,8 +61,8 @@ async def reward_worker(self, task: Task, solutions: List[Solution], current_wor def sync_reward_worker(self, task: Task, solutions: List[Solution], current_work_dir: str) -> np.ndarray: try: - # Timeout of 1 hour for visual reward processing - VISUAL_REWARD_TIMEOUT = 60 * 60 * 2# seconds + # Timeout of 2 hours for visual reward processing + VISUAL_REWARD_TIMEOUT = 60 * 60 * 2 # 2 hours # Run the async reward worker with timeout return asyncio.run( From 49e22c863650de841de2732cde91742a87ae63ef Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 02:57:42 +0000 Subject: [PATCH 307/554] chore: remove unneccessary logs --- neurons/validators/genie_validator.py | 1 - webgenie/rewards/visual_reward/common/extract_html_elements.py | 1 - 2 files changed, 2 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 96758012..8adc9d66 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -219,7 +219,6 @@ async def score(self): } try: - bt.logging.info(f"Storing results to database: {payload}") store_results_to_database(payload) except Exception as e: bt.logging.error(f"Error storing results to database: {e}") diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index 1a93e81c..ef74928a 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -146,7 +146,6 @@ async def traverse(node): stack = [node] while stack: current_node = stack.pop() - bt.logging.info(f"Traversing node: {current_node}") children = await current_node.query_selector_all(':scope > *') for child in children: stack.append(child) From d1d7043c7d7fa86008445993e06f6ad2929d9131 Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 03:13:08 +0000 Subject: [PATCH 308/554] fix: fix bugs --- webgenie/rewards/quality_reward.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/webgenie/rewards/quality_reward.py b/webgenie/rewards/quality_reward.py index e0cf1c17..12a7b985 100644 --- a/webgenie/rewards/quality_reward.py +++ b/webgenie/rewards/quality_reward.py @@ -20,14 +20,18 @@ class ScoreResponse(BaseModel): class QualityReward(Reward): async def _get_score(self, solution: Solution) -> float: - response = await openai_call( - messages = [ - {"role": "system", "content": PROMPT_QUALITY.format(html=solution.html)}, - ], - response_format = ScoreResponse, - deterministic=True, - ) - return response.score / 100 + try: + response = await openai_call( + messages = [ + {"role": "system", "content": PROMPT_QUALITY.format(html=solution.html)}, + ], + response_format = ScoreResponse, + deterministic=True, + ) + return response.score / 100 + except Exception as e: + bt.logging.error(f"Error getting quality score: {e}") + return 0 async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: bt.logging.info(f"Rewarding task in quality reward") From 552c979d86060a49e6f0a83c010fe32047ec9bb1 Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 03:36:20 +0000 Subject: [PATCH 309/554] fix: fix bugs --- neurons/validators/genie_validator.py | 11 ++++------ .../lighthouse_reward/get_lighthouse_score.py | 4 ++-- webgenie/storage/utils.py | 20 +++++++++---------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 8adc9d66..2acb7461 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -112,7 +112,7 @@ async def query_miners(self): synapse=synapse, timeout=TASK_REVEAL_TIMEOUT, ) - + solutions = [] for reveal_synapse, hash_synapse, miner_uid in zip(all_synapse_reveal_results, all_synapse_hash_results, miner_uids): reveal_synapse.html_hash = hash_synapse.html_hash @@ -137,13 +137,13 @@ async def query_miners(self): async def score(self): with self.lock: if not self.miner_results: - bt.logging.info("No miner results to score") + # No miner results to score return challenge = self.miner_results.pop(0) if not challenge.solutions: - bt.logging.info("No solutions to score") + # No solutions to score return with self.lock: @@ -227,10 +227,7 @@ async def synthensize_task(self): try: with self.lock: if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: - bt.logging.info( - f"Synthetic task size {len(self.synthetic_tasks)} exceeds " - f"max size {MAX_SYNTHETIC_TASK_SIZE}, skipping" - ) + # synthetic_tasks is full, skipping return bt.logging.info(f"Synthensize task") diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 2248c756..3d819b77 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -32,8 +32,8 @@ def get_lighthouse_score_from_subprocess(url): } return scores else: - bt.logging.error(f"Stderr from lighthouse: {result.stderr}") - raise Exception(f"Stderr from lighthouse: {result.stderr}") + bt.logging.error(f"Stderr from lighthouse: {result.stderr}, returncode: {result.returncode}") + raise Exception(f"Stderr from lighthouse: {result.stderr}, returncode: {result.returncode}") except Exception as e: bt.logging.error(f"Error running Lighthouse: {e}") return { diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index d02a65e3..e5c232e3 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -52,16 +52,16 @@ def get_neuron_id(hotkey: str): finally: session.close() # Ensure the session is closed -def create_leaderboard_session(session: int, created_at: datetime, competition_id: int): +def create_leaderboard_session(session_number: int, created_at: datetime, competition_id: int): # Check if the session with the given id already exists - existing_session = session.query(LeaderboardSession).filter_by(id=session).first() + existing_session = session.query(LeaderboardSession).filter_by(id=session_number).first() if existing_session: - bt.logging.info(f"Session with id {session} already exists. Skipping creation.") + bt.logging.info(f"Session with id {session_number} already exists. Skipping creation.") return existing_session.id # Return the existing session id return create_record(session, LeaderboardSession, - id=session, + id=session_number, created_at=created_at, competition_id=competition_id) def create_competition(name: str): @@ -111,13 +111,13 @@ def store_results_to_database(results: dict): scores = results["scores"] challenge = results["challenge"] - session = challenge["session"] + session_number = challenge["session_number"] session_start_datetime = results["session_start_datetime"] ground_truth_html = challenge["task"] competition_type = challenge["competition_type"] competition_id = create_competition(competition_type) - session_id = create_leaderboard_session(session, session_start_datetime, competition_id) + session_id = create_leaderboard_session(session_number, session_start_datetime, competition_id) challenge_id = create_challenge(session_id, ground_truth_html) # Iterate over miner_uids to store TaskSolution data @@ -151,7 +151,7 @@ def get_session_data(session_number: int): } for leaderboard_session in competition.sessions: - if leaderboard_session.id != session: + if leaderboard_session.id != session_number: continue # Skip sessions that do not match the session number session_data = { @@ -248,8 +248,8 @@ def make_signed_request( response = requests.request(method, url, headers=headers, files=files, json=payload, timeout=5) return response -def send_challenge_to_stats_collector(wallet: "bt.Wallet", session: int) -> None: - session_data = get_session_data(session) +def send_challenge_to_stats_collector(wallet: "bt.Wallet", session_number: int) -> None: + session_data = get_session_data(session_number) response = make_signed_request( wallet=wallet, url="https://webgenie-collector.bactensor.io/api/competitions/", @@ -258,4 +258,4 @@ def send_challenge_to_stats_collector(wallet: "bt.Wallet", session: int) -> None payload=session_data, ) if not response.ok: - print(response.json()) + print(response.json()) \ No newline at end of file From 8754aff2c91d0e33d246cb3fc08f00397a694b23 Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 16:22:04 +0000 Subject: [PATCH 310/554] chore: fix bugs --- genie_validator.py | 313 ++++++++++++++ neurons/validators/genie_validator.py | 4 +- neurons/validators/score_manager.py | 126 +++--- state.json | 1 + test.npz | Bin 0 -> 699 bytes uids.py | 118 ++++++ validator.py | 399 ++++++++++++++++++ webgenie/challenges/challenge.py | 6 + .../lighthouse_reward/get_lighthouse_score.py | 42 +- .../rewards/visual_reward/visual_reward.py | 22 +- webgenie/storage/utils.py | 10 +- webgenie/utils/uids.py | 9 +- 12 files changed, 960 insertions(+), 90 deletions(-) create mode 100644 genie_validator.py create mode 100644 state.json create mode 100644 test.npz create mode 100644 uids.py create mode 100644 validator.py diff --git a/genie_validator.py b/genie_validator.py new file mode 100644 index 00000000..8a8b1671 --- /dev/null +++ b/genie_validator.py @@ -0,0 +1,313 @@ +import os +import bittensor as bt +import numpy as np +import random +import threading +import time + +from datetime import datetime, timedelta +from typing import Union + +from webgenie.base.neuron import BaseNeuron +from webgenie.constants import ( + MAX_COMPETETION_HISTORY_SIZE, + MAX_SYNTHETIC_TASK_SIZE, + WORK_DIR, + LIGHTHOUSE_SERVER_WORK_DIR, + TASK_REVEAL_TIME, + TASK_REVEAL_TIMEOUT, + SESSION_WINDOW_BLOCKS, + BLOCK_IN_SECONDS, +) +from webgenie.challenges import ( + AccuracyChallenge, + QualityChallenge, + SeoChallenge, +) +from webgenie.helpers.htmls import preprocess_html, is_valid_resources +from webgenie.helpers.images import image_debug_str +from webgenie.protocol import ( + WebgenieImageSynapse, + WebgenieTextSynapse, + verify_answer_hash, +) +from webgenie.storage import store_results_to_database +from webgenie.tasks import Solution +from webgenie.tasks.metric_types import ( + ACCURACY_METRIC_NAME, + QUALITY_METRIC_NAME, + SEO_METRIC_NAME, +) +from webgenie.tasks.image_task_generator import ImageTaskGenerator +from webgenie.utils.uids import get_all_available_uids + + +class GenieValidator: + def __init__(self, neuron: BaseNeuron): + self.neuron = neuron + self.lock = neuron.lock + self.config = neuron.config + self.miner_results = [] + self.synthetic_tasks = [] + + self.task_generators = [ + (ImageTaskGenerator(), 1.0), # currently only image task generator is supported + ] + + async def query_miners(self): + try: + with self.lock: + if len(self.miner_results) > MAX_COMPETETION_HISTORY_SIZE: + bt.logging.info( + f"Competition history size {len(self.miner_results)} " + f"exceeds max size {MAX_COMPETETION_HISTORY_SIZE}, skipping" + ) + return + + if not self.synthetic_tasks: + bt.logging.info("No synthetic tasks available, skipping") + return + + task, synapse = self.synthetic_tasks.pop(0) + + bt.logging.info("querying miners") + miner_uids = get_all_available_uids(self.neuron) + if len(miner_uids) == 0: + bt.logging.warning("No miners available") + return + + available_challenges_classes = [ + AccuracyChallenge, + QualityChallenge, + SeoChallenge, + ] + + with self.lock: + session = self.neuron.session + + challenge_class = available_challenges_classes[session % len(available_challenges_classes)] + challenge = challenge_class(task=task, session=session) + + synapse.competition_type = challenge.competition_type + + bt.logging.debug(f"Querying {len(miner_uids)} miners") + + query_time = time.time() + async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: + all_synapse_hash_results = await dendrite( + axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], + synapse=synapse, + timeout=task.timeout, + ) + + elapsed_time = time.time() - query_time + sleep_time_before_reveal = max(0, task.timeout - elapsed_time) + TASK_REVEAL_TIME + time.sleep(sleep_time_before_reveal) + + bt.logging.debug(f"Revealing task {task.task_id}") + + async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: + all_synapse_reveal_results = await dendrite( + axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], + synapse=synapse, + timeout=TASK_REVEAL_TIMEOUT, + ) + + solutions = [] + for reveal_synapse, hash_synapse, miner_uid in zip(all_synapse_reveal_results, all_synapse_hash_results, miner_uids): + reveal_synapse.html_hash = hash_synapse.html_hash + checked_synapse = await self.checked_synapse(reveal_synapse) + if checked_synapse is not None: + solutions.append( + Solution( + html = checked_synapse.html, + miner_uid = miner_uid, + ) + ) + challenge.solutions = solutions + + bt.logging.info(f"Received {len(solutions)} valid solutions") + with self.lock: + self.miner_results.append(challenge) + + except Exception as e: + bt.logging.error(f"Error in query_miners: {e}") + raise e + + async def score(self): + with self.lock: + if not self.miner_results: + # No miner results to score + return + + challenge = self.miner_results.pop(0) + + if not challenge.solutions: + # No solutions to score + return + + with self.lock: + if challenge.session != self.neuron.session: + bt.logging.info( + f"Session number mismatch: {challenge.session} != {self.neuron.session}" + f"This is the previous session's challenge, skipping" + ) + return + + bt.logging.info( + f"Scoring - Session number: {challenge.session}, " + f"Competition type: {challenge.competition_type}, " + f"Task source: {challenge.task.src}" + ) + + solutions = challenge.solutions + miner_uids = [solution.miner_uid for solution in solutions] + aggregated_scores, scores = await challenge.calculate_scores() + + bt.logging.success(f"Task Source: {challenge.task.src}") + bt.logging.success(f"Competition Type: {challenge.competition_type}") + bt.logging.success(f"Scores: {scores}") + bt.logging.success(f"Final scores for {miner_uids}: {aggregated_scores}") + + with self.lock: + self.neuron.score_manager.update_scores( + aggregated_scores, + miner_uids, + challenge.session, + ) + + with self.lock: + current_block = self.neuron.block + session = self.neuron.session + session_start_block = session * SESSION_WINDOW_BLOCKS + session_start_datetime = ( + datetime.now() - + timedelta( + seconds=(current_block - session_start_block) * BLOCK_IN_SECONDS + ) + ) + payload = { + "validator": { + "hotkey": self.neuron.metagraph.axons[self.neuron.uid].hotkey, + "coldkey": self.neuron.metagraph.axons[self.neuron.uid].coldkey, + }, + "miners": [ + { + "coldkey": self.neuron.metagraph.axons[miner_uids[i]].coldkey, + "hotkey": self.neuron.metagraph.axons[miner_uids[i]].hotkey, + } for i in range(len(miner_uids)) + ], + "solutions": [ + { + "miner_answer": { "html": solution.html }, + } for solution in solutions + ], + "scores": [ + { + "aggregated_score": aggregated_scores[i], + "accuracy": scores[ACCURACY_METRIC_NAME][i], + "seo": scores[SEO_METRIC_NAME][i], + "code_quality": scores[QUALITY_METRIC_NAME][i], + } for i in range(len(miner_uids)) + ], + "challenge": { + "task": challenge.task.ground_truth_html, + "competition_type": challenge.competition_type, + "session_number": challenge.session, + }, + "session_start_datetime": session_start_datetime, + } + + try: + store_results_to_database(payload) + except Exception as e: + bt.logging.error(f"Error storing results to database: {e}") + + async def synthensize_task(self): + try: + with self.lock: + if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: + # synthetic_tasks is full, skipping + return + + bt.logging.info(f"Synthensize task") + + task_generator, _ = random.choices( + self.task_generators, + weights=[weight for _, weight in self.task_generators], + )[0] + + task, synapse = await task_generator.generate_task() + with self.lock: + self.synthetic_tasks.append((task, synapse)) + + bt.logging.success(f"Successfully generated task for {task.src}") + + except Exception as e: + bt.logging.error(f"Error in synthensize_task: {e}") + + async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImageSynapse]): + if isinstance(synapse, WebgenieTextSynapse): + bt.logging.debug(f"Organic text forward: {synapse.prompt}") + bt.logging.info("Not supported yet.") + synapse.html = "Not supported yet." + return synapse + else: + bt.logging.debug(f"Organic image forward: {image_debug_str(synapse.base64_image)}...") + + all_miner_uids = get_all_available_uids(self.neuron) + try: + if not all_miner_uids: + raise Exception("No miners available") + + query_time = time.time() + async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: + responses = await dendrite( + axons=[self.neuron.metagraph.axons[uid] for uid in all_miner_uids], + synapse=synapse, + timeout=synapse.timeout, + ) + + elapsed_time = time.time() - query_time + sleep_time_before_reveal = max(0, synapse.timeout - elapsed_time) + TASK_REVEAL_TIME + time.sleep(sleep_time_before_reveal) + + async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: + responses = await dendrite( + axons=[self.neuron.metagraph.axons[uid] for uid in all_miner_uids], + synapse=synapse, + timeout=TASK_REVEAL_TIMEOUT, + ) + + # Sort miner UIDs and responses by incentive scores + incentives = self.neuron.metagraph.I[all_miner_uids] + sorted_indices = np.argsort(-incentives) # Negative for descending order + all_miner_uids = [all_miner_uids[i] for i in sorted_indices] + + responses = [responses[i] for i in sorted_indices] + for response in responses: + checked_synapse = await self.checked_synapse(response) + if checked_synapse is None: + continue + return checked_synapse + + raise Exception(f"No valid solution received") + except Exception as e: + bt.logging.error(f"[forward_organic_synapse] Error querying dendrite: {e}") + synapse.html = f"Error: {e}" + return synapse + + async def checked_synapse(self, synapse: bt.Synapse) -> bt.Synapse: + if synapse.dendrite.status_code == 200: + if not verify_answer_hash(synapse): + bt.logging.warning(f"Invalid answer hash: {synapse.html_hash}") + return None + + html = preprocess_html(synapse.html) + if not html or not is_valid_resources(html): + bt.logging.warning(f"Invalid html or resources: {html}") + return None + + synapse.html = html + return synapse + return None diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 2acb7461..6430808b 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -173,7 +173,7 @@ async def score(self): self.neuron.score_manager.update_scores( aggregated_scores, miner_uids, - challenge.session, + challenge, ) with self.lock: @@ -213,7 +213,7 @@ async def score(self): "challenge": { "task": challenge.task.ground_truth_html, "competition_type": challenge.competition_type, - "session": challenge.session, + "session_number": challenge.session, }, "session_start_datetime": session_start_datetime, } diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 61156ff3..4384db28 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -1,10 +1,12 @@ import bittensor as bt import copy import numpy as np +import pickle from typing import List from webgenie.base.neuron import BaseNeuron +from webgenie.challenges.challenge import Challenge, RESERVED_WEIGHTS from webgenie.constants import CONSIDERING_SESSION_COUNTS from webgenie.storage import send_challenge_to_stats_collector @@ -12,58 +14,54 @@ class ScoreManager: def __init__(self, neuron: BaseNeuron): self.neuron = neuron + self.state_path = self.neuron.config.neuron.full_path + "/state.npz" self.lock = neuron.lock self.should_save = False - + self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) - self.scoring_session = -1 - self.session_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + self.current_session = -1 + self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.last_send_stats_collector_session = -1 - self.winners = [] + self.winners = {} def load_scores(self): try: - bt.logging.info("Loading scores") - state = np.load(self.neuron.config.neuron.full_path + "/state.npz") + bt.logging.info(f"Loading scores from {self.state_path}") + data = np.load(self.state_path, allow_pickle=True) - self.hotkeys = state.get( - "hotkeys", - copy.deepcopy(self.neuron.metagraph.hotkeys) - ) - self.scoring_session = state.get( - "scoring_session", - -1 - ) - self.session_accumulated_scores = state.get( - "tempo_accumulated_scores", - np.zeros(self.neuron.metagraph.n, dtype=np.float32) - ) - self.last_send_stats_collector_session = state.get( - "last_send_stats_collector_session", - -1 - ) - self.winners = state.get( - "winners", - [] - ) + self.hotkeys = data["hotkeys"] + self.current_session = data["current_session"] + self.total_scores = data["total_scores"] + self.last_send_stats_collector_session = data["last_send_stats_collector_session"] + self.winners = dict(data["winners"].item()) + bt.logging.info(f"Winners: {self.winners}") except Exception as e: - bt.logging.warning(f"Error loading scores: {e}") + bt.logging.error(f"Error loading state: {e}") + self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) + self.current_session = -1 + self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + self.last_send_stats_collector_session = -1 + self.winners = {} def save_scores(self): if not self.should_save: return - - self.should_save = False - bt.logging.info("Saving scores") - np.savez( - self.neuron.config.neuron.full_path + "/state.npz", - hotkeys=self.hotkeys, - scoring_session=self.scoring_session, - tempo_accumulated_scores=self.session_accumulated_scores, - last_send_stats_collector_session=self.last_send_stats_collector_session, - winners=self.winners, - ) + + try: + bt.logging.info(f"Saving scores to {self.state_path}") + np.savez( + self.state_path, + hotkeys=self.hotkeys, + current_session=self.current_session, + total_scores=self.total_scores, + last_send_stats_collector_session=self.last_send_stats_collector_session, + winners=self.winners, + allow_pickle=True, + ) + self.should_save = False + except Exception as e: + bt.logging.error(f"Error saving state: {e}") def set_new_hotkeys(self, new_hotkeys: List[str]): bt.logging.info( @@ -72,37 +70,42 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): # Zero out all hotkeys that have been replaced. for uid, hotkey in enumerate(self.hotkeys): if hotkey != new_hotkeys[uid]: - self.session_accumulated_scores[uid] = 0 + self.total_scores[uid] = 0 # Check to see if the metagraph has changed size. # If so, we need to add new hotkeys and moving averages. if len(self.hotkeys) < len(new_hotkeys): - new_session_accumulated_scores = np.zeros((len(new_hotkeys))) - min_len = min(len(self.hotkeys), len(self.session_accumulated_scores)) - new_session_accumulated_scores[:min_len] = self.session_accumulated_scores[:min_len] - self.session_accumulated_scores = new_session_accumulated_scores + new_total_scores = np.zeros((len(new_hotkeys))) + min_len = min(len(self.hotkeys), len(self.total_scores)) + new_total_scores[:min_len] = self.total_scores[:min_len] + self.total_scores = new_total_scores # Update the hotkeys. self.hotkeys = copy.deepcopy(new_hotkeys) self.should_save = True - def update_scores(self, rewards: np.ndarray, uids: List[int], session: int): - if self.scoring_session != session: + def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challenge): + bt.logging.info("Updating scores") + session = challenge.session + competition_type = challenge.competition_type + if self.current_session != session: # This is a new session, reset the scores and winners. - self.scoring_session = session - self.session_accumulated_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - if len(self.winners) > CONSIDERING_SESSION_COUNTS: - self.winners.pop(0) - self.winners.append(-1) - - if not self.winners: - self.winners.append(-1) - + self.current_session = session + self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) # Update accumulated scores and track best performer - self.session_accumulated_scores[uids] += rewards - bt.logging.info(f"Updated scores: {self.session_accumulated_scores}") - self.winners[-1] = np.argmax(self.session_accumulated_scores) - bt.logging.info(f"Updated winners: {self.winners}") + bt.logging.info(f"Updating scores for uids: {uids}") + bt.logging.info(f"Rewards: {rewards}") + bt.logging.info(f"Total scores: {self.total_scores}") + self.total_scores[uids] += rewards + bt.logging.info("Updating winners table") + print(np.argmax(self.total_scores), competition_type) + print(self.winners) + self.winners[session] = (np.argmax(self.total_scores), competition_type) + bt.logging.info(f"Winners: {self.winners}") + for session_number in self.winners: + if session_number < session - CONSIDERING_SESSION_COUNTS: + self.winners.pop(session_number) + bt.logging.info(f"Winners: {self.winners}") self.should_save = True def send_challenge_to_stats_collector(self): @@ -117,9 +120,10 @@ def send_challenge_to_stats_collector(self): bt.logging.error(f"Error sending challenge to stats collector: {e}") def get_scores(self): + scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) with self.lock: - scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - for winner in self.winners: - scores[winner] += 1 + for session_number in self.winners: + winner, competition_type = self.winners[session_number] + scores[winner] += RESERVED_WEIGHTS[competition_type] return scores \ No newline at end of file diff --git a/state.json b/state.json new file mode 100644 index 00000000..e6b7a61b --- /dev/null +++ b/state.json @@ -0,0 +1 @@ +{"hotkeys": ["hotkey1", "hotkey2"], "current_session": 1, "total_scores": [0, 0], "last_send_stats_collector_session": 2, "winners": {"1": [1, "competition_type"]}} \ No newline at end of file diff --git a/test.npz b/test.npz new file mode 100644 index 0000000000000000000000000000000000000000..3168518f1f7d8ee9d38d6d4385f0e9fcd4c447d3 GIT binary patch literal 699 zcmcgq!AiqG5Zz5$3&le~@f=TSp$+Ik#ES>PgNtbpJa~|hCMyl5*|6DEf-O|RY9Y6- zpWw;Q@eBM5Z@SwghZb*wZGmNEvL+c literal 0 HcmV?d00001 diff --git a/uids.py b/uids.py new file mode 100644 index 00000000..bb023421 --- /dev/null +++ b/uids.py @@ -0,0 +1,118 @@ +import bittensor as bt +import random +import re +import numpy as np +from typing import List, Tuple +from webgenie.constants import ( + VPERMIT_TAO_LIMIT, +) + + +def is_validator(metagraph: "bt.metagraph.Metagraph", uid: int) -> bool: + return metagraph.S[uid] > VPERMIT_TAO_LIMIT + + +def get_validator_index(self, uid: int) -> Tuple[int, int]: + validator_uids = [] + for each_uid in range(self.metagraph.n.item()): + if is_validator(self.metagraph, each_uid): + validator_uids.append(each_uid) + validator_uids.sort(key=lambda uid: self.metagraph.S[uid], reverse=True) + try: + return validator_uids.index(uid), len(validator_uids) + except ValueError: + return -1, len(validator_uids) + + +def check_uid_availability( + metagraph: "bt.metagraph.Metagraph", uid: int +) -> bool: + """Check if uid is available. The UID should be available if it is serving and has less than vpermit_tao_limit stake + Args: + metagraph (:obj: bt.metagraph.Metagraph): Metagraph object + uid (int): uid to be checked + Returns: + bool: True if uid is available, False otherwise + """ + # Filter non serving axons. + # if not metagraph.axons[uid].is_serving: + # return False + # Filter validator permit > 1024 stake. + if metagraph.S[uid] > VPERMIT_TAO_LIMIT: + return False + # Available otherwise. + return True + + +def get_most_available_uid(self, exclude: List[int] = None) -> int: + """Returns the most available uid from the metagraph. + Returns: + uid (int): Most available uid. + """ + candidate_uids = [] + avail_uids = [] + + for uid in range(self.metagraph.n.item()): + uid_is_available = check_uid_availability( + self.metagraph, uid + ) + uid_is_not_excluded = exclude is None or uid not in exclude + + if uid_is_available: + avail_uids.append(uid) + if uid_is_not_excluded: + candidate_uids.append(uid) + + return candidate_uids[np.argmax(self.metagraph.I[candidate_uids])] + + +def get_all_available_uids( + self, exclude: List[int] = None +) -> np.ndarray: + avail_uids = [] + bt.logging.debug(f"Number of neurons: {self.metagraph.n.item()}") + for uid in range(self.metagraph.n.item()): + uid_is_available = check_uid_availability(self.metagraph, uid) + uid_is_not_excluded = exclude is None or uid not in exclude + if uid_is_available and uid_is_not_excluded: + avail_uids.append(uid) + bt.logging.debug(f"Number of available neurons: {len(avail_uids)}") + return np.array(avail_uids) + + +def get_random_uids( + self, k: int, exclude: List[int] = None +) -> np.ndarray: + """Returns k available random uids from the metagraph. + Args: + k (int): Number of uids to return. + exclude (List[int]): List of uids to exclude from the random sampling. + Returns: + uids (np.ndarray): Randomly sampled available uids. + Notes: + If `k` is larger than the number of available `uids`, set `k` to the number of available `uids`. + """ + candidate_uids = [] + avail_uids = [] + + for uid in range(self.metagraph.n.item()): + uid_is_available = check_uid_availability( + self.metagraph, uid + ) + uid_is_not_excluded = exclude is None or uid not in exclude + + if uid_is_available: + avail_uids.append(uid) + if uid_is_not_excluded: + candidate_uids.append(uid) + # If k is larger than the number of available uids, set k to the number of available uids. + k = min(k, len(avail_uids)) + # Check if candidate_uids contain enough for querying, if not grab all avaliable uids + available_uids = candidate_uids + if len(candidate_uids) < k: + available_uids += random.sample( + [uid for uid in avail_uids if uid not in candidate_uids], + k - len(candidate_uids), + ) + uids = np.array(random.sample(available_uids, k)) + return uids diff --git a/validator.py b/validator.py new file mode 100644 index 00000000..6a294e07 --- /dev/null +++ b/validator.py @@ -0,0 +1,399 @@ +# The MIT License (MIT) +# Copyright © 2023 Yuma Rao +# Copyright © 2024 pycorn, Sangar + +import bittensor as bt +import asyncio +import copy +import numpy as np +import threading +import time + +from dotenv import load_dotenv, find_dotenv +load_dotenv(find_dotenv(filename=".env.validator")) + +from typing import Tuple, Union + +from webgenie.base.validator import BaseValidatorNeuron +from webgenie.base.utils.weight_utils import ( + process_weights_for_netuid, + convert_weights_and_uids_for_emit, +) +from webgenie.constants import ( + API_HOTKEY, + BLOCK_IN_SECONDS, + SESSION_WINDOW_BLOCKS, + QUERING_WINDOW_BLOCKS, + WEIGHT_SETTING_WINDOW_BLOCKS, + AXON_OFF, +) +from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse +from webgenie.rewards.lighthouse_reward import start_lighthouse_server_thread, stop_lighthouse_server +from webgenie.utils.uids import get_validator_index + +from neurons.validators.genie_validator import GenieValidator +from neurons.validators.score_manager import ScoreManager + + +class Validator(BaseValidatorNeuron): + """ + Your validator neuron class. You should use this class to define your validator's behavior. In particular, you should replace the forward function with your own logic. + + This class inherits from the BaseValidatorNeuron class, which in turn inherits from BaseNeuron. The BaseNeuron class takes care of routine tasks such as setting up wallet, subtensor, metagraph, logging directory, parsing config, etc. You can override any of the methods in BaseNeuron if you need to customize the behavior. + + This class provides reasonable default behavior for a validator such as keeping a moving average of the scores of the miners and using them to set weights at the end of each epoch. Additionally, the scores are reset for new hotkeys at the end of each epoch. + """ + + @property + def session(self): + return self.block // SESSION_WINDOW_BLOCKS + + def __init__(self, config=None): + super(Validator, self).__init__(config=config) + + # Create asyncio event loop to manage async tasks. + self.synthensize_task_event_loop = asyncio.new_event_loop() + self.query_miners_event_loop = asyncio.new_event_loop() + self.score_event_loop = asyncio.new_event_loop() + self.set_weights_event_loop = asyncio.new_event_loop() + + # Instantiate runners + self.should_exit: bool = False + self.is_running: bool = False + self.synthensize_task_thread: Union[threading.Thread, None] = None + self.query_miners_thread: Union[threading.Thread, None] = None + self.score_thread: Union[threading.Thread, None] = None + self.set_weights_thread: Union[threading.Thread, None] = None + self.lock = threading.Lock() + + self.genie_validator = GenieValidator(neuron=self) + self.score_manager = ScoreManager(neuron=self) + + bt.logging.info("load_state()") + self.load_state() + + self.sync() + + if not AXON_OFF: + self.serve_axon() + + def resync_metagraph(self): + """Resyncs the metagraph and updates the hotkeys and moving averages based on the new metagraph.""" + # Copies state of metagraph before syncing. + previous_metagraph = copy.deepcopy(self.metagraph) + + # Sync the metagraph. + self.metagraph.sync(subtensor=self.subtensor) + + # Check if the metagraph axon info has changed. + if previous_metagraph.axons == self.metagraph.axons: + return + + bt.logging.info( + "Metagraph updated, re-syncing hotkeys, dendrite pool and moving averages" + ) + self.score_manager.set_new_hotkeys(self.metagraph.hotkeys) + + def set_weights(self): + if not self.should_set_weights(): + return + + self.score_manager.send_challenge_to_stats_collector() + + scores = self.score_manager.get_scores() + # Calculate the average reward for each uid across non-zero values. + # Replace any NaN values with 0. + # Compute the norm of the scores + norm = np.linalg.norm(scores, ord=1, axis=0, keepdims=True) + + # Check if the norm is zero or contains NaN values + if np.any(norm == 0) or np.isnan(norm).any(): + norm = np.ones_like(norm) # Avoid division by zero or NaN + + # Compute raw_weights safely + raw_weights = scores / norm + + # Process the raw weights to final_weights via subtensor limitations. + ( + processed_weight_uids, + processed_weights, + ) = process_weights_for_netuid( + uids=self.metagraph.uids, + weights=raw_weights, + netuid=self.config.netuid, + subtensor=self.subtensor, + metagraph=self.metagraph, + ) + + # Convert to uint16 weights and uids. + ( + uint_uids, + uint_weights, + ) = convert_weights_and_uids_for_emit( + uids=processed_weight_uids, weights=processed_weights + ) + # Set the weights on chain via our subtensor connection. + with self.lock: + result, msg = self.subtensor.set_weights( + wallet=self.wallet, + netuid=self.config.netuid, + uids=uint_uids, + weights=uint_weights, + wait_for_finalization=False, + wait_for_inclusion=False, + version_key=self.spec_version, + ) + if result is True: + bt.logging.success("set_weights on chain successfully!") + else: + bt.logging.error("set_weights failed", msg) + + + def save_state(self): + """Saves the state of the validator to a file.""" + self.score_manager.save_scores() + + def load_state(self): + """Loads the state of the validator from a file.""" + bt.logging.info("Loading validator state.") + self.score_manager.load_scores() + + async def blacklist_text(self, synapse: WebgenieTextSynapse) -> Tuple[bool, str]: + """ + Only allow the backend owner to send synapse to the validator. + """ + if synapse.dendrite.hotkey == API_HOTKEY: + return False, "Backend hotkey" + return True, "Blacklisted" + + async def blacklist_image(self, synapse: WebgenieImageSynapse) -> Tuple[bool, str]: + """ + Only allow the backend owner to send synapse to the validator. + """ + if synapse.dendrite.hotkey == API_HOTKEY: + return False, "Backend hotkey" + return True, "Blacklisted" + + async def organic_forward_text(self, synapse: WebgenieTextSynapse): + return await self.genie_validator.organic_forward(synapse) + + async def organic_forward_image(self, synapse: WebgenieImageSynapse): + return await self.genie_validator.organic_forward(synapse) + + def serve_axon(self): + """Serve axon to enable external connections.""" + bt.logging.info("serving ip to chain...") + try: + self.axon = bt.axon(wallet=self.wallet, config=self.config) + + self.axon.attach( + forward_fn = self.organic_forward_text, + blacklist_fn = self.blacklist_text, + ).attach( + forward_fn = self.organic_forward_image, + blacklist_fn = self.blacklist_image, + ) + + self.axon.serve( + netuid=self.config.netuid, + subtensor=self.subtensor, + ) + self.axon.start() + bt.logging.info(f"Validator running in organic mode on port {self.config.neuron.axon_port}") + except Exception as e: + bt.logging.error(f"Failed to serve Axon with exception: {e}") + + def query_miners_loop(self): + bt.logging.info(f"Query miners loop starting") + while True: + time.sleep(1) + try: + with self.lock: + self.sync() + validator_index, validator_count = get_validator_index(self, self.uid) + + if validator_index == -1: + bt.logging.error(f"No enough stake for the validator.") + continue + + bt.logging.info(f"Validator index: {validator_index}, Validator count: {validator_count}") + # Calculate query period blocks + with self.lock: + current_block = self.block + + all_validator_query_period_blocks = validator_count * QUERING_WINDOW_BLOCKS + # Calculate query period blocks + start_period_block = ( + (current_block // all_validator_query_period_blocks) * + all_validator_query_period_blocks + + validator_index * QUERING_WINDOW_BLOCKS + ) + end_period_block = start_period_block + QUERING_WINDOW_BLOCKS / 2 + bt.logging.info(f"Query window - " + f"Start: {start_period_block}, " + f"End: {end_period_block}, " + f"Current: {current_block}") + # Sleep if outside query window + if current_block < start_period_block: + sleep_blocks = start_period_block - current_block + bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") + time.sleep(sleep_blocks * BLOCK_IN_SECONDS) + continue + elif current_block > end_period_block: + sleep_blocks = (start_period_block - current_block + all_validator_query_period_blocks) + bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") + time.sleep(sleep_blocks * BLOCK_IN_SECONDS) + continue + + QUERY_MINERS_TIMEOUT = 60 * 15 # 15 minutes + self.query_miners_event_loop.run_until_complete( + asyncio.wait_for( + self.genie_validator.query_miners(), + timeout=QUERY_MINERS_TIMEOUT + ) + ) + except Exception as e: + bt.logging.error(f"Error during query miners loop: {str(e)}") + if self.should_exit: + break + + def score_loop(self): + bt.logging.info(f"Scoring loop starting") + while True: + time.sleep(1) + try: + with self.lock: + self.sync() + + SCORE_TIMEOUT = 60 * 60 * 2 # 2 hours + self.score_event_loop.run_until_complete( + asyncio.wait_for( + self.genie_validator.score(), + timeout=SCORE_TIMEOUT + ) + ) + except Exception as e: + bt.logging.error(f"Error during scoring: {str(e)}") + if self.should_exit: + break + + def synthensize_task_loop(self): + bt.logging.info(f"Synthensize task loop starting") + while True: + time.sleep(1) + try: + with self.lock: + self.sync() + + SYNTHETIC_TASK_TIMEOUT = 60 * 15 # 15 minutes + self.synthensize_task_event_loop.run_until_complete( + asyncio.wait_for( + self.genie_validator.synthensize_task(), + timeout=SYNTHETIC_TASK_TIMEOUT + ) + ) + except Exception as e: + bt.logging.error(f"Error during synthensize task: {str(e)}") + if self.should_exit: + break + + def set_weights_loop(self): + """ + Every three tempos, set the weights. + """ + bt.logging.info(f"Set weights loop starting") + + while True: + time.sleep(1) + try: + with self.lock: + self.sync() + current_block = self.block + + # Calculate the end block number for the next weight setting period + # This aligns with 3 tempo boundaries + set_weights_end_block = ( + (current_block + SESSION_WINDOW_BLOCKS - 1) + // SESSION_WINDOW_BLOCKS + * SESSION_WINDOW_BLOCKS + ) + + # Start setting weights 50 blocks before the end + set_weights_start_block = set_weights_end_block - WEIGHT_SETTING_WINDOW_BLOCKS + bt.logging.info( + f"Set weights window - " + f"Start: {set_weights_start_block}, " + f"End: {set_weights_end_block}, " + f"Current: {current_block}" + ) + # Check if we're in the weight setting window + if (current_block >= set_weights_start_block and + current_block <= set_weights_end_block): + + bt.logging.info(f"Trying to set weights at block {current_block}") + self.set_weights() + else: + # Sleep until next weight setting window + sleep_blocks = set_weights_start_block - current_block + bt.logging.info(f"Sleeping for {sleep_blocks} blocks before setting weights") + time.sleep(sleep_blocks * BLOCK_IN_SECONDS) + except Exception as e: + bt.logging.error(f"Error during set weights: {str(e)}") + if self.should_exit: + break + + def run_background_threads(self): + if not self.is_running: + bt.logging.info("Starting validator in background thread") + self.is_running = True + self.should_exit = False + + self.synthensize_task_thread = threading.Thread(target=self.synthensize_task_loop, daemon=True) + self.query_miners_thread = threading.Thread(target=self.query_miners_loop, daemon=True) + self.score_thread = threading.Thread(target=self.score_loop, daemon=True) + self.set_weights_thread = threading.Thread(target=self.set_weights_loop, daemon=True) + + self.synthensize_task_thread.start() + self.query_miners_thread.start() + self.score_thread.start() + self.set_weights_thread.start() + start_lighthouse_server_thread() + bt.logging.info("Started background threads") + bt.logging.info("=" * 40) + + def stop_background_threads(self): + if self.is_running: + bt.logging.info("Stopping background threads") + self.should_exit = True + self.is_running = False + + self.synthensize_task_thread.join(5) + self.query_miners_thread.join(5) + self.score_thread.join(5) + self.set_weights_thread.join(5) + stop_lighthouse_server() + + self.synthensize_task_thread = None + self.query_miners_thread = None + self.score_thread = None + self.set_weights_thread = None + bt.logging.info("Stopped background threads") + + def __enter__(self): + self.run_background_threads() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.stop_background_threads() + + +# The main function parses the configuration and runs the validator. +if __name__ == "__main__": + with Validator() as validator: + while True: + try: + time.sleep(5) + except KeyboardInterrupt: + bt.logging.info("Keyboard interrupt detected, stopping main loop") + break + diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py index a94a19c9..60b90236 100644 --- a/webgenie/challenges/challenge.py +++ b/webgenie/challenges/challenge.py @@ -55,3 +55,9 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: quality_scores = scores[QUALITY_METRIC_NAME] aggregated_scores = np.where(accuracy_scores > 0.7, quality_scores, 0) return aggregated_scores, scores + +RESERVED_WEIGHTS = { + ACCURACY_COMPETITION_TYPE: 50, + SEO_COMPETITION_TYPE: 30, + QUALITY_COMPETITION_TYPE: 20, +} \ No newline at end of file diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 3d819b77..7d06786f 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -18,22 +18,32 @@ def get_lighthouse_score(htmls: List[str]) -> List[Dict[str, float]]: def get_lighthouse_score_from_subprocess(url): bt.logging.info(f"Getting lighthouse score from {url}...") try: - result = subprocess.run( - ['lighthouse', url, '--output=json', '--quiet', '--chrome-flags="--headless --no-sandbox"'], - capture_output=True, text=True, timeout=180 - ) - if result.returncode == 0: - lighthouse_report = json.loads(result.stdout) - scores = { - 'performance': lighthouse_report['categories']['performance']['score'], - 'accessibility': lighthouse_report['categories']['accessibility']['score'], - 'best-practices': lighthouse_report['categories']['best-practices']['score'], - 'seo': lighthouse_report['categories']['seo']['score'] - } - return scores - else: - bt.logging.error(f"Stderr from lighthouse: {result.stderr}, returncode: {result.returncode}") - raise Exception(f"Stderr from lighthouse: {result.stderr}, returncode: {result.returncode}") + command = f"lighthouse {url} --output=json --chrome-flags='--headless --no-sandbox' --quiet" + output = os.popen(command).read() + loaded_json = json.loads(output) + scores = { + 'performance': loaded_json['categories']['performance']['score'], + 'accessibility': loaded_json['categories']['accessibility']['score'], + 'best-practices': loaded_json['categories']['best-practices']['score'], + 'seo': loaded_json['categories']['seo']['score'] + } + return scores + # result = subprocess.run( + # ['lighthouse', url, '--output=json', '--quiet', '--chrome-flags="--headless --no-sandbox"'], + # capture_output=True, text=True, timeout=180 + # ) + # if result.returncode == 0: + # lighthouse_report = json.loads(result.stdout) + # scores = { + # 'performance': lighthouse_report['categories']['performance']['score'], + # 'accessibility': lighthouse_report['categories']['accessibility']['score'], + # 'best-practices': lighthouse_report['categories']['best-practices']['score'], + # 'seo': lighthouse_report['categories']['seo']['score'] + # } + # return scores + # else: + # bt.logging.error(f"Stderr from lighthouse: {result.stderr}, returncode: {result.returncode}") + # raise Exception(f"Stderr from lighthouse: {result.stderr}, returncode: {result.returncode}") except Exception as e: bt.logging.error(f"Error running Lighthouse: {e}") return { diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index 6fc57312..0c8d3ad3 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -52,11 +52,25 @@ async def reward_worker(self, task: Task, solutions: List[Solution], current_wor scores = high_level_scores * 0.3 + low_level_scores * 0.7 await stop_browser() - - for html_path in miner_html_paths: - os.remove(html_path) - os.remove(original_html_path) + # Clean up files + for miner_html_path in miner_html_paths: + inpainted_png_path = miner_html_path.replace(".html", "_inpainted.png") + erased_html_path = miner_html_path.replace(".html", "_erased.html") + png_path = miner_html_path.replace(".html", ".png") + os.remove(miner_html_path) + os.remove(inpainted_png_path) + os.remove(erased_html_path) + os.remove(png_path) + + inpainted_png_path = original_html_path.replace(".html", "_inpainted.png") + erased_html_path = original_html_path.replace(".html", "_erased.html") + png_path = original_html_path.replace(".html", ".png") + os.remove(original_html_path) + os.remove(inpainted_png_path) + os.remove(erased_html_path) + os.remove(png_path) + return scores def sync_reward_worker(self, task: Task, solutions: List[Solution], current_work_dir: str) -> np.ndarray: diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index e5c232e3..b9b747ba 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -110,15 +110,21 @@ def store_results_to_database(results: dict): solutions = results["solutions"] scores = results["scores"] challenge = results["challenge"] - + bt.logging.info(f"Storing challenge to database: {challenge}") session_number = challenge["session_number"] + bt.logging.info(f"Storing session_number to database: {session_number}") session_start_datetime = results["session_start_datetime"] + bt.logging.info(f"Storing session_start_datetime to database: {session_start_datetime}") ground_truth_html = challenge["task"] + bt.logging.info(f"Storing ground_truth_html to database: {ground_truth_html}") competition_type = challenge["competition_type"] - + bt.logging.info(f"Storing competition_type to database: {competition_type}") competition_id = create_competition(competition_type) + bt.logging.info(f"Storing competition_id to database: {competition_id}") session_id = create_leaderboard_session(session_number, session_start_datetime, competition_id) + bt.logging.info(f"Storing session_id to database: {session_id}") challenge_id = create_challenge(session_id, ground_truth_html) + bt.logging.info(f"Storing challenge_id to database: {challenge_id}") # Iterate over miner_uids to store TaskSolution data for miner, solution, score in zip(miners, solutions, scores): diff --git a/webgenie/utils/uids.py b/webgenie/utils/uids.py index 3d8d8e73..a49037c3 100644 --- a/webgenie/utils/uids.py +++ b/webgenie/utils/uids.py @@ -35,12 +35,11 @@ def check_uid_availability( bool: True if uid is available, False otherwise """ # Filter non serving axons. - if not metagraph.axons[uid].is_serving: - return False + # if not metagraph.axons[uid].is_serving: + # return False # Filter validator permit > 1024 stake. - if metagraph.validator_permit[uid]: - if metagraph.S[uid] > VPERMIT_TAO_LIMIT: - return False + if metagraph.S[uid] > VPERMIT_TAO_LIMIT: + return False # Available otherwise. return True From 8def70ac65daeb32abb505b2afb48b1817484b71 Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 16:30:45 +0000 Subject: [PATCH 311/554] chore: disable sql logging --- webgenie/rewards/lighthouse_reward/get_lighthouse_score.py | 2 +- webgenie/storage/database.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 7d06786f..39fd20e9 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -18,7 +18,7 @@ def get_lighthouse_score(htmls: List[str]) -> List[Dict[str, float]]: def get_lighthouse_score_from_subprocess(url): bt.logging.info(f"Getting lighthouse score from {url}...") try: - command = f"lighthouse {url} --output=json --chrome-flags='--headless --no-sandbox' --quiet" + command = f"lighthouse {url} --output=json --chrome-flags='--headless --no-sandbox --disable-gpu' --quiet --verbose" output = os.popen(command).read() loaded_json = json.loads(output) scores = { diff --git a/webgenie/storage/database.py b/webgenie/storage/database.py index 69e60429..28d8a7c1 100644 --- a/webgenie/storage/database.py +++ b/webgenie/storage/database.py @@ -2,7 +2,7 @@ from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import DeclarativeBase # Create the database engine -engine = create_engine('sqlite:///webgenie-validator.db', echo=True) +engine = create_engine('sqlite:///webgenie-validator.db', echo=False) # Create the session maker Session = sessionmaker(engine) From e84d2774c5944a67eeaad8ceb3c643f154a829c4 Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 16:41:52 +0000 Subject: [PATCH 312/554] chore: remove files --- genie_validator.py | 313 ------------------------- neurons/validators/validator.py | 21 +- state.json | 1 - test.npz | Bin 699 -> 0 bytes uids.py | 118 ---------- validator.py | 399 -------------------------------- 6 files changed, 11 insertions(+), 841 deletions(-) delete mode 100644 genie_validator.py delete mode 100644 state.json delete mode 100644 test.npz delete mode 100644 uids.py delete mode 100644 validator.py diff --git a/genie_validator.py b/genie_validator.py deleted file mode 100644 index 8a8b1671..00000000 --- a/genie_validator.py +++ /dev/null @@ -1,313 +0,0 @@ -import os -import bittensor as bt -import numpy as np -import random -import threading -import time - -from datetime import datetime, timedelta -from typing import Union - -from webgenie.base.neuron import BaseNeuron -from webgenie.constants import ( - MAX_COMPETETION_HISTORY_SIZE, - MAX_SYNTHETIC_TASK_SIZE, - WORK_DIR, - LIGHTHOUSE_SERVER_WORK_DIR, - TASK_REVEAL_TIME, - TASK_REVEAL_TIMEOUT, - SESSION_WINDOW_BLOCKS, - BLOCK_IN_SECONDS, -) -from webgenie.challenges import ( - AccuracyChallenge, - QualityChallenge, - SeoChallenge, -) -from webgenie.helpers.htmls import preprocess_html, is_valid_resources -from webgenie.helpers.images import image_debug_str -from webgenie.protocol import ( - WebgenieImageSynapse, - WebgenieTextSynapse, - verify_answer_hash, -) -from webgenie.storage import store_results_to_database -from webgenie.tasks import Solution -from webgenie.tasks.metric_types import ( - ACCURACY_METRIC_NAME, - QUALITY_METRIC_NAME, - SEO_METRIC_NAME, -) -from webgenie.tasks.image_task_generator import ImageTaskGenerator -from webgenie.utils.uids import get_all_available_uids - - -class GenieValidator: - def __init__(self, neuron: BaseNeuron): - self.neuron = neuron - self.lock = neuron.lock - self.config = neuron.config - self.miner_results = [] - self.synthetic_tasks = [] - - self.task_generators = [ - (ImageTaskGenerator(), 1.0), # currently only image task generator is supported - ] - - async def query_miners(self): - try: - with self.lock: - if len(self.miner_results) > MAX_COMPETETION_HISTORY_SIZE: - bt.logging.info( - f"Competition history size {len(self.miner_results)} " - f"exceeds max size {MAX_COMPETETION_HISTORY_SIZE}, skipping" - ) - return - - if not self.synthetic_tasks: - bt.logging.info("No synthetic tasks available, skipping") - return - - task, synapse = self.synthetic_tasks.pop(0) - - bt.logging.info("querying miners") - miner_uids = get_all_available_uids(self.neuron) - if len(miner_uids) == 0: - bt.logging.warning("No miners available") - return - - available_challenges_classes = [ - AccuracyChallenge, - QualityChallenge, - SeoChallenge, - ] - - with self.lock: - session = self.neuron.session - - challenge_class = available_challenges_classes[session % len(available_challenges_classes)] - challenge = challenge_class(task=task, session=session) - - synapse.competition_type = challenge.competition_type - - bt.logging.debug(f"Querying {len(miner_uids)} miners") - - query_time = time.time() - async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: - all_synapse_hash_results = await dendrite( - axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], - synapse=synapse, - timeout=task.timeout, - ) - - elapsed_time = time.time() - query_time - sleep_time_before_reveal = max(0, task.timeout - elapsed_time) + TASK_REVEAL_TIME - time.sleep(sleep_time_before_reveal) - - bt.logging.debug(f"Revealing task {task.task_id}") - - async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: - all_synapse_reveal_results = await dendrite( - axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], - synapse=synapse, - timeout=TASK_REVEAL_TIMEOUT, - ) - - solutions = [] - for reveal_synapse, hash_synapse, miner_uid in zip(all_synapse_reveal_results, all_synapse_hash_results, miner_uids): - reveal_synapse.html_hash = hash_synapse.html_hash - checked_synapse = await self.checked_synapse(reveal_synapse) - if checked_synapse is not None: - solutions.append( - Solution( - html = checked_synapse.html, - miner_uid = miner_uid, - ) - ) - challenge.solutions = solutions - - bt.logging.info(f"Received {len(solutions)} valid solutions") - with self.lock: - self.miner_results.append(challenge) - - except Exception as e: - bt.logging.error(f"Error in query_miners: {e}") - raise e - - async def score(self): - with self.lock: - if not self.miner_results: - # No miner results to score - return - - challenge = self.miner_results.pop(0) - - if not challenge.solutions: - # No solutions to score - return - - with self.lock: - if challenge.session != self.neuron.session: - bt.logging.info( - f"Session number mismatch: {challenge.session} != {self.neuron.session}" - f"This is the previous session's challenge, skipping" - ) - return - - bt.logging.info( - f"Scoring - Session number: {challenge.session}, " - f"Competition type: {challenge.competition_type}, " - f"Task source: {challenge.task.src}" - ) - - solutions = challenge.solutions - miner_uids = [solution.miner_uid for solution in solutions] - aggregated_scores, scores = await challenge.calculate_scores() - - bt.logging.success(f"Task Source: {challenge.task.src}") - bt.logging.success(f"Competition Type: {challenge.competition_type}") - bt.logging.success(f"Scores: {scores}") - bt.logging.success(f"Final scores for {miner_uids}: {aggregated_scores}") - - with self.lock: - self.neuron.score_manager.update_scores( - aggregated_scores, - miner_uids, - challenge.session, - ) - - with self.lock: - current_block = self.neuron.block - session = self.neuron.session - session_start_block = session * SESSION_WINDOW_BLOCKS - session_start_datetime = ( - datetime.now() - - timedelta( - seconds=(current_block - session_start_block) * BLOCK_IN_SECONDS - ) - ) - payload = { - "validator": { - "hotkey": self.neuron.metagraph.axons[self.neuron.uid].hotkey, - "coldkey": self.neuron.metagraph.axons[self.neuron.uid].coldkey, - }, - "miners": [ - { - "coldkey": self.neuron.metagraph.axons[miner_uids[i]].coldkey, - "hotkey": self.neuron.metagraph.axons[miner_uids[i]].hotkey, - } for i in range(len(miner_uids)) - ], - "solutions": [ - { - "miner_answer": { "html": solution.html }, - } for solution in solutions - ], - "scores": [ - { - "aggregated_score": aggregated_scores[i], - "accuracy": scores[ACCURACY_METRIC_NAME][i], - "seo": scores[SEO_METRIC_NAME][i], - "code_quality": scores[QUALITY_METRIC_NAME][i], - } for i in range(len(miner_uids)) - ], - "challenge": { - "task": challenge.task.ground_truth_html, - "competition_type": challenge.competition_type, - "session_number": challenge.session, - }, - "session_start_datetime": session_start_datetime, - } - - try: - store_results_to_database(payload) - except Exception as e: - bt.logging.error(f"Error storing results to database: {e}") - - async def synthensize_task(self): - try: - with self.lock: - if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: - # synthetic_tasks is full, skipping - return - - bt.logging.info(f"Synthensize task") - - task_generator, _ = random.choices( - self.task_generators, - weights=[weight for _, weight in self.task_generators], - )[0] - - task, synapse = await task_generator.generate_task() - with self.lock: - self.synthetic_tasks.append((task, synapse)) - - bt.logging.success(f"Successfully generated task for {task.src}") - - except Exception as e: - bt.logging.error(f"Error in synthensize_task: {e}") - - async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImageSynapse]): - if isinstance(synapse, WebgenieTextSynapse): - bt.logging.debug(f"Organic text forward: {synapse.prompt}") - bt.logging.info("Not supported yet.") - synapse.html = "Not supported yet." - return synapse - else: - bt.logging.debug(f"Organic image forward: {image_debug_str(synapse.base64_image)}...") - - all_miner_uids = get_all_available_uids(self.neuron) - try: - if not all_miner_uids: - raise Exception("No miners available") - - query_time = time.time() - async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: - responses = await dendrite( - axons=[self.neuron.metagraph.axons[uid] for uid in all_miner_uids], - synapse=synapse, - timeout=synapse.timeout, - ) - - elapsed_time = time.time() - query_time - sleep_time_before_reveal = max(0, synapse.timeout - elapsed_time) + TASK_REVEAL_TIME - time.sleep(sleep_time_before_reveal) - - async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: - responses = await dendrite( - axons=[self.neuron.metagraph.axons[uid] for uid in all_miner_uids], - synapse=synapse, - timeout=TASK_REVEAL_TIMEOUT, - ) - - # Sort miner UIDs and responses by incentive scores - incentives = self.neuron.metagraph.I[all_miner_uids] - sorted_indices = np.argsort(-incentives) # Negative for descending order - all_miner_uids = [all_miner_uids[i] for i in sorted_indices] - - responses = [responses[i] for i in sorted_indices] - for response in responses: - checked_synapse = await self.checked_synapse(response) - if checked_synapse is None: - continue - return checked_synapse - - raise Exception(f"No valid solution received") - except Exception as e: - bt.logging.error(f"[forward_organic_synapse] Error querying dendrite: {e}") - synapse.html = f"Error: {e}" - return synapse - - async def checked_synapse(self, synapse: bt.Synapse) -> bt.Synapse: - if synapse.dendrite.status_code == 200: - if not verify_answer_hash(synapse): - bt.logging.warning(f"Invalid answer hash: {synapse.html_hash}") - return None - - html = preprocess_html(synapse.html) - if not html or not is_valid_resources(html): - bt.logging.warning(f"Invalid html or resources: {html}") - return None - - synapse.html = html - return synapse - return None diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 9cf55c39..7340312d 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -132,16 +132,17 @@ def set_weights(self): ) = convert_weights_and_uids_for_emit( uids=processed_weight_uids, weights=processed_weights ) - # Set the weights on chain via our subtensor connection. - result, msg = self.subtensor.set_weights( - wallet=self.wallet, - netuid=self.config.netuid, - uids=uint_uids, - weights=uint_weights, - wait_for_finalization=False, - wait_for_inclusion=False, - version_key=self.spec_version, - ) + with self.lock: + # Set the weights on chain via our subtensor connection. + result, msg = self.subtensor.set_weights( + wallet=self.wallet, + netuid=self.config.netuid, + uids=uint_uids, + weights=uint_weights, + wait_for_finalization=False, + wait_for_inclusion=False, + version_key=self.spec_version, + ) if result is True: bt.logging.success("set_weights on chain successfully!") else: diff --git a/state.json b/state.json deleted file mode 100644 index e6b7a61b..00000000 --- a/state.json +++ /dev/null @@ -1 +0,0 @@ -{"hotkeys": ["hotkey1", "hotkey2"], "current_session": 1, "total_scores": [0, 0], "last_send_stats_collector_session": 2, "winners": {"1": [1, "competition_type"]}} \ No newline at end of file diff --git a/test.npz b/test.npz deleted file mode 100644 index 3168518f1f7d8ee9d38d6d4385f0e9fcd4c447d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 699 zcmcgq!AiqG5Zz5$3&le~@f=TSp$+Ik#ES>PgNtbpJa~|hCMyl5*|6DEf-O|RY9Y6- zpWw;Q@eBM5Z@SwghZb*wZGmNEvL+c diff --git a/uids.py b/uids.py deleted file mode 100644 index bb023421..00000000 --- a/uids.py +++ /dev/null @@ -1,118 +0,0 @@ -import bittensor as bt -import random -import re -import numpy as np -from typing import List, Tuple -from webgenie.constants import ( - VPERMIT_TAO_LIMIT, -) - - -def is_validator(metagraph: "bt.metagraph.Metagraph", uid: int) -> bool: - return metagraph.S[uid] > VPERMIT_TAO_LIMIT - - -def get_validator_index(self, uid: int) -> Tuple[int, int]: - validator_uids = [] - for each_uid in range(self.metagraph.n.item()): - if is_validator(self.metagraph, each_uid): - validator_uids.append(each_uid) - validator_uids.sort(key=lambda uid: self.metagraph.S[uid], reverse=True) - try: - return validator_uids.index(uid), len(validator_uids) - except ValueError: - return -1, len(validator_uids) - - -def check_uid_availability( - metagraph: "bt.metagraph.Metagraph", uid: int -) -> bool: - """Check if uid is available. The UID should be available if it is serving and has less than vpermit_tao_limit stake - Args: - metagraph (:obj: bt.metagraph.Metagraph): Metagraph object - uid (int): uid to be checked - Returns: - bool: True if uid is available, False otherwise - """ - # Filter non serving axons. - # if not metagraph.axons[uid].is_serving: - # return False - # Filter validator permit > 1024 stake. - if metagraph.S[uid] > VPERMIT_TAO_LIMIT: - return False - # Available otherwise. - return True - - -def get_most_available_uid(self, exclude: List[int] = None) -> int: - """Returns the most available uid from the metagraph. - Returns: - uid (int): Most available uid. - """ - candidate_uids = [] - avail_uids = [] - - for uid in range(self.metagraph.n.item()): - uid_is_available = check_uid_availability( - self.metagraph, uid - ) - uid_is_not_excluded = exclude is None or uid not in exclude - - if uid_is_available: - avail_uids.append(uid) - if uid_is_not_excluded: - candidate_uids.append(uid) - - return candidate_uids[np.argmax(self.metagraph.I[candidate_uids])] - - -def get_all_available_uids( - self, exclude: List[int] = None -) -> np.ndarray: - avail_uids = [] - bt.logging.debug(f"Number of neurons: {self.metagraph.n.item()}") - for uid in range(self.metagraph.n.item()): - uid_is_available = check_uid_availability(self.metagraph, uid) - uid_is_not_excluded = exclude is None or uid not in exclude - if uid_is_available and uid_is_not_excluded: - avail_uids.append(uid) - bt.logging.debug(f"Number of available neurons: {len(avail_uids)}") - return np.array(avail_uids) - - -def get_random_uids( - self, k: int, exclude: List[int] = None -) -> np.ndarray: - """Returns k available random uids from the metagraph. - Args: - k (int): Number of uids to return. - exclude (List[int]): List of uids to exclude from the random sampling. - Returns: - uids (np.ndarray): Randomly sampled available uids. - Notes: - If `k` is larger than the number of available `uids`, set `k` to the number of available `uids`. - """ - candidate_uids = [] - avail_uids = [] - - for uid in range(self.metagraph.n.item()): - uid_is_available = check_uid_availability( - self.metagraph, uid - ) - uid_is_not_excluded = exclude is None or uid not in exclude - - if uid_is_available: - avail_uids.append(uid) - if uid_is_not_excluded: - candidate_uids.append(uid) - # If k is larger than the number of available uids, set k to the number of available uids. - k = min(k, len(avail_uids)) - # Check if candidate_uids contain enough for querying, if not grab all avaliable uids - available_uids = candidate_uids - if len(candidate_uids) < k: - available_uids += random.sample( - [uid for uid in avail_uids if uid not in candidate_uids], - k - len(candidate_uids), - ) - uids = np.array(random.sample(available_uids, k)) - return uids diff --git a/validator.py b/validator.py deleted file mode 100644 index 6a294e07..00000000 --- a/validator.py +++ /dev/null @@ -1,399 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 Yuma Rao -# Copyright © 2024 pycorn, Sangar - -import bittensor as bt -import asyncio -import copy -import numpy as np -import threading -import time - -from dotenv import load_dotenv, find_dotenv -load_dotenv(find_dotenv(filename=".env.validator")) - -from typing import Tuple, Union - -from webgenie.base.validator import BaseValidatorNeuron -from webgenie.base.utils.weight_utils import ( - process_weights_for_netuid, - convert_weights_and_uids_for_emit, -) -from webgenie.constants import ( - API_HOTKEY, - BLOCK_IN_SECONDS, - SESSION_WINDOW_BLOCKS, - QUERING_WINDOW_BLOCKS, - WEIGHT_SETTING_WINDOW_BLOCKS, - AXON_OFF, -) -from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse -from webgenie.rewards.lighthouse_reward import start_lighthouse_server_thread, stop_lighthouse_server -from webgenie.utils.uids import get_validator_index - -from neurons.validators.genie_validator import GenieValidator -from neurons.validators.score_manager import ScoreManager - - -class Validator(BaseValidatorNeuron): - """ - Your validator neuron class. You should use this class to define your validator's behavior. In particular, you should replace the forward function with your own logic. - - This class inherits from the BaseValidatorNeuron class, which in turn inherits from BaseNeuron. The BaseNeuron class takes care of routine tasks such as setting up wallet, subtensor, metagraph, logging directory, parsing config, etc. You can override any of the methods in BaseNeuron if you need to customize the behavior. - - This class provides reasonable default behavior for a validator such as keeping a moving average of the scores of the miners and using them to set weights at the end of each epoch. Additionally, the scores are reset for new hotkeys at the end of each epoch. - """ - - @property - def session(self): - return self.block // SESSION_WINDOW_BLOCKS - - def __init__(self, config=None): - super(Validator, self).__init__(config=config) - - # Create asyncio event loop to manage async tasks. - self.synthensize_task_event_loop = asyncio.new_event_loop() - self.query_miners_event_loop = asyncio.new_event_loop() - self.score_event_loop = asyncio.new_event_loop() - self.set_weights_event_loop = asyncio.new_event_loop() - - # Instantiate runners - self.should_exit: bool = False - self.is_running: bool = False - self.synthensize_task_thread: Union[threading.Thread, None] = None - self.query_miners_thread: Union[threading.Thread, None] = None - self.score_thread: Union[threading.Thread, None] = None - self.set_weights_thread: Union[threading.Thread, None] = None - self.lock = threading.Lock() - - self.genie_validator = GenieValidator(neuron=self) - self.score_manager = ScoreManager(neuron=self) - - bt.logging.info("load_state()") - self.load_state() - - self.sync() - - if not AXON_OFF: - self.serve_axon() - - def resync_metagraph(self): - """Resyncs the metagraph and updates the hotkeys and moving averages based on the new metagraph.""" - # Copies state of metagraph before syncing. - previous_metagraph = copy.deepcopy(self.metagraph) - - # Sync the metagraph. - self.metagraph.sync(subtensor=self.subtensor) - - # Check if the metagraph axon info has changed. - if previous_metagraph.axons == self.metagraph.axons: - return - - bt.logging.info( - "Metagraph updated, re-syncing hotkeys, dendrite pool and moving averages" - ) - self.score_manager.set_new_hotkeys(self.metagraph.hotkeys) - - def set_weights(self): - if not self.should_set_weights(): - return - - self.score_manager.send_challenge_to_stats_collector() - - scores = self.score_manager.get_scores() - # Calculate the average reward for each uid across non-zero values. - # Replace any NaN values with 0. - # Compute the norm of the scores - norm = np.linalg.norm(scores, ord=1, axis=0, keepdims=True) - - # Check if the norm is zero or contains NaN values - if np.any(norm == 0) or np.isnan(norm).any(): - norm = np.ones_like(norm) # Avoid division by zero or NaN - - # Compute raw_weights safely - raw_weights = scores / norm - - # Process the raw weights to final_weights via subtensor limitations. - ( - processed_weight_uids, - processed_weights, - ) = process_weights_for_netuid( - uids=self.metagraph.uids, - weights=raw_weights, - netuid=self.config.netuid, - subtensor=self.subtensor, - metagraph=self.metagraph, - ) - - # Convert to uint16 weights and uids. - ( - uint_uids, - uint_weights, - ) = convert_weights_and_uids_for_emit( - uids=processed_weight_uids, weights=processed_weights - ) - # Set the weights on chain via our subtensor connection. - with self.lock: - result, msg = self.subtensor.set_weights( - wallet=self.wallet, - netuid=self.config.netuid, - uids=uint_uids, - weights=uint_weights, - wait_for_finalization=False, - wait_for_inclusion=False, - version_key=self.spec_version, - ) - if result is True: - bt.logging.success("set_weights on chain successfully!") - else: - bt.logging.error("set_weights failed", msg) - - - def save_state(self): - """Saves the state of the validator to a file.""" - self.score_manager.save_scores() - - def load_state(self): - """Loads the state of the validator from a file.""" - bt.logging.info("Loading validator state.") - self.score_manager.load_scores() - - async def blacklist_text(self, synapse: WebgenieTextSynapse) -> Tuple[bool, str]: - """ - Only allow the backend owner to send synapse to the validator. - """ - if synapse.dendrite.hotkey == API_HOTKEY: - return False, "Backend hotkey" - return True, "Blacklisted" - - async def blacklist_image(self, synapse: WebgenieImageSynapse) -> Tuple[bool, str]: - """ - Only allow the backend owner to send synapse to the validator. - """ - if synapse.dendrite.hotkey == API_HOTKEY: - return False, "Backend hotkey" - return True, "Blacklisted" - - async def organic_forward_text(self, synapse: WebgenieTextSynapse): - return await self.genie_validator.organic_forward(synapse) - - async def organic_forward_image(self, synapse: WebgenieImageSynapse): - return await self.genie_validator.organic_forward(synapse) - - def serve_axon(self): - """Serve axon to enable external connections.""" - bt.logging.info("serving ip to chain...") - try: - self.axon = bt.axon(wallet=self.wallet, config=self.config) - - self.axon.attach( - forward_fn = self.organic_forward_text, - blacklist_fn = self.blacklist_text, - ).attach( - forward_fn = self.organic_forward_image, - blacklist_fn = self.blacklist_image, - ) - - self.axon.serve( - netuid=self.config.netuid, - subtensor=self.subtensor, - ) - self.axon.start() - bt.logging.info(f"Validator running in organic mode on port {self.config.neuron.axon_port}") - except Exception as e: - bt.logging.error(f"Failed to serve Axon with exception: {e}") - - def query_miners_loop(self): - bt.logging.info(f"Query miners loop starting") - while True: - time.sleep(1) - try: - with self.lock: - self.sync() - validator_index, validator_count = get_validator_index(self, self.uid) - - if validator_index == -1: - bt.logging.error(f"No enough stake for the validator.") - continue - - bt.logging.info(f"Validator index: {validator_index}, Validator count: {validator_count}") - # Calculate query period blocks - with self.lock: - current_block = self.block - - all_validator_query_period_blocks = validator_count * QUERING_WINDOW_BLOCKS - # Calculate query period blocks - start_period_block = ( - (current_block // all_validator_query_period_blocks) * - all_validator_query_period_blocks + - validator_index * QUERING_WINDOW_BLOCKS - ) - end_period_block = start_period_block + QUERING_WINDOW_BLOCKS / 2 - bt.logging.info(f"Query window - " - f"Start: {start_period_block}, " - f"End: {end_period_block}, " - f"Current: {current_block}") - # Sleep if outside query window - if current_block < start_period_block: - sleep_blocks = start_period_block - current_block - bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") - time.sleep(sleep_blocks * BLOCK_IN_SECONDS) - continue - elif current_block > end_period_block: - sleep_blocks = (start_period_block - current_block + all_validator_query_period_blocks) - bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") - time.sleep(sleep_blocks * BLOCK_IN_SECONDS) - continue - - QUERY_MINERS_TIMEOUT = 60 * 15 # 15 minutes - self.query_miners_event_loop.run_until_complete( - asyncio.wait_for( - self.genie_validator.query_miners(), - timeout=QUERY_MINERS_TIMEOUT - ) - ) - except Exception as e: - bt.logging.error(f"Error during query miners loop: {str(e)}") - if self.should_exit: - break - - def score_loop(self): - bt.logging.info(f"Scoring loop starting") - while True: - time.sleep(1) - try: - with self.lock: - self.sync() - - SCORE_TIMEOUT = 60 * 60 * 2 # 2 hours - self.score_event_loop.run_until_complete( - asyncio.wait_for( - self.genie_validator.score(), - timeout=SCORE_TIMEOUT - ) - ) - except Exception as e: - bt.logging.error(f"Error during scoring: {str(e)}") - if self.should_exit: - break - - def synthensize_task_loop(self): - bt.logging.info(f"Synthensize task loop starting") - while True: - time.sleep(1) - try: - with self.lock: - self.sync() - - SYNTHETIC_TASK_TIMEOUT = 60 * 15 # 15 minutes - self.synthensize_task_event_loop.run_until_complete( - asyncio.wait_for( - self.genie_validator.synthensize_task(), - timeout=SYNTHETIC_TASK_TIMEOUT - ) - ) - except Exception as e: - bt.logging.error(f"Error during synthensize task: {str(e)}") - if self.should_exit: - break - - def set_weights_loop(self): - """ - Every three tempos, set the weights. - """ - bt.logging.info(f"Set weights loop starting") - - while True: - time.sleep(1) - try: - with self.lock: - self.sync() - current_block = self.block - - # Calculate the end block number for the next weight setting period - # This aligns with 3 tempo boundaries - set_weights_end_block = ( - (current_block + SESSION_WINDOW_BLOCKS - 1) - // SESSION_WINDOW_BLOCKS - * SESSION_WINDOW_BLOCKS - ) - - # Start setting weights 50 blocks before the end - set_weights_start_block = set_weights_end_block - WEIGHT_SETTING_WINDOW_BLOCKS - bt.logging.info( - f"Set weights window - " - f"Start: {set_weights_start_block}, " - f"End: {set_weights_end_block}, " - f"Current: {current_block}" - ) - # Check if we're in the weight setting window - if (current_block >= set_weights_start_block and - current_block <= set_weights_end_block): - - bt.logging.info(f"Trying to set weights at block {current_block}") - self.set_weights() - else: - # Sleep until next weight setting window - sleep_blocks = set_weights_start_block - current_block - bt.logging.info(f"Sleeping for {sleep_blocks} blocks before setting weights") - time.sleep(sleep_blocks * BLOCK_IN_SECONDS) - except Exception as e: - bt.logging.error(f"Error during set weights: {str(e)}") - if self.should_exit: - break - - def run_background_threads(self): - if not self.is_running: - bt.logging.info("Starting validator in background thread") - self.is_running = True - self.should_exit = False - - self.synthensize_task_thread = threading.Thread(target=self.synthensize_task_loop, daemon=True) - self.query_miners_thread = threading.Thread(target=self.query_miners_loop, daemon=True) - self.score_thread = threading.Thread(target=self.score_loop, daemon=True) - self.set_weights_thread = threading.Thread(target=self.set_weights_loop, daemon=True) - - self.synthensize_task_thread.start() - self.query_miners_thread.start() - self.score_thread.start() - self.set_weights_thread.start() - start_lighthouse_server_thread() - bt.logging.info("Started background threads") - bt.logging.info("=" * 40) - - def stop_background_threads(self): - if self.is_running: - bt.logging.info("Stopping background threads") - self.should_exit = True - self.is_running = False - - self.synthensize_task_thread.join(5) - self.query_miners_thread.join(5) - self.score_thread.join(5) - self.set_weights_thread.join(5) - stop_lighthouse_server() - - self.synthensize_task_thread = None - self.query_miners_thread = None - self.score_thread = None - self.set_weights_thread = None - bt.logging.info("Stopped background threads") - - def __enter__(self): - self.run_background_threads() - return self - - def __exit__(self, exc_type, exc_value, traceback): - self.stop_background_threads() - - -# The main function parses the configuration and runs the validator. -if __name__ == "__main__": - with Validator() as validator: - while True: - try: - time.sleep(5) - except KeyboardInterrupt: - bt.logging.info("Keyboard interrupt detected, stopping main loop") - break - From cfaed718af46db462844b1251cc46894acd2c25c Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 16:44:48 +0000 Subject: [PATCH 313/554] chore: change log level --- .../rewards/visual_reward/common/extract_html_elements.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index ef74928a..536585cc 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -152,7 +152,7 @@ async def traverse(node): try: await add_element(current_node, bool(children)) except Exception as e: - bt.logging.error(f"Error adding element: {e}") + bt.logging.warning(f"Error adding element: {e}") # Dispose the node when done await current_node.dispose() @@ -177,7 +177,7 @@ def preprocess_html_elements(html_path, html_elements): try: element.avg_color = np.mean(color_image[y:y+h, x:x+w], axis=(0, 1)) except Exception as e: - bt.logging.error(f"Error calculating avg color of html elements from {html_path}: {e}") + bt.logging.warning(f"Error calculating avg color of html elements from {html_path}: {e}") element.avg_color = (0, 0, 0) gray_image = color.rgb2gray(color_image) @@ -187,6 +187,6 @@ def preprocess_html_elements(html_path, html_elements): try: element.keypoints, element.descriptors = extract_sift_from_roi(gray_image, (x, y, w, h)) except Exception as e: - bt.logging.error(f"Error extracting sift from html elements from {html_path}: {e}") + bt.logging.warning(f"Error extracting sift from html elements from {html_path}: {e}") element.keypoints = None element.descriptors = None From 6a9828fd2a5a2ef1fdc5c0ec42880fab541532da Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 16:47:49 +0000 Subject: [PATCH 314/554] chore: remove command arg --- webgenie/rewards/lighthouse_reward/get_lighthouse_score.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 39fd20e9..7d06786f 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -18,7 +18,7 @@ def get_lighthouse_score(htmls: List[str]) -> List[Dict[str, float]]: def get_lighthouse_score_from_subprocess(url): bt.logging.info(f"Getting lighthouse score from {url}...") try: - command = f"lighthouse {url} --output=json --chrome-flags='--headless --no-sandbox --disable-gpu' --quiet --verbose" + command = f"lighthouse {url} --output=json --chrome-flags='--headless --no-sandbox' --quiet" output = os.popen(command).read() loaded_json = json.loads(output) scores = { From 49551cd31b39a587180eb5eca49be942482d8458 Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 16:56:16 +0000 Subject: [PATCH 315/554] chore: print richtable --- neurons/validators/genie_validator.py | 36 ++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 6430808b..16ba7a02 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -6,6 +6,8 @@ import time from datetime import datetime, timedelta +from rich.console import Console +from rich.table import Table from typing import Union from webgenie.base.neuron import BaseNeuron @@ -164,10 +166,36 @@ async def score(self): miner_uids = [solution.miner_uid for solution in solutions] aggregated_scores, scores = await challenge.calculate_scores() - bt.logging.success(f"Task Source: {challenge.task.src}") - bt.logging.success(f"Competition Type: {challenge.competition_type}") - bt.logging.success(f"Scores: {scores}") - bt.logging.success(f"Final scores for {miner_uids}: {aggregated_scores}") + # Create a rich table to display the scoring results + table = Table( + title=f"Scoring Results - Session {challenge.session} - {challenge.competition_type} - {challenge.task.src}", + show_header=True, + header_style="bold magenta", + title_style="bold blue", + border_style="blue" + ) + table.add_column( + "Miner UID", + justify="right", + style="cyan", + header_style="bold cyan" + ) + table.add_column("Aggregated Score", justify="right", style="green") + table.add_column("Accuracy", justify="right") + table.add_column("SEO", justify="right") + table.add_column("Code Quality", justify="right") + + for i, miner_uid in enumerate(miner_uids): + table.add_row( + str(miner_uid), + f"{aggregated_scores[i]:.4f}", + f"{scores[ACCURACY_METRIC_NAME][i]:.4f}", + f"{scores[SEO_METRIC_NAME][i]:.4f}", + f"{scores[QUALITY_METRIC_NAME][i]:.4f}" + ) + + console = Console() + console.print(table) with self.lock: self.neuron.score_manager.update_scores( From f632bb5f1838ef67df3922bd1d70656832bb313c Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 16:56:24 +0000 Subject: [PATCH 316/554] chore: remove logs --- webgenie/storage/utils.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index b9b747ba..94fa2077 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -110,21 +110,13 @@ def store_results_to_database(results: dict): solutions = results["solutions"] scores = results["scores"] challenge = results["challenge"] - bt.logging.info(f"Storing challenge to database: {challenge}") session_number = challenge["session_number"] - bt.logging.info(f"Storing session_number to database: {session_number}") session_start_datetime = results["session_start_datetime"] - bt.logging.info(f"Storing session_start_datetime to database: {session_start_datetime}") ground_truth_html = challenge["task"] - bt.logging.info(f"Storing ground_truth_html to database: {ground_truth_html}") competition_type = challenge["competition_type"] - bt.logging.info(f"Storing competition_type to database: {competition_type}") competition_id = create_competition(competition_type) - bt.logging.info(f"Storing competition_id to database: {competition_id}") session_id = create_leaderboard_session(session_number, session_start_datetime, competition_id) - bt.logging.info(f"Storing session_id to database: {session_id}") challenge_id = create_challenge(session_id, ground_truth_html) - bt.logging.info(f"Storing challenge_id to database: {challenge_id}") # Iterate over miner_uids to store TaskSolution data for miner, solution, score in zip(miners, solutions, scores): From bda0ae2de625507d27b5b8d24fc7f93322a0e5b4 Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 17:48:30 +0000 Subject: [PATCH 317/554] chore: add rich table print --- neurons/validators/genie_validator.py | 8 +------ neurons/validators/score_manager.py | 34 ++++++++++++++++++++------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 16ba7a02..e77081ec 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -155,13 +155,7 @@ async def score(self): f"This is the previous session's challenge, skipping" ) return - - bt.logging.info( - f"Scoring - Session number: {challenge.session}, " - f"Competition type: {challenge.competition_type}, " - f"Task source: {challenge.task.src}" - ) - + solutions = challenge.solutions miner_uids = [solution.miner_uid for solution in solutions] aggregated_scores, scores = await challenge.calculate_scores() diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 4384db28..575b434c 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -1,8 +1,9 @@ import bittensor as bt import copy import numpy as np -import pickle +from rich.console import Console +from rich.table import Table from typing import List from webgenie.base.neuron import BaseNeuron @@ -93,19 +94,34 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen self.current_session = session self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) # Update accumulated scores and track best performer - bt.logging.info(f"Updating scores for uids: {uids}") - bt.logging.info(f"Rewards: {rewards}") - bt.logging.info(f"Total scores: {self.total_scores}") self.total_scores[uids] += rewards - bt.logging.info("Updating winners table") - print(np.argmax(self.total_scores), competition_type) - print(self.winners) self.winners[session] = (np.argmax(self.total_scores), competition_type) - bt.logging.info(f"Winners: {self.winners}") for session_number in self.winners: if session_number < session - CONSIDERING_SESSION_COUNTS: self.winners.pop(session_number) - bt.logging.info(f"Winners: {self.winners}") + + # Create a rich table to display the winners + table = Table( + title="Winners by Session", + show_header=True, + header_style="bold magenta", + title_style="bold blue", + border_style="blue" + ) + table.add_column("Session", justify="right", style="cyan", header_style="bold cyan") + table.add_column("Winner UID", justify="right", style="green") + table.add_column("Competition Type", justify="left") + # Add rows sorted by session number + for session_number in sorted(self.winners.keys()): + winner_uid, competition_type = self.winners[session_number] + table.add_row( + str(session_number), + str(winner_uid), + str(competition_type), + ) + + console = Console() + console.print(table) self.should_save = True def send_challenge_to_stats_collector(self): From 3dbb09c2d7e54cfcdce2d18cc6a815ff221715fb Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 18:14:04 +0000 Subject: [PATCH 318/554] chore: fix thread mutex issue for subtensor --- neurons/validators/score_manager.py | 25 +++++++++++++++++++ neurons/validators/validator.py | 38 ++++++++++++++--------------- webgenie/base/utils/weight_utils.py | 27 +++++--------------- webgenie/storage/utils.py | 1 + 4 files changed, 51 insertions(+), 40 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 575b434c..87dfca29 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -95,6 +95,30 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) # Update accumulated scores and track best performer self.total_scores[uids] += rewards + # Create a rich table to display total scores + + total_scores_table = Table( + title=f"Total Scores This Session-{session}", + show_header=True, + header_style="bold magenta", + title_style="bold blue", + border_style="blue" + ) + total_scores_table.add_column("UID", justify="right", style="cyan", header_style="bold cyan") + total_scores_table.add_column("Total Score", justify="right", style="green") + + # Add rows for non-zero scores, sorted by score + scored_uids = [(uid, score) for uid, score in enumerate(self.total_scores) if score > 0] + scored_uids.sort(key=lambda x: x[1], reverse=True) + + for uid, score in scored_uids: + total_scores_table.add_row( + str(uid), + f"{score:.4f}" + ) + + console = Console() + console.print(total_scores_table) self.winners[session] = (np.argmax(self.total_scores), competition_type) for session_number in self.winners: if session_number < session - CONSIDERING_SESSION_COUNTS: @@ -130,6 +154,7 @@ def send_challenge_to_stats_collector(self): if current_session != self.last_send_stats_collector_session: try: + bt.logging.info(f"Sending challenge to stats collector for session {current_session}") send_challenge_to_stats_collector(self.neuron.wallet, current_session) self.last_send_stats_collector_session = current_session except Exception as e: diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 7340312d..156ce85a 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -113,26 +113,26 @@ def set_weights(self): # Compute raw_weights safely raw_weights = scores / norm - # Process the raw weights to final_weights via subtensor limitations. - ( - processed_weight_uids, - processed_weights, - ) = process_weights_for_netuid( - uids=self.metagraph.uids, - weights=raw_weights, - netuid=self.config.netuid, - subtensor=self.subtensor, - metagraph=self.metagraph, - ) - - # Convert to uint16 weights and uids. - ( - uint_uids, - uint_weights, - ) = convert_weights_and_uids_for_emit( - uids=processed_weight_uids, weights=processed_weights - ) with self.lock: + # Process the raw weights to final_weights via subtensor limitations. + ( + processed_weight_uids, + processed_weights, + ) = process_weights_for_netuid( + uids=self.metagraph.uids, + weights=raw_weights, + netuid=self.config.netuid, + subtensor=self.subtensor, + metagraph=self.metagraph, + ) + + # Convert to uint16 weights and uids. + ( + uint_uids, + uint_weights, + ) = convert_weights_and_uids_for_emit( + uids=processed_weight_uids, weights=processed_weights + ) # Set the weights on chain via our subtensor connection. result, msg = self.subtensor.set_weights( wallet=self.wallet, diff --git a/webgenie/base/utils/weight_utils.py b/webgenie/base/utils/weight_utils.py index d772be65..8058453e 100644 --- a/webgenie/base/utils/weight_utils.py +++ b/webgenie/base/utils/weight_utils.py @@ -132,12 +132,7 @@ def process_weights_for_netuid( ) -> Union[tuple[ndarray[Any, dtype[Any]], Union[ Union[ndarray[Any, dtype[floating[Any]]], ndarray[Any, dtype[complexfloating[Any, Any]]]], Any]], tuple[ ndarray[Any, dtype[Any]], ndarray], tuple[Any, ndarray]]: - bittensor.logging.debug("process_weights_for_netuid()") - bittensor.logging.debug("weights", weights) - bittensor.logging.debug("netuid", netuid) - bittensor.logging.debug("subtensor", subtensor) - bittensor.logging.debug("metagraph", metagraph) - + # Get latest metagraph from chain if metagraph is None. if metagraph is None: metagraph = subtensor.metagraph(netuid) @@ -151,10 +146,7 @@ def process_weights_for_netuid( quantile = exclude_quantile / U16_MAX min_allowed_weights = subtensor.min_allowed_weights(netuid=netuid) max_weight_limit = subtensor.max_weight_limit(netuid=netuid) - bittensor.logging.debug("quantile", quantile) - bittensor.logging.debug("min_allowed_weights", min_allowed_weights) - bittensor.logging.debug("max_weight_limit", max_weight_limit) - + # Find all non zero weights. non_zero_weight_idx = np.argwhere(weights > 0).squeeze() non_zero_weight_idx = np.atleast_1d(non_zero_weight_idx) @@ -180,28 +172,21 @@ def process_weights_for_netuid( ) return np.arange(len(normalized_weights)), normalized_weights - bittensor.logging.debug("non_zero_weights", non_zero_weights) - + # Compute the exclude quantile and find the weights in the lowest quantile max_exclude = max(0, len(non_zero_weights) - min_allowed_weights) / len( non_zero_weights ) exclude_quantile = min([quantile, max_exclude]) lowest_quantile = np.quantile(non_zero_weights, exclude_quantile) - bittensor.logging.debug("max_exclude", max_exclude) - bittensor.logging.debug("exclude_quantile", exclude_quantile) - bittensor.logging.debug("lowest_quantile", lowest_quantile) - + # Exclude all weights below the allowed quantile. non_zero_weight_uids = non_zero_weight_uids[lowest_quantile <= non_zero_weights] non_zero_weights = non_zero_weights[lowest_quantile <= non_zero_weights] - bittensor.logging.debug("non_zero_weight_uids", non_zero_weight_uids) - bittensor.logging.debug("non_zero_weights", non_zero_weights) - + # Normalize weights and return. normalized_weights = normalize_max_weight( x=non_zero_weights, limit=max_weight_limit ) - bittensor.logging.debug("final_weights", normalized_weights) - + return non_zero_weight_uids, normalized_weights diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index 94fa2077..ece30848 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -248,6 +248,7 @@ def make_signed_request( def send_challenge_to_stats_collector(wallet: "bt.Wallet", session_number: int) -> None: session_data = get_session_data(session_number) + bt.logging.info(f"Sending challenge to stats collector for session {session_data}") response = make_signed_request( wallet=wallet, url="https://webgenie-collector.bactensor.io/api/competitions/", From 97068a656765d091047d24ceabe58d58b9b2b1ad Mon Sep 17 00:00:00 2001 From: pycorn Date: Thu, 23 Jan 2025 23:13:05 +0000 Subject: [PATCH 319/554] chore: disable too many logs --- .../rewards/visual_reward/common/extract_html_elements.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index 536585cc..e80b980c 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -152,7 +152,7 @@ async def traverse(node): try: await add_element(current_node, bool(children)) except Exception as e: - bt.logging.warning(f"Error adding element: {e}") + #bt.logging.warning(f"Error adding element: {e}") # Dispose the node when done await current_node.dispose() @@ -177,7 +177,7 @@ def preprocess_html_elements(html_path, html_elements): try: element.avg_color = np.mean(color_image[y:y+h, x:x+w], axis=(0, 1)) except Exception as e: - bt.logging.warning(f"Error calculating avg color of html elements from {html_path}: {e}") + #bt.logging.warning(f"Error calculating avg color of html elements from {html_path}: {e}") element.avg_color = (0, 0, 0) gray_image = color.rgb2gray(color_image) @@ -187,6 +187,6 @@ def preprocess_html_elements(html_path, html_elements): try: element.keypoints, element.descriptors = extract_sift_from_roi(gray_image, (x, y, w, h)) except Exception as e: - bt.logging.warning(f"Error extracting sift from html elements from {html_path}: {e}") + #bt.logging.warning(f"Error extracting sift from html elements from {html_path}: {e}") element.keypoints = None element.descriptors = None From f71fe74034ecd48ebc9e209cf3da567c95ed7e4a Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 02:57:35 +0000 Subject: [PATCH 320/554] chore: add neuron_epoch_length env variable --- .env.miner.example | 1 + .env.validator.example | 1 + webgenie/base/miner.py | 3 ++- webgenie/base/neuron.py | 5 +++-- webgenie/constants.py | 3 +++ .../visual_reward/common/extract_html_elements.py | 1 + webgenie/storage/utils.py | 10 +++++----- 7 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.env.miner.example b/.env.miner.example index 12066860..885ef86a 100644 --- a/.env.miner.example +++ b/.env.miner.example @@ -7,3 +7,4 @@ LLM_API_KEY = your_openai_api_key LLM_MODEL_ID = your_openai_model_id LLM_MODEL_URL = your_openai_model_url # https://api.openai.com/v1/ +NEURON_EPOCH_LENGTH = 25 \ No newline at end of file diff --git a/.env.validator.example b/.env.validator.example index bf8c7ead..e930f537 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -7,5 +7,6 @@ LLM_MODEL_URL = your_openai_model_url # https://api.openai.com/v1/ LIGHTHOUSE_SERVER_PORT = 5000 # fast api server port to get the lighthouse score +NEURON_EPOCH_LENGTH = 25 AXON_OFF = True diff --git a/webgenie/base/miner.py b/webgenie/base/miner.py index 4f449b0d..b9b1d14b 100644 --- a/webgenie/base/miner.py +++ b/webgenie/base/miner.py @@ -24,6 +24,7 @@ import bittensor as bt from webgenie.base.neuron import BaseNeuron +from webgenie.constants import NEURON_EPOCH_LENGTH from webgenie.utils.config import add_miner_args from typing import Union @@ -107,7 +108,7 @@ def run(self): while not self.should_exit: while ( self.block - self.metagraph.last_update[self.uid] - < self.config.neuron.epoch_length + < NEURON_EPOCH_LENGTH ): # Wait before checking again. time.sleep(1) diff --git a/webgenie/base/neuron.py b/webgenie/base/neuron.py index 05f61bc9..88575cfd 100644 --- a/webgenie/base/neuron.py +++ b/webgenie/base/neuron.py @@ -23,6 +23,7 @@ from abc import ABC, abstractmethod # Sync calls set weights and also resyncs the metagraph. +from webgenie.constants import NEURON_EPOCH_LENGTH from webgenie.utils.config import check_config, add_args, config from webgenie.utils.misc import ttl_get_block from webgenie import __spec_version__ as spec_version @@ -139,7 +140,7 @@ def should_sync_metagraph(self): """ return ( self.block - self.metagraph.last_update[self.uid] - ) > self.config.neuron.epoch_length + ) > NEURON_EPOCH_LENGTH def should_set_weights(self) -> bool: # Check if enough epoch blocks have elapsed since the last epoch. @@ -149,7 +150,7 @@ def should_set_weights(self) -> bool: # Define appropriate logic for when set weights. return ( (self.block - self.metagraph.last_update[self.uid]) - > self.config.neuron.epoch_length + > NEURON_EPOCH_LENGTH and self.neuron_type != "MinerNeuron" ) # don't set weights if you're a miner diff --git a/webgenie/constants.py b/webgenie/constants.py index fdcfcb3a..4987b7f3 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -107,3 +107,6 @@ # axon off AXON_OFF = os.getenv("AXON_OFF", "False").lower() == "true" +# neuron epoch length +NEURON_EPOCH_LENGTH = int(os.getenv("NEURON_EPOCH_LENGTH", 25)) + diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index e80b980c..20824e2a 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -153,6 +153,7 @@ async def traverse(node): await add_element(current_node, bool(children)) except Exception as e: #bt.logging.warning(f"Error adding element: {e}") + pass # Dispose the node when done await current_node.dispose() diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index ece30848..0c857c80 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -34,7 +34,7 @@ def add_neuron(coldkey: str, hotkey: str): existing_neuron = session.query(Neuron).filter_by(hotkey=hotkey).first() if existing_neuron: - bt.logging.info(f"neuron with hotkey {hotkey} already exists. Skipping creation.") + #bt.logging.info(f"neuron with hotkey {hotkey} already exists. Skipping creation.") return existing_neuron.id # Return the existing session id return create_record(session, Neuron, coldkey=coldkey, hotkey=hotkey) @@ -57,7 +57,7 @@ def create_leaderboard_session(session_number: int, created_at: datetime, compet existing_session = session.query(LeaderboardSession).filter_by(id=session_number).first() if existing_session: - bt.logging.info(f"Session with id {session_number} already exists. Skipping creation.") + #bt.logging.info(f"Session with id {session_number} already exists. Skipping creation.") return existing_session.id # Return the existing session id return create_record(session, LeaderboardSession, @@ -68,7 +68,7 @@ def create_competition(name: str): # Check if the competition with the given name already exists existing_competition = session.query(Competition).filter_by(name=name).first() if existing_competition: - bt.logging.info(f"Competition with name {name} already exists. Skipping creation.") + #bt.logging.info(f"Competition with name {name} already exists. Skipping creation.") return existing_competition.id # Return the existing competition id return create_record(session, Competition, name=name) @@ -80,7 +80,7 @@ def create_judgement(validator_id: int, miner_id: int): # Check if the judgement with the given validator and miner id already exists existing_judgement = session.query(Judgement).filter_by(validator_id=validator_id, miner_id=miner_id).first() if existing_judgement: - bt.logging.info(f"judgement with given {validator_id} and {miner_id} already exists. Skipping creation.") + #bt.logging.info(f"judgement with given {validator_id} and {miner_id} already exists. Skipping creation.") return existing_judgement.id # Return the existing competition id return create_record(session, Judgement, validator_id=validator_id, miner_id=miner_id) @@ -89,7 +89,7 @@ def create_evaluation_type(name: str): # Check if the competition with the given name already exists existing_evaluation_type = session.query(EvaluationType).filter_by(name=name).first() if existing_evaluation_type: - bt.logging.info(f"Evaluation type with name {name} already exists. Skipping creation.") + #bt.logging.info(f"Evaluation type with name {name} already exists. Skipping creation.") return existing_evaluation_type.id # Return the existing evaluation type id return create_record(session, EvaluationType, name=name) From fbc3117017f81f5848ff56643455690bcb978c0f Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 03:18:05 +0000 Subject: [PATCH 321/554] chore: print final raw weights table --- neurons/validators/validator.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 156ce85a..a89ff78c 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -11,7 +11,8 @@ from dotenv import load_dotenv, find_dotenv load_dotenv(find_dotenv(filename=".env.validator")) - +from rich.table import Table +from rich.console import Console from typing import Tuple, Union from webgenie.base.validator import BaseValidatorNeuron @@ -94,6 +95,31 @@ def resync_metagraph(self): ) self.score_manager.set_new_hotkeys(self.metagraph.hotkeys) + def print_weights(self, raw_weights: np.ndarray): + weights_table = Table( + title="Raw Weights (Sorted)", + show_header=True, + header_style="bold magenta", + title_style="bold blue", + border_style="blue" + ) + weights_table.add_column("UID", justify="right", style="cyan", header_style="bold cyan") + weights_table.add_column("Weight", justify="right", style="green") + + # Create list of (uid, weight) tuples and sort by weight descending + uid_weights = [(uid, weight) for uid, weight in enumerate(raw_weights) if weight > 0] + uid_weights.sort(key=lambda x: x[1], reverse=True) + + # Add rows to table + for uid, weight in uid_weights: + weights_table.add_row( + str(uid), + f"{weight:.4f}" + ) + + console = Console() + console.print(weights_table) + def set_weights(self): if not self.should_set_weights(): return @@ -112,7 +138,9 @@ def set_weights(self): # Compute raw_weights safely raw_weights = scores / norm - + + self.print_weights(raw_weights) + with self.lock: # Process the raw weights to final_weights via subtensor limitations. ( From 3184a36f240654b4236deafa1d40504261d02567 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 03:25:24 +0000 Subject: [PATCH 322/554] chore: remove work_dir by shutil --- .../rewards/visual_reward/visual_reward.py | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index 0c8d3ad3..4f9780f2 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -6,6 +6,7 @@ import asyncio import multiprocessing import numpy as np +import shutil import uuid from datetime import datetime from typing import List @@ -52,25 +53,6 @@ async def reward_worker(self, task: Task, solutions: List[Solution], current_wor scores = high_level_scores * 0.3 + low_level_scores * 0.7 await stop_browser() - - # Clean up files - for miner_html_path in miner_html_paths: - inpainted_png_path = miner_html_path.replace(".html", "_inpainted.png") - erased_html_path = miner_html_path.replace(".html", "_erased.html") - png_path = miner_html_path.replace(".html", ".png") - os.remove(miner_html_path) - os.remove(inpainted_png_path) - os.remove(erased_html_path) - os.remove(png_path) - - inpainted_png_path = original_html_path.replace(".html", "_inpainted.png") - erased_html_path = original_html_path.replace(".html", "_erased.html") - png_path = original_html_path.replace(".html", ".png") - os.remove(original_html_path) - os.remove(inpainted_png_path) - os.remove(erased_html_path) - os.remove(png_path) - return scores def sync_reward_worker(self, task: Task, solutions: List[Solution], current_work_dir: str) -> np.ndarray: @@ -115,4 +97,10 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: chunk_scores.extend(future.get()) scores = np.array(chunk_scores) + # Clean up work directory and its contents + try: + shutil.rmtree(current_work_dir) + except Exception as e: + bt.logging.warning(f"Error cleaning up work directory: {e}") + return scores From 48e897e9b088a330d66bf3073e9cac2152c1b485 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 03:30:20 +0000 Subject: [PATCH 323/554] chore: remove logs --- .../rewards/visual_reward/common/extract_html_elements.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/webgenie/rewards/visual_reward/common/extract_html_elements.py b/webgenie/rewards/visual_reward/common/extract_html_elements.py index 20824e2a..0d0d8352 100644 --- a/webgenie/rewards/visual_reward/common/extract_html_elements.py +++ b/webgenie/rewards/visual_reward/common/extract_html_elements.py @@ -73,15 +73,11 @@ async def extract_html_elements(file_path, load_time = DEFAULT_LOAD_TIME): animations="disabled", timeout=CHROME_HTML_LOAD_TIME, ) - else: - bt.logging.info(f"Screenshot already exists for {file_path}") - bt.logging.info(f"Extracting html elements from {file_path}") with open(screenshot_path, "rb") as f: screenshot = Image.open(f) W, H = screenshot.size - bt.logging.info(f"Extracted screenshot from {file_path}") async def add_element(node, has_children): # Combine all necessary evaluations into one to reduce overhead rendered_data = await node.evaluate(""" @@ -159,11 +155,9 @@ async def traverse(node): await traverse(await page.query_selector('body')) await page.close() - bt.logging.info(f"Extracted html elements from {file_path}") preprocess_html_elements(file_path, button_elements) preprocess_html_elements(file_path, input_elements) preprocess_html_elements(file_path, anchor_elements) - bt.logging.info(f"Preprocessed html elements from {file_path}") except Exception as e: bt.logging.error(f"Error extracting html elements from {file_path}: {e}") return text_elements, button_elements, input_elements, anchor_elements From d63a04f954b66a2f720628d46ad63eb37503e675 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 03:35:47 +0000 Subject: [PATCH 324/554] chore: remove logs --- webgenie/rewards/lighthouse_reward/get_lighthouse_score.py | 2 -- .../high_level_matching_score/clip_matching_score.py | 3 +-- .../high_level_matching_score/high_level_matching_score.py | 3 +-- .../visual_reward/high_level_matching_score/histogram.py | 1 - .../low_level_matching_score/low_level_matching_score.py | 3 +-- 5 files changed, 3 insertions(+), 9 deletions(-) diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index 7d06786f..d0d91833 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -16,7 +16,6 @@ def get_lighthouse_score(htmls: List[str]) -> List[Dict[str, float]]: def get_lighthouse_score_from_subprocess(url): - bt.logging.info(f"Getting lighthouse score from {url}...") try: command = f"lighthouse {url} --output=json --chrome-flags='--headless --no-sandbox' --quiet" output = os.popen(command).read() @@ -53,7 +52,6 @@ def get_lighthouse_score_from_subprocess(url): 'seo': 0 } - bt.logging.info(f"Getting lighthouse scores from localhost:{LIGHTHOUSE_SERVER_PORT}...") scores = [] for i in range(len(htmls)): diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py b/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py index 7b5c5ba0..e9eaf3ae 100644 --- a/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py +++ b/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py @@ -55,8 +55,7 @@ def calculate_embedding_vector(image_path, model, preprocess, device): async def calculate_clip_score(predict_html_path_list, original_html_path): - bt.logging.info(f"Calculating clip score.") - + device = "cuda" if torch.cuda.is_available() else "cpu" model, preprocess = clip.load("ViT-B/32", device=device) original_img_path = original_html_path.replace(HTML_EXTENSION, f"_inpainted{IMAGE_EXTENSION}") diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/high_level_matching_score.py b/webgenie/rewards/visual_reward/high_level_matching_score/high_level_matching_score.py index 04698f17..ff2b884a 100644 --- a/webgenie/rewards/visual_reward/high_level_matching_score/high_level_matching_score.py +++ b/webgenie/rewards/visual_reward/high_level_matching_score/high_level_matching_score.py @@ -7,8 +7,7 @@ async def high_level_matching_score(predict_html_path_list, original_html_path): - bt.logging.info(f"Calculating high level matching score.") - + clip_score = await calculate_clip_score(predict_html_path_list, original_html_path) histogram_score = await histogram_matching_score(predict_html_path_list, original_html_path) diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py b/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py index 8f3a81d0..f0e265e7 100644 --- a/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py +++ b/webgenie/rewards/visual_reward/high_level_matching_score/histogram.py @@ -34,7 +34,6 @@ def compare_histograms(hist1, hist2): async def histogram_matching_score(predict_html_path_list, original_html_path): - bt.logging.info(f"Calculating histogram score.") original_img_path = original_html_path.replace(HTML_EXTENSION, IMAGE_EXTENSION) await take_screenshot(original_html_path, original_img_path) original_hist = compute_grayscale_histogram(original_img_path) diff --git a/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py b/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py index e12d98dd..2147ca42 100644 --- a/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py +++ b/webgenie/rewards/visual_reward/low_level_matching_score/low_level_matching_score.py @@ -16,8 +16,7 @@ async def low_level_matching_score(predict_html_path_list, original_html_path): original_input_elements, original_anchor_elements, ) = await extract_html_elements(original_html_path) - bt.logging.info(f"Extracted original html elements.") - + results = [] for predict_html_path in predict_html_path_list: try: From f7f185efd0d4157bb468b8d9bca6d8acc377245a Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 03:36:51 +0000 Subject: [PATCH 325/554] chore: remove logs --- webgenie/rewards/visual_reward/common/take_screenshot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/webgenie/rewards/visual_reward/common/take_screenshot.py b/webgenie/rewards/visual_reward/common/take_screenshot.py index a69e3982..fa16dcd6 100644 --- a/webgenie/rewards/visual_reward/common/take_screenshot.py +++ b/webgenie/rewards/visual_reward/common/take_screenshot.py @@ -11,7 +11,6 @@ async def take_screenshot(url, output_file_path, load_time = DEFAULT_LOAD_TIME, overwrite = False): - bt.logging.info(f"Taking screenshot from {url} to {output_file_path}.") if os.path.exists(url): url = f"file:///{os.path.abspath(url)}" From bd33ab672664897ec9c7e990b2a11318748ee88e Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 03:39:58 +0000 Subject: [PATCH 326/554] chore: stats collector logging --- webgenie/storage/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index 0c857c80..19cbf996 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -89,7 +89,7 @@ def create_evaluation_type(name: str): # Check if the competition with the given name already exists existing_evaluation_type = session.query(EvaluationType).filter_by(name=name).first() if existing_evaluation_type: - #bt.logging.info(f"Evaluation type with name {name} already exists. Skipping creation.") + bt.logging.info(f"Evaluation type with name {name} already exists. Skipping creation.") return existing_evaluation_type.id # Return the existing evaluation type id return create_record(session, EvaluationType, name=name) @@ -257,4 +257,6 @@ def send_challenge_to_stats_collector(wallet: "bt.Wallet", session_number: int) payload=session_data, ) if not response.ok: - print(response.json()) \ No newline at end of file + bt.logging.error(response.json()) + else: + bt.logging.success(response.json()) \ No newline at end of file From b63b60250998eb65e5da8cf50ecaaca3467e963e Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 11:15:12 +0000 Subject: [PATCH 327/554] chore: remove logs --- webgenie/rewards/visual_reward/visual_reward.py | 3 ++- webgenie/storage/utils.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index 4f9780f2..0ecab1fd 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -25,7 +25,6 @@ def __init__(self): async def reward_worker(self, task: Task, solutions: List[Solution], current_work_dir: str) -> np.ndarray: await start_browser() - bt.logging.info(f"Rewarding image task in visual reward") original_html_path = f"{current_work_dir}/original_{uuid.uuid4()}.html" with open(original_html_path, "w") as f: @@ -75,6 +74,8 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: if not isinstance(task, ImageTask): raise ValueError(f"Task is not a ImageTask: {type(task)}") + bt.logging.info(f"Rewarding image task in visual reward") + timestamp = datetime.now().strftime("%Y_%m_%d_%H_%M") current_work_dir = f"{WORK_DIR}/task_{timestamp}_{task.task_id}" os.makedirs(current_work_dir, exist_ok=True) diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index 19cbf996..8b4a7f6f 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -89,7 +89,7 @@ def create_evaluation_type(name: str): # Check if the competition with the given name already exists existing_evaluation_type = session.query(EvaluationType).filter_by(name=name).first() if existing_evaluation_type: - bt.logging.info(f"Evaluation type with name {name} already exists. Skipping creation.") + #bt.logging.info(f"Evaluation type with name {name} already exists. Skipping creation.") return existing_evaluation_type.id # Return the existing evaluation type id return create_record(session, EvaluationType, name=name) From 65d201208226083732838083d71ca8f749b46030 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 11:15:23 +0000 Subject: [PATCH 328/554] chore: remove logs --- neurons/validators/genie_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index e77081ec..8ae45c92 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -327,7 +327,7 @@ async def checked_synapse(self, synapse: bt.Synapse) -> bt.Synapse: html = preprocess_html(synapse.html) if not html or not is_valid_resources(html): - bt.logging.warning(f"Invalid html or resources: {html}") + bt.logging.warning(f"Invalid html or resources") return None synapse.html = html From 003ffbb66d398f75a04046c97c631453a9a73688 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 12:29:19 +0000 Subject: [PATCH 329/554] feat: change set_weights logic --- neurons/validators/score_manager.py | 30 ++++------ neurons/validators/validator.py | 85 ++++++++++------------------- 2 files changed, 38 insertions(+), 77 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 87dfca29..ea8f9b9f 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -9,7 +9,6 @@ from webgenie.base.neuron import BaseNeuron from webgenie.challenges.challenge import Challenge, RESERVED_WEIGHTS from webgenie.constants import CONSIDERING_SESSION_COUNTS -from webgenie.storage import send_challenge_to_stats_collector class ScoreManager: @@ -23,7 +22,7 @@ def __init__(self, neuron: BaseNeuron): self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.current_session = -1 self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - self.last_send_stats_collector_session = -1 + self.last_set_weights_session = -1 self.winners = {} def load_scores(self): @@ -34,7 +33,7 @@ def load_scores(self): self.hotkeys = data["hotkeys"] self.current_session = data["current_session"] self.total_scores = data["total_scores"] - self.last_send_stats_collector_session = data["last_send_stats_collector_session"] + self.last_set_weights_session = data.get("last_set_weights_session", -1) self.winners = dict(data["winners"].item()) bt.logging.info(f"Winners: {self.winners}") except Exception as e: @@ -42,7 +41,7 @@ def load_scores(self): self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.current_session = -1 self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - self.last_send_stats_collector_session = -1 + self.last_set_weights_session = -1 self.winners = {} def save_scores(self): @@ -56,7 +55,7 @@ def save_scores(self): hotkeys=self.hotkeys, current_session=self.current_session, total_scores=self.total_scores, - last_send_stats_collector_session=self.last_send_stats_collector_session, + last_set_weights_session=self.last_set_weights_session, winners=self.winners, allow_pickle=True, ) @@ -121,7 +120,7 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen console.print(total_scores_table) self.winners[session] = (np.argmax(self.total_scores), competition_type) for session_number in self.winners: - if session_number < session - CONSIDERING_SESSION_COUNTS: + if session_number < session - CONSIDERING_SESSION_COUNTS * 2: self.winners.pop(session_number) # Create a rich table to display the winners @@ -147,24 +146,15 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen console = Console() console.print(table) self.should_save = True - - def send_challenge_to_stats_collector(self): - with self.lock: - current_session = self.neuron.session - - if current_session != self.last_send_stats_collector_session: - try: - bt.logging.info(f"Sending challenge to stats collector for session {current_session}") - send_challenge_to_stats_collector(self.neuron.wallet, current_session) - self.last_send_stats_collector_session = current_session - except Exception as e: - bt.logging.error(f"Error sending challenge to stats collector: {e}") - def get_scores(self): + def get_scores(self, session_upto: int): scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) with self.lock: for session_number in self.winners: + if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or + session_number > session_upto): + continue + winner, competition_type = self.winners[session_number] scores[winner] += RESERVED_WEIGHTS[competition_type] - return scores \ No newline at end of file diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index a89ff78c..d30e5f6c 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -30,11 +30,12 @@ ) from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from webgenie.rewards.lighthouse_reward import start_lighthouse_server_thread, stop_lighthouse_server +from webgenie.storage import send_challenge_to_stats_collector from webgenie.utils.uids import get_validator_index from neurons.validators.genie_validator import GenieValidator from neurons.validators.score_manager import ScoreManager - + class Validator(BaseValidatorNeuron): """ @@ -64,7 +65,7 @@ def __init__(self, config=None): self.synthensize_task_thread: Union[threading.Thread, None] = None self.query_miners_thread: Union[threading.Thread, None] = None self.score_thread: Union[threading.Thread, None] = None - self.set_weights_thread: Union[threading.Thread, None] = None + self.sync_thread: Union[threading.Thread, None] = None self.lock = threading.Lock() self.genie_validator = GenieValidator(neuron=self) @@ -120,13 +121,14 @@ def print_weights(self, raw_weights: np.ndarray): console = Console() console.print(weights_table) - def set_weights(self): - if not self.should_set_weights(): - return - - self.score_manager.send_challenge_to_stats_collector() + def set_weights(self): + with self.lock: + current_session = self.session + last_set_weights_session = self.score_manager.last_set_weights_session + if last_set_weights_session >= current_session - 1: + return - scores = self.score_manager.get_scores() + scores = self.score_manager.get_scores(current_session - 1) # Calculate the average reward for each uid across non-zero values. # Replace any NaN values with 0. # Compute the norm of the scores @@ -172,6 +174,15 @@ def set_weights(self): version_key=self.spec_version, ) if result is True: + self.score_manager.last_set_weights_session = current_session - 1 + + try: + bt.logging.info(f"Sending challenge to stats collector for session {current_session-1}") + send_challenge_to_stats_collector(self.wallet, current_session-1) + except Exception as e: + bt.logging.error(f"Error sending challenge to stats collector: {e}") + self.score_manager.last_send_stats_collector_session = current_session - 1 + bt.logging.success("set_weights on chain successfully!") else: bt.logging.error("set_weights failed", msg) @@ -236,10 +247,7 @@ def query_miners_loop(self): while True: time.sleep(1) try: - with self.lock: - self.sync() - validator_index, validator_count = get_validator_index(self, self.uid) - + validator_index, validator_count = get_validator_index(self, self.uid) if validator_index == -1: bt.logging.error(f"No enough stake for the validator.") continue @@ -290,9 +298,6 @@ def score_loop(self): while True: time.sleep(1) try: - with self.lock: - self.sync() - SCORE_TIMEOUT = 60 * 60 * 2 # 2 hours self.score_event_loop.run_until_complete( asyncio.wait_for( @@ -310,9 +315,6 @@ def synthensize_task_loop(self): while True: time.sleep(1) try: - with self.lock: - self.sync() - SYNTHETIC_TASK_TIMEOUT = 60 * 15 # 15 minutes self.synthensize_task_event_loop.run_until_complete( asyncio.wait_for( @@ -325,48 +327,17 @@ def synthensize_task_loop(self): if self.should_exit: break - def set_weights_loop(self): - """ - Every three tempos, set the weights. - """ - bt.logging.info(f"Set weights loop starting") + def sync_loop(self): + bt.logging.info(f"Sync loop starting") while True: - time.sleep(1) + time.sleep(BLOCK_IN_SECONDS * 10) try: with self.lock: self.sync() - current_block = self.block - - # Calculate the end block number for the next weight setting period - # This aligns with 3 tempo boundaries - set_weights_end_block = ( - (current_block + SESSION_WINDOW_BLOCKS - 1) - // SESSION_WINDOW_BLOCKS - * SESSION_WINDOW_BLOCKS - ) - - # Start setting weights 50 blocks before the end - set_weights_start_block = set_weights_end_block - WEIGHT_SETTING_WINDOW_BLOCKS - bt.logging.info( - f"Set weights window - " - f"Start: {set_weights_start_block}, " - f"End: {set_weights_end_block}, " - f"Current: {current_block}" - ) - # Check if we're in the weight setting window - if (current_block >= set_weights_start_block and - current_block <= set_weights_end_block): - - bt.logging.info(f"Trying to set weights at block {current_block}") self.set_weights() - else: - # Sleep until next weight setting window - sleep_blocks = set_weights_start_block - current_block - bt.logging.info(f"Sleeping for {sleep_blocks} blocks before setting weights") - time.sleep(sleep_blocks * BLOCK_IN_SECONDS) except Exception as e: - bt.logging.error(f"Error during set weights: {str(e)}") + bt.logging.error(f"Error during sync: {str(e)}") if self.should_exit: break @@ -379,12 +350,12 @@ def run_background_threads(self): self.synthensize_task_thread = threading.Thread(target=self.synthensize_task_loop, daemon=True) self.query_miners_thread = threading.Thread(target=self.query_miners_loop, daemon=True) self.score_thread = threading.Thread(target=self.score_loop, daemon=True) - self.set_weights_thread = threading.Thread(target=self.set_weights_loop, daemon=True) + self.sync_thread = threading.Thread(target=self.sync_loop, daemon=True) self.synthensize_task_thread.start() self.query_miners_thread.start() self.score_thread.start() - self.set_weights_thread.start() + self.sync_thread.start() start_lighthouse_server_thread() bt.logging.info("Started background threads") bt.logging.info("=" * 40) @@ -398,13 +369,13 @@ def stop_background_threads(self): self.synthensize_task_thread.join(5) self.query_miners_thread.join(5) self.score_thread.join(5) - self.set_weights_thread.join(5) + self.sync_thread.join(5) stop_lighthouse_server() self.synthensize_task_thread = None self.query_miners_thread = None self.score_thread = None - self.set_weights_thread = None + self.sync_thread = None bt.logging.info("Stopped background threads") def __enter__(self): From 92331913f0fc5723033a74725dc76b17d3404081 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 12:45:58 +0000 Subject: [PATCH 330/554] feat: add miner log for validator uid --- neurons/miners/miner.py | 2 ++ webgenie/__init__.py | 12 ------------ webgenie/constants.py | 6 ++++++ webgenie/helpers/weights.py | 2 +- webgenie/protocol.py | 8 +++++++- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index 7f5c4628..391cf5a2 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -70,6 +70,8 @@ def __init__(self, config=None): async def forward_image( self, synapse: WebgenieImageSynapse ) -> WebgenieImageSynapse: + validator_uid = self.metagraph.hotkeys.index(synapse.dendrite.hotkey) + bt.logging.debug(f"Validator {validator_uid}'s repo version: {synapse.VERSION}") bt.logging.debug(f"Miner image forward called with image: {image_debug_str(synapse.base64_image)}...") if synapse.task_id not in self.task_state: diff --git a/webgenie/__init__.py b/webgenie/__init__.py index 5a811b92..bc9a2a51 100644 --- a/webgenie/__init__.py +++ b/webgenie/__init__.py @@ -16,18 +16,6 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -# Change this value when updating your code base. -# Define the version of the webgenie. -__version__ = "1.0.0" -version_split = __version__.split(".") -__spec_version__ = ( - (1000 * int(version_split[0])) - + (10 * int(version_split[1])) - + (1 * int(version_split[2])) -) - -PROJECT_NAME = f"webgenie-{__version__}" - # Import all submodules. from . import protocol from . import base diff --git a/webgenie/constants.py b/webgenie/constants.py index 4987b7f3..cc1f9bd6 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -110,3 +110,9 @@ # neuron epoch length NEURON_EPOCH_LENGTH = int(os.getenv("NEURON_EPOCH_LENGTH", 25)) + +# Change this value when updating your code base. +# Define the version of the webgenie. +__VERSION__ = "1.0.0" + +WANDB_PROJECT_NAME = f"webgenie-{__VERSION__}" diff --git a/webgenie/helpers/weights.py b/webgenie/helpers/weights.py index 90899535..d0b7ce29 100644 --- a/webgenie/helpers/weights.py +++ b/webgenie/helpers/weights.py @@ -26,7 +26,7 @@ def init_wandb(self): run_name = f"{self.config.neuron.name}-{self.uid}" run = wandb.init( - project=webgenie.PROJECT_NAME, + project=WANDB_PROJECT_NAME, entity=WANDB_ENTITY_NAME, name=run_name, config=self.config, diff --git a/webgenie/protocol.py b/webgenie/protocol.py index 9620e76b..7729d641 100644 --- a/webgenie/protocol.py +++ b/webgenie/protocol.py @@ -5,8 +5,8 @@ import hashlib import pydantic import random -import uuid +from webgenie.constants import __VERSION__ class WebgenieTextSynapse(bt.Synapse): """ @@ -35,6 +35,12 @@ class WebgenieImageSynapse(bt.Synapse): """ A protocol for the webgenie image task. """ + VERSION: str = pydantic.Field( + __VERSION__, + title="Version", + description="The version of the protocol.", + ) + task_id: str = pydantic.Field( "", title="Task ID", From 7b2125241c6a78de3fc372eed39b1af940fb258a Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 12:53:20 +0000 Subject: [PATCH 331/554] chore: update load state --- neurons/validators/score_manager.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index ea8f9b9f..964f2476 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -30,12 +30,11 @@ def load_scores(self): bt.logging.info(f"Loading scores from {self.state_path}") data = np.load(self.state_path, allow_pickle=True) - self.hotkeys = data["hotkeys"] - self.current_session = data["current_session"] - self.total_scores = data["total_scores"] + self.hotkeys = data.get("hotkeys", copy.deepcopy(self.neuron.metagraph.hotkeys)) + self.current_session = data.get("current_session", -1) + self.total_scores = data.get("total_scores", np.zeros(self.neuron.metagraph.n, dtype=np.float32)) self.last_set_weights_session = data.get("last_set_weights_session", -1) - self.winners = dict(data["winners"].item()) - bt.logging.info(f"Winners: {self.winners}") + self.winners = dict(data.get("winners", {}).item()) except Exception as e: bt.logging.error(f"Error loading state: {e}") self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) From 26de7b5511c571649c986e99defed23a9e266096 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 13:45:24 +0000 Subject: [PATCH 332/554] chore: do stuff --- neurons/validators/genie_validator.py | 3 ++- neurons/validators/validator.py | 4 ++-- webgenie/base/neuron.py | 5 ++--- webgenie/constants.py | 6 ++++++ webgenie/protocol.py | 10 +++++----- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 8ae45c92..d3c3f11c 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -155,7 +155,8 @@ async def score(self): f"This is the previous session's challenge, skipping" ) return - + + bt.logging.info(f"Scoring challenge {challenge.session} {challenge.competition_type} {challenge.task.src}") solutions = challenge.solutions miner_uids = [solution.miner_uid for solution in solutions] aggregated_scores, scores = await challenge.calculate_scores() diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index d30e5f6c..fcb07f88 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -66,7 +66,7 @@ def __init__(self, config=None): self.query_miners_thread: Union[threading.Thread, None] = None self.score_thread: Union[threading.Thread, None] = None self.sync_thread: Union[threading.Thread, None] = None - self.lock = threading.Lock() + self.lock = threading.RLock() self.genie_validator = GenieValidator(neuron=self) self.score_manager = ScoreManager(neuron=self) @@ -335,7 +335,7 @@ def sync_loop(self): try: with self.lock: self.sync() - self.set_weights() + self.set_weights() except Exception as e: bt.logging.error(f"Error during sync: {str(e)}") if self.should_exit: diff --git a/webgenie/base/neuron.py b/webgenie/base/neuron.py index 88575cfd..364ff8bb 100644 --- a/webgenie/base/neuron.py +++ b/webgenie/base/neuron.py @@ -23,10 +23,9 @@ from abc import ABC, abstractmethod # Sync calls set weights and also resyncs the metagraph. -from webgenie.constants import NEURON_EPOCH_LENGTH +from webgenie.constants import NEURON_EPOCH_LENGTH, SPEC_VERSION from webgenie.utils.config import check_config, add_args, config from webgenie.utils.misc import ttl_get_block -from webgenie import __spec_version__ as spec_version from webgenie.mock import MockSubtensor, MockMetagraph @@ -54,7 +53,7 @@ def config(cls): subtensor: "bt.subtensor" wallet: "bt.wallet" metagraph: "bt.metagraph" - spec_version: int = spec_version + spec_version: int = SPEC_VERSION @property def block(self): diff --git a/webgenie/constants.py b/webgenie/constants.py index cc1f9bd6..ce121681 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -115,4 +115,10 @@ # Define the version of the webgenie. __VERSION__ = "1.0.0" +SPEC_VERSION = ( + (1000 * int(__VERSION__.split(".")[0])) + + (10 * int(__VERSION__.split(".")[1])) + + (1 * int(__VERSION__.split(".")[2])) +) + WANDB_PROJECT_NAME = f"webgenie-{__VERSION__}" diff --git a/webgenie/protocol.py b/webgenie/protocol.py index 7729d641..b6fdd38b 100644 --- a/webgenie/protocol.py +++ b/webgenie/protocol.py @@ -35,11 +35,11 @@ class WebgenieImageSynapse(bt.Synapse): """ A protocol for the webgenie image task. """ - VERSION: str = pydantic.Field( - __VERSION__, - title="Version", - description="The version of the protocol.", - ) + # VERSION: str = pydantic.Field( + # __VERSION__, + # title="Version", + # description="The version of the protocol.", + # ) task_id: str = pydantic.Field( "", From 70effb0cb950b8dd130eb6af717d13c7bca9511f Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 14:02:05 +0000 Subject: [PATCH 333/554] chore: change send_stats_collector timout --- webgenie/storage/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index 8b4a7f6f..a5d47879 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -243,7 +243,7 @@ def make_signed_request( ).hex() headers["Signature"] = signature - response = requests.request(method, url, headers=headers, files=files, json=payload, timeout=5) + response = requests.request(method, url, headers=headers, files=files, json=payload, timeout=30) return response def send_challenge_to_stats_collector(wallet: "bt.Wallet", session_number: int) -> None: From bc891bfc8f8d326510e178f20b80c879770f5d99 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 14:11:04 +0000 Subject: [PATCH 334/554] chore: add version to protocol --- webgenie/protocol.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/webgenie/protocol.py b/webgenie/protocol.py index b6fdd38b..7729d641 100644 --- a/webgenie/protocol.py +++ b/webgenie/protocol.py @@ -35,11 +35,11 @@ class WebgenieImageSynapse(bt.Synapse): """ A protocol for the webgenie image task. """ - # VERSION: str = pydantic.Field( - # __VERSION__, - # title="Version", - # description="The version of the protocol.", - # ) + VERSION: str = pydantic.Field( + __VERSION__, + title="Version", + description="The version of the protocol.", + ) task_id: str = pydantic.Field( "", From 1bcba445a66faa0b73474b5c54e31b94eb6c33f0 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 14:29:20 +0000 Subject: [PATCH 335/554] feat: set nonce as uid --- neurons/miners/miner.py | 2 +- neurons/validators/genie_validator.py | 39 +++++++++++++++------------ webgenie/protocol.py | 7 +++-- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index 391cf5a2..f52713b4 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -79,7 +79,7 @@ async def forward_image( create_time = time.time() synapse = await self.genie_miner.forward_image(synapse) - nonce = add_answer_hash(synapse, synapse.html) + nonce = add_answer_hash(synapse, self.uid, synapse.html) self.task_state[synapse.task_id] = { "html": synapse.html, "nonce": nonce, diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index d3c3f11c..888b81b7 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -118,7 +118,7 @@ async def query_miners(self): solutions = [] for reveal_synapse, hash_synapse, miner_uid in zip(all_synapse_reveal_results, all_synapse_hash_results, miner_uids): reveal_synapse.html_hash = hash_synapse.html_hash - checked_synapse = await self.checked_synapse(reveal_synapse) + checked_synapse = await self.checked_synapse(reveal_synapse, miner_uid) if checked_synapse is not None: solutions.append( Solution( @@ -308,8 +308,8 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag all_miner_uids = [all_miner_uids[i] for i in sorted_indices] responses = [responses[i] for i in sorted_indices] - for response in responses: - checked_synapse = await self.checked_synapse(response) + for response, miner_uid in zip(responses, all_miner_uids): + checked_synapse = await self.checked_synapse(response, miner_uid) if checked_synapse is None: continue return checked_synapse @@ -320,17 +320,22 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag synapse.html = f"Error: {e}" return synapse - async def checked_synapse(self, synapse: bt.Synapse) -> bt.Synapse: - if synapse.dendrite.status_code == 200: - if not verify_answer_hash(synapse): - bt.logging.warning(f"Invalid answer hash: {synapse.html_hash}") - return None - - html = preprocess_html(synapse.html) - if not html or not is_valid_resources(html): - bt.logging.warning(f"Invalid html or resources") - return None - - synapse.html = html - return synapse - return None + async def checked_synapse(self, synapse: bt.Synapse, miner_uid: int) -> bt.Synapse: + if synapse.dendrite.status_code != 200: + return None + + if synapse.nonce != miner_uid: + bt.logging.warning(f"Invalid nonce: {synapse.nonce} != {miner_uid}") + return None + + if not verify_answer_hash(synapse): + bt.logging.warning(f"Invalid answer hash: {synapse.html_hash}") + return None + + html = preprocess_html(synapse.html) + if not html or not is_valid_resources(html): + bt.logging.warning(f"Invalid html or resources") + return None + + synapse.html = html + return synapse diff --git a/webgenie/protocol.py b/webgenie/protocol.py index 7729d641..e68116ab 100644 --- a/webgenie/protocol.py +++ b/webgenie/protocol.py @@ -78,8 +78,8 @@ class WebgenieImageSynapse(bt.Synapse): ) -def add_answer_hash(self, html: str) -> int: - nonce = random.randint(0, 1000000) +def add_answer_hash(self, uid: int, html: str) -> int: + nonce = uid hash_input = html + str(nonce) self.html_hash = hashlib.sha256(hash_input.encode()).hexdigest() self.nonce = nonce @@ -92,5 +92,4 @@ def verify_answer_hash(self) -> bool: def hide_secret_info(self): - self.html = "" - self.nonce = 0 \ No newline at end of file + self.html = "" \ No newline at end of file From 9416adef61507a962beb13f829ad853f21c469c2 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 14:46:13 +0000 Subject: [PATCH 336/554] chore: update wandb config --- .env.miner.example | 1 + .env.validator.example | 1 + webgenie/constants.py | 3 +++ webgenie/helpers/weights.py | 22 ++++------------------ 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/.env.miner.example b/.env.miner.example index 885ef86a..1a38440a 100644 --- a/.env.miner.example +++ b/.env.miner.example @@ -1,3 +1,4 @@ +WANDB_OFF = True WANDB_API_KEY = your_wandb_api_key WANDB_ENTITY_NAME = your_wandb_entity_name diff --git a/.env.validator.example b/.env.validator.example index e930f537..e17267d2 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -1,3 +1,4 @@ +WANDB_OFF = False WANDB_API_KEY = your_wandb_api_key WANDB_ENTITY_NAME = your_wandb_entity_name diff --git a/webgenie/constants.py b/webgenie/constants.py index ce121681..8624a7d2 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -92,6 +92,9 @@ # llm model url LLM_MODEL_URL = os.getenv("LLM_MODEL_URL") +# wandb off +WANDB_OFF = os.getenv("WANDB_OFF", "False").lower() == "true" + # wandb api key WANDB_API_KEY = os.getenv("WANDB_API_KEY") diff --git a/webgenie/helpers/weights.py b/webgenie/helpers/weights.py index d0b7ce29..adcbcd3b 100644 --- a/webgenie/helpers/weights.py +++ b/webgenie/helpers/weights.py @@ -1,29 +1,19 @@ import bittensor as bt -import os import wandb -import webgenie - from webgenie.constants import ( + WANDB_OFF, WANDB_API_KEY, WANDB_PROJECT_NAME, WANDB_ENTITY_NAME, ) -wandb_on = False - - def init_wandb(self): try: - global wandb_on - if self.config.wandb.off: - wandb_on = False + if WANDB_OFF: return - - wandb_on = True wandb.login(key=WANDB_API_KEY) - run_name = f"{self.config.neuron.name}-{self.uid}" run = wandb.init( project=WANDB_PROJECT_NAME, @@ -32,24 +22,20 @@ def init_wandb(self): config=self.config, reinit=True, ) - signature = self.wallet.hotkey.sign(run.id.encode()).hex() self.config.signature = signature wandb.config.update(self.config , allow_val_change=True) - bt.logging.success(f"Wandb initialized with run id: {run.id}") except Exception as e: bt.logging.error(f"Error initializing wandb: {e}") raise e - def log_wandb(data: dict): try: - if not wandb_on: + if WANDB_OFF: return wandb.log(data) except Exception as e: bt.logging.error(f"Error logging to wandb: {e}") - raise e - + raise e \ No newline at end of file From 514a7d51575a7c6805356e455a1cabe102136a14 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 15:03:22 +0000 Subject: [PATCH 337/554] chore: remove unnessary env variable --- .env.miner.example | 2 -- 1 file changed, 2 deletions(-) diff --git a/.env.miner.example b/.env.miner.example index 1a38440a..63656220 100644 --- a/.env.miner.example +++ b/.env.miner.example @@ -7,5 +7,3 @@ HF_TOKEN = your_huggingface_token LLM_API_KEY = your_openai_api_key LLM_MODEL_ID = your_openai_model_id LLM_MODEL_URL = your_openai_model_url # https://api.openai.com/v1/ - -NEURON_EPOCH_LENGTH = 25 \ No newline at end of file From b1d94820f9472caaabbaa0fb502c9fb23eb21e93 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 15:16:47 +0000 Subject: [PATCH 338/554] doc: edit env variables --- .env.miner.example | 15 +++++++-------- .env.validator.example | 17 +++++++---------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/.env.miner.example b/.env.miner.example index 63656220..f5ed1ac0 100644 --- a/.env.miner.example +++ b/.env.miner.example @@ -1,9 +1,8 @@ -WANDB_OFF = True -WANDB_API_KEY = your_wandb_api_key -WANDB_ENTITY_NAME = your_wandb_entity_name +WANDB_OFF = False # Turn off wandb. +WANDB_API_KEY = your_wandb_api_key # your wandb api key, for example: sk-proj-1234567890 +WANDB_ENTITY_NAME = your_wandb_entity_name #The name of the project where you are sending the new run. +HF_TOKEN = your_huggingface_token # your huggingface token, for example: hf_1234567890 +LLM_API_KEY = your_openai_api_key # your openai api key, for example: sk-proj-1234567890 +LLM_MODEL_ID = your_openai_model_id # minimun model_id: gpt-4o +LLM_MODEL_URL = your_openai_model_url # your openai model url, for example: https://api.openai.com/v1/ -HF_TOKEN = your_huggingface_token - -LLM_API_KEY = your_openai_api_key -LLM_MODEL_ID = your_openai_model_id -LLM_MODEL_URL = your_openai_model_url # https://api.openai.com/v1/ diff --git a/.env.validator.example b/.env.validator.example index e17267d2..8256dc54 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -1,13 +1,10 @@ -WANDB_OFF = False -WANDB_API_KEY = your_wandb_api_key -WANDB_ENTITY_NAME = your_wandb_entity_name - -LLM_API_KEY = your_openai_api_key +WANDB_OFF = False # Turn off wandb. +WANDB_API_KEY = your_wandb_api_key # your wandb api key, for example: sk-proj-1234567890 +WANDB_ENTITY_NAME = your_wandb_entity_name #The name of the project where you are sending the new run. +LLM_API_KEY = your_openai_api_key # your openai api key, for example: sk-proj-1234567890 LLM_MODEL_ID = your_openai_model_id # minimun model_id: gpt-4o -LLM_MODEL_URL = your_openai_model_url # https://api.openai.com/v1/ - +LLM_MODEL_URL = your_openai_model_url # your openai model url, for example: https://api.openai.com/v1/ LIGHTHOUSE_SERVER_PORT = 5000 # fast api server port to get the lighthouse score - -NEURON_EPOCH_LENGTH = 25 -AXON_OFF = True +NEURON_EPOCH_LENGTH = 25 # The default epoch length (how often we set weights, measured in 12 second blocks). +AXON_OFF = True # Set this flag to not attempt to serve an Axon. From 1e1b37e16ea8c167f975a81737ffa2ed6384dadc Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 15:17:51 +0000 Subject: [PATCH 339/554] chore: edit env variable --- .env.validator.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.validator.example b/.env.validator.example index 8256dc54..4527aa26 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -5,6 +5,6 @@ LLM_API_KEY = your_openai_api_key # your openai api key, for example: sk-proj-12 LLM_MODEL_ID = your_openai_model_id # minimun model_id: gpt-4o LLM_MODEL_URL = your_openai_model_url # your openai model url, for example: https://api.openai.com/v1/ LIGHTHOUSE_SERVER_PORT = 5000 # fast api server port to get the lighthouse score -NEURON_EPOCH_LENGTH = 25 # The default epoch length (how often we set weights, measured in 12 second blocks). +NEURON_EPOCH_LENGTH = 25 # The default epoch length (how often we sync with chain, measured in 12 second blocks). AXON_OFF = True # Set this flag to not attempt to serve an Axon. From df287f6902a9729bcbeffa8c3b65fc92e06a1580 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 15:19:08 +0000 Subject: [PATCH 340/554] doc: edit env variable help --- .env.miner.example | 10 +++++----- .env.validator.example | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.env.miner.example b/.env.miner.example index f5ed1ac0..3585248c 100644 --- a/.env.miner.example +++ b/.env.miner.example @@ -1,8 +1,8 @@ WANDB_OFF = False # Turn off wandb. -WANDB_API_KEY = your_wandb_api_key # your wandb api key, for example: sk-proj-1234567890 +WANDB_API_KEY = your_wandb_api_key # Your wandb api key, for example: sk-proj-1234567890 WANDB_ENTITY_NAME = your_wandb_entity_name #The name of the project where you are sending the new run. -HF_TOKEN = your_huggingface_token # your huggingface token, for example: hf_1234567890 -LLM_API_KEY = your_openai_api_key # your openai api key, for example: sk-proj-1234567890 -LLM_MODEL_ID = your_openai_model_id # minimun model_id: gpt-4o -LLM_MODEL_URL = your_openai_model_url # your openai model url, for example: https://api.openai.com/v1/ +HF_TOKEN = your_huggingface_token # Your huggingface token, for example: hf_1234567890 +LLM_API_KEY = your_openai_api_key # Your openai api key, for example: sk-proj-1234567890 +LLM_MODEL_ID = your_openai_model_id # Minimun model_id: gpt-4o +LLM_MODEL_URL = your_openai_model_url # Your openai model url, for example: https://api.openai.com/v1/ diff --git a/.env.validator.example b/.env.validator.example index 4527aa26..6be33f6a 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -1,10 +1,10 @@ WANDB_OFF = False # Turn off wandb. -WANDB_API_KEY = your_wandb_api_key # your wandb api key, for example: sk-proj-1234567890 +WANDB_API_KEY = your_wandb_api_key # Your wandb api key, for example: sk-proj-1234567890 WANDB_ENTITY_NAME = your_wandb_entity_name #The name of the project where you are sending the new run. -LLM_API_KEY = your_openai_api_key # your openai api key, for example: sk-proj-1234567890 -LLM_MODEL_ID = your_openai_model_id # minimun model_id: gpt-4o -LLM_MODEL_URL = your_openai_model_url # your openai model url, for example: https://api.openai.com/v1/ -LIGHTHOUSE_SERVER_PORT = 5000 # fast api server port to get the lighthouse score -NEURON_EPOCH_LENGTH = 25 # The default epoch length (how often we sync with chain, measured in 12 second blocks). +LLM_API_KEY = your_openai_api_key # Your openai api key, for example: sk-proj-1234567890 +LLM_MODEL_ID = your_openai_model_id # Minimun model_id: gpt-4o +LLM_MODEL_URL = your_openai_model_url # Your openai model url, for example: https://api.openai.com/v1/ +LIGHTHOUSE_SERVER_PORT = 5000 # Fast api server port to get the lighthouse score +NEURON_EPOCH_LENGTH = 25 # The default epoch length (how often you sync with chain, measured in 12 second blocks). AXON_OFF = True # Set this flag to not attempt to serve an Axon. From d5e0484e06fdb8adb870fa55cb4d88c66880ab1c Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 15:23:30 +0000 Subject: [PATCH 341/554] style: edit constants helper --- webgenie/constants.py | 126 ++++++++++++++---------------------------- 1 file changed, 43 insertions(+), 83 deletions(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index 8624a7d2..818f939b 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -1,127 +1,87 @@ import bittensor as bt import os -# backend api hotkey -API_HOTKEY = "5DXDCYTuPfLqQXbxfvvnarG31SdTDtaubqpQrzjrcMgoP9dp" +# Change this value when updating your code base. +# Define the version of the webgenie. +__VERSION__ = "1.0.0" # version -# image task timeout -IMAGE_TASK_TIMEOUT = 72 +SPEC_VERSION = ( + (1000 * int(__VERSION__.split(".")[0])) + + (10 * int(__VERSION__.split(".")[1])) + + (1 * int(__VERSION__.split(".")[2])) +) -# text task timeout -TEXT_TASK_TIMEOUT = 72 +API_HOTKEY = "5DXDCYTuPfLqQXbxfvvnarG31SdTDtaubqpQrzjrcMgoP9dp" # backend api hotkey -# reveal time -TASK_REVEAL_TIME = 20 +IMAGE_TASK_TIMEOUT = 72 # image task timeout -# reveal time out -TASK_REVEAL_TIMEOUT = 20 +TEXT_TASK_TIMEOUT = 72 # text task timeout -# lighthouse server port -LIGHTHOUSE_SERVER_PORT = int(os.getenv("LIGHTHOUSE_SERVER_PORT",5000)) +TASK_REVEAL_TIME = 20 # reveal time -# max competition history size -MAX_COMPETETION_HISTORY_SIZE = 10 +TASK_REVEAL_TIMEOUT = 20 # reveal time out -# max synthetic task size -MAX_SYNTHETIC_TASK_SIZE = 10 +LIGHTHOUSE_SERVER_PORT = int(os.getenv("LIGHTHOUSE_SERVER_PORT",5000)) # lighthouse server port -# max debug image string length -MAX_DEBUG_IMAGE_STRING_LENGTH = 20 +MAX_COMPETETION_HISTORY_SIZE = 10 # max competition history size -# place holder image url -PLACE_HOLDER_IMAGE_URL = "https://picsum.photos/seed/picsum/800/600" +MAX_SYNTHETIC_TASK_SIZE = 10 # max synthetic task size -# default load time -DEFAULT_LOAD_TIME = 1000 +MAX_DEBUG_IMAGE_STRING_LENGTH = 20 # max debug image string length -# max page load time -GROUND_TRUTH_HTML_LOAD_TIME = 20000 +PLACE_HOLDER_IMAGE_URL = "https://picsum.photos/seed/picsum/800/600" # place holder image url -# miner html load time -CHROME_HTML_LOAD_TIME = 60000 +DEFAULT_LOAD_TIME = 1000 # default load time -# javascript running time -JAVASCRIPT_RUNNING_TIME = 1000 +GROUND_TRUTH_HTML_LOAD_TIME = 20000 # max page load time +CHROME_HTML_LOAD_TIME = 60000 # miner html load time -# miner html load time -MINER_HTML_LOAD_TIME = 2000 +JAVASCRIPT_RUNNING_TIME = 1000 # javascript running time -# max miner html length -MAX_MINER_HTML_LEN = 1000000 +MINER_HTML_LOAD_TIME = 2000 # miner html load time -# work dir -WORK_DIR = "work" +MAX_MINER_HTML_LEN = 1000000 # max miner html length -# lighthouse server work dir -LIGHTHOUSE_SERVER_WORK_DIR = f"{WORK_DIR}/lighthouse_server_work" +WORK_DIR = "work" # work dir -# html extension -HTML_EXTENSION = ".html" +LIGHTHOUSE_SERVER_WORK_DIR = f"{WORK_DIR}/lighthouse_server_work" # lighthouse server work dir -# image extension -IMAGE_EXTENSION = ".png" +HTML_EXTENSION = ".html" # html extension -# max count of validators -MAX_COUNT_VALIDATORS = 1 +IMAGE_EXTENSION = ".png" # image extension -# block in seconds -BLOCK_IN_SECONDS = 12 +MAX_COUNT_VALIDATORS = 1 # max count of validators -# tempo blocks -TEMPO_BLOCKS = 360 +BLOCK_IN_SECONDS = 12 # block in seconds -# session window blocks -SESSION_WINDOW_BLOCKS = TEMPO_BLOCKS * 3 +TEMPO_BLOCKS = 360 # tempo blocks + +SESSION_WINDOW_BLOCKS = TEMPO_BLOCKS * 3 # session window blocks -# considering session number CONSIDERING_SESSION_COUNTS = 8 -# querying window blocks QUERING_WINDOW_BLOCKS = 10 -# weight setting window blocks WEIGHT_SETTING_WINDOW_BLOCKS = 50 # 50 blocks = 10 minutes -# llm model id -LLM_MODEL_ID = os.getenv("LLM_MODEL_ID") - -# llm api key -LLM_API_KEY = os.getenv("LLM_API_KEY") +LLM_MODEL_ID = os.getenv("LLM_MODEL_ID") # llm model id -# llm model url -LLM_MODEL_URL = os.getenv("LLM_MODEL_URL") +LLM_API_KEY = os.getenv("LLM_API_KEY") # llm api key -# wandb off -WANDB_OFF = os.getenv("WANDB_OFF", "False").lower() == "true" +LLM_MODEL_URL = os.getenv("LLM_MODEL_URL") # llm model url -# wandb api key -WANDB_API_KEY = os.getenv("WANDB_API_KEY") +WANDB_OFF = os.getenv("WANDB_OFF", "False").lower() == "true" # wandb off -# wandb project name -WANDB_PROJECT_NAME = os.getenv("WANDB_PROJECT_NAME") +WANDB_API_KEY = os.getenv("WANDB_API_KEY") # wandb api key -# wandb entity name -WANDB_ENTITY_NAME = os.getenv("WANDB_ENTITY_NAME") +WANDB_PROJECT_NAME = f"webgenie-{__VERSION__}" # wandb project name -# vpermit tao limit -VPERMIT_TAO_LIMIT = 1000 +WANDB_ENTITY_NAME = os.getenv("WANDB_ENTITY_NAME") # wandb entity name -# axon off -AXON_OFF = os.getenv("AXON_OFF", "False").lower() == "true" +VPERMIT_TAO_LIMIT = 1000 # vpermit tao limit -# neuron epoch length -NEURON_EPOCH_LENGTH = int(os.getenv("NEURON_EPOCH_LENGTH", 25)) +AXON_OFF = os.getenv("AXON_OFF", "False").lower() == "true" # axon off +NEURON_EPOCH_LENGTH = int(os.getenv("NEURON_EPOCH_LENGTH", 25)) # neuron epoch length -# Change this value when updating your code base. -# Define the version of the webgenie. -__VERSION__ = "1.0.0" - -SPEC_VERSION = ( - (1000 * int(__VERSION__.split(".")[0])) - + (10 * int(__VERSION__.split(".")[1])) - + (1 * int(__VERSION__.split(".")[2])) -) - -WANDB_PROJECT_NAME = f"webgenie-{__VERSION__}" From 1dd2b0428ce0499437d34024737c16ba460f7d59 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 15:52:51 +0000 Subject: [PATCH 342/554] chore: update image debug string --- webgenie/helpers/images.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webgenie/helpers/images.py b/webgenie/helpers/images.py index 26cfe883..6d38c5f7 100644 --- a/webgenie/helpers/images.py +++ b/webgenie/helpers/images.py @@ -26,4 +26,6 @@ def base64_to_image(base64_str: str) -> Image.Image: def image_debug_str(base64_image: str) -> str: - return base64_image[:MAX_DEBUG_IMAGE_STRING_LENGTH] + from_offset = 300 + to_offset = MAX_DEBUG_IMAGE_STRING_LENGTH + from_offset + return base64_image[from_offset:to_offset] From ff178e17f8ccaac19c3b95fc8bbd835933236ee0 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 15:54:08 +0000 Subject: [PATCH 343/554] chore: add version to synapse --- neurons/validators/genie_validator.py | 3 +++ webgenie/protocol.py | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 888b81b7..fa3d7be0 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -20,6 +20,7 @@ TASK_REVEAL_TIMEOUT, SESSION_WINDOW_BLOCKS, BLOCK_IN_SECONDS, + __VERSION__, ) from webgenie.challenges import ( AccuracyChallenge, @@ -91,6 +92,7 @@ async def query_miners(self): challenge = challenge_class(task=task, session=session) synapse.competition_type = challenge.competition_type + synapse.VERSION = __VERSION__ bt.logging.debug(f"Querying {len(miner_uids)} miners") @@ -278,6 +280,7 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag else: bt.logging.debug(f"Organic image forward: {image_debug_str(synapse.base64_image)}...") + synapse.VERSION = __VERSION__ all_miner_uids = get_all_available_uids(self.neuron) try: if not all_miner_uids: diff --git a/webgenie/protocol.py b/webgenie/protocol.py index e68116ab..c4851f6e 100644 --- a/webgenie/protocol.py +++ b/webgenie/protocol.py @@ -6,7 +6,6 @@ import pydantic import random -from webgenie.constants import __VERSION__ class WebgenieTextSynapse(bt.Synapse): """ @@ -36,7 +35,7 @@ class WebgenieImageSynapse(bt.Synapse): A protocol for the webgenie image task. """ VERSION: str = pydantic.Field( - __VERSION__, + "NONE", title="Version", description="The version of the protocol.", ) From b75a1a9a0e16369585dd15fb6a4ae0534844c858 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 16:00:29 +0000 Subject: [PATCH 344/554] chore: update check_requirements of miner --- neurons/miners/hf_miner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neurons/miners/hf_miner.py b/neurons/miners/hf_miner.py index 5155c104..18149ad9 100644 --- a/neurons/miners/hf_miner.py +++ b/neurons/miners/hf_miner.py @@ -14,8 +14,8 @@ def check_requirements(): bt.logging.info(f"Total memory: {total_memory_mb}") - if total_memory_mb < 1024 * 25: - raise ValueError("Insufficient GPU memory. HfMiner requires at least 25GB of GPU memory.") + if total_memory_mb < 1024 * 24: + raise ValueError("Insufficient GPU memory. HfMiner requires at least 24GB of GPU memory.") check_requirements() From 9047c97be786483c9e25103197b3e24fc064a006 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 16:05:02 +0000 Subject: [PATCH 345/554] chore: update image_debug_string --- webgenie/helpers/images.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/helpers/images.py b/webgenie/helpers/images.py index 6d38c5f7..29b421e5 100644 --- a/webgenie/helpers/images.py +++ b/webgenie/helpers/images.py @@ -26,6 +26,6 @@ def base64_to_image(base64_str: str) -> Image.Image: def image_debug_str(base64_image: str) -> str: - from_offset = 300 - to_offset = MAX_DEBUG_IMAGE_STRING_LENGTH + from_offset + from_offset = len(base64_image) // 2 - MAX_DEBUG_IMAGE_STRING_LENGTH + to_offset = len(base64_image) // 2 + MAX_DEBUG_IMAGE_STRING_LENGTH return base64_image[from_offset:to_offset] From 587ba27f683d022496f4ab97697e43372f93d625 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 16:11:20 +0000 Subject: [PATCH 346/554] fix: fix hf_miner --- neurons/miners/hf_miner.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/neurons/miners/hf_miner.py b/neurons/miners/hf_miner.py index 18149ad9..4ec70a49 100644 --- a/neurons/miners/hf_miner.py +++ b/neurons/miners/hf_miner.py @@ -3,9 +3,11 @@ from webgenie.base.neuron import BaseNeuron from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from webgenie.helpers.images import base64_to_image - from webgenie.utils.gpus import get_gpu_info +from neurons.miners.hf_models.websight_finetuned import generate_html_from_image + + def check_requirements(): total_memory_mb, _, _ = get_gpu_info() @@ -43,6 +45,6 @@ async def forward_image(self, synapse: WebgenieImageSynapse) -> WebgenieImageSyn bt.logging.debug(f"Generated HTML: {synapse.html}") return synapse except Exception as e: - bt.logging.error(f"Error in OpenaiMiner forward_image: {e}") - synapse.html = f"Error in OpenaiMiner forward_image: {e}" + bt.logging.error(f"Error in HfMiner forward_image: {e}") + synapse.html = f"Error in HfMiner forward_image: {e}" return synapse \ No newline at end of file From 84711ee4c4107d9255a940f2834fb389b2682bd7 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 16:58:15 +0000 Subject: [PATCH 347/554] chore: add .env to search list --- neurons/miners/miner.py | 6 ++++-- neurons/validators/genie_validator.py | 3 +-- neurons/validators/validator.py | 6 ++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index f52713b4..0402f40b 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -15,8 +15,10 @@ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -from dotenv import load_dotenv, find_dotenv -load_dotenv(find_dotenv(filename=".env.miner")) +from dotenv import load_dotenv + +load_dotenv(".env.miner") +load_dotenv(".env") import time diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index fa3d7be0..2022aac9 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -109,7 +109,6 @@ async def query_miners(self): time.sleep(sleep_time_before_reveal) bt.logging.debug(f"Revealing task {task.task_id}") - async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: all_synapse_reveal_results = await dendrite( axons = [self.neuron.metagraph.axons[uid] for uid in miner_uids], @@ -158,7 +157,7 @@ async def score(self): ) return - bt.logging.info(f"Scoring challenge {challenge.session} {challenge.competition_type} {challenge.task.src}") + bt.logging.info(f"Scoring session, {challenge.session}, {challenge.competition_type}, {challenge.task.src}") solutions = challenge.solutions miner_uids = [solution.miner_uid for solution in solutions] aggregated_scores, scores = await challenge.calculate_scores() diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index fcb07f88..efacf0b0 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -9,8 +9,10 @@ import threading import time -from dotenv import load_dotenv, find_dotenv -load_dotenv(find_dotenv(filename=".env.validator")) +from dotenv import load_dotenv +load_dotenv(".env.validator") +load_dotenv(".env") + from rich.table import Table from rich.console import Console from typing import Tuple, Union From ea0b37863de9807a540d6171a084a16b97ce1240 Mon Sep 17 00:00:00 2001 From: pycorn Date: Fri, 24 Jan 2025 19:21:14 +0000 Subject: [PATCH 348/554] doc: add changelog file --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..3ad29b7d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - 2025-01-24 +### Added +- Initial release of the project. +- Features include Image2Html. From 9271c6cbbe88512d3d9fd560e98b839996b1fa89 Mon Sep 17 00:00:00 2001 From: pycorn Date: Sat, 25 Jan 2025 03:36:15 +0000 Subject: [PATCH 349/554] chore: remove setting non-existant variable --- neurons/validators/validator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index efacf0b0..c3a7f36e 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -183,7 +183,6 @@ def set_weights(self): send_challenge_to_stats_collector(self.wallet, current_session-1) except Exception as e: bt.logging.error(f"Error sending challenge to stats collector: {e}") - self.score_manager.last_send_stats_collector_session = current_session - 1 bt.logging.success("set_weights on chain successfully!") else: From 7887ce5207cde82d57b6333986b057aca0177701 Mon Sep 17 00:00:00 2001 From: pycorn Date: Sat, 25 Jan 2025 03:40:11 +0000 Subject: [PATCH 350/554] doc: edit changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad29b7d..d073fc3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,3 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial release of the project. - Features include Image2Html. + +## [1.0.1] - 2025-01-25 +### Fixed +- Fixed an issue with sending session results to the stats collector From 61c242f11ede80a87fa4633c4237c357c43efdd4 Mon Sep 17 00:00:00 2001 From: pycorn Date: Sat, 25 Jan 2025 03:41:03 +0000 Subject: [PATCH 351/554] chore: upgrade project version --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index 818f939b..b22dd685 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.0" # version +__VERSION__ = "1.0.1" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From fadba1654f36acefd9517f94ff4641d3513a350b Mon Sep 17 00:00:00 2001 From: pycorn Date: Sat, 25 Jan 2025 05:46:09 +0000 Subject: [PATCH 352/554] fix: resolve error when the data is empty --- webgenie/storage/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index a5d47879..867b7887 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -248,6 +248,9 @@ def make_signed_request( def send_challenge_to_stats_collector(wallet: "bt.Wallet", session_number: int) -> None: session_data = get_session_data(session_number) + if not session_data: + bt.logging.warning(f"No session data found for session {session_number}") + return bt.logging.info(f"Sending challenge to stats collector for session {session_data}") response = make_signed_request( wallet=wallet, From e880debca29a4c49d708f56db74ea78e15c2ceae Mon Sep 17 00:00:00 2001 From: pycorn Date: Sat, 25 Jan 2025 06:27:59 +0000 Subject: [PATCH 353/554] chore: set the device to cpu because of multiprocessing --- .../high_level_matching_score/clip_matching_score.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py b/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py index e9eaf3ae..65a34d10 100644 --- a/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py +++ b/webgenie/rewards/visual_reward/high_level_matching_score/clip_matching_score.py @@ -56,7 +56,8 @@ def calculate_embedding_vector(image_path, model, preprocess, device): async def calculate_clip_score(predict_html_path_list, original_html_path): - device = "cuda" if torch.cuda.is_available() else "cpu" + #device = "cuda" if torch.cuda.is_available() else "cpu" + device = "cpu" model, preprocess = clip.load("ViT-B/32", device=device) original_img_path = original_html_path.replace(HTML_EXTENSION, f"_inpainted{IMAGE_EXTENSION}") await inpaint_image(original_html_path, original_img_path) From 5d6fa26908deb0b6a3a38789d720a82d6abc99b4 Mon Sep 17 00:00:00 2001 From: pycorn Date: Sat, 25 Jan 2025 06:39:59 +0000 Subject: [PATCH 354/554] chore: increase timeout --- CHANGELOG.md | 3 ++- webgenie/storage/utils.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d073fc3d..a82dec4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,4 +12,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.1] - 2025-01-25 ### Fixed -- Fixed an issue with sending session results to the stats collector +- Fixed an issue with sending session results to the stats collector. +- Fixed an issue with the high level matching score because of re-initializing CUDA in forked processes. diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index 867b7887..e3140aa3 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -243,7 +243,7 @@ def make_signed_request( ).hex() headers["Signature"] = signature - response = requests.request(method, url, headers=headers, files=files, json=payload, timeout=30) + response = requests.request(method, url, headers=headers, files=files, json=payload, timeout=120) return response def send_challenge_to_stats_collector(wallet: "bt.Wallet", session_number: int) -> None: From ba5a654f850b0da00e478add3078f96a61d1e87a Mon Sep 17 00:00:00 2001 From: pycorn Date: Sat, 25 Jan 2025 10:46:33 +0000 Subject: [PATCH 355/554] chore: add logs for lighthouse_score --- webgenie/rewards/lighthouse_reward/get_lighthouse_score.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py index d0d91833..fdf8c52c 100644 --- a/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py +++ b/webgenie/rewards/lighthouse_reward/get_lighthouse_score.py @@ -44,7 +44,7 @@ def get_lighthouse_score_from_subprocess(url): # bt.logging.error(f"Stderr from lighthouse: {result.stderr}, returncode: {result.returncode}") # raise Exception(f"Stderr from lighthouse: {result.stderr}, returncode: {result.returncode}") except Exception as e: - bt.logging.error(f"Error running Lighthouse: {e}") + bt.logging.error(f"Error running Lighthouse: {e}, output: {output}") return { 'performance': 0, 'accessibility': 0, From 5b9020014af34d36bd4daf5e973464ab8ba020e9 Mon Sep 17 00:00:00 2001 From: pycorn Date: Sat, 25 Jan 2025 10:53:48 +0000 Subject: [PATCH 356/554] chore: change log info --- webgenie/storage/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index e3140aa3..b53ef3cb 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -251,7 +251,7 @@ def send_challenge_to_stats_collector(wallet: "bt.Wallet", session_number: int) if not session_data: bt.logging.warning(f"No session data found for session {session_number}") return - bt.logging.info(f"Sending challenge to stats collector for session {session_data}") + bt.logging.info(f"Sending challenge to stats collector for session {session_number}") response = make_signed_request( wallet=wallet, url="https://webgenie-collector.bactensor.io/api/competitions/", From 8c6f5f6451c36cf9e4dabf405747748a620c5233 Mon Sep 17 00:00:00 2001 From: pycorn Date: Sat, 25 Jan 2025 14:32:28 +0000 Subject: [PATCH 357/554] fix: fix issue with selecting winners --- CHANGELOG.md | 4 ++++ neurons/validators/score_manager.py | 11 +++++++++-- webgenie/constants.py | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a82dec4d..503d526b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,3 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed an issue with sending session results to the stats collector. - Fixed an issue with the high level matching score because of re-initializing CUDA in forked processes. + +## [1.0.2] - 2025-01-25 +### Fixed +- Fixed an issue that happens in selecting winners when the total scores are all 0. diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 964f2476..5f447823 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -117,7 +117,12 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen console = Console() console.print(total_scores_table) - self.winners[session] = (np.argmax(self.total_scores), competition_type) + + if np.max(self.total_scores) > 0: + self.winners[session] = (np.argmax(self.total_scores), competition_type) + else: + self.winners[session] = (-1, competition_type) + for session_number in self.winners: if session_number < session - CONSIDERING_SESSION_COUNTS * 2: self.winners.pop(session_number) @@ -155,5 +160,7 @@ def get_scores(self, session_upto: int): continue winner, competition_type = self.winners[session_number] + if winner == -1: + continue scores[winner] += RESERVED_WEIGHTS[competition_type] - return scores \ No newline at end of file + return scores diff --git a/webgenie/constants.py b/webgenie/constants.py index b22dd685..7d86a72d 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.1" # version +__VERSION__ = "1.0.2" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 9f4135bb82679f454f9bb55f54d1799112aaa50e Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 25 Jan 2025 12:41:33 -0300 Subject: [PATCH 358/554] docs: update readme --- README.md | 53 +--- docs/miner.md | 77 +++++ docs/miner_compute.yml | 47 +++ docs/running_on_mainnet.md | 248 ---------------- docs/running_on_staging.md | 341 --------------------- docs/running_on_testnet.md | 243 --------------- docs/stream_tutorial/README.md | 490 ------------------------------- docs/stream_tutorial/client.py | 104 ------- docs/stream_tutorial/config.py | 116 -------- docs/stream_tutorial/miner.py | 398 ------------------------- docs/stream_tutorial/protocol.py | 154 ---------- docs/validator.md | 93 ++++++ docs/validator_compute.yml | 38 +++ min_compute.yml | 87 ------ 14 files changed, 257 insertions(+), 2232 deletions(-) create mode 100644 docs/miner.md create mode 100644 docs/miner_compute.yml delete mode 100644 docs/running_on_mainnet.md delete mode 100644 docs/running_on_staging.md delete mode 100644 docs/running_on_testnet.md delete mode 100644 docs/stream_tutorial/README.md delete mode 100644 docs/stream_tutorial/client.py delete mode 100644 docs/stream_tutorial/config.py delete mode 100644 docs/stream_tutorial/miner.py delete mode 100644 docs/stream_tutorial/protocol.py create mode 100644 docs/validator.md create mode 100644 docs/validator_compute.yml delete mode 100644 min_compute.yml diff --git a/README.md b/README.md index 02dea79c..d8049e85 100644 --- a/README.md +++ b/README.md @@ -114,57 +114,8 @@ The WebGenieAI subnet incentivizes miners and validators to ensure high-quality ## Installation -- See [Running on Staging](docs/running_on_staging.md) for instructions on how to run the subnet on staging. -- See [Running on Testnet](docs/running_on_testnet.md) for instructions on how to run the subnet on testnet. -- See [Running on Mainnet](docs/running_on_mainnet.md) for instructions on how to run the subnet on mainnet. - -Clone the web-genie-ai repository: -```bash -git clone https://github.com/web-genie-ai/web-genie-ai.git -cd web-genie-ai -``` - -#### 1) Running miners and validators using bash files - -```bash -bash scripts/install_requirements.sh -``` - -Configure your Bittensor wallets and environment variables before proceeding: -```bash -bash scripts/start.sh -``` - -#### 2) Scripts for running miners and validators manually -```bash -npm install pm2 -g -curl -LsSf https://astral.sh/uv/install.sh | sh -``` -Install the packages in a new terminal: -```bash -uv sync -``` -- miner -```bash -export PYTHONPATH="." -pm2 start "uv run neurons/miners/miner.py --netuid [54 | 214] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port]" --name webgenie_miner -``` -- validator -```bash -npm install -g lighthouse -wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -sudo dpkg -i google-chrome-stable_current_amd64.deb -sudo apt-get install -f -source .venv/bin/activate -playwright install-deps -playwright install -export PYTHONPATH="." -pm2 start "uv run neurons/validators/validator.py --netuid [54 | 214] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port]" --name webgenie_validator -``` -- running auto_update script for validators -```bash -pm2 start --name auto_update auto_update.sh -``` +- See [Validator](docs/validator.md) for instructions on how to run a validator. +- See [Miner](docs/miner.md) for instructions on how to run a miner. ## Roadmap diff --git a/docs/miner.md b/docs/miner.md new file mode 100644 index 00000000..110db15d --- /dev/null +++ b/docs/miner.md @@ -0,0 +1,77 @@ +# WebGenieAI: Miner Documentation + +## Compute Requirement +For detailed specifications, please refer to the [Compute Requirement](miner_compute.yml). + +## Miner Setup + +Follow the steps below to configure and run the Miner. You can use either a closed-source AI service like OpenAI or your customized model. + +### Setup Instructions + +1. **Clone the WebGenieAI Repository:** + ```bash + git clone https://github.com/web-genie-ai/web-genie-ai.git + cd web-genie-ai + ``` + +2. **Configure the Environment:** + - **Create the `.env` File:** + ```bash + echo "LLM_API_KEY = your_openai_api_key" >> .env # Your OpenAI API key when using OpenAI for mining; not required for custom models + echo "LLM_MODEL_ID = openai_model_id" >> .env # Minimum model ID: gpt-4o when using OpenAI for mining; not required for custom models + echo "LLM_MODEL_URL = openai_model_url" >> .env # OpenAI model URL when using OpenAI for mining; not required for custom models + echo "HF_TOKEN = your_huggingface_token" >> .env # Your Hugging Face token, e.g., hf_1234567890; not required when using OpenAI for mining + ``` + - Alternatively, rename `.env.miner.example` and customize it with your values. + - **Source the `.env` File:** + ```bash + source .env + ``` + +3. **Execute the Miner:** + - **Using Bash Scripts:** + - Install necessary packages: + ```bash + bash scripts/install_requirements.sh + ``` + - Configure your Bittensor wallets and environment variables before proceeding: + ```bash + bash scripts/start.sh + ``` + - **Manual Execution:** + - Install `pm2` and `uv`: + ```bash + npm install pm2 -g + curl -LsSf https://astral.sh/uv/install.sh | sh + ``` + - Install the packages in a new terminal: + ```bash + uv sync + ``` + - Start the miner: + ```bash + export PYTHONPATH="." + pm2 start "uv run neurons/miners/miner.py --netuid [54 | 214] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port]" --name webgenie_miner + ``` + +--- + +### Troubleshooting & Support + +- **Logs:** + - For detailed logs, use the following command: + ```bash + pm2 logs webgenie_miner + ``` + +- **Common Issues:** + - Missing or incorrect `.env` constants. + - Unmatched server resource issues. + - Connectivity problems. + +- **Contact Support:** For assistance, please reach out to the WebGenieAI team. + +--- + +Happy Mining! \ No newline at end of file diff --git a/docs/miner_compute.yml b/docs/miner_compute.yml new file mode 100644 index 00000000..b9c0f7fe --- /dev/null +++ b/docs/miner_compute.yml @@ -0,0 +1,47 @@ +# Compute Requirements for Miners +# This document outlines the recommended hardware specifications for running miners on your subnet. +# It provides a rough estimate to help users decide if their machine is suitable for running a miner or validator. + +# Note: Specifications for miners may differ from those for validators. + +version: '1.0' # Update this version key as needed to match your release version. + +compute_spec: + + cpu: + min_cores: 4 # Minimum number of CPU cores + min_speed: 2.5 GHz # Minimum speed per core + recommended_cores: 8 # Recommended number of CPU cores + recommended_speed: 3.5 GHz # Recommended speed per core + architecture: "x86_64" # Architecture type (e.g., x86_64, arm64) + + gpu: + required: true # Is a GPU required? + min_vram: 24 GB # Minimum GPU VRAM + recommended_vram: 24 GB # Recommended GPU VRAM + cuda_cores: 1024 # Minimum number of CUDA cores + min_compute_capability: 6.0 # Minimum CUDA compute capability + recommended_compute_capability: 7.0 # Recommended CUDA compute capability + recommended_gpu: "NVIDIA RTX 4090" # Recommended GPU model + + memory: + min_ram: 16 GB # Minimum RAM + min_swap: 4 GB # Minimum swap space + recommended_swap: 8 GB # Recommended swap space + ram_type: "DDR4" # RAM type (e.g., DDR4, DDR3) + + storage: + min_space: 320 GB # Minimum free storage space + recommended_space: 320 GB # Recommended free storage space + recommended_type: "SSD" # Preferred storage type (e.g., SSD, HDD) + min_iops: 1000 # Minimum I/O operations per second + recommended_iops: 5000 # Recommended I/O operations per second + + os: + name: "Ubuntu" # Preferred operating system + version: 20.04 # Preferred operating system version + +network_spec: + bandwidth: + download: 100 Mbps # Minimum download bandwidth + upload: 20 Mbps # Minimum upload bandwidth \ No newline at end of file diff --git a/docs/running_on_mainnet.md b/docs/running_on_mainnet.md deleted file mode 100644 index 2d3ef37a..00000000 --- a/docs/running_on_mainnet.md +++ /dev/null @@ -1,248 +0,0 @@ -# Running Subnet on Mainnet - -This tutorial shows how to use the bittensor `btcli` to create a subnetwork and connect your incentive mechanism to it. - -**IMPORTANT:** Before attempting to register on mainnet, we strongly recommend that you: -- First run [Running Subnet Locally](running_on_staging.md), and -- Then run [Running on the Testnet](running_on_testnet.md). - -Your incentive mechanisms running on the mainnet are open to anyone. They emit real TAO. Creating these mechanisms incur a `lock_cost` in TAO. - -**DANGER** -- Do not expose your private keys. -- Only use your testnet wallet. -- Do not reuse the password of your mainnet wallet. -- Make sure your incentive mechanism is resistant to abuse. - -## Prerequisites - -Before proceeding further, make sure that you have installed Bittensor. See the below instructions: - -- [Install `bittensor`](https://github.com/opentensor/bittensor#install). - -After installing `bittensor`, proceed as below: - -## Steps - -## 1. Install web-genie-ai - -**NOTE: Skip this step if** you already did this during local testing and development. - -In your project directory: - -```bash -npm install pm2 -g -git clone https://github.com/web-genie-ai/web-genie-ai.git -``` - -Next, `cd` into `web-genie-ai` repo directory: - -```bash -cd web-genie-ai -``` - -Install the Bittensor subnet template package: - -```bash -curl -LsSf https://astral.sh/uv/install.sh | sh -uv sync -``` - -## 2. Create wallets - -Create wallets for subnet owner, subnet validator and for subnet miner. - -This step creates local coldkey and hotkey pairs for your three identities: subnet owner, subnet validator and subnet miner. - -The owner will create and control the subnet. The owner must have at least 100 TAO before the owner can run next steps. - -The validator and miner will be registered to the subnet created by the owner. This ensures that the validator and miner can run the respective validator and miner scripts. - -**NOTE**: You can also use existing wallets to register. Creating new keys is shown here for reference. - -Create a coldkey for the owner wallet: - -```bash -btcli wallet new_coldkey --wallet.name owner -``` - -Create a coldkey and hotkey for the subnet miner wallet: -```bash -btcli wallet new_coldkey --wallet.name miner -``` - -and - -```bash -btcli wallet new_hotkey --wallet.name miner --wallet.hotkey default -``` - -Create a coldkey and hotkey for the subnet validator wallet: - -```bash -btcli wallet new_coldkey --wallet.name validator -``` - -and - -```bash -btcli wallet new_hotkey --wallet.name validator --wallet.hotkey default -``` - -## 3. Getting the price of subnet creation - -Creating subnets on mainnet is competitive. The cost is determined by the rate at which new subnets are being registered onto the Bittensor blockchain. - -By default you must have at least 100 TAO on your owner wallet to create a subnet. However, the exact amount will fluctuate based on demand. The below code shows how to get the current price of creating a subnet. - -```bash -btcli subnet lock_cost -``` - -The above command will show: - -```bash ->> Subnet lock cost: τ100.000000000 -``` - -## 4. Purchasing a slot - -Using your TAO balance, you can register your subnet to the mainchain. This will create a new subnet on the mainchain and give you the owner permissions to it. The below command shows how to purchase a slot. - -**NOTE**: Slots cost TAO to lock. You will get this TAO back when the subnet is deregistered. - -```bash -btcli subnet create -``` - -Enter the owner wallet name. This gives permissions to the coldkey. - -```bash ->> Enter wallet name (default): owner # Enter your owner wallet name ->> Enter password to unlock key: # Enter your wallet password. ->> Register subnet? [y/n]: # Select yes (y) ->> ⠇ 📡 Registering subnet... -✅ Registered subnetwork with netuid: 1 # Your subnet netuid will show here, save this for later. -``` - -## 5. (Optional) Register keys - -**NOTE**: While this is not enforced, we recommend subnet owners to run a subnet validator and a subnet miner on the subnet to demonstrate proper use to the community. - -This step registers your subnet validator and subnet miner keys to the subnet giving them the **first two slots** on the subnet. - -Register your miner key to the subnet: - -```bash -btcli subnet recycle_register --netuid [mainnet_netuid] --subtensor.network finney --wallet.name miner --wallet.hotkey default -``` - -Follow the below prompts: - -```bash ->> Enter netuid [1] (1): # Enter netuid 1 to specify the subnet you just created. ->> Continue Registration? - hotkey: ... - coldkey: ... - network: finney [y/n]: # Select yes (y) ->> ✅ Registered -``` - -Next, register your validator key to the subnet: - -```bash -btcli subnet recycle_register --netuid [mainnet_netuid] --subtensor.network finney --wallet.name validator --wallet.hotkey default -``` - -Follow the below prompts: - -```bash ->> Enter netuid [1] (1): # Enter netuid [mainnet_netuid] to specify the subnet you just created. ->> Continue Registration? - hotkey: ... - coldkey: ... - network: finney [y/n]: # Select yes (y) ->> ✅ Registered -``` - -## 6. Check that your keys have been registered - -Check that your subnet validator key has been registered: - -```bash -btcli wallet overview --wallet.name validator -``` - -The output will be similar to the below: - -```bash -Subnet: 1 -COLDKEY HOTKEY UID ACTIVE STAKE(τ) RANK TRUST CONSENSUS INCENTIVE DIVIDENDS EMISSION(ρ) VTRUST VPERMIT UPDATED AXON HOTKEY_SS58 -miner default 0 True 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0 0.00000 14 none 5GTFrsEQfvTsh3WjiEVFeKzFTc2xcf… -1 1 2 τ0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 ρ0 0.00000 - Wallet balance: τ0.0 -``` - -Check that your subnet miner has been registered: - -```bash -btcli wallet overview --wallet.name miner -``` - -The output will be similar to the below: - -```bash -Subnet: 1 -COLDKEY HOTKEY UID ACTIVE STAKE(τ) RANK TRUST CONSENSUS INCENTIVE DIVIDENDS EMISSION(ρ) VTRUST VPERMIT UPDATED AXON HOTKEY_SS58 -miner default 1 True 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0 0.00000 14 none 5GTFrsEQfvTsh3WjiEVFeKzFTc2xcf… -1 1 2 τ0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 ρ0 0.00000 - Wallet balance: τ0.0 -``` - -## 7. Run subnet miner and subnet validator - -Run the subnet miner: - -```bash -export PYTHONPATH="." -pm2 start "uv run neurons/miners/miner.py --netuid 54 --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port]" --name webgenie_miner -``` - -You will see the below terminal output: - -```bash ->> 2023-08-08 16:58:11.223 | INFO | Running miner for subnet: 54 on network: wss://entrypoint-finney.opentensor.ai:443 with config: ... -``` - -Run the subnet validator: - -```bash -export PYTHONPATH="." -pm2 start "uv run neurons/validators/validator.py --netuid 54 --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port]" --name webgenie_validator -``` - -You will see the below terminal output: - -```bash ->> 2023-08-08 16:58:11.223 | INFO | Running validator for subnet: 54 on network: wss://entrypoint-finney.opentensor.ai:443 with config: ... -``` - -## 8. Get emissions flowing - -Register to the root subnet using the `btcli`: - -```bash -btcli root register -``` - -Then set your weights for the subnet: - -```bash -btcli root weights -``` - -## 9. Stopping your nodes - -To stop your nodes, press CTRL + C in the terminal where the nodes are running. - ---- diff --git a/docs/running_on_staging.md b/docs/running_on_staging.md deleted file mode 100644 index 16bf028b..00000000 --- a/docs/running_on_staging.md +++ /dev/null @@ -1,341 +0,0 @@ -# Running Subnet Locally - -This document will guide you through: - -- Setting up a local blockchain that is not connected to either Bittensor testchain or mainchain -- Creating a subnet -- Run your incentive mechanism on the subnet. - -## Local blockchain vs local subtensor node - -Running a local blockchain is sometimes synonymously referred as running on staging. This is **different** from running a local subtensor node that connects to the Bittensor mainchain. - -A local subtensor node will connect to the mainchain and sync with the mainchain, giving you your own access point to the mainchain. - -Running a local blockchain spins up two authority nodes locally, not connected to any other nodes or testchain or mainchain. This tutorial is for running a local blockchain. - -## Prerequisites - -Before proceeding further, make sure that you have installed Bittensor. See the below instructions: - -- [Install `bittensor`](https://github.com/opentensor/bittensor#install). - -After installing `bittensor`, proceed as below: - -## 1. Install Substrate dependencies - -Begin by installing the required dependencies for running a Substrate node. - -Update your system packages: - -```bash -sudo apt update -``` - -Install additional required libraries and tools - -```bash -sudo apt install --assume-yes make build-essential git clang curl libssl-dev llvm libudev-dev protobuf-compiler -``` - -## 2. Install Rust and Cargo - -Rust is the programming language used in Substrate development. Cargo is Rust package manager. - -Install rust and cargo: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -``` - -Update your shell's source to include Cargo's path: - -```bash -source "$HOME/.cargo/env" -``` - -## 3. Clone the subtensor repository - -This step fetches the subtensor codebase to your local machine. - -```bash -git clone https://github.com/opentensor/subtensor.git -``` - -## 4. Setup Rust - -This step ensures that you have the nightly toolchain and the WebAssembly (wasm) compilation target. Note that this step will run the subtensor chain on your terminal directly, hence we advise that you run this as a background process using PM2 or other software. - -Update to the nightly version of Rust: - -```bash -./subtensor/scripts/init.sh -``` - -## 5. Initialize - -These steps initialize your local subtensor chain in development mode. These commands will set up and run a local subtensor. - -Build the binary with the faucet feature enabled: - -```bash -cargo build -p node-subtensor --profile production --features pow-faucet -``` - -**NOTE**: The `--features pow-faucet` option in the above is required if we want to use the command `btcli wallet faucet` [See the below Mint tokens step](#8-mint-tokens-from-faucet). - -Next, run the localnet script and turn off the attempt to build the binary (as we have already done this above): - -```bash -BUILD_BINARY=0 ./scripts/localnet.sh -``` - -**NOTE**: Watch for any build or initialization outputs in this step. If you are building the project for the first time, this step will take a while to finish building, depending on your hardware. - -## 6. Install web-genie-ai subnet - -`cd` to your project directory and clone the web-genie-ai repository: - -```bash -git clone https://github.com/web-genie-ai/web-genie-ai.git -``` - -Navigate to the cloned repository: - -```bash -cd web-genie-ai -``` - -Install the web-genie-ai Python package: - -```bash -curl -LsSf https://astral.sh/uv/install.sh | sh -uv sync -``` - -## 7. Set up wallets - -You will need wallets for the different roles, i.e., subnet owner, subnet validator and subnet miner, in the subnet. - -- The owner wallet creates and controls the subnet. -- The validator and miner will be registered to the subnet created by the owner. This ensures that the validator and miner can run the respective validator and miner scripts. - -Create a coldkey for the owner role: - -```bash -btcli wallet new_coldkey --wallet.name owner -``` - -Set up the miner's wallets: - -```bash -btcli wallet new_coldkey --wallet.name miner -``` - -```bash -btcli wallet new_hotkey --wallet.name miner --wallet.hotkey default -``` - -Set up the validator's wallets: - -```bash -btcli wallet new_coldkey --wallet.name validator -``` -```bash -btcli wallet new_hotkey --wallet.name validator --wallet.hotkey default -``` - -## 8. Mint tokens from faucet - -You will need tokens to initialize the intentive mechanism on the chain as well as for registering the subnet. - -Run the following commands to mint faucet tokens for the owner and for the validator. - -Mint faucet tokens for the owner: - -```bash -btcli wallet faucet --wallet.name owner --subtensor.chain_endpoint ws://127.0.0.1:9946 -``` - -You will see: - -```bash ->> Balance: τ0.000000000 ➡ τ100.000000000 -``` - -Mint tokens for the validator: - -```bash -btcli wallet faucet --wallet.name validator --subtensor.chain_endpoint ws://127.0.0.1:9946 -``` - -You will see: - -```bash ->> Balance: τ0.000000000 ➡ τ100.000000000 -``` - -## 9. Create a subnet - -The below commands establish a new subnet on the local chain. The cost will be exactly τ1000.000000000 for the first subnet you create and you'll have to run the faucet several times to get enough tokens. - -```bash -btcli subnet create --wallet.name owner --subtensor.chain_endpoint ws://127.0.0.1:9946 -``` - -You will see: - -```bash ->> Your balance is: τ200.000000000 ->> Do you want to register a subnet for τ1000.000000000? [y/n]: ->> Enter password to unlock key: [YOUR_PASSWORD] ->> ✅ Registered subnetwork with netuid: 1 -``` - -**NOTE**: The local chain will now have a default `netuid` of 1. The second registration will create a `netuid` 2 and so on, until you reach the subnet limit of 8. If you register more than 8 subnets, then a subnet with the least staked TAO will be replaced by the 9th subnet you register. - -## 10. Register keys - -Register your subnet validator and subnet miner on the subnet. This gives your two keys unique slots on the subnet. The subnet has a current limit of 128 slots. - -Register the subnet miner: - -```bash -btcli subnet register --wallet.name miner --wallet.hotkey default --subtensor.chain_endpoint ws://127.0.0.1:9946 -``` - -Follow the below prompts: - -```bash ->> Enter netuid [1] (1): 1 ->> Continue Registration? [y/n]: y ->> ✅ Registered -``` - -Register the subnet validator: - -```bash - -btcli subnet register --wallet.name validator --wallet.hotkey default --subtensor.chain_endpoint ws://127.0.0.1:9946 -``` - -Follow the below prompts: - -``` ->> Enter netuid [1] (1): 1 ->> Continue Registration? [y/n]: y ->> ✅ Registered -``` - -## 11. Add stake - -This step bootstraps the incentives on your new subnet by adding stake into its incentive mechanism. - -```bash -btcli stake add --wallet.name validator --wallet.hotkey default --subtensor.chain_endpoint ws://127.0.0.1:9946 -``` - -Follow the below prompts: - -```bash ->> Stake all Tao from account: 'validator'? [y/n]: y ->> Stake: - τ0.000000000 ➡ τ100.000000000 -``` - -## 12. Validate key registrations - -Verify that both the miner and validator keys are successfully registered: - -```bash -btcli subnet list --subtensor.chain_endpoint ws://127.0.0.1:9946 -``` - -You will see the `2` entry under `NEURONS` column for the `NETUID` of 1, indicating that you have registered a validator and a miner in this subnet: - -```bash -NETUID NEURONS MAX_N DIFFICULTY TEMPO CON_REQ EMISSION BURN(τ) - 1 2 256.00 10.00 M 1000 None 0.00% τ1.00000 - 2 128 -``` - -See the subnet validator's registered details: - -```bash -btcli wallet overview --wallet.name validator --subtensor.chain_endpoint ws://127.0.0.1:9946 -``` - -You will see: - -``` -Subnet: 1 -COLDKEY HOTKEY UID ACTIVE STAKE(τ) RANK TRUST CONSENSUS INCENTIVE DIVIDENDS EMISSION(ρ) VTRUST VPERMIT UPDATED AXON HOTKEY_SS58 -miner default 0 True 100.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0 0.00000 14 none 5GTFrsEQfvTsh3WjiEVFeKzFTc2xcf… -1 1 2 τ100.00000 0.00000 0.00000 0.00000 0.00000 0.00000 ρ0 0.00000 - Wallet balance: τ0.0 -``` - -See the subnet miner's registered details: - -```bash -btcli wallet overview --wallet.name miner --subtensor.chain_endpoint ws://127.0.0.1:9946 -``` - -You will see: - -```bash -Subnet: 1 -COLDKEY HOTKEY UID ACTIVE STAKE(τ) RANK TRUST CONSENSUS INCENTIVE DIVIDENDS EMISSION(ρ) VTRUST VPERMIT UPDATED AXON HOTKEY_SS58 -miner default 1 True 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0 0.00000 14 none 5GTFrsEQfvTsh3WjiEVFeKzFTc2xcf… -1 1 2 τ0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 ρ0 0.00000 - Wallet balance: τ0.0 - -``` - -## 13. Run subnet miner and subnet validator - -Run the subnet miner and subnet validator. Make sure to specify your subnet parameters. - -Run the subnet miner: - -```bash -uv run neurons/miners/miner.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name miner --wallet.hotkey default --logging.debug -``` - -Run the subnet validator: - -```bash -uv run neurons/validators/validator.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name validator --wallet.hotkey default --logging.debug -``` - -## 14. Set weights for your subnet - -Register a validator on the root subnet and boost to set weights for your subnet. This is a necessary step to ensure that the subnet is able to receive emmissions. - -### Register your validator on the root subnet - -```bash -btcli root register --wallet.name validator --wallet.hotkey default --subtensor.chain_endpoint ws://127.0.0.1:9946 -``` - -### Boost your subnet on the root subnet -```bash -btcli root boost --netuid 1 --increase 1 --wallet.name validator --wallet.hotkey default --subtensor.chain_endpoint ws://127.0.0.1:9946 -``` - -## 15. Verify your incentive mechanism - -After a few blocks the subnet validator will set weights. This indicates that the incentive mechanism is active. Then after a subnet tempo elapses (360 blocks or 72 minutes) you will see your incentive mechanism beginning to distribute TAO to the subnet miner. - -```bash -btcli wallet overview --wallet.name miner --subtensor.chain_endpoint ws://127.0.0.1:9946 -``` - -## Ending your session - -To halt your nodes: -```bash -# Press CTRL + C keys in the terminal. -``` - ---- diff --git a/docs/running_on_testnet.md b/docs/running_on_testnet.md deleted file mode 100644 index 4c5ea266..00000000 --- a/docs/running_on_testnet.md +++ /dev/null @@ -1,243 +0,0 @@ -# Running Subnet on Testnet - -This tutorial shows how to use the Bittensor testnet to create a subnet and run your incentive mechanism on it. - -**IMPORTANT:** We strongly recommend that you first run [Running Subnet Locally](running_on_staging.md) before running on the testnet. Incentive mechanisms running on the testnet are open to anyone, and although these mechanisms on testnet do not emit real TAO, they cost you test TAO which you must create. - -**DANGER** -- Do not expose your private keys. -- Only use your testnet wallet. -- Do not reuse the password of your mainnet wallet. -- Make sure your incentive mechanism is resistant to abuse. - -## Prerequisites - -Before proceeding further, make sure that you have installed Bittensor. See the below instructions: - -- [Install `bittensor`](https://github.com/opentensor/bittensor#install). - -After installing `bittensor`, proceed as below: - -## 1. Install web-genie-ai - -**NOTE: Skip this step if** you already did this during local testing and development. - -`cd` into your project directory and clone the web-genie-ai repo: - -```bash -git clone https://github.com/web-genie-ai/web-genie-ai.git -``` - -Next, `cd` into web-genie-ai repo directory: - -```bash -cd web-genie-ai # Enter the -``` - -Install the web-genie-ai package: - -```bash -curl -LsSf https://astral.sh/uv/install.sh | sh -uv sync -``` - -## 2. Create wallets - -Create wallets for subnet owner, subnet validator and for subnet miner. - -This step creates local coldkey and hotkey pairs for your three identities: subnet owner, subnet validator and subnet miner. - -The owner will create and control the subnet. The owner must have at least 100 testnet TAO before the owner can run next steps. - -The validator and miner will be registered to the subnet created by the owner. This ensures that the validator and miner can run the respective validator and miner scripts. - -Create a coldkey for your owner wallet: - -```bash -btcli wallet new_coldkey --wallet.name owner -``` - -Create a coldkey and hotkey for your miner wallet: - -```bash -btcli wallet new_coldkey --wallet.name miner -``` - -and - -```bash -btcli wallet new_hotkey --wallet.name miner --wallet.hotkey default -``` - -Create a coldkey and hotkey for your validator wallet: - -```bash -btcli wallet new_coldkey --wallet.name validator -``` - -and - -```bash -btcli wallet new_hotkey --wallet.name validator --wallet.hotkey default -``` - -## 3. Get the price of subnet creation - -Creating subnets on the testnet is competitive. The cost is determined by the rate at which new subnets are being registered onto the chain. - -By default you must have at least 100 testnet TAO in your owner wallet to create a subnet. However, the exact amount will fluctuate based on demand. The below command shows how to get the current price of creating a subnet. - -```bash -btcli subnet lock_cost --subtensor.network test -``` - -The above command will show: - -```bash ->> Subnet lock cost: τ100.000000000 -``` - -## 4. (Optional) Get faucet tokens - -Faucet is disabled on the testnet. Hence, if you don't have sufficient faucet tokens, ask the [Bittensor Discord community](https://discord.com/channels/799672011265015819/830068283314929684) for faucet tokens. - -## 5. Purchase a slot - -Using the test TAO from the previous step you can register your subnet on the testnet. This will create a new subnet on the testnet and give you the owner permissions to it. - -The below command shows how to purchase a slot. - -**NOTE**: Slots cost TAO to lock. You will get this TAO back when the subnet is deregistered. - -```bash -btcli subnet create --subtensor.network test -``` - -Enter the owner wallet name which gives permissions to the coldkey: - -```bash ->> Enter wallet name (default): owner # Enter your owner wallet name ->> Enter password to unlock key: # Enter your wallet password. ->> Register subnet? [y/n]: # Select yes (y) ->> ⠇ 📡 Registering subnet... -✅ Registered subnetwork with netuid: 1 # Your subnet netuid will show here, save this for later. -``` - -## 6. Register keys - -This step registers your subnet validator and subnet miner keys to the subnet, giving them the **first two slots** on the subnet. - -Register your miner key to the subnet: - -```bash -btcli subnet register --netuid 214 --subtensor.network test --wallet.name miner --wallet.hotkey default -``` - -Follow the below prompts: - -```bash ->> Enter netuid [1] (1): # Enter netuid 214 to specify web-genie-ai test subnet. ->> Continue Registration? - hotkey: ... - coldkey: ... - network: finney [y/n]: # Select yes (y) ->> ✅ Registered -``` - -Next, register your validator key to the subnet: - -```bash -btcli subnet register --netuid 214 --subtensor.network test --wallet.name validator --wallet.hotkey default -``` - -Follow the prompts: - -```bash ->> Enter netuid [1] (1): # Enter netuid 214 to specify web-genie-ai test subnet. ->> Continue Registration? - hotkey: ... - coldkey: ... - network: finney [y/n]: # Select yes (y) ->> ✅ Registered -``` - -## 7. Check that your keys have been registered - -This step returns information about your registered keys. - -Check that your validator key has been registered: - -```bash -btcli wallet overview --wallet.name validator --subtensor.network test -``` - -The above command will display the below: - -```bash -Subnet: 1 -COLDKEY HOTKEY UID ACTIVE STAKE(τ) RANK TRUST CONSENSUS INCENTIVE DIVIDENDS EMISSION(ρ) VTRUST VPERMIT UPDATED AXON HOTKEY_SS58 -miner default 0 True 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0 0.00000 14 none 5GTFrsEQfvTsh3WjiEVFeKzFTc2xcf… -1 1 2 τ0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 ρ0 0.00000 - Wallet balance: τ0.0 -``` - -Check that your miner has been registered: - -```bash -btcli wallet overview --wallet.name miner --subtensor.network test -``` - -The above command will display the below: - -```bash -Subnet: 1 -COLDKEY HOTKEY UID ACTIVE STAKE(τ) RANK TRUST CONSENSUS INCENTIVE DIVIDENDS EMISSION(ρ) VTRUST VPERMIT UPDATED AXON HOTKEY_SS58 -miner default 1 True 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0 0.00000 14 none 5GTFrsEQfvTsh3WjiEVFeKzFTc2xcf… -1 1 2 τ0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 ρ0 0.00000 - Wallet balance: τ0.0 -``` - -## 8. Run subnet miner and subnet validator - -Run the subnet miner: - -```bash -uv run neurons/miners/miner.py --netuid 214 --subtensor.network test --wallet.name miner --wallet.hotkey default --logging.debug -``` - -You will see the below terminal output: - -```bash ->> 2023-08-08 16:58:11.223 | INFO | Running miner for subnet: 214 on network: ws://127.0.0.1:9946 with config: ... -``` - -Next, run the subnet validator: - -```bash -uv run neurons/validators/validator.py --netuid 214 --subtensor.network test --wallet.name validator --wallet.hotkey default --logging.debug -``` - -You will see the below terminal output: - -```bash ->> 2023-08-08 16:58:11.223 | INFO | Running validator for subnet: 214 on network: ws://127.0.0.1:9946 with config: ... -``` - - -## 9. Get emissions flowing - -Register to the root network using the `btcli`: - -```bash -btcli root register --subtensor.network test -``` - -Then set your weights for the subnet: - -```bash -btcli root weights --subtensor.network test -``` - -## 10. Stopping your nodes - -To stop your nodes, press CTRL + C in the terminal where the nodes are running. diff --git a/docs/stream_tutorial/README.md b/docs/stream_tutorial/README.md deleted file mode 100644 index f213fd3a..00000000 --- a/docs/stream_tutorial/README.md +++ /dev/null @@ -1,490 +0,0 @@ -# Bittensor Streaming Tutorial -This document is intented as a developer-friendly walkthrough of integrating streaming into your bittensor application. - -If you prefer to jump right into a complete stand-alone example, see: -- `miner.py` -- `protocol.py` -- `client.py` - -Start your miner: -```bash -python miner.py --netuid 8 --wallet.name default --wallet.hotkey miner --subtensor.network test --axon.port 10000 --logging.trace -``` - -Run the client: -```bash -python client.py --netuid 8 --my_uid 1 --network test -``` - -## Overview -This tutorial is designed to show you how to use the streaming API to integrate into your application. It will cover the following topics: -- writing your streaming protocol (inherits from bittensor.StreamingSynapse) -- writing your streaming server (uses your streaming protocol) -- writing your streaming client (uses your streaming protocol) - -### Defining your streaming protocol -When designing your protocol, it would be helpful to look at the bittensor.StreamingSynapse for reference. Below is a condensed snippet of the abstract methods that you will need to implement in your subclass. - -You will need to implement two methods: - -- `process_streaming_response` -- `extract_response_json` - -These two methods are the core of your streaming protocol. The first method process_streaming_response is called as the response is being streamed from the network. It is responsible for handling the streaming response, such as parsing and accumulating data. The second method extract_response_json is called after the response has been processed and is responsible for retrieving structured data to be post-processed in the dendrite in bittensor core code. - -```python -class StreamingSynapse(bittensor.Synapse, ABC): - ... - class BTStreamingResponse(_StreamingResponse): - ... - @abstractmethod - async def process_streaming_response(self, response: Response): - """ - Abstract method that must be implemented by the subclass. - This method should provide logic to handle the streaming response, such as parsing and accumulating data. - It is called as the response is being streamed from the network, and should be implemented to handle the specific - streaming data format and requirements of the subclass. - - Args: - response: The response object to be processed, typically containing chunks of data. - """ - ... - - @abstractmethod - def extract_response_json(self, response: Response) -> dict: - """ - Abstract method that must be implemented by the subclass. - This method should provide logic to extract JSON data from the response, including headers and content. - It is called after the response has been processed and is responsible for retrieving structured data - that can be used by the application. - - Args: - response: The response object from which to extract JSON data. - """ - ... - ... -``` - -See the full reference code at the bittensor [repo](https://github.com/opentensor/bittensor/blob/master/bittensor/stream.py). - - -#### Create your protocol -Let's walk through how to create a protocol using the bittensor.StreamingSynapse class. -```python -class MyStreamingSynapse(bt.StreamingSynapse): - # define your expected data fields here as pydantic field objects - # This allows you to control what information is passed along the network - messages: List[str] = pydantic.Field( - ..., # this ellipsis (...) indicates the object is required - title="Messages", # What is the name of this field? - description="A list of messages in the Prompting scenario. Immutable.", - allow_mutation=False, # disallow modification of this field after creation - ) - completion: str = pydantic.Field( - "", - title="Completion", - ) - # add fields as necessary - ... - - # This method controls how your synapse is deserialized from the network - # E.g. you can extract whatever information you want to receive at the final - # yield in the async generator returned by the server, without receiving - # the entire synapse object itself. - # In this example, we just want the completion string at the end. - def deserialize(self) -> str: - return self.completion - - # implement your `process_streaming_response` logic to actually yield objects to the streamer - # this effectively defines the async generator that you'll recieve on the client side - async def process_streaming_response(self, response: MyStreamingSynapse): - # this is an example of how you might process a streaming response - # iterate over the response content and yield each line - async for chunk in response.content.iter_any(): - tokens = chunk.decode("utf-8").split("\n") - yield tokens - - # implement `extract_response_json` to extract the JSON data from the response headers - # this will be dependent on the data you are streaming and how you want to structure it - # it MUST conform to the following format expected by the bittensor dendrite: - """ - { - # METADATA AND HEADERS - "name": ..., - "timeout": float(...), - "total_size": int(...), - "header_size": int(...), - "dendrite": ..., - "axon": ..., - # YOUR FIELDS - "messages": self.messages, - ... - } - """ - def extract_response_json(self, response: MyStreamingSynapse) -> dict: - # iterate over the response headers and extract the necessary data - headers = { - k.decode("utf-8"): v.decode("utf-8") - for k, v in response.__dict__["_raw_headers"] - } - # helper function to extract data from headers - def extract_info(prefix): - return { - key.split("_")[-1]: value - for key, value in headers.items() - if key.startswith(prefix) - } - # return the extracted data in the expected format - return { - "name": headers.get("name", ""), - "timeout": float(headers.get("timeout", 0)), - "total_size": int(headers.get("total_size", 0)), - "header_size": int(headers.get("header_size", 0)), - "dendrite": extract_info("bt_header_dendrite"), # dendrite info - "axon": extract_info("bt_header_axon"), # axon info - "messages": self.messages, # field object - } -``` - -[Here](https://github.com/opentensor/text-prompting/blob/main/prompting/protocol.py#L131) is a full example implementation of a streaming protocol based on the text-prompting network. - -Please read the docstrings provided, they can be very helpful! - -### Writing the server -Great! Now we have our protocol defined, let's see how to define our server. -This will generate the tokens to be streamed in this prompting example. - -For brevity we will not be building a full miner, but inspecting the central components. -```python -class MyStreamPromptingMiner(bt.Miner): - ... # any relevant methods you'd need for your miner - - # define your server forward here - # NOTE: It is crucial that your typehints are correct and reflect your streaming protocol object - # otherwise the axon will reject adding your route to the server. - def forward(self, synapse: MyStreamingSynapse) -> MyStreamingSynapse: - # Let's use a GPT2 tokenizer for this toy example - tokenizer = GPT2Tokenizer.from_pretrained("gpt2") - - # Simulated function to decode token IDs into strings. In a real-world scenario, - # this can be replaced with an actual model inference step. - def model(ids): - return (tokenizer.decode(id) for id in ids) - - # This function is called asynchronously to process the input text and send back tokens - # as a streaming response. It essentially produces the async generator that will be - # consumed by the client with an `async for` loop. - async def _forward(text: str, send: Send): - # `text` may be the input prompt to your model in a real-world scenario. - # let's tokenize them into IDs for the sake of this example. - input_ids = tokenizer(text, return_tensors="pt").input_ids.squeeze() - - # You may want to buffer your tokens before sending them back to the client. - # this can be useful so we aren't flooding the client with individual tokens - # and allows you more fine-grained control over how much data is sent back - # with each yield. - N = 3 # Number of tokens to send back to the client at a time - buffer = [] - # Iterate over the tokens and send the generationed tokens back to the client - # when we have sufficient (N) tokens in the buffer. - for token in model(input_ids): - buffer.append(token) # Add token to buffer - - # If buffer has N tokens, send them back to the client. - if len(buffer) == N: - joined_buffer = "".join(buffer) - # Send the tokens back to the client - # This is the core of the streaming response and the format - # is important. The `send` function is provided by the ASGI server - # and is responsible for sending the response back to the client. - # This buffer will be received by the client as a single chunk of - # data, which can then be split into individual tokens! - await send( - { - "type": "http.response.body", - "body": joined_buffer.encode("utf-8"), - "more_body": True, - } - ) - buffer = [] # Clear the buffer for next batch of tokens - - # Create a streaming response object using the `_forward` function - # It is useful to wrap your _forward function in a partial function - # to pass in the text argument lazily. - token_streamer = partial(_forward, synapse.messages[0]) - # Return the streaming response object, which is an instance of the - # `BTStreamingResponse` class. - return synapse.create_streaming_response(token_streamer) -``` - -#### Complete Example -Here is a full example for reference: -> This inherits from the prompting (text-prompting) miner base class. -> Take a look at the `prompting/baseminer/miner.py` file [here](https://github.com/opentensor/text-prompting/blob/main/prompting/baseminer/miner.py) for more details. - -```python -class StreamingTemplateMiner(prompting.Miner): - def config(self) -> "bt.Config": - """ - Returns the configuration object specific to this miner. - - Implement and extend this method to provide custom configurations for the miner. - Currently, it sets up a basic configuration parser. - - Returns: - bt.Config: A configuration object with the miner's operational parameters. - """ - parser = argparse.ArgumentParser(description="Streaming Miner Configs") - self.add_args(parser) - return bt.config(parser) - - def add_args(cls, parser: argparse.ArgumentParser): - """ - Adds custom arguments to the command line parser. - - Developers can introduce additional command-line arguments specific to the miner's - functionality in this method. These arguments can then be used to configure the miner's operation. - - Args: - parser (argparse.ArgumentParser): - The command line argument parser to which custom arguments should be added. - """ - pass - - def prompt(self, synapse: StreamPrompting) -> StreamPrompting: - """ - Generates a streaming response for the provided synapse. - - This function serves as the main entry point for handling streaming prompts. It takes - the incoming synapse which contains messages to be processed and returns a streaming - response. The function uses the GPT-2 tokenizer and a simulated model to tokenize and decode - the incoming message, and then sends the response back to the client token by token. - - Args: - synapse (StreamPrompting): The incoming StreamPrompting instance containing the messages to be processed. - - Returns: - StreamPrompting: The streaming response object which can be used by other functions to - stream back the response to the client. - - Usage: - This function can be extended and customized based on specific requirements of the - miner. Developers can swap out the tokenizer, model, or adjust how streaming responses - are generated to suit their specific applications. - """ - bt.logging.trace("In outer PROMPT()") - tokenizer = GPT2Tokenizer.from_pretrained("gpt2") - - # Simulated function to decode token IDs into strings. In a real-world scenario, - # this can be replaced with an actual model inference step. - def model(ids): - return (tokenizer.decode(id) for id in ids) - - async def _prompt(text: str, send: Send): - """ - Asynchronously processes the input text and sends back tokens as a streaming response. - - This function takes an input text, tokenizes it using the GPT-2 tokenizer, and then - uses the simulated model to decode token IDs into strings. It then sends each token - back to the client as a streaming response, with a delay between tokens to simulate - the effect of real-time streaming. - - Args: - text (str): The input text message to be processed. - send (Send): An asynchronous function that allows sending back the streaming response. - - Usage: - This function can be adjusted based on the streaming requirements, speed of - response, or the model being used. Developers can also introduce more sophisticated - processing steps or modify how tokens are sent back to the client. - """ - bt.logging.trace("In inner _PROMPT()") - input_ids = tokenizer(text, return_tensors="pt").input_ids.squeeze() - buffer = [] - bt.logging.debug(f"Input text: {text}") - bt.logging.debug(f"Input ids: {input_ids}") - - N = 3 # Number of tokens to send back to the client at a time - for token in model(input_ids): - bt.logging.trace(f"appending token: {token}") - buffer.append(token) - # If buffer has N tokens, send them back to the client. - if len(buffer) == N: - time.sleep(0.1) - joined_buffer = "".join(buffer) - bt.logging.debug(f"sedning tokens: {joined_buffer}") - await send( - { - "type": "http.response.body", - "body": joined_buffer.encode("utf-8"), - "more_body": True, - } - ) - bt.logging.debug(f"Streamed tokens: {joined_buffer}") - buffer = [] # Clear the buffer for next batch of tokens - - # Send any remaining tokens in the buffer - if buffer: - joined_buffer = "".join(buffer) - await send( - { - "type": "http.response.body", - "body": joined_buffer.encode("utf-8"), - "more_body": False, # No more tokens to send - } - ) - bt.logging.trace(f"Streamed tokens: {joined_buffer}") - - message = synapse.messages[0] - bt.logging.trace(f"message in _prompt: {message}") - token_streamer = partial(_prompt, message) - bt.logging.trace(f"token streamer: {token_streamer}") - return synapse.create_streaming_response(token_streamer) -``` - -### Writing the client -Excellent! Now we have defined our server, now we can define our client. - -This has assumed you have: -1. Registered your miner on the chain (`finney`/`test`) -2. Are serving your miner on an open port (e.g. `12345`) - -Steps: -- Instantiate your synapse subclass with the relevant information. E.g. `messages`, `roles`, etc. -- Instantiate your wallet and a dendrite client -- Query the dendrite client with your synapse object -- Iterate over the async generator to extract the yielded tokens on the server side - -```python - -# Import bittensor -import bittensor as bt - -# Create your streaming synapse subclass object to house the request body -syn = MyStreamingSynapse( - roles=["user"], - messages=["hello this is a test of a streaming response. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."] -) - -# Create a wallet instance that must be registered on the network -wallet = bt.wallet(name="default", hotkey="default") - -# Instantiate the metagraph -metagraph = bt.metagraph( - netuid=8, network="test", sync=True, lite=False -) - -# Grab the axon you're serving -my_uid = 1 -axon = metagraph.axons[my_uid] - -# Create a Dendrite instance to handle client-side communication. -dendrite = bt.dendrite(wallet=wallet) - - -This is an async function so we can use the `await` keyword when querying the server with the dendrite object. -async def main(): - # Send a request to the Axon using the Dendrite, passing in a StreamPrompting - # instance with roles and messages. The response is awaited, as the Dendrite - # communicates asynchronously with the Axon. Returns a list of async generator. - responses = await dendrite( - [axon], - syn, - deserialize=False, - streaming=True - ) - - # Now that we have our responses we want to iterate over the yielded tokens - # iterate over the async generator to extract the yielded tokens on server side - for resp in responses: - i=0 - async for chunk in resp: - i += 1 - if i % 5 == 0: - print() - if isinstance(chunk, list): - print(chunk[0], end="", flush=True) - else: - # last object yielded is the synapse itself with completion filled - synapse = chunk - break - - # The synapse object contains the completion attribute which contains the - # accumulated tokens from the streaming response. - -if __name__ == "__main__": - # Run the main function with asyncio - asyncio.run(main()) - -``` -There you have it! - -### Complete example -If you would like to see a complete standalone example that only depends on bittensor>=6.2.0, look below: - -- client.py -- streaming_miner.py -- - -# client.py -```python -# Import bittensor and the text-prompting packages -import bittensor as bt -import prompting - -# Create a StreamPrompting synapse object to house the request body -syn = prompting.protocol.StreamPrompting( - roles=["user"], - messages=["hello this is a test of a streaming response. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."]) -syn - -# create a wallet instance that must be registered on the network -wallet = bt.wallet(name="default", hotkey="default") -wallet - -# instantiate the metagraph -metagraph = bt.metagraph( - netuid=8, network="test", sync=True, lite=False -) -metagraph - -# Grab the axon you're serving -axon = metagraph.axons[62] -axon - -# Create a Dendrite instance to handle client-side communication. -d = bt.dendrite(wallet=wallet) -d - - -async def main(): - - # Send a request to the Axon using the Dendrite, passing in a StreamPrompting - # instance with roles and messages. The response is awaited, as the Dendrite - # communicates asynchronously with the Axon. Returns a list of async generator. - responses = await d( - [axon], - syn, - deserialize=False, - streaming=True - ) - responses - - # iterate over the async generator to extract the yielded tokens on server side - for resp in responses: - i=0 - async for chunk in resp: - i += 1 - if i % 5 == 0: - print() - if isinstance(chunk, list): - print(chunk[0], end="", flush=True) - else: - # last object yielded is the synapse itself with completion filled - synapse = chunk - break - -if __name__ == "__main__": - import asyncio - asyncio.run(main()) -``` diff --git a/docs/stream_tutorial/client.py b/docs/stream_tutorial/client.py deleted file mode 100644 index 4823f715..00000000 --- a/docs/stream_tutorial/client.py +++ /dev/null @@ -1,104 +0,0 @@ -import argparse -import asyncio -import bittensor as bt - -from protocol import StreamPrompting - -""" -This has assumed you have: -1. Registered your miner on the chain (finney/test) -2. Are serving your miner on an open(e.g. 12345) - -Steps: -- Instantiate your synapse subclass with the relevant information. E.g. messages, roles, etc. -- Instantiate your wallet and a dendrite client -- Query the dendrite client with your synapse object -- Iterate over the async generator to extract the yielded tokens on the server side -""" - - -async def query_synapse(my_uid, wallet_name, hotkey, network, netuid): - syn = StreamPrompting( - roles=["user"], - messages=[ - "hello this is a test of a streaming response. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." - ], - ) - - # create a wallet instance with provided wallet name and hotkey - wallet = bt.wallet(name=wallet_name, hotkey=hotkey) - - # instantiate the metagraph with provided network and netuid - metagraph = bt.metagraph( - netuid=netuid, network=network, sync=True, lite=False - ) - - # Grab the axon you're serving - axon = metagraph.axons[my_uid] - - # Create a Dendrite instance to handle client-side communication. - dendrite = bt.dendrite(wallet=wallet) - - async def main(): - responses = await dendrite( - [axon], syn, deserialize=False, streaming=True - ) - - for resp in responses: - i = 0 - async for chunk in resp: - i += 1 - if i % 5 == 0: - print() - if isinstance(chunk, list): - print(chunk[0], end="", flush=True) - else: - # last object yielded is the synapse itself with completion filled - synapse = chunk - break - - # Run the main function with asyncio - await main() - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Query a Bittensor synapse with given parameters." - ) - - # Adding arguments - parser.add_argument( - "--my_uid", - type=int, - required=True, - help="Your unique miner ID on the chain", - ) - parser.add_argument( - "--netuid", type=int, required=True, help="Network Unique ID" - ) - parser.add_argument( - "--wallet_name", type=str, default="default", help="Name of the wallet" - ) - parser.add_argument( - "--hotkey", type=str, default="default", help="Hotkey for the wallet" - ) - parser.add_argument( - "--network", - type=str, - default="test", - help='Network type, e.g., "test" or "mainnet"', - ) - - # Parse arguments - args = parser.parse_args() - - # Running the async function with provided arguments - asyncio.run( - query_synapse( - args.my_uid, - args.wallet_name, - args.hotkey, - args.network, - args.netuid, - ) - ) diff --git a/docs/stream_tutorial/config.py b/docs/stream_tutorial/config.py deleted file mode 100644 index 7cbe82ca..00000000 --- a/docs/stream_tutorial/config.py +++ /dev/null @@ -1,116 +0,0 @@ -import bittensor as bt -import argparse -import os - - -def check_config(cls, config: "bt.Config"): - bt.axon.check_config(config) - bt.logging.check_config(config) - full_path = os.path.expanduser( - "{}/{}/{}/{}".format( - config.logging.logging_dir, - config.wallet.get("name", bt.defaults.wallet.name), - config.wallet.get("hotkey", bt.defaults.wallet.hotkey), - config.miner.name, - ) - ) - config.miner.full_path = os.path.expanduser(full_path) - if not os.path.exists(config.miner.full_path): - os.makedirs(config.miner.full_path) - - -def get_config() -> "bt.Config": - parser = argparse.ArgumentParser() - parser.add_argument( - "--axon.port", type=int, default=8098, help="Port to run the axon on." - ) - # Subtensor network to connect to - parser.add_argument( - "--subtensor.network", - default="finney", - help="Bittensor network to connect to.", - ) - # Chain endpoint to connect to - parser.add_argument( - "--subtensor.chain_endpoint", - default="wss://entrypoint-finney.opentensor.ai:443", - help="Chain endpoint to connect to.", - ) - # Adds override arguments for network and netuid. - parser.add_argument( - "--netuid", type=int, default=1, help="The chain subnet uid." - ) - - parser.add_argument( - "--miner.root", - type=str, - help="Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name ", - default="~/.bittensor/miners/", - ) - parser.add_argument( - "--miner.name", - type=str, - help="Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name ", - default="Bittensor Miner", - ) - - # Run config. - parser.add_argument( - "--miner.blocks_per_epoch", - type=str, - help="Blocks until the miner repulls the metagraph from the chain", - default=100, - ) - - # Switches. - parser.add_argument( - "--miner.no_serve", - action="store_true", - help="If True, the miner doesnt serve the axon.", - default=False, - ) - parser.add_argument( - "--miner.no_start_axon", - action="store_true", - help="If True, the miner doesnt start the axon.", - default=False, - ) - - # Mocks. - parser.add_argument( - "--miner.mock_subtensor", - action="store_true", - help="If True, the miner will allow non-registered hotkeys to mine.", - default=False, - ) - - # Adds subtensor specific arguments i.e. --subtensor.chain_endpoint ... --subtensor.network ... - bt.subtensor.add_args(parser) - - # Adds logging specific arguments i.e. --logging.debug ..., --logging.trace .. or --logging.logging_dir ... - bt.logging.add_args(parser) - - # Adds wallet specific arguments i.e. --wallet.name ..., --wallet.hotkey ./. or --wallet.path ... - bt.wallet.add_args(parser) - - # Adds axon specific arguments i.e. --axon.port ... - bt.axon.add_args(parser) - - # Activating the parser to read any command-line inputs. - # To print help message, run python3 template/miner.py --help - config = bt.config(parser) - - # Logging captures events for diagnosis or understanding miner's behavior. - config.full_path = os.path.expanduser( - "{}/{}/{}/netuid{}/{}".format( - config.logging.logging_dir, - config.wallet.name, - config.wallet.hotkey, - config.netuid, - "miner", - ) - ) - # Ensure the directory for logging exists, else create one. - if not os.path.exists(config.full_path): - os.makedirs(config.full_path, exist_ok=True) - return config diff --git a/docs/stream_tutorial/miner.py b/docs/stream_tutorial/miner.py deleted file mode 100644 index a62814d2..00000000 --- a/docs/stream_tutorial/miner.py +++ /dev/null @@ -1,398 +0,0 @@ -import copy -import time -import asyncio -import argparse -import threading -import traceback -from abc import ABC, abstractmethod -from functools import partial -from starlette.types import Send - -import bittensor as bt -from transformers import GPT2Tokenizer -from typing import List, Dict, Tuple, Union, Callable, Awaitable - -from protocol import StreamPrompting -from config import get_config, check_config - - -class StreamMiner(ABC): - def __init__(self, config=None, axon=None, wallet=None, subtensor=None): - # Setup base config from Miner.config() and merge with subclassed config. - base_config = copy.deepcopy(config or get_config()) - self.config = self.config() - self.config.merge(base_config) - - check_config(StreamMiner, self.config) - bt.logging.info(self.config) # TODO: duplicate print? - - self.prompt_cache: Dict[str, Tuple[str, int]] = {} - - # Activating Bittensor's logging with the set configurations. - bt.logging.set_config(config=self.config.logging) - - # Wallet holds cryptographic information, ensuring secure transactions and communication. - self.wallet = wallet or bt.wallet(config=self.config) - bt.logging.info(f"Wallet {self.wallet}") - - # subtensor manages the blockchain connection, facilitating interaction with the Bittensor blockchain. - self.subtensor = subtensor or bt.subtensor(config=self.config) - bt.logging.info(f"Subtensor: {self.subtensor}") - bt.logging.info( - f"Running miner for subnet: {self.config.netuid} on network: {self.subtensor.chain_endpoint} with config:" - ) - - # metagraph provides the network's current state, holding state about other participants in a subnet. - self.metagraph = self.subtensor.metagraph(self.config.netuid) - bt.logging.info(f"Metagraph: {self.metagraph}") - - if self.wallet.hotkey.ss58_address not in self.metagraph.hotkeys: - bt.logging.error( - f"\nYour validator: {self.wallet} if not registered to chain connection: {self.subtensor} \nRun btcli register and try again. " - ) - exit() - else: - # Each miner gets a unique identity (UID) in the network for differentiation. - self.my_subnet_uid = self.metagraph.hotkeys.index( - self.wallet.hotkey.ss58_address - ) - bt.logging.info(f"Running miner on uid: {self.my_subnet_uid}") - - # The axon handles request processing, allowing validators to send this process requests. - self.axon = axon or bt.axon( - wallet=self.wallet, port=self.config.axon.port - ) - # Attach determiners which functions are called when servicing a request. - bt.logging.info(f"Attaching forward function to axon.") - print(f"Attaching forward function to axon. {self._prompt}") - self.axon.attach( - forward_fn=self._prompt, - ) - bt.logging.info(f"Axon created: {self.axon}") - - # Instantiate runners - self.should_exit: bool = False - self.is_running: bool = False - self.thread: threading.Thread = None - self.lock = asyncio.Lock() - self.request_timestamps: Dict = {} - - @abstractmethod - def config(self) -> "bt.Config": - ... - - @classmethod - @abstractmethod - def add_args(cls, parser: argparse.ArgumentParser): - ... - - def _prompt(self, synapse: StreamPrompting) -> StreamPrompting: - """ - A wrapper method around the `prompt` method that will be defined by the subclass. - - This method acts as an intermediary layer to perform pre-processing before calling the - actual `prompt` method implemented in the subclass. Specifically, it checks whether a - prompt is in cache to avoid reprocessing recent requests. If the prompt is not in the - cache, the subclass `prompt` method is called. - - Args: - synapse (StreamPrompting): The incoming request object encapsulating the details of the request. - - Returns: - StreamPrompting: The response object to be sent back in reply to the incoming request, essentially - the filled synapse request object. - - Raises: - ValueError: If the prompt is found in the cache indicating it was sent recently. - - Example: - This method is not meant to be called directly but is invoked internally when a request - is received, and it subsequently calls the `prompt` method of the subclass. - """ - return self.prompt(synapse) - - @abstractmethod - def prompt(self, synapse: StreamPrompting) -> StreamPrompting: - """ - Abstract method to handle and respond to incoming requests to the miner. - - Subclasses should implement this method to define their custom logic for processing and - responding to requests. This method is designed to be overridden, and its behavior will - be dependent on the specific implementation provided in the subclass. - - Args: - synapse (StreamPrompting): The incoming request object encapsulating the details - of the request. This must contain `messages` and `roles` as fields. - - Returns: - StreamPrompting: The response object that should be sent back in reply to the - incoming request. This is essentially the filled synapse request object. - - Example: - class CustomMiner(Miner): - def prompt(self, synapse: StreamPrompting) -> StreamPrompting: - # Custom logic to process and respond to the request. - synapse.completion = "The meaning of life is 42." - return synapse - """ - ... - - def run(self): - """ - Runs the miner logic. This method starts the miner's operations, including - listening for incoming requests and periodically updating the miner's knowledge - of the network graph. - """ - if not self.subtensor.is_hotkey_registered( - netuid=self.config.netuid, - hotkey_ss58=self.wallet.hotkey.ss58_address, - ): - bt.logging.error( - f"Wallet: {self.wallet} is not registered on netuid {self.config.netuid}" - f"Please register the hotkey using `btcli subnets register` before trying again" - ) - exit() - - # Serve passes the axon information to the network + netuid we are hosting on. - # This will auto-update if the axon port of external ip have changed. - bt.logging.info( - f"Serving axon {StreamPrompting} on network: {self.config.subtensor.chain_endpoint} with netuid: {self.config.netuid}" - ) - self.axon.serve(netuid=self.config.netuid, subtensor=self.subtensor) - - # Start starts the miner's axon, making it active on the network. - bt.logging.info( - f"Starting axon server on port: {self.config.axon.port}" - ) - self.axon.start() - - # --- Run until should_exit = True. - self.last_epoch_block = self.subtensor.get_current_block() - bt.logging.info(f"Miner starting at block: {self.last_epoch_block}") - - # This loop maintains the miner's operations until intentionally stopped. - bt.logging.info(f"Starting main loop") - step = 0 - try: - while not self.should_exit: - start_epoch = time.time() - - # --- Wait until next epoch. - current_block = self.subtensor.get_current_block() - while ( - current_block - self.last_epoch_block - < self.config.miner.blocks_per_epoch - ): - # --- Wait for next bloc. - time.sleep(1) - current_block = self.subtensor.get_current_block() - - # --- Check if we should exit. - if self.should_exit: - break - - # --- Update the metagraph with the latest network state. - self.last_epoch_block = self.subtensor.get_current_block() - - metagraph = self.subtensor.metagraph( - netuid=self.config.netuid, - lite=True, - block=self.last_epoch_block, - ) - log = ( - f"Step:{step} | " - f"Block:{metagraph.block.item()} | " - f"Stake:{metagraph.S[self.my_subnet_uid]} | " - f"Rank:{metagraph.R[self.my_subnet_uid]} | " - f"Trust:{metagraph.T[self.my_subnet_uid]} | " - f"Consensus:{metagraph.C[self.my_subnet_uid] } | " - f"Incentive:{metagraph.I[self.my_subnet_uid]} | " - f"Emission:{metagraph.E[self.my_subnet_uid]}" - ) - bt.logging.info(log) - - step += 1 - - # If someone intentionally stops the miner, it'll safely terminate operations. - except KeyboardInterrupt: - self.axon.stop() - bt.logging.success("Miner killed by keyboard interrupt.") - exit() - - # In case of unforeseen errors, the miner will log the error and continue operations. - except Exception as e: - bt.logging.error(traceback.format_exc()) - - def run_in_background_thread(self): - """ - Starts the miner's operations in a separate background thread. - This is useful for non-blocking operations. - """ - if not self.is_running: - bt.logging.debug("Starting miner in background thread.") - self.should_exit = False - self.thread = threading.Thread(target=self.run, daemon=True) - self.thread.start() - self.is_running = True - bt.logging.debug("Started") - - def stop_run_thread(self): - """ - Stops the miner's operations that are running in the background thread. - """ - if self.is_running: - bt.logging.debug("Stopping miner in background thread.") - self.should_exit = True - self.thread.join(5) - self.is_running = False - bt.logging.debug("Stopped") - - def __enter__(self): - """ - Starts the miner's operations in a background thread upon entering the context. - This method facilitates the use of the miner in a 'with' statement. - """ - self.run_in_background_thread() - - def __exit__(self, exc_type, exc_value, traceback): - """ - Stops the miner's background operations upon exiting the context. - This method facilitates the use of the miner in a 'with' statement. - - Args: - exc_type: The type of the exception that caused the context to be exited. - None if the context was exited without an exception. - exc_value: The instance of the exception that caused the context to be exited. - None if the context was exited without an exception. - traceback: A traceback object encoding the stack trace. - None if the context was exited without an exception. - """ - self.stop_run_thread() - - -class StreamingTemplateMiner(StreamMiner): - def config(self) -> "bt.Config": - """ - Returns the configuration object specific to this miner. - - Implement and extend this method to provide custom configurations for the miner. - Currently, it sets up a basic configuration parser. - - Returns: - bt.Config: A configuration object with the miner's operational parameters. - """ - parser = argparse.ArgumentParser(description="Streaming Miner Configs") - self.add_args(parser) - return bt.config(parser) - - def add_args(cls, parser: argparse.ArgumentParser): - """ - Adds custom arguments to the command line parser. - - Developers can introduce additional command-line arguments specific to the miner's - functionality in this method. These arguments can then be used to configure the miner's operation. - - Args: - parser (argparse.ArgumentParser): - The command line argument parser to which custom arguments should be added. - """ - pass - - def prompt(self, synapse: StreamPrompting) -> StreamPrompting: - """ - Generates a streaming response for the provided synapse. - - This function serves as the main entry point for handling streaming prompts. It takes - the incoming synapse which contains messages to be processed and returns a streaming - response. The function uses the GPT-2 tokenizer and a simulated model to tokenize and decode - the incoming message, and then sends the response back to the client token by token. - - Args: - synapse (StreamPrompting): The incoming StreamPrompting instance containing the messages to be processed. - - Returns: - StreamPrompting: The streaming response object which can be used by other functions to - stream back the response to the client. - - Usage: - This function can be extended and customized based on specific requirements of the - miner. Developers can swap out the tokenizer, model, or adjust how streaming responses - are generated to suit their specific applications. - """ - bt.logging.trace("HI. PROMPT()") - tokenizer = GPT2Tokenizer.from_pretrained("gpt2") - - # Simulated function to decode token IDs into strings. In a real-world scenario, - # this can be replaced with an actual model inference step. - def model(ids): - return (tokenizer.decode(id) for id in ids) - - async def _prompt(text: str, send: Send): - """ - Asynchronously processes the input text and sends back tokens as a streaming response. - - This function takes an input text, tokenizes it using the GPT-2 tokenizer, and then - uses the simulated model to decode token IDs into strings. It then sends each token - back to the client as a streaming response, with a delay between tokens to simulate - the effect of real-time streaming. - - Args: - text (str): The input text message to be processed. - send (Send): An asynchronous function that allows sending back the streaming response. - - Usage: - This function can be adjusted based on the streaming requirements, speed of - response, or the model being used. Developers can also introduce more sophisticated - processing steps or modify how tokens are sent back to the client. - """ - bt.logging.trace("HI. _PROMPT()") - input_ids = tokenizer( - text, return_tensors="pt" - ).input_ids.squeeze() - buffer = [] - bt.logging.debug(f"Input text: {text}") - bt.logging.debug(f"Input ids: {input_ids}") - - N = 3 # Number of tokens to send back to the client at a time - for token in model(input_ids): - bt.logging.trace(f"appending token: {token}") - buffer.append(token) - # If buffer has N tokens, send them back to the client. - if len(buffer) == N: - time.sleep(0.1) - joined_buffer = "".join(buffer) - bt.logging.debug(f"sedning tokens: {joined_buffer}") - await send( - { - "type": "http.response.body", - "body": joined_buffer.encode("utf-8"), - "more_body": True, - } - ) - bt.logging.debug(f"Streamed tokens: {joined_buffer}") - buffer = [] # Clear the buffer for next batch of tokens - - # Send any remaining tokens in the buffer - if buffer: - joined_buffer = "".join(buffer) - await send( - { - "type": "http.response.body", - "body": joined_buffer.encode("utf-8"), - "more_body": False, # No more tokens to send - } - ) - bt.logging.trace(f"Streamed tokens: {joined_buffer}") - - message = synapse.messages[0] - bt.logging.trace(f"message in _prompt: {message}") - token_streamer = partial(_prompt, message) - bt.logging.trace(f"token streamer: {token_streamer}") - return synapse.create_streaming_response(token_streamer) - - -# This is the main function, which runs the miner. -if __name__ == "__main__": - with StreamingTemplateMiner(): - while True: - time.sleep(1) diff --git a/docs/stream_tutorial/protocol.py b/docs/stream_tutorial/protocol.py deleted file mode 100644 index 26e91fdc..00000000 --- a/docs/stream_tutorial/protocol.py +++ /dev/null @@ -1,154 +0,0 @@ -import pydantic -import bittensor as bt - -from abc import ABC, abstractmethod -from typing import List, Union, Callable, Awaitable -from starlette.responses import StreamingResponse - - -class StreamPrompting(bt.StreamingSynapse): - """ - StreamPrompting is a specialized implementation of the `StreamingSynapse` tailored for prompting functionalities within - the Bittensor network. This class is intended to interact with a streaming response that contains a sequence of tokens, - which represent prompts or messages in a certain scenario. - - As a developer, when using or extending the `StreamPrompting` class, you should be primarily focused on the structure - and behavior of the prompts you are working with. The class has been designed to seamlessly handle the streaming, - decoding, and accumulation of tokens that represent these prompts. - - Attributes: - - `roles` (List[str]): A list of roles involved in the prompting scenario. This could represent different entities - or agents involved in the conversation or use-case. They are immutable to ensure consistent - interaction throughout the lifetime of the object. - - - `messages` (List[str]): These represent the actual prompts or messages in the prompting scenario. They are also - immutable to ensure consistent behavior during processing. - - - `completion` (str): Stores the processed result of the streaming tokens. As tokens are streamed, decoded, and - processed, they are accumulated in the completion attribute. This represents the "final" - product or result of the streaming process. - - `required_hash_fields` (List[str]): A list of fields that are required for the hash. - - Methods: - - `process_streaming_response`: This method asynchronously processes the incoming streaming response by decoding - the tokens and accumulating them in the `completion` attribute. - - - `deserialize`: Converts the `completion` attribute into its desired data format, in this case, a string. - - - `extract_response_json`: Extracts relevant JSON data from the response, useful for gaining insights on the response's - metadata or for debugging purposes. - - Note: While you can directly use the `StreamPrompting` class, it's designed to be extensible. Thus, you can create - subclasses to further customize behavior for specific prompting scenarios or requirements. - """ - - roles: List[str] = pydantic.Field( - ..., - title="Roles", - description="A list of roles in the StreamPrompting scenario. Immuatable.", - allow_mutation=False, - ) - - messages: List[str] = pydantic.Field( - ..., - title="Messages", - description="A list of messages in the StreamPrompting scenario. Immutable.", - allow_mutation=False, - ) - - required_hash_fields: List[str] = pydantic.Field( - ["messages"], - title="Required Hash Fields", - description="A list of required fields for the hash.", - allow_mutation=False, - ) - - completion: str = pydantic.Field( - "", - title="Completion", - description="Completion status of the current StreamPrompting object. This attribute is mutable and can be updated.", - ) - - async def process_streaming_response(self, response: StreamingResponse): - """ - `process_streaming_response` is an asynchronous method designed to process the incoming streaming response from the - Bittensor network. It's the heart of the StreamPrompting class, ensuring that streaming tokens, which represent - prompts or messages, are decoded and appropriately managed. - - As the streaming response is consumed, the tokens are decoded from their 'utf-8' encoded format, split based on - newline characters, and concatenated into the `completion` attribute. This accumulation of decoded tokens in the - `completion` attribute allows for a continuous and coherent accumulation of the streaming content. - - Args: - response: The streaming response object containing the content chunks to be processed. Each chunk in this - response is expected to be a set of tokens that can be decoded and split into individual messages or prompts. - """ - if self.completion is None: - self.completion = "" - bt.logging.debug( - "Processing streaming response (StreamingSynapse base class)." - ) - async for chunk in response.content.iter_any(): - bt.logging.debug(f"Processing chunk: {chunk}") - tokens = chunk.decode("utf-8").split("\n") - for token in tokens: - bt.logging.debug(f"--processing token: {token}") - if token: - self.completion += token - bt.logging.debug(f"yielding tokens {tokens}") - yield tokens - - def deserialize(self) -> str: - """ - Deserializes the response by returning the completion attribute. - - Returns: - str: The completion result. - """ - return self.completion - - def extract_response_json(self, response: StreamingResponse) -> dict: - """ - `extract_response_json` is a method that performs the crucial task of extracting pertinent JSON data from the given - response. The method is especially useful when you need a detailed insight into the streaming response's metadata - or when debugging response-related issues. - - Beyond just extracting the JSON data, the method also processes and structures the data for easier consumption - and understanding. For instance, it extracts specific headers related to dendrite and axon, offering insights - about the Bittensor network's internal processes. The method ultimately returns a dictionary with a structured - view of the extracted data. - - Args: - response: The response object from which to extract the JSON data. This object typically includes headers and - content which can be used to glean insights about the response. - - Returns: - dict: A structured dictionary containing: - - Basic response metadata such as name, timeout, total_size, and header_size. - - Dendrite and Axon related information extracted from headers. - - Roles and Messages pertaining to the current StreamPrompting instance. - - The accumulated completion. - """ - headers = { - k.decode("utf-8"): v.decode("utf-8") - for k, v in response.__dict__["_raw_headers"] - } - - def extract_info(prefix): - return { - key.split("_")[-1]: value - for key, value in headers.items() - if key.startswith(prefix) - } - - return { - "name": headers.get("name", ""), - "timeout": float(headers.get("timeout", 0)), - "total_size": int(headers.get("total_size", 0)), - "header_size": int(headers.get("header_size", 0)), - "dendrite": extract_info("bt_header_dendrite"), - "axon": extract_info("bt_header_axon"), - "roles": self.roles, - "messages": self.messages, - "completion": self.completion, - } diff --git a/docs/validator.md b/docs/validator.md new file mode 100644 index 00000000..bf8ee784 --- /dev/null +++ b/docs/validator.md @@ -0,0 +1,93 @@ +# WebGenieAI: Validator Documentation + +## Compute Requirement +For detailed specifications, please refer to the [Compute Requirement](validator_compute.yml). + +## Validator Setup + +Follow the steps below to configure and run the Validator. + +### Steps + +1. **Clone the WebGenieAI Repository:** + ```bash + git clone https://github.com/web-genie-ai/web-genie-ai.git + cd web-genie-ai + ``` + +2. **Set Up and Source the `.env` File:** + - **Create the `.env` File:** + ```bash + echo "WANDB_OFF = False" >> .env # Disable Weights & Biases + echo "WANDB_API_KEY = your_wandb_api_key" >> .env # Your Weights & Biases API key + echo "WANDB_ENTITY_NAME = your_wandb_entity_name" >> .env # Project name for Weights & Biases + echo "LLM_API_KEY = your_openai_api_key" >> .env # Your OpenAI API key + echo "LLM_MODEL_ID = openai_model_id" >> .env # Minimum model ID: gpt-4o + echo "LLM_MODEL_URL = openai_model_url" >> .env # OpenAI model URL + echo "LIGHTHOUSE_SERVER_PORT = 5000" >> .env # FastAPI server port for Lighthouse score + echo "NEURON_EPOCH_LENGTH = 25" >> .env # Default epoch length + echo "AXON_OFF = True" >> .env # Disable Axon serving + ``` + - Alternatively, rename `.env.validator.example` and customize it with your values. + - **Source the `.env` File:** + ```bash + source .env + ``` + +3. **Run the Validator:** + - **Using Bash Files:** + - Install necessary packages: + ```bash + bash scripts/install_requirements.sh + ``` + - Configure your Bittensor wallets and environment variables before proceeding: + ```bash + bash scripts/start.sh + ``` + - **Manually Through Scripts:** + - Install `pm2` and `uv`: + ```bash + npm install pm2 -g + curl -LsSf https://astral.sh/uv/install.sh | sh + ``` + - Install the packages in a new terminal: + ```bash + uv sync + ``` + - Install additional dependencies and start the validator: + ```bash + npm install -g lighthouse + wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb + sudo dpkg -i google-chrome-stable_current_amd64.deb + sudo apt-get install -f + source .venv/bin/activate + playwright install-deps + playwright install + export PYTHONPATH="." + pm2 start "uv run neurons/validators/validator.py --netuid [54 | 214] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port]" --name webgenie_validator + ``` + - **Through Auto Update Script:** + ```bash + pm2 start --name auto_update auto_update.sh + ``` + +--- + +### Troubleshooting & Support + +- **Logs:** + - For detailed logs, use the following command: + ```bash + pm2 logs webgenie_validator + ``` + +- **Common Issues:** + - Missing or incorrect `.env` constants. + - Unmatched server resource issues. + - Connectivity problems. + +- **Contact Support:** For assistance, please reach out to the WebGenieAI team. + +--- + +Happy Validating! \ No newline at end of file diff --git a/docs/validator_compute.yml b/docs/validator_compute.yml new file mode 100644 index 00000000..1ffa790e --- /dev/null +++ b/docs/validator_compute.yml @@ -0,0 +1,38 @@ +# Validator Compute Requirements Specification +# This document outlines the compute requirements for validators. +# It serves as a guide to recommend suitable hardware for your subnet. + +# Note: Specifications for miners may differ from those for validators. + +version: '1.0' # Update this version key as needed to match your release version. + +compute_spec: + + cpu: + min_cores: 24 # Minimum number of CPU cores + min_speed: 2.5 # Minimum speed per core (GHz) + recommended_cores: 24 # Recommended number of CPU cores + recommended_speed: 3.5 # Recommended speed per core (GHz) + architecture: "x86_64" # Architecture type (e.g., x86_64, arm64) + + memory: + min_ram: 32 # Minimum RAM (GB) + min_swap: 4 # Minimum swap space (GB) + recommended_swap: 8 # Recommended swap space (GB) + ram_type: "DDR4" # RAM type (e.g., DDR4, DDR3, etc.) + + storage: + min_space: 500 # Minimum free storage space (GB) + recommended_space: 1000 # Recommended free storage space (GB) + type: "SSD" # Preferred storage type (e.g., SSD, HDD) + min_iops: 1000 # Minimum I/O operations per second (if applicable) + recommended_iops: 5000 # Recommended I/O operations per second + + os: + name: "Ubuntu" # Preferred operating system + version: 20.04 # Preferred operating system version + +network_spec: + bandwidth: + download: 100 # Minimum download bandwidth (Mbps) + upload: 20 # Minimum upload bandwidth (Mbps) \ No newline at end of file diff --git a/min_compute.yml b/min_compute.yml deleted file mode 100644 index d80fe34b..00000000 --- a/min_compute.yml +++ /dev/null @@ -1,87 +0,0 @@ -# Use this document to specify the minimum compute requirements. -# This document will be used to generate a list of recommended hardware for your subnet. - -# This is intended to give a rough estimate of the minimum requirements -# so that the user can make an informed decision about whether or not -# they want to run a miner or validator on their machine. - -# NOTE: Specification for miners may be different from validators - -version: '1.0' # update this version key as needed, ideally should match your release version - -compute_spec: - - miner: - - cpu: - min_cores: 4 # Minimum number of CPU cores - min_speed: 2.5 # Minimum speed per core (GHz) - recommended_cores: 8 # Recommended number of CPU cores - recommended_speed: 3.5 # Recommended speed per core (GHz) - architecture: "x86_64" # Architecture type (e.g., x86_64, arm64) - - gpu: - required: True # Does the application require a GPU? - min_vram: 24 # Minimum GPU VRAM (GB) - recommended_vram: 24 # Recommended GPU VRAM (GB) - cuda_cores: 1024 # Minimum number of CUDA cores (if applicable) - min_compute_capability: 6.0 # Minimum CUDA compute capability - recommended_compute_capability: 7.0 # Recommended CUDA compute capability - recommended_gpu: "NVIDIA RTX 4090" # provide a recommended GPU to purchase/rent - - memory: - min_ram: 16 # Minimum RAM (GB) - min_swap: 4 # Minimum swap space (GB) - recommended_swap: 8 # Recommended swap space (GB) - ram_type: "DDR4" # RAM type (e.g., DDR4, DDR3, etc.) - - storage: - min_space: 320 # Minimum free storage space (GB) - recommended_space: 320 # Recommended free storage space (GB) - recommended_type: "SSD" # Preferred storage type (e.g., SSD, HDD) - min_iops: 1000 # Minimum I/O operations per second (if applicable) - recommended_iops: 5000 # Recommended I/O operations per second - - os: - name: "Ubuntu" # Name of the preferred operating system(s) - version: 20.04 # Version of the preferred operating system(s) - - validator: - - cpu: - min_cores: 24 # Minimum number of CPU cores - min_speed: 2.5 # Minimum speed per core (GHz) - recommended_cores: 24 # Recommended number of CPU cores - recommended_speed: 3.5 # Recommended speed per core (GHz) - architecture: "x86_64" # Architecture type (e.g., x86_64, arm64) - - gpu: - required: False # Does the application require a GPU? - min_vram: 24 # Minimum GPU VRAM (GB) - recommended_vram: 100 # Recommended GPU VRAM (GB) - cuda_cores: 1024 # Minimum number of CUDA cores (if applicable) - min_compute_capability: 6.0 # Minimum CUDA compute capability - recommended_compute_capability: 7.0 # Recommended CUDA compute capability - recommended_gpu: "NVIDIA A100" # provide a recommended GPU to purchase/rent - - memory: - min_ram: 32 # Minimum RAM (GB) - min_swap: 4 # Minimum swap space (GB) - recommended_swap: 8 # Recommended swap space (GB) - ram_type: "DDR4" # RAM type (e.g., DDR4, DDR3, etc.) - - storage: - min_space: 500 # Minimum free storage space (GB) - recommended_space: 1000 # Recommended free storage space (GB) - type: "SSD" # Preferred storage type (e.g., SSD, HDD) - min_iops: 1000 # Minimum I/O operations per second (if applicable) - recommended_iops: 5000 # Recommended I/O operations per second - - os: - name: "Ubuntu" # Name of the preferred operating system(s) - version: 20.04 # Version of the preferred operating system(s) - -network_spec: - bandwidth: - download: 100 # Minimum download bandwidth (Mbps) - upload: 20 # Minimum upload bandwidth (Mbps) From 0875a56cb666c4354a0a902061754f1d18c639c3 Mon Sep 17 00:00:00 2001 From: pycorn Date: Sun, 26 Jan 2025 12:09:30 +0000 Subject: [PATCH 359/554] fix: fix issue with last set weights --- CHANGELOG.md | 4 ++++ neurons/validators/score_manager.py | 11 ++++------- neurons/validators/validator.py | 8 ++------ webgenie/base/neuron.py | 3 --- webgenie/constants.py | 2 +- 5 files changed, 11 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 503d526b..d096b3d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,3 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.2] - 2025-01-25 ### Fixed - Fixed an issue that happens in selecting winners when the total scores are all 0. + +## [1.0.3] - 2025-01-26 +### Fixed +- Fixed an issue with saving the validator state, specifically the session number when the validator last set weights. diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 5f447823..6d424cde 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -16,8 +16,6 @@ def __init__(self, neuron: BaseNeuron): self.neuron = neuron self.state_path = self.neuron.config.neuron.full_path + "/state.npz" self.lock = neuron.lock - - self.should_save = False self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.current_session = -1 @@ -44,9 +42,6 @@ def load_scores(self): self.winners = {} def save_scores(self): - if not self.should_save: - return - try: bt.logging.info(f"Saving scores to {self.state_path}") np.savez( @@ -81,7 +76,8 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): # Update the hotkeys. self.hotkeys = copy.deepcopy(new_hotkeys) - self.should_save = True + with self.lock: + self.save_scores() def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challenge): bt.logging.info("Updating scores") @@ -149,7 +145,8 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen console = Console() console.print(table) - self.should_save = True + with self.lock: + self.save_scores() def get_scores(self, session_upto: int): scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index c3a7f36e..bdb93ab9 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -177,7 +177,8 @@ def set_weights(self): ) if result is True: self.score_manager.last_set_weights_session = current_session - 1 - + with self.lock: + self.score_manager.save_scores() try: bt.logging.info(f"Sending challenge to stats collector for session {current_session-1}") send_challenge_to_stats_collector(self.wallet, current_session-1) @@ -187,11 +188,6 @@ def set_weights(self): bt.logging.success("set_weights on chain successfully!") else: bt.logging.error("set_weights failed", msg) - - - def save_state(self): - """Saves the state of the validator to a file.""" - self.score_manager.save_scores() def load_state(self): """Loads the state of the validator from a file.""" diff --git a/webgenie/base/neuron.py b/webgenie/base/neuron.py index 364ff8bb..bdfab555 100644 --- a/webgenie/base/neuron.py +++ b/webgenie/base/neuron.py @@ -118,9 +118,6 @@ def sync(self): if self.should_sync_metagraph(): self.resync_metagraph() - # Always save state. - self.save_state() - def check_registered(self): # --- Check for registration. if not self.subtensor.is_hotkey_registered( diff --git a/webgenie/constants.py b/webgenie/constants.py index 7d86a72d..c6903df0 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.2" # version +__VERSION__ = "1.0.3" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 0f89bf127d47fff541637eb049e53e4e7ff09e0b Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sun, 26 Jan 2025 19:14:06 -0700 Subject: [PATCH 360/554] feat: update miner and validator guide documents --- docs/miner.md | 53 +++++++++++++++------------------------- docs/validator.md | 62 +++++++++++++++-------------------------------- 2 files changed, 39 insertions(+), 76 deletions(-) diff --git a/docs/miner.md b/docs/miner.md index 110db15d..6a054bca 100644 --- a/docs/miner.md +++ b/docs/miner.md @@ -9,68 +9,53 @@ Follow the steps below to configure and run the Miner. You can use either a clos ### Setup Instructions -1. **Clone the WebGenieAI Repository:** +1. **Clone the WebGenieAI Repository** ```bash git clone https://github.com/web-genie-ai/web-genie-ai.git cd web-genie-ai ``` -2. **Configure the Environment:** - - **Create the `.env` File:** +2. **Configure the Environment Variables** + - **Create the `.env`| `.env.miner` File** ```bash - echo "LLM_API_KEY = your_openai_api_key" >> .env # Your OpenAI API key when using OpenAI for mining; not required for custom models + echo "WANDB_OFF = False" >> .env # Turn off wandb. + echo "WANDB_API_KEY = your_wandb_api_key" >> .env # Your wandb api key, for example: sk-proj-1234567890 + echo "WANDB_ENTITY_NAME = your_wandb_entity_name" >> .env # The name of the project where you are sending the new run. + echo "LLM_API_KEY = your_openai_api_key" >> .env # Your openai api key, for example: sk-proj-1234567890 echo "LLM_MODEL_ID = openai_model_id" >> .env # Minimum model ID: gpt-4o when using OpenAI for mining; not required for custom models echo "LLM_MODEL_URL = openai_model_url" >> .env # OpenAI model URL when using OpenAI for mining; not required for custom models echo "HF_TOKEN = your_huggingface_token" >> .env # Your Hugging Face token, e.g., hf_1234567890; not required when using OpenAI for mining ``` - - Alternatively, rename `.env.miner.example` and customize it with your values. - - **Source the `.env` File:** - ```bash - source .env - ``` + - Alternatively, rename `.env.miner.example` and customize it with your environment variables. -3. **Execute the Miner:** - - **Using Bash Scripts:** - - Install necessary packages: +3. **Execute the Miner** + - **Using Bash Scripts** + - Install necessary packages ```bash bash scripts/install_requirements.sh ``` - - Configure your Bittensor wallets and environment variables before proceeding: + - Configure your Bittensor wallets + - Start the miner ```bash bash scripts/start.sh ``` - - **Manual Execution:** - - Install `pm2` and `uv`: - ```bash - npm install pm2 -g - curl -LsSf https://astral.sh/uv/install.sh | sh - ``` - - Install the packages in a new terminal: - ```bash - uv sync - ``` - - Start the miner: - ```bash - export PYTHONPATH="." - pm2 start "uv run neurons/miners/miner.py --netuid [54 | 214] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --axon.port [axon_port]" --name webgenie_miner - ``` --- ### Troubleshooting & Support -- **Logs:** - - For detailed logs, use the following command: +- **Logs** + - For detailed logs, use the following command ```bash pm2 logs webgenie_miner ``` - **Common Issues:** - - Missing or incorrect `.env` constants. - - Unmatched server resource issues. - - Connectivity problems. + - Missing or incorrect `.env` variables. -- **Contact Support:** For assistance, please reach out to the WebGenieAI team. +- **Contact Support** + - Email: support@webgenieai.co | sangar@webgenieai.co + - Discord: https://discord.com/channels/799672011265015819/1316415472563916841 --- diff --git a/docs/validator.md b/docs/validator.md index bf8ee784..66361bcf 100644 --- a/docs/validator.md +++ b/docs/validator.md @@ -9,14 +9,14 @@ Follow the steps below to configure and run the Validator. ### Steps -1. **Clone the WebGenieAI Repository:** +1. **Clone the WebGenieAI Repository** ```bash git clone https://github.com/web-genie-ai/web-genie-ai.git cd web-genie-ai ``` -2. **Set Up and Source the `.env` File:** - - **Create the `.env` File:** +2. **Configure the Environment Variables** + - **Create the `.env` | `.env.validator` File:** ```bash echo "WANDB_OFF = False" >> .env # Disable Weights & Biases echo "WANDB_API_KEY = your_wandb_api_key" >> .env # Your Weights & Biases API key @@ -28,45 +28,20 @@ Follow the steps below to configure and run the Validator. echo "NEURON_EPOCH_LENGTH = 25" >> .env # Default epoch length echo "AXON_OFF = True" >> .env # Disable Axon serving ``` - - Alternatively, rename `.env.validator.example` and customize it with your values. - - **Source the `.env` File:** - ```bash - source .env - ``` + - Alternatively, rename `.env.validator.example` and customize it with your environment variables. -3. **Run the Validator:** - - **Using Bash Files:** - - Install necessary packages: +3. **Run the Validator** + - **Using Bash Files** + - Install necessary packages ```bash bash scripts/install_requirements.sh ``` - - Configure your Bittensor wallets and environment variables before proceeding: + - Configure your Bittensor wallets + - Start the validator ```bash bash scripts/start.sh ``` - - **Manually Through Scripts:** - - Install `pm2` and `uv`: - ```bash - npm install pm2 -g - curl -LsSf https://astral.sh/uv/install.sh | sh - ``` - - Install the packages in a new terminal: - ```bash - uv sync - ``` - - Install additional dependencies and start the validator: - ```bash - npm install -g lighthouse - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - sudo dpkg -i google-chrome-stable_current_amd64.deb - sudo apt-get install -f - source .venv/bin/activate - playwright install-deps - playwright install - export PYTHONPATH="." - pm2 start "uv run neurons/validators/validator.py --netuid [54 | 214] --subtensor.network [finney | test] --wallet.name [coldkey_name] --wallet.hotkey [hotkey_name] --logging.debug --neuron.axon_port [axon_port]" --name webgenie_validator - ``` - - **Through Auto Update Script:** + - **Through Auto Update Script** ```bash pm2 start --name auto_update auto_update.sh ``` @@ -75,18 +50,21 @@ Follow the steps below to configure and run the Validator. ### Troubleshooting & Support -- **Logs:** - - For detailed logs, use the following command: +- **Logs** + - For detailed logs, use the following command ```bash pm2 logs webgenie_validator ``` -- **Common Issues:** - - Missing or incorrect `.env` constants. - - Unmatched server resource issues. - - Connectivity problems. +- **Common Issues** + - Missing or incorrect `.env` variables. + - If nodejs version is below v20, you may encounter issues with lighthouse score. + - If your lighthouse fastapi server is not running, for example port binding error, you may encounter issues with lighthouse score. -- **Contact Support:** For assistance, please reach out to the WebGenieAI team. +- **Contact Support** + - For assistance, please reach out to the WebGenieAI team.
+ Email: support@webgenieai.co | sangar@webgenieai.co
+ Discord: https://discord.com/channels/799672011265015819/1316415472563916841 --- From 265b3ad91a2522c09dd7be8bfb49a81cee50108a Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sun, 26 Jan 2025 20:03:15 -0700 Subject: [PATCH 361/554] hotfix: update documentation --- docs/validator.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/validator.md b/docs/validator.md index 66361bcf..ec09ab20 100644 --- a/docs/validator.md +++ b/docs/validator.md @@ -41,10 +41,6 @@ Follow the steps below to configure and run the Validator. ```bash bash scripts/start.sh ``` - - **Through Auto Update Script** - ```bash - pm2 start --name auto_update auto_update.sh - ``` --- @@ -59,7 +55,7 @@ Follow the steps below to configure and run the Validator. - **Common Issues** - Missing or incorrect `.env` variables. - If nodejs version is below v20, you may encounter issues with lighthouse score. - - If your lighthouse fastapi server is not running, for example port binding error, you may encounter issues with lighthouse score. + - If your lighthouse fastapi server(default port 5000) is not running, for example port binding error, you may encounter issues with lighthouse score. - **Contact Support** - For assistance, please reach out to the WebGenieAI team.
From 12ff4bede2817ee8234147c55d3545ad927d98cc Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 27 Jan 2025 04:54:10 +0000 Subject: [PATCH 362/554] chore: add version to wandb run name --- CHANGELOG.md | 5 +++++ webgenie/constants.py | 2 +- webgenie/helpers/weights.py | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d096b3d5..f705e428 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,3 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.3] - 2025-01-26 ### Fixed - Fixed an issue with saving the validator state, specifically the session number when the validator last set weights. + +## [1.0.4] - 2025-01-27 +### Changed +- Changed the wandb run name to include the version. + diff --git a/webgenie/constants.py b/webgenie/constants.py index c6903df0..47ecec68 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -75,7 +75,7 @@ WANDB_API_KEY = os.getenv("WANDB_API_KEY") # wandb api key -WANDB_PROJECT_NAME = f"webgenie-{__VERSION__}" # wandb project name +WANDB_PROJECT_NAME = f"webgenie" # wandb project name WANDB_ENTITY_NAME = os.getenv("WANDB_ENTITY_NAME") # wandb entity name diff --git a/webgenie/helpers/weights.py b/webgenie/helpers/weights.py index adcbcd3b..d488d6c5 100644 --- a/webgenie/helpers/weights.py +++ b/webgenie/helpers/weights.py @@ -6,6 +6,7 @@ WANDB_API_KEY, WANDB_PROJECT_NAME, WANDB_ENTITY_NAME, + __VERSION__, ) @@ -14,7 +15,7 @@ def init_wandb(self): if WANDB_OFF: return wandb.login(key=WANDB_API_KEY) - run_name = f"{self.config.neuron.name}-{self.uid}" + run_name = f"{self.config.neuron.name}-{self.uid}--{__VERSION__}" run = wandb.init( project=WANDB_PROJECT_NAME, entity=WANDB_ENTITY_NAME, From a82184f9d309cbbe96b2cab8f8a1146964c85b2d Mon Sep 17 00:00:00 2001 From: Sangar <8627971+sangar-1028@users.noreply.github.com> Date: Mon, 27 Jan 2025 03:55:29 -0700 Subject: [PATCH 363/554] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8049e85..ab9a1cd4 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ The WebGenieAI subnet incentivizes miners and validators to ensure high-quality ### Phase 2: Upgrade (Q1 2025) - [x] Launch on mainnet -- [ ] Build a leaderboard to track miner performance and progress +- [x] Build a leaderboard to track miner performance and progress - [ ] Upgrade front-end application to v2 - Online IDE like code sandbox and auto-deployment with one click - [ ] Upgrade incentive mechanism to v2 From b15b3049f95e66659b6fe9618c49f819ee526e6e Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 27 Jan 2025 10:58:18 +0000 Subject: [PATCH 364/554] fix: fix issue with dictionary size change --- CHANGELOG.md | 3 +++ neurons/validators/score_manager.py | 3 ++- webgenie/constants.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f705e428..392d92f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,3 +27,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Changed the wandb run name to include the version. +## [1.0.5] - 2025-01-27 +### Fixed +- Fixed an issue with dictionary's size change during iteration. diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 6d424cde..59eea99d 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -119,7 +119,8 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen else: self.winners[session] = (-1, competition_type) - for session_number in self.winners: + # Remove old winners + for session_number in list(self.winners.keys()): if session_number < session - CONSIDERING_SESSION_COUNTS * 2: self.winners.pop(session_number) diff --git a/webgenie/constants.py b/webgenie/constants.py index 47ecec68..72f8db7f 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.3" # version +__VERSION__ = "1.0.5" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From b4ec2bec2fce34369e9df5f04d27fae6bd68a4da Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Wed, 29 Jan 2025 10:28:24 -0600 Subject: [PATCH 365/554] feat: add organic traffic --- CHANGELOG.md | 4 ++++ neurons/validators/validator.py | 34 --------------------------------- webgenie/constants.py | 2 +- 3 files changed, 5 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 392d92f3..19fe7f72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,3 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.5] - 2025-01-27 ### Fixed - Fixed an issue with dictionary's size change during iteration. + +## [1.0.6] - 2025-01-29 +### Added +- Added an organic task. diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index bdb93ab9..26e9903a 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -244,40 +244,6 @@ def query_miners_loop(self): while True: time.sleep(1) try: - validator_index, validator_count = get_validator_index(self, self.uid) - if validator_index == -1: - bt.logging.error(f"No enough stake for the validator.") - continue - - bt.logging.info(f"Validator index: {validator_index}, Validator count: {validator_count}") - # Calculate query period blocks - with self.lock: - current_block = self.block - - all_validator_query_period_blocks = validator_count * QUERING_WINDOW_BLOCKS - # Calculate query period blocks - start_period_block = ( - (current_block // all_validator_query_period_blocks) * - all_validator_query_period_blocks + - validator_index * QUERING_WINDOW_BLOCKS - ) - end_period_block = start_period_block + QUERING_WINDOW_BLOCKS / 2 - bt.logging.info(f"Query window - " - f"Start: {start_period_block}, " - f"End: {end_period_block}, " - f"Current: {current_block}") - # Sleep if outside query window - if current_block < start_period_block: - sleep_blocks = start_period_block - current_block - bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") - time.sleep(sleep_blocks * BLOCK_IN_SECONDS) - continue - elif current_block > end_period_block: - sleep_blocks = (start_period_block - current_block + all_validator_query_period_blocks) - bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") - time.sleep(sleep_blocks * BLOCK_IN_SECONDS) - continue - QUERY_MINERS_TIMEOUT = 60 * 15 # 15 minutes self.query_miners_event_loop.run_until_complete( asyncio.wait_for( diff --git a/webgenie/constants.py b/webgenie/constants.py index 72f8db7f..5a8ea6a8 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.5" # version +__VERSION__ = "1.0.6" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From f075426a924eacc5bf964232da0497f5e670edca Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Wed, 29 Jan 2025 20:26:20 -0600 Subject: [PATCH 366/554] feat: resolve vtrust --- CHANGELOG.md | 4 ++++ neurons/validators/score_manager.py | 25 +++++++++++++------------ pyproject.toml | 2 +- webgenie/constants.py | 2 +- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19fe7f72..11a2b985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,3 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.6] - 2025-01-29 ### Added - Added an organic task. + +## [1.0.7] - 2025-01-29 +### Changed +- Set weights based on the total scores. diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 59eea99d..351978cb 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -86,7 +86,7 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen if self.current_session != session: # This is a new session, reset the scores and winners. self.current_session = session - self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + # self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) # Update accumulated scores and track best performer self.total_scores[uids] += rewards # Create a rich table to display total scores @@ -150,15 +150,16 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen self.save_scores() def get_scores(self, session_upto: int): - scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - with self.lock: - for session_number in self.winners: - if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or - session_number > session_upto): - continue + return self.total_scores + # scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + # with self.lock: + # for session_number in self.winners: + # if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or + # session_number > session_upto): + # continue - winner, competition_type = self.winners[session_number] - if winner == -1: - continue - scores[winner] += RESERVED_WEIGHTS[competition_type] - return scores + # winner, competition_type = self.winners[session_number] + # if winner == -1: + # continue + # scores[winner] += RESERVED_WEIGHTS[competition_type] + # return scores diff --git a/pyproject.toml b/pyproject.toml index 9f2a9edd..28eba7be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.0.0" +version = "1.0.7" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 5a8ea6a8..8c70f0ce 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.6" # version +__VERSION__ = "1.0.7" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 8329c767d3ed100de19ac60bd8dba216d5c2e633 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Thu, 30 Jan 2025 05:42:37 -0600 Subject: [PATCH 367/554] feat: set weights to 0 in new session --- CHANGELOG.md | 4 ++++ neurons/validators/score_manager.py | 2 +- pyproject.toml | 2 +- webgenie/constants.py | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11a2b985..fd817704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,3 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.7] - 2025-01-29 ### Changed - Set weights based on the total scores. + +## [1.0.8] - 2025-01-29 +### Changed +- Set weights to zero in the new session. diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 351978cb..dae49a6c 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -86,7 +86,7 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen if self.current_session != session: # This is a new session, reset the scores and winners. self.current_session = session - # self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) # Update accumulated scores and track best performer self.total_scores[uids] += rewards # Create a rich table to display total scores diff --git a/pyproject.toml b/pyproject.toml index 28eba7be..d69223b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.0.7" +version = "1.0.8" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 8c70f0ce..3745df7a 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.7" # version +__VERSION__ = "1.0.8" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 6bd065d9695428f5e1bd8c6a1fed9ca2626b4fbf Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Thu, 30 Jan 2025 20:27:28 -0600 Subject: [PATCH 368/554] feat: increase session period --- CHANGELOG.md | 4 ++++ neurons/validators/score_manager.py | 24 ++++++++++++------------ pyproject.toml | 2 +- webgenie/constants.py | 4 ++-- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd817704..21f08c11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,3 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.8] - 2025-01-29 ### Changed - Set weights to zero in the new session. + +## [1.0.9] - 2025-01-30 +### Changed +- Set weights based on the winner-take-all strategy. diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index dae49a6c..3ecd2e51 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -150,16 +150,16 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen self.save_scores() def get_scores(self, session_upto: int): - return self.total_scores - # scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - # with self.lock: - # for session_number in self.winners: - # if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or - # session_number > session_upto): - # continue + #return self.total_scores + scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + with self.lock: + for session_number in self.winners: + if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or + session_number > session_upto): + continue - # winner, competition_type = self.winners[session_number] - # if winner == -1: - # continue - # scores[winner] += RESERVED_WEIGHTS[competition_type] - # return scores + winner, competition_type = self.winners[session_number] + if winner == -1: + continue + scores[winner] += RESERVED_WEIGHTS[competition_type] + return scores diff --git a/pyproject.toml b/pyproject.toml index d69223b9..9f09b066 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.0.8" +version = "1.0.9" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 3745df7a..dbccd5db 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.8" # version +__VERSION__ = "1.0.9" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) @@ -57,7 +57,7 @@ TEMPO_BLOCKS = 360 # tempo blocks -SESSION_WINDOW_BLOCKS = TEMPO_BLOCKS * 3 # session window blocks +SESSION_WINDOW_BLOCKS = TEMPO_BLOCKS * 5 # session window blocks CONSIDERING_SESSION_COUNTS = 8 From f99ca20aebe38008749dea265a6a1b2a4d262eaa Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Thu, 30 Jan 2025 20:43:44 -0600 Subject: [PATCH 369/554] feat: query miners within given window --- neurons/validators/validator.py | 36 ++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 26e9903a..504a8472 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -244,7 +244,41 @@ def query_miners_loop(self): while True: time.sleep(1) try: - QUERY_MINERS_TIMEOUT = 60 * 15 # 15 minutes + validator_index, validator_count = get_validator_index(self, self.uid) + if validator_index == -1: + bt.logging.error(f"No enough stake for the validator.") + continue + + bt.logging.info(f"Validator index: {validator_index}, Validator count: {validator_count}") + # Calculate query period blocks + with self.lock: + current_block = self.block + + all_validator_query_period_blocks = validator_count * QUERING_WINDOW_BLOCKS + # Calculate query period blocks + start_period_block = ( + (current_block // all_validator_query_period_blocks) * + all_validator_query_period_blocks + + validator_index * QUERING_WINDOW_BLOCKS + ) + end_period_block = start_period_block + QUERING_WINDOW_BLOCKS / 2 + bt.logging.info(f"Query window - " + f"Start: {start_period_block}, " + f"End: {end_period_block}, " + f"Current: {current_block}") + # Sleep if outside query window + if current_block < start_period_block: + sleep_blocks = start_period_block - current_block + bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") + time.sleep(sleep_blocks * BLOCK_IN_SECONDS) + continue + elif current_block > end_period_block: + sleep_blocks = (start_period_block - current_block + all_validator_query_period_blocks) + bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") + time.sleep(sleep_blocks * BLOCK_IN_SECONDS) + continue + + QUERY_MINERS_TIMEOUT = 60 * 15 self.query_miners_event_loop.run_until_complete( asyncio.wait_for( self.genie_validator.query_miners(), From 91bfda712f7fd77063479f2fc734276e7de42a93 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Thu, 30 Jan 2025 21:04:27 -0600 Subject: [PATCH 370/554] feat: add state version --- neurons/validators/score_manager.py | 38 ++++++++++++++++++++--------- webgenie/constants.py | 2 ++ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 3ecd2e51..afdb4dd0 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -8,7 +8,7 @@ from webgenie.base.neuron import BaseNeuron from webgenie.challenges.challenge import Challenge, RESERVED_WEIGHTS -from webgenie.constants import CONSIDERING_SESSION_COUNTS +from webgenie.constants import CONSIDERING_SESSION_COUNTS, __STATE_VERSION__ class ScoreManager: @@ -28,11 +28,27 @@ def load_scores(self): bt.logging.info(f"Loading scores from {self.state_path}") data = np.load(self.state_path, allow_pickle=True) - self.hotkeys = data.get("hotkeys", copy.deepcopy(self.neuron.metagraph.hotkeys)) - self.current_session = data.get("current_session", -1) - self.total_scores = data.get("total_scores", np.zeros(self.neuron.metagraph.n, dtype=np.float32)) - self.last_set_weights_session = data.get("last_set_weights_session", -1) - self.winners = dict(data.get("winners", {}).item()) + self.hotkeys = data.get( + f"hotkeys", + copy.deepcopy(self.neuron.metagraph.hotkeys) + ) + + self.current_session = data.get( + f"current_session", + -1 + ) + + self.last_set_weights_session = data.get( + f"last_set_weights_session", + -1 + ) + + self.total_scores = data.get( + f"total_scores_{__STATE_VERSION__}", + np.zeros(self.neuron.metagraph.n, dtype=np.float32), + ) + + self.winners = dict(data.get(f"winners_{__STATE_VERSION__}", {}).item()) except Exception as e: bt.logging.error(f"Error loading state: {e}") self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) @@ -45,12 +61,12 @@ def save_scores(self): try: bt.logging.info(f"Saving scores to {self.state_path}") np.savez( - self.state_path, - hotkeys=self.hotkeys, - current_session=self.current_session, - total_scores=self.total_scores, + self.state_path, + hotkeys=self.hotkeys, + **{f"current_session": self.current_session}, last_set_weights_session=self.last_set_weights_session, - winners=self.winners, + **{f"total_scores_{__STATE_VERSION__}": self.total_scores}, + **{f"winners_{__STATE_VERSION__}": self.winners}, allow_pickle=True, ) self.should_save = False diff --git a/webgenie/constants.py b/webgenie/constants.py index dbccd5db..64437351 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -11,6 +11,8 @@ + (1 * int(__VERSION__.split(".")[2])) ) +__STATE_VERSION__ = "1.0.0" # state version + API_HOTKEY = "5DXDCYTuPfLqQXbxfvvnarG31SdTDtaubqpQrzjrcMgoP9dp" # backend api hotkey IMAGE_TASK_TIMEOUT = 72 # image task timeout From 17757af57b6a5fabc48cebabde9d7664446e5d30 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Fri, 31 Jan 2025 13:26:59 -0600 Subject: [PATCH 371/554] feat: query miners without splitting window --- CHANGELOG.md | 4 ++ neurons/validators/validator.py | 66 ++++++++++++++++----------------- pyproject.toml | 2 +- webgenie/constants.py | 2 +- 4 files changed, 39 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21f08c11..2e8433c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,3 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.9] - 2025-01-30 ### Changed - Set weights based on the winner-take-all strategy. + +## [1.0.10] - 2025-01-31 +### Changed +- Query miners without splitting the query window. \ No newline at end of file diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 504a8472..87ba31d5 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -244,39 +244,39 @@ def query_miners_loop(self): while True: time.sleep(1) try: - validator_index, validator_count = get_validator_index(self, self.uid) - if validator_index == -1: - bt.logging.error(f"No enough stake for the validator.") - continue - - bt.logging.info(f"Validator index: {validator_index}, Validator count: {validator_count}") - # Calculate query period blocks - with self.lock: - current_block = self.block - - all_validator_query_period_blocks = validator_count * QUERING_WINDOW_BLOCKS - # Calculate query period blocks - start_period_block = ( - (current_block // all_validator_query_period_blocks) * - all_validator_query_period_blocks + - validator_index * QUERING_WINDOW_BLOCKS - ) - end_period_block = start_period_block + QUERING_WINDOW_BLOCKS / 2 - bt.logging.info(f"Query window - " - f"Start: {start_period_block}, " - f"End: {end_period_block}, " - f"Current: {current_block}") - # Sleep if outside query window - if current_block < start_period_block: - sleep_blocks = start_period_block - current_block - bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") - time.sleep(sleep_blocks * BLOCK_IN_SECONDS) - continue - elif current_block > end_period_block: - sleep_blocks = (start_period_block - current_block + all_validator_query_period_blocks) - bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") - time.sleep(sleep_blocks * BLOCK_IN_SECONDS) - continue + # validator_index, validator_count = get_validator_index(self, self.uid) + # if validator_index == -1: + # bt.logging.error(f"No enough stake for the validator.") + # continue + + # bt.logging.info(f"Validator index: {validator_index}, Validator count: {validator_count}") + # # Calculate query period blocks + # with self.lock: + # current_block = self.block + + # all_validator_query_period_blocks = validator_count * QUERING_WINDOW_BLOCKS + # # Calculate query period blocks + # start_period_block = ( + # (current_block // all_validator_query_period_blocks) * + # all_validator_query_period_blocks + + # validator_index * QUERING_WINDOW_BLOCKS + # ) + # end_period_block = start_period_block + QUERING_WINDOW_BLOCKS / 2 + # bt.logging.info(f"Query window - " + # f"Start: {start_period_block}, " + # f"End: {end_period_block}, " + # f"Current: {current_block}") + # # Sleep if outside query window + # if current_block < start_period_block: + # sleep_blocks = start_period_block - current_block + # bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") + # time.sleep(sleep_blocks * BLOCK_IN_SECONDS) + # continue + # elif current_block > end_period_block: + # sleep_blocks = (start_period_block - current_block + all_validator_query_period_blocks) + # bt.logging.info(f"Sleeping for {sleep_blocks} blocks before querying miners") + # time.sleep(sleep_blocks * BLOCK_IN_SECONDS) + # continue QUERY_MINERS_TIMEOUT = 60 * 15 self.query_miners_event_loop.run_until_complete( diff --git a/pyproject.toml b/pyproject.toml index 9f09b066..de199a08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.0.9" +version = "1.0.10" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 64437351..f8d4953e 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.9" # version +__VERSION__ = "1.0.10" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 434f9d4c736db9f91b2d5750f8cca588979d23ed Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sat, 1 Feb 2025 07:11:23 -0600 Subject: [PATCH 372/554] feat: kill used ports --- neurons/validators/validator.py | 5 ++++- webgenie/helpers/ports.py | 4 ++++ .../rewards/lighthouse_reward/lighthouse_server_fastapi.py | 7 ++++++- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 webgenie/helpers/ports.py diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 87ba31d5..b5a784f2 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -22,6 +22,7 @@ process_weights_for_netuid, convert_weights_and_uids_for_emit, ) +from webgenie.helpers.ports import kill_process_on_port from webgenie.constants import ( API_HOTKEY, BLOCK_IN_SECONDS, @@ -220,8 +221,10 @@ def serve_axon(self): """Serve axon to enable external connections.""" bt.logging.info("serving ip to chain...") try: - self.axon = bt.axon(wallet=self.wallet, config=self.config) + kill_process_on_port(self.config.neuron.axon_port) + time.sleep(1) + self.axon = bt.axon(wallet=self.wallet, config=self.config) self.axon.attach( forward_fn = self.organic_forward_text, blacklist_fn = self.blacklist_text, diff --git a/webgenie/helpers/ports.py b/webgenie/helpers/ports.py new file mode 100644 index 00000000..1477a656 --- /dev/null +++ b/webgenie/helpers/ports.py @@ -0,0 +1,4 @@ +import os + +def kill_process_on_port(port): + os.system(f"sudo kill -9 $(sudo lsof -t -i :{port})") \ No newline at end of file diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py index 33434c76..6044e1ce 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py @@ -3,6 +3,10 @@ import sys import threading import uvicorn +import psutil +import signal +import subprocess + from fastapi import FastAPI from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles @@ -12,7 +16,8 @@ LIGHTHOUSE_SERVER_WORK_DIR, LIGHTHOUSE_SERVER_PORT, ) - +from webgenie.helpers.ports import kill_process_on_port +kill_process_on_port(LIGHTHOUSE_SERVER_PORT) app = FastAPI() static_folder = f"/{LIGHTHOUSE_SERVER_WORK_DIR}" From fd7cc32728f72942cb97b5c8a92aaba19267e19a Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sat, 1 Feb 2025 07:53:00 -0600 Subject: [PATCH 373/554] feat: kill port --- CHANGELOG.md | 6 +++++- neurons/validators/validator.py | 8 ++++---- pyproject.toml | 2 +- webgenie/constants.py | 2 +- webgenie/helpers/ports.py | 22 +++++++++++++++++++++- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e8433c3..ccc9b6a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,4 +49,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.10] - 2025-01-31 ### Changed -- Query miners without splitting the query window. \ No newline at end of file +- Query miners without splitting the query window. + +## [1.0.11] - 2025-01-31 +### Changed +- Kill the process on the port before starting the axon and the lighthouse server. \ No newline at end of file diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index b5a784f2..9aede1fc 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -221,9 +221,9 @@ def serve_axon(self): """Serve axon to enable external connections.""" bt.logging.info("serving ip to chain...") try: - kill_process_on_port(self.config.neuron.axon_port) - time.sleep(1) - + bt.logging.info(f"Killing process on port {self.config.axon.port}") + kill_process_on_port(self.config.axon.port) + self.axon = bt.axon(wallet=self.wallet, config=self.config) self.axon.attach( forward_fn = self.organic_forward_text, @@ -238,7 +238,7 @@ def serve_axon(self): subtensor=self.subtensor, ) self.axon.start() - bt.logging.info(f"Validator running in organic mode on port {self.config.neuron.axon_port}") + bt.logging.info(f"Validator running in organic mode on port {self.config.axon.port}") except Exception as e: bt.logging.error(f"Failed to serve Axon with exception: {e}") diff --git a/pyproject.toml b/pyproject.toml index de199a08..2e5a69c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.0.10" +version = "1.0.11" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index f8d4953e..e56ab646 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.10" # version +__VERSION__ = "1.0.11" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) diff --git a/webgenie/helpers/ports.py b/webgenie/helpers/ports.py index 1477a656..0a15ae2d 100644 --- a/webgenie/helpers/ports.py +++ b/webgenie/helpers/ports.py @@ -1,4 +1,24 @@ +import bittensor as bt import os +import time +import psutil + def kill_process_on_port(port): - os.system(f"sudo kill -9 $(sudo lsof -t -i :{port})") \ No newline at end of file + try: + cmd = f"sudo kill -9 $(sudo lsof -t -i :{port})" + os.system(cmd) + time.sleep(1) + except Exception as e: + bt.logging.error(f"Error killing process on port {port}: {e}") + raise Exception(f"Error killing process on port {port}: {e}") + + for proc in psutil.process_iter(['pid', 'name']): + try: + for conn in proc.connections(kind='inet'): + if conn.laddr.port == port: + raise Exception(f"Error killing process on port {port}: {e}") + except Exception as e: + bt.logging.error(f"Error killing process on port {port}: {e}") + raise Exception(f"Error killing process on port {port}: {e}") + From 276b66ac1ddae186c5f9faa77e5e8bfcfd883437 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sat, 1 Feb 2025 10:04:56 -0600 Subject: [PATCH 374/554] feat: sudo-install --- CHANGELOG.md | 6 +++++- pyproject.toml | 2 +- webgenie/constants.py | 2 +- webgenie/helpers/ports.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccc9b6a9..22ea1d39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,4 +53,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.11] - 2025-01-31 ### Changed -- Kill the process on the port before starting the axon and the lighthouse server. \ No newline at end of file +- Kill the process on the port before starting the axon and the lighthouse server. + +## [1.0.12] - 2025-02-01 +### Changed +- Install sudo if it is not installed. diff --git a/pyproject.toml b/pyproject.toml index 2e5a69c1..53216d55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.0.11" +version = "1.0.12" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index e56ab646..946550ee 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.11" # version +__VERSION__ = "1.0.12" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) diff --git a/webgenie/helpers/ports.py b/webgenie/helpers/ports.py index 0a15ae2d..91e688e1 100644 --- a/webgenie/helpers/ports.py +++ b/webgenie/helpers/ports.py @@ -4,6 +4,38 @@ import psutil +def install_sudo(): + # Check if sudo is already installed + if os.system("sudo --version") == 0: + bt.logging.info("✅ Sudo is already installed.") + return + + bt.logging.info("❌ Sudo is not installed. Installing now...") + + # Detect Linux distribution and install sudo + if os.path.exists("/etc/debian_version"): + package_manager = "apt" + install_cmd = "apt update && apt install -y sudo" + elif os.path.exists("/etc/redhat-release"): + package_manager = "yum" if os.path.exists("/usr/bin/yum") else "dnf" + install_cmd = f"{package_manager} install -y sudo" + else: + bt.logging.error("❌ Unsupported Linux distribution. Cannot install sudo.") + return + + # Run the installation command as root + bt.logging.info(f"🔄 Installing sudo using {package_manager}...") + os.system(f"su -c '{install_cmd}'") + + # Verify installation + if os.system("sudo --version") == 0: + bt.logging.info("✅ Sudo has been successfully installed.") + else: + bt.logging.error("❌ Failed to install sudo.") + +# Run the function +install_sudo() + def kill_process_on_port(port): try: cmd = f"sudo kill -9 $(sudo lsof -t -i :{port})" From 9b0563bdf807ebb5ec73ebdaa893def672dc8bbc Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sat, 1 Feb 2025 18:00:24 -0600 Subject: [PATCH 375/554] feat: not-raise-execption --- CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- webgenie/constants.py | 2 +- webgenie/helpers/ports.py | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22ea1d39..ce5c21ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,3 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.12] - 2025-02-01 ### Changed - Install sudo if it is not installed. + +## [1.0.13] - 2025-02-01 +### Changed +- Do not raise an exception if the error occurs while killing the process on the port. diff --git a/pyproject.toml b/pyproject.toml index 53216d55..b9fd9c1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.0.12" +version = "1.0.13" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 946550ee..565a3f96 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.12" # version +__VERSION__ = "1.0.13" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) diff --git a/webgenie/helpers/ports.py b/webgenie/helpers/ports.py index 91e688e1..339f61df 100644 --- a/webgenie/helpers/ports.py +++ b/webgenie/helpers/ports.py @@ -43,7 +43,7 @@ def kill_process_on_port(port): time.sleep(1) except Exception as e: bt.logging.error(f"Error killing process on port {port}: {e}") - raise Exception(f"Error killing process on port {port}: {e}") + #raise Exception(f"Error killing process on port {port}: {e}") for proc in psutil.process_iter(['pid', 'name']): try: @@ -52,5 +52,5 @@ def kill_process_on_port(port): raise Exception(f"Error killing process on port {port}: {e}") except Exception as e: bt.logging.error(f"Error killing process on port {port}: {e}") - raise Exception(f"Error killing process on port {port}: {e}") + #raise Exception(f"Error killing process on port {port}: {e}") From a9a85cefb84337d31b06a081120766d3e5e24320 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sat, 1 Feb 2025 18:32:57 -0600 Subject: [PATCH 376/554] feat: add balanced competition --- neurons/validators/genie_validator.py | 6 +++--- webgenie/challenges/__init__.py | 2 ++ webgenie/challenges/challenge.py | 17 ++++++++++++++++- webgenie/challenges/challenge_types.py | 1 + 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 2022aac9..4fb5491f 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -14,8 +14,6 @@ from webgenie.constants import ( MAX_COMPETETION_HISTORY_SIZE, MAX_SYNTHETIC_TASK_SIZE, - WORK_DIR, - LIGHTHOUSE_SERVER_WORK_DIR, TASK_REVEAL_TIME, TASK_REVEAL_TIMEOUT, SESSION_WINDOW_BLOCKS, @@ -26,6 +24,7 @@ AccuracyChallenge, QualityChallenge, SeoChallenge, + BalancedChallenge, ) from webgenie.helpers.htmls import preprocess_html, is_valid_resources from webgenie.helpers.images import image_debug_str @@ -82,7 +81,8 @@ async def query_miners(self): available_challenges_classes = [ AccuracyChallenge, QualityChallenge, - SeoChallenge, + #SeoChallenge, + BalancedChallenge, ] with self.lock: diff --git a/webgenie/challenges/__init__.py b/webgenie/challenges/__init__.py index 771b0857..edd30740 100644 --- a/webgenie/challenges/__init__.py +++ b/webgenie/challenges/__init__.py @@ -3,10 +3,12 @@ AccuracyChallenge, QualityChallenge, SeoChallenge, + BalancedChallenge, ) from .challenge_types import ( ACCURACY_COMPETITION_TYPE, QUALITY_COMPETITION_TYPE, SEO_COMPETITION_TYPE, + BALANCED_COMPETITION_TYPE, ) \ No newline at end of file diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py index 60b90236..c663da5a 100644 --- a/webgenie/challenges/challenge.py +++ b/webgenie/challenges/challenge.py @@ -6,6 +6,7 @@ ACCURACY_COMPETITION_TYPE, QUALITY_COMPETITION_TYPE, SEO_COMPETITION_TYPE, + BALANCED_COMPETITION_TYPE, ) from webgenie.tasks.metric_types import ( ACCURACY_METRIC_NAME, @@ -56,8 +57,22 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: aggregated_scores = np.where(accuracy_scores > 0.7, quality_scores, 0) return aggregated_scores, scores + +class BalancedChallenge(Challenge): + competition_type: str = Field(default=BALANCED_COMPETITION_TYPE, description="The type of competition") + + async def calculate_scores(self) -> dict[str, np.ndarray]: + scores = await self.task.generator.calculate_scores(self.task, self.solutions) + accuracy_scores = scores[ACCURACY_METRIC_NAME] + quality_scores = scores[QUALITY_METRIC_NAME] + seo_scores = scores[SEO_METRIC_NAME] + aggregated_scores = accuracy_scores * 0.5 + quality_scores * 0.5 + seo_scores * 0.0 + return aggregated_scores, scores + + RESERVED_WEIGHTS = { ACCURACY_COMPETITION_TYPE: 50, - SEO_COMPETITION_TYPE: 30, + SEO_COMPETITION_TYPE: 0, QUALITY_COMPETITION_TYPE: 20, + BALANCED_COMPETITION_TYPE: 30, } \ No newline at end of file diff --git a/webgenie/challenges/challenge_types.py b/webgenie/challenges/challenge_types.py index 2cb7bbb0..e923f8b5 100644 --- a/webgenie/challenges/challenge_types.py +++ b/webgenie/challenges/challenge_types.py @@ -1,3 +1,4 @@ ACCURACY_COMPETITION_TYPE = "accuracy_competition" QUALITY_COMPETITION_TYPE = "quality_competition" SEO_COMPETITION_TYPE = "seo_competition" +BALANCED_COMPETITION_TYPE = "balanced_competition" \ No newline at end of file From d7b4888e4d25dd087c08bcbaccb62963bd900dac Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sat, 1 Feb 2025 18:36:21 -0600 Subject: [PATCH 377/554] chore: remove logs --- neurons/validators/genie_validator.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 4fb5491f..31b63c03 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -60,14 +60,14 @@ async def query_miners(self): try: with self.lock: if len(self.miner_results) > MAX_COMPETETION_HISTORY_SIZE: - bt.logging.info( - f"Competition history size {len(self.miner_results)} " - f"exceeds max size {MAX_COMPETETION_HISTORY_SIZE}, skipping" - ) + # bt.logging.info( + # f"Competition history size {len(self.miner_results)} " + # f"exceeds max size {MAX_COMPETETION_HISTORY_SIZE}, skipping" + # ) return if not self.synthetic_tasks: - bt.logging.info("No synthetic tasks available, skipping") + #bt.logging.info("No synthetic tasks available, skipping") return task, synapse = self.synthetic_tasks.pop(0) From aeebfc4f3628c4671df54071d06fb7dcd64661e6 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sat, 1 Feb 2025 18:37:15 -0600 Subject: [PATCH 378/554] chore: update log info --- neurons/validators/genie_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 31b63c03..57fb9cde 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -254,7 +254,7 @@ async def synthensize_task(self): # synthetic_tasks is full, skipping return - bt.logging.info(f"Synthensize task") + bt.logging.warning(f"Synthensize task") task_generator, _ = random.choices( self.task_generators, From 34fa4a6d6b1fc2094120e5d2e85a72fe4f00b032 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sat, 1 Feb 2025 18:41:02 -0600 Subject: [PATCH 379/554] chore: update log info and refactor code --- neurons/validators/genie_validator.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 57fb9cde..dcca625a 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -120,13 +120,14 @@ async def query_miners(self): for reveal_synapse, hash_synapse, miner_uid in zip(all_synapse_reveal_results, all_synapse_hash_results, miner_uids): reveal_synapse.html_hash = hash_synapse.html_hash checked_synapse = await self.checked_synapse(reveal_synapse, miner_uid) - if checked_synapse is not None: - solutions.append( - Solution( - html = checked_synapse.html, - miner_uid = miner_uid, - ) + if checked_synapse is None: + continue + solutions.append( + Solution( + html = checked_synapse.html, + miner_uid = miner_uid, ) + ) challenge.solutions = solutions bt.logging.info(f"Received {len(solutions)} valid solutions") @@ -151,7 +152,7 @@ async def score(self): with self.lock: if challenge.session != self.neuron.session: - bt.logging.info( + bt.logging.warning( f"Session number mismatch: {challenge.session} != {self.neuron.session}" f"This is the previous session's challenge, skipping" ) @@ -254,7 +255,7 @@ async def synthensize_task(self): # synthetic_tasks is full, skipping return - bt.logging.warning(f"Synthensize task") + bt.logging.info(f"Synthensizing task...") task_generator, _ = random.choices( self.task_generators, From 84e4c5f3ef1040d870257a6d3912d2e22084cffd Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sat, 1 Feb 2025 18:42:44 -0600 Subject: [PATCH 380/554] chore: shorten htmls --- neurons/validators/genie_validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index dcca625a..2f6e3cde 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -224,7 +224,7 @@ async def score(self): ], "solutions": [ { - "miner_answer": { "html": solution.html }, + "miner_answer": { "html": solution.html[0:100] }, } for solution in solutions ], "scores": [ @@ -236,7 +236,7 @@ async def score(self): } for i in range(len(miner_uids)) ], "challenge": { - "task": challenge.task.ground_truth_html, + "task": challenge.task.ground_truth_html[0:100], "competition_type": challenge.competition_type, "session_number": challenge.session, }, From 4db0d99a6b395fa78f4ea01a516eeccdf247bbe6 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sat, 1 Feb 2025 18:54:31 -0600 Subject: [PATCH 381/554] doc: edit changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5c21ba..7a1efca0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,5 +60,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Install sudo if it is not installed. ## [1.0.13] - 2025-02-01 +### Added +- Added a balanced competition type. ### Changed - Do not raise an exception if the error occurs while killing the process on the port. + + From 65d884324f944164178385c11ff53dca71732f7c Mon Sep 17 00:00:00 2001 From: pycorn Date: Sun, 2 Feb 2025 14:07:30 +0000 Subject: [PATCH 382/554] feat: set log level info in release --- scripts/install_staging.sh | 8 ++++---- scripts/start.sh | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/install_staging.sh b/scripts/install_staging.sh index 24280ced..0863ef4e 100644 --- a/scripts/install_staging.sh +++ b/scripts/install_staging.sh @@ -131,15 +131,15 @@ cd ../bittensor-subnet-template # Check if inside a tmux session if [ -z "$TMUX" ]; then # Start a new tmux session and run the miner in the first pane - tmux new-session -d -s bittensor -n 'miner' 'python neurons/miner.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name miner --wallet.hotkey default --logging.debug' + tmux new-session -d -s bittensor -n 'miner' 'python neurons/miner.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name miner --wallet.hotkey default --logging.info' # Split the window and run the validator in the new pane - tmux split-window -h -t bittensor:miner 'python neurons/validator.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name validator --wallet.hotkey default --logging.debug' + tmux split-window -h -t bittensor:miner 'python neurons/validator.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name validator --wallet.hotkey default --logging.info' # Attach to the new tmux session tmux attach-session -t bittensor else # If already in a tmux session, create two panes in the current window - tmux split-window -h 'python neurons/miner.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name miner --wallet.hotkey default --logging.debug' - tmux split-window -v -t 0 'python neurons/validator.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name3 validator --wallet.hotkey default --logging.debug' + tmux split-window -h 'python neurons/miner.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name miner --wallet.hotkey default --logging.info' + tmux split-window -v -t 0 'python neurons/validator.py --netuid 1 --subtensor.chain_endpoint ws://127.0.0.1:9946 --wallet.name3 validator --wallet.hotkey default --logging.info' fi diff --git a/scripts/start.sh b/scripts/start.sh index 52556927..045b8f63 100644 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -53,8 +53,8 @@ export PYTHONPATH="." # Set netuid based on network type NETUID=$([ "$NETWORK" == "finney" ] && echo "54" || echo "214") if [[ "$NEURON_TYPE" == "validator" ]]; then - pm2 start "$HOME/.local/bin/uv run neurons/validators/validator.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.debug --axon.port $AXON_PORT" --name webgenie_validator + pm2 start "$HOME/.local/bin/uv run neurons/validators/validator.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.info --axon.port $AXON_PORT" --name webgenie_validator pm2 start --name auto_update scripts/auto_update.sh else - pm2 start "$HOME/.local/bin/uv run neurons/miners/miner.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.debug --axon.port $AXON_PORT" --name $PROCESS_NAME + pm2 start "$HOME/.local/bin/uv run neurons/miners/miner.py --netuid $NETUID --subtensor.network $NETWORK --wallet.name $COLDKEY --wallet.hotkey $HOTKEY --logging.info --axon.port $AXON_PORT" --name $PROCESS_NAME fi \ No newline at end of file From 4c17d75e9803047693d2901083b861b84af6f93a Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 3 Feb 2025 03:20:02 +0000 Subject: [PATCH 383/554] feat: update logging info --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- webgenie/constants.py | 2 +- .../rewards/lighthouse_reward/lighthouse_server_fastapi.py | 2 +- webgenie/rewards/visual_reward/common/browser.py | 5 +++-- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a1efca0..c7f02f79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,4 +65,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Do not raise an exception if the error occurs while killing the process on the port. +## [1.0.14] - 2025-02-02 +### Changed +- Update logging information. + + + + diff --git a/pyproject.toml b/pyproject.toml index b9fd9c1f..f5a9b327 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.0.13" +version = "1.0.14" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 565a3f96..5d30bf36 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.13" # version +__VERSION__ = "1.0.14" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py index 6044e1ce..0509ef28 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_server_fastapi.py @@ -47,7 +47,7 @@ def stop_lighthouse_server(): def start_lighthouse_server(): try: bt.logging.success(f"Trying to start lighthouse server on port {LIGHTHOUSE_SERVER_PORT}") - uvicorn.run(app, host="0.0.0.0", port=LIGHTHOUSE_SERVER_PORT) + uvicorn.run(app, host="0.0.0.0", port=LIGHTHOUSE_SERVER_PORT, log_level="error") except Exception as e: bt.logging.error(f"Error starting lighthouse server: {e}") stop_lighthouse_server() diff --git a/webgenie/rewards/visual_reward/common/browser.py b/webgenie/rewards/visual_reward/common/browser.py index 2111489b..6bcb618d 100644 --- a/webgenie/rewards/visual_reward/common/browser.py +++ b/webgenie/rewards/visual_reward/common/browser.py @@ -14,7 +14,8 @@ async def start_browser(): browser = await web_driver.chromium.launch(headless=True) web_player["web_driver"] = web_driver web_player["browser"] = browser - bt.logging.info(f"Started browser.") + bt.logging.debug(f"Started browser.") + async def stop_browser(): @@ -23,4 +24,4 @@ async def stop_browser(): await web_player["web_driver"].stop() web_player["web_driver"] = None web_player["browser"] = None - bt.logging.info(f"Stopped browser.") + bt.logging.debug(f"Stopped browser.") From 43be7e69e5961a297f49443c608896fd5bf5f3c2 Mon Sep 17 00:00:00 2001 From: pycorn Date: Mon, 3 Feb 2025 04:48:07 +0000 Subject: [PATCH 384/554] feat: open seo score --- neurons/validators/genie_validator.py | 2 +- neurons/validators/validator.py | 2 +- webgenie/challenges/challenge.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 2f6e3cde..c7538df2 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -81,7 +81,7 @@ async def query_miners(self): available_challenges_classes = [ AccuracyChallenge, QualityChallenge, - #SeoChallenge, + SeoChallenge, BalancedChallenge, ] diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 9aede1fc..2f216358 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -128,7 +128,7 @@ def set_weights(self): with self.lock: current_session = self.session last_set_weights_session = self.score_manager.last_set_weights_session - if last_set_weights_session >= current_session - 1: + if last_set_weights_session == current_session - 1: return scores = self.score_manager.get_scores(current_session - 1) diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py index c663da5a..92ba527b 100644 --- a/webgenie/challenges/challenge.py +++ b/webgenie/challenges/challenge.py @@ -66,13 +66,13 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: accuracy_scores = scores[ACCURACY_METRIC_NAME] quality_scores = scores[QUALITY_METRIC_NAME] seo_scores = scores[SEO_METRIC_NAME] - aggregated_scores = accuracy_scores * 0.5 + quality_scores * 0.5 + seo_scores * 0.0 + aggregated_scores = accuracy_scores * 0.4 + quality_scores * 0.3 + seo_scores * 0.3 return aggregated_scores, scores RESERVED_WEIGHTS = { ACCURACY_COMPETITION_TYPE: 50, - SEO_COMPETITION_TYPE: 0, - QUALITY_COMPETITION_TYPE: 20, BALANCED_COMPETITION_TYPE: 30, + SEO_COMPETITION_TYPE: 10, + QUALITY_COMPETITION_TYPE: 10, } \ No newline at end of file From be14c978138c9edbca27a7246eb451a42aab5e23 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 3 Feb 2025 19:55:06 -0600 Subject: [PATCH 385/554] feat: add number of tasks --- neurons/validators/score_manager.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index afdb4dd0..3a2cf087 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -19,6 +19,7 @@ def __init__(self, neuron: BaseNeuron): self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.current_session = -1 + self.number_of_tasks = 0 self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.last_set_weights_session = -1 self.winners = {} @@ -55,6 +56,7 @@ def load_scores(self): self.current_session = -1 self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.last_set_weights_session = -1 + self.number_of_tasks = 0 self.winners = {} def save_scores(self): @@ -65,6 +67,7 @@ def save_scores(self): hotkeys=self.hotkeys, **{f"current_session": self.current_session}, last_set_weights_session=self.last_set_weights_session, + number_of_tasks=self.number_of_tasks, **{f"total_scores_{__STATE_VERSION__}": self.total_scores}, **{f"winners_{__STATE_VERSION__}": self.winners}, allow_pickle=True, @@ -102,13 +105,14 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen if self.current_session != session: # This is a new session, reset the scores and winners. self.current_session = session + self.number_of_tasks = 0 self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) # Update accumulated scores and track best performer self.total_scores[uids] += rewards # Create a rich table to display total scores total_scores_table = Table( - title=f"Total Scores This Session-{session}", + title=f"Total Scores - Session:#{session}, Number of Tasks:#{self.number_of_tasks}", show_header=True, header_style="bold magenta", title_style="bold blue", @@ -116,6 +120,7 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen ) total_scores_table.add_column("UID", justify="right", style="cyan", header_style="bold cyan") total_scores_table.add_column("Total Score", justify="right", style="green") + total_scores_table.add_column("Average Score", justify="right", style="yellow") # Add rows for non-zero scores, sorted by score scored_uids = [(uid, score) for uid, score in enumerate(self.total_scores) if score > 0] @@ -124,14 +129,21 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen for uid, score in scored_uids: total_scores_table.add_row( str(uid), - f"{score:.4f}" + f"{score:.4f}", + f"{score / self.number_of_tasks:.4f}", ) console = Console() console.print(total_scores_table) - if np.max(self.total_scores) > 0: - self.winners[session] = (np.argmax(self.total_scores), competition_type) + mask = np.ones_like(self.total_scores, dtype=bool) + if session-1 in self.winners and self.winners[session-1][0] != -1: + mask[self.winners[session-1][0]] = False + masked_scores = np.where(mask, self.total_scores, -np.inf) + current_winner = np.argmax(masked_scores) + + if self.total_scores[current_winner] > 0: + self.winners[session] = (current_winner, competition_type) else: self.winners[session] = (-1, competition_type) @@ -168,14 +180,19 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen def get_scores(self, session_upto: int): #return self.total_scores scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + tiny_weight = 1 / 128 + big_weight = 1.0 with self.lock: for session_number in self.winners: if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or session_number > session_upto): continue - winner, competition_type = self.winners[session_number] + winner, _ = self.winners[session_number] if winner == -1: continue - scores[winner] += RESERVED_WEIGHTS[competition_type] + if session_number == session_upto: + scores[winner] += big_weight + else: + scores[winner] += tiny_weight return scores From 6e6ca8fc86a0d4b74eb954c8431f0fbfc011c76c Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 3 Feb 2025 20:02:19 -0600 Subject: [PATCH 386/554] doc: edit changelog and version --- CHANGELOG.md | 11 +++++++---- pyproject.toml | 2 +- webgenie/constants.py | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7f02f79..2a22a111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,7 +69,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Update logging information. - - - - +## [1.1.0] - 2025-02-03 +### Added +- Added a new column to the total scores table to show the average score. +### Changed +- Only reward the winner of current session. +- Reward the winners of previous sessions with a tiny weight to prevent deregistration. +- Changed the scoring mechanism to exclude the previous winner. diff --git a/pyproject.toml b/pyproject.toml index f5a9b327..7033fceb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.0.14" +version = "1.1.0" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 5d30bf36..13b9308a 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.0.14" # version +__VERSION__ = "1.1.0" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From f8eaa8818af75b4210d78567d6bd71283af004d6 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 3 Feb 2025 20:03:43 -0600 Subject: [PATCH 387/554] fix: fix bugs in organic forward --- neurons/validators/genie_validator.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index c7538df2..5f37a999 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -273,22 +273,23 @@ async def synthensize_task(self): async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImageSynapse]): if isinstance(synapse, WebgenieTextSynapse): - bt.logging.debug(f"Organic text forward: {synapse.prompt}") + bt.logging.info(f"Organic text forward: {synapse.prompt}") bt.logging.info("Not supported yet.") synapse.html = "Not supported yet." return synapse else: - bt.logging.debug(f"Organic image forward: {image_debug_str(synapse.base64_image)}...") + bt.logging.info(f"Organic image forward: {image_debug_str(synapse.base64_image)}...") synapse.VERSION = __VERSION__ all_miner_uids = get_all_available_uids(self.neuron) try: - if not all_miner_uids: + if len(all_miner_uids) == 0: raise Exception("No miners available") + bt.logging.info(f"Querying {len(all_miner_uids)} miners in organic forward") query_time = time.time() async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: - responses = await dendrite( + all_synapse_hash_results = await dendrite( axons=[self.neuron.metagraph.axons[uid] for uid in all_miner_uids], synapse=synapse, timeout=synapse.timeout, @@ -296,23 +297,28 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag elapsed_time = time.time() - query_time sleep_time_before_reveal = max(0, synapse.timeout - elapsed_time) + TASK_REVEAL_TIME - time.sleep(sleep_time_before_reveal) + bt.logging.info(f"Revealing task in organic forward") + time.sleep(sleep_time_before_reveal) async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: - responses = await dendrite( + all_synapse_reveal_results = await dendrite( axons=[self.neuron.metagraph.axons[uid] for uid in all_miner_uids], synapse=synapse, timeout=TASK_REVEAL_TIMEOUT, ) + bt.logging.info(f"Received {len(all_synapse_reveal_results)} responses in organic forward") + # Sort miner UIDs and responses by incentive scores incentives = self.neuron.metagraph.I[all_miner_uids] sorted_indices = np.argsort(-incentives) # Negative for descending order all_miner_uids = [all_miner_uids[i] for i in sorted_indices] + all_synapse_reveal_results = [all_synapse_reveal_results[i] for i in sorted_indices] + all_synapse_hash_results = [all_synapse_hash_results[i] for i in sorted_indices] - responses = [responses[i] for i in sorted_indices] - for response, miner_uid in zip(responses, all_miner_uids): - checked_synapse = await self.checked_synapse(response, miner_uid) + for reveal_synapse, hash_synapse, miner_uid in zip(all_synapse_reveal_results, all_synapse_hash_results, all_miner_uids): + reveal_synapse.html_hash = hash_synapse.html_hash + checked_synapse = await self.checked_synapse(reveal_synapse, miner_uid) if checked_synapse is None: continue return checked_synapse From a17219a3d386aa10aa57bba8d5c8e83b2d4781cd Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 3 Feb 2025 20:05:09 -0600 Subject: [PATCH 388/554] fix: fix bugs in loading state of score --- neurons/validators/score_manager.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 3a2cf087..6149725e 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -38,6 +38,11 @@ def load_scores(self): f"current_session", -1 ) + + self.number_of_tasks = data.get( + f"number_of_tasks", + 0 + ) self.last_set_weights_session = data.get( f"last_set_weights_session", @@ -49,7 +54,7 @@ def load_scores(self): np.zeros(self.neuron.metagraph.n, dtype=np.float32), ) - self.winners = dict(data.get(f"winners_{__STATE_VERSION__}", {}).item()) + self.winners = dict(data.get(f"winners_{__STATE_VERSION__}", np.array({})).item()) except Exception as e: bt.logging.error(f"Error loading state: {e}") self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) From 103b076d8cfed5a937e8b8ffd9e8cfd8487787a0 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 3 Feb 2025 20:06:45 -0600 Subject: [PATCH 389/554] doc: edit .env.example files --- .env.miner.example | 2 +- .env.validator.example | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.miner.example b/.env.miner.example index 3585248c..c88cde1d 100644 --- a/.env.miner.example +++ b/.env.miner.example @@ -1,6 +1,6 @@ WANDB_OFF = False # Turn off wandb. WANDB_API_KEY = your_wandb_api_key # Your wandb api key, for example: sk-proj-1234567890 -WANDB_ENTITY_NAME = your_wandb_entity_name #The name of the project where you are sending the new run. +WANDB_ENTITY_NAME = your_wandb_entity_name #The entity name of the project. HF_TOKEN = your_huggingface_token # Your huggingface token, for example: hf_1234567890 LLM_API_KEY = your_openai_api_key # Your openai api key, for example: sk-proj-1234567890 LLM_MODEL_ID = your_openai_model_id # Minimun model_id: gpt-4o diff --git a/.env.validator.example b/.env.validator.example index 6be33f6a..b360059e 100644 --- a/.env.validator.example +++ b/.env.validator.example @@ -1,6 +1,6 @@ WANDB_OFF = False # Turn off wandb. WANDB_API_KEY = your_wandb_api_key # Your wandb api key, for example: sk-proj-1234567890 -WANDB_ENTITY_NAME = your_wandb_entity_name #The name of the project where you are sending the new run. +WANDB_ENTITY_NAME = your_wandb_entity_name #The entity name of the project. LLM_API_KEY = your_openai_api_key # Your openai api key, for example: sk-proj-1234567890 LLM_MODEL_ID = your_openai_model_id # Minimun model_id: gpt-4o LLM_MODEL_URL = your_openai_model_url # Your openai model url, for example: https://api.openai.com/v1/ From b8b1284f0020217f782fa678c264f80fe89198e2 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 3 Feb 2025 20:09:15 -0600 Subject: [PATCH 390/554] feat: change backend hotkey --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index 13b9308a..3c8642c3 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -13,7 +13,7 @@ __STATE_VERSION__ = "1.0.0" # state version -API_HOTKEY = "5DXDCYTuPfLqQXbxfvvnarG31SdTDtaubqpQrzjrcMgoP9dp" # backend api hotkey +API_HOTKEY = "5HW7jKgKHfxwkpziTTiVbGwLGQrymZsEFiSpBPhqKQjnAW9S" # backend api hotkey IMAGE_TASK_TIMEOUT = 72 # image task timeout From 070c56693f1c57a206ec2fbbb57c6669978f5a35 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 3 Feb 2025 20:15:43 -0600 Subject: [PATCH 391/554] fix: fix bugs in number of tasks --- neurons/validators/score_manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 6149725e..0c333054 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -113,6 +113,7 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen self.number_of_tasks = 0 self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) # Update accumulated scores and track best performer + self.number_of_tasks += 1 self.total_scores[uids] += rewards # Create a rich table to display total scores From 0cca5f051e1152f46052359e14bc9201c2a54a26 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 3 Feb 2025 20:38:37 -0600 Subject: [PATCH 392/554] chore: add logs in organic forwad --- neurons/validators/genie_validator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 5f37a999..9b42fa3b 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -321,6 +321,7 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag checked_synapse = await self.checked_synapse(reveal_synapse, miner_uid) if checked_synapse is None: continue + bt.logging.info(f"Received valid solution from miner {miner_uid}") return checked_synapse raise Exception(f"No valid solution received") From 9461d448f7c581fb6743944d0cc5d2064dfcfdb5 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 3 Feb 2025 20:39:18 -0600 Subject: [PATCH 393/554] style: remove line --- neurons/validators/genie_validator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 9b42fa3b..7b2ae572 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -307,7 +307,6 @@ async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImag timeout=TASK_REVEAL_TIMEOUT, ) bt.logging.info(f"Received {len(all_synapse_reveal_results)} responses in organic forward") - # Sort miner UIDs and responses by incentive scores incentives = self.neuron.metagraph.I[all_miner_uids] From d69392fe4f0333130b71eb7ea4d46edd0d019def Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 3 Feb 2025 20:46:09 -0600 Subject: [PATCH 394/554] chore: fix log info --- neurons/validators/validator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 2f216358..3f578359 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -83,7 +83,6 @@ def __init__(self, config=None): self.serve_axon() def resync_metagraph(self): - """Resyncs the metagraph and updates the hotkeys and moving averages based on the new metagraph.""" # Copies state of metagraph before syncing. previous_metagraph = copy.deepcopy(self.metagraph) @@ -95,7 +94,7 @@ def resync_metagraph(self): return bt.logging.info( - "Metagraph updated, re-syncing hotkeys, dendrite pool and moving averages" + "Metagraph updated, re-syncing hotkeys, dendrite pool and miner scores" ) self.score_manager.set_new_hotkeys(self.metagraph.hotkeys) From 4e229155ddff0ea2a2683275d2ef1641177c1f60 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Thu, 6 Feb 2025 18:03:37 -0600 Subject: [PATCH 395/554] feat: reward all --- neurons/validators/score_manager.py | 34 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 0c333054..8789557f 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -184,21 +184,21 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen self.save_scores() def get_scores(self, session_upto: int): - #return self.total_scores - scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - tiny_weight = 1 / 128 - big_weight = 1.0 - with self.lock: - for session_number in self.winners: - if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or - session_number > session_upto): - continue + return np.power(self.total_scores, 3) + # scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + # tiny_weight = 1 / 128 + # big_weight = 1.0 + # with self.lock: + # for session_number in self.winners: + # if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or + # session_number > session_upto): + # continue - winner, _ = self.winners[session_number] - if winner == -1: - continue - if session_number == session_upto: - scores[winner] += big_weight - else: - scores[winner] += tiny_weight - return scores + # winner, _ = self.winners[session_number] + # if winner == -1: + # continue + # if session_number == session_upto: + # scores[winner] += big_weight + # else: + # scores[winner] += tiny_weight + # return scores From e699d8b31ece55de59cbeb7ab165426d52d0a3b4 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Thu, 6 Feb 2025 18:06:19 -0600 Subject: [PATCH 396/554] doc: edit version --- CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- webgenie/constants.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a22a111..bed6d4d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,3 +76,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Only reward the winner of current session. - Reward the winners of previous sessions with a tiny weight to prevent deregistration. - Changed the scoring mechanism to exclude the previous winner. + +## [1.1.1] - 2025-02-06 +### Changed +- Changed the scoring mechanism to reward all miners. diff --git a/pyproject.toml b/pyproject.toml index 7033fceb..baf89353 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.0" +version = "1.1.1" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 3c8642c3..3df5f1ae 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.0" # version +__VERSION__ = "1.1.1" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 06a142be4571b35ae14ba394c66094dbcb4ecde5 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Fri, 7 Feb 2025 06:39:44 -0600 Subject: [PATCH 397/554] hotfix(score): clearing score --- neurons/validators/score_manager.py | 18 ++++++++++++++++-- webgenie/constants.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 8789557f..a40e506f 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -21,6 +21,7 @@ def __init__(self, neuron: BaseNeuron): self.current_session = -1 self.number_of_tasks = 0 self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.last_set_weights_session = -1 self.winners = {} @@ -53,6 +54,11 @@ def load_scores(self): f"total_scores_{__STATE_VERSION__}", np.zeros(self.neuron.metagraph.n, dtype=np.float32), ) + + self.scores = data.get( + f"scores_{__STATE_VERSION__}", + np.zeros(self.neuron.metagraph.n, dtype=np.float32), + ) self.winners = dict(data.get(f"winners_{__STATE_VERSION__}", np.array({})).item()) except Exception as e: @@ -60,6 +66,7 @@ def load_scores(self): self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.current_session = -1 self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.last_set_weights_session = -1 self.number_of_tasks = 0 self.winners = {} @@ -74,10 +81,10 @@ def save_scores(self): last_set_weights_session=self.last_set_weights_session, number_of_tasks=self.number_of_tasks, **{f"total_scores_{__STATE_VERSION__}": self.total_scores}, + **{f"scores_{__STATE_VERSION__}": self.scores}, **{f"winners_{__STATE_VERSION__}": self.winners}, allow_pickle=True, ) - self.should_save = False except Exception as e: bt.logging.error(f"Error saving state: {e}") @@ -89,6 +96,7 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): for uid, hotkey in enumerate(self.hotkeys): if hotkey != new_hotkeys[uid]: self.total_scores[uid] = 0 + self.scores[uid] = 0 # Check to see if the metagraph has changed size. # If so, we need to add new hotkeys and moving averages. @@ -98,6 +106,11 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): new_total_scores[:min_len] = self.total_scores[:min_len] self.total_scores = new_total_scores + new_scores = np.zeros((len(new_hotkeys))) + min_len = min(len(self.hotkeys), len(self.scores)) + new_scores[:min_len] = self.scores[:min_len] + self.scores = new_scores + # Update the hotkeys. self.hotkeys = copy.deepcopy(new_hotkeys) with self.lock: @@ -111,6 +124,7 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen # This is a new session, reset the scores and winners. self.current_session = session self.number_of_tasks = 0 + self.scores = self.total_scores.copy() self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) # Update accumulated scores and track best performer self.number_of_tasks += 1 @@ -184,7 +198,7 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen self.save_scores() def get_scores(self, session_upto: int): - return np.power(self.total_scores, 3) + return np.power(self.scores, 3) # scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) # tiny_weight = 1 / 128 # big_weight = 1.0 diff --git a/webgenie/constants.py b/webgenie/constants.py index 3df5f1ae..9bb6a99d 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -11,7 +11,7 @@ + (1 * int(__VERSION__.split(".")[2])) ) -__STATE_VERSION__ = "1.0.0" # state version +__STATE_VERSION__ = "1.0.1" # state version API_HOTKEY = "5HW7jKgKHfxwkpziTTiVbGwLGQrymZsEFiSpBPhqKQjnAW9S" # backend api hotkey From 623ce87f03a28177db34dc0b797229ed2fb93baa Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Fri, 7 Feb 2025 06:40:57 -0600 Subject: [PATCH 398/554] doc: edit version --- CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- webgenie/constants.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bed6d4d7..534575a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,3 +80,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.1.1] - 2025-02-06 ### Changed - Changed the scoring mechanism to reward all miners. + +## [1.1.2] - 2025-02-07 +### Fixed +- Fixed an issue with clearing the scores. diff --git a/pyproject.toml b/pyproject.toml index baf89353..e7d615ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.1" +version = "1.1.2" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 9bb6a99d..5ecd1c32 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.1" # version +__VERSION__ = "1.1.2" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 2bf574a5a35a6020d47164ea2da0364301778007 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Fri, 7 Feb 2025 10:07:27 -0600 Subject: [PATCH 399/554] chore: search only landing pages --- webgenie/datasets/random_website_dataset.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index a797882f..81c04f8b 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -31,6 +31,7 @@ async def get_random_website_url(self, retries: int = 3) -> Optional[str]: ddg = DDGS() for _ in range(retries): random_words = " ".join(random.sample(self.english_words, 5)) + random_words = random_words + " official website" results = list(ddg.text(random_words)) if results: website_url = random.choice(results)["href"] From f2c196dea4f7f81e7f49a6730211ba0650e129dd Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Fri, 7 Feb 2025 10:34:07 -0600 Subject: [PATCH 400/554] test: random website --- tests/datasets/test_randomwebsite_dataset.py | 19 +++++++++++++++++++ webgenie/datasets/random_website_dataset.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 tests/datasets/test_randomwebsite_dataset.py diff --git a/tests/datasets/test_randomwebsite_dataset.py b/tests/datasets/test_randomwebsite_dataset.py new file mode 100644 index 00000000..dc5b802a --- /dev/null +++ b/tests/datasets/test_randomwebsite_dataset.py @@ -0,0 +1,19 @@ +import sys +import os + +parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(parent_dir) + +from dotenv import load_dotenv, find_dotenv +load_dotenv(find_dotenv(filename=".env.validator")) + +import asyncio + +from webgenie.datasets.random_website_dataset import RandomWebsiteDataset + +async def test_random_website_dataset(): + dataset = RandomWebsiteDataset() + await dataset.generate_context() + +if __name__ == "__main__": + asyncio.run(test_random_website_dataset()) \ No newline at end of file diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index 81c04f8b..e4bb4790 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -38,7 +38,7 @@ async def get_random_website_url(self, retries: int = 3) -> Optional[str]: return website_url except Exception as ex: - print(f"Failed to get search results from DuckDuckGo: {ex}") + bt.logging.error(f"Failed to get search results from DuckDuckGo: {ex}") return None async def get_rendered_html(self, url): From c04738b71696e6b7c62df522eba2001803214670 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Fri, 7 Feb 2025 10:36:09 -0600 Subject: [PATCH 401/554] doc: edit version --- CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- webgenie/constants.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 534575a6..17a9d4f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,3 +84,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.1.2] - 2025-02-07 ### Fixed - Fixed an issue with clearing the scores. + +## [1.1.3] - 2025-02-07 +### Changed +- Only search for official websites. diff --git a/pyproject.toml b/pyproject.toml index e7d615ad..5504adc0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.2" +version = "1.1.3" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 5ecd1c32..95befdfc 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.2" # version +__VERSION__ = "1.1.3" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From e0b9096de8a891f5b137e8df9bac248b82ef1471 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sun, 9 Feb 2025 20:42:38 -0600 Subject: [PATCH 402/554] chore: log status code --- webgenie/storage/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index b53ef3cb..e43ee666 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -260,6 +260,6 @@ def send_challenge_to_stats_collector(wallet: "bt.Wallet", session_number: int) payload=session_data, ) if not response.ok: - bt.logging.error(response.json()) + bt.logging.error(f"Failed to send challenge to stats collector | status code: {response.status_code}") else: bt.logging.success(response.json()) \ No newline at end of file From 393bce9cb2f2723152c88dca8fa20b97f2cfc7a0 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Sun, 9 Feb 2025 20:42:49 -0600 Subject: [PATCH 403/554] feat: score^9 --- neurons/validators/score_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index a40e506f..cb80114b 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -198,7 +198,7 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen self.save_scores() def get_scores(self, session_upto: int): - return np.power(self.scores, 3) + return np.power(self.scores, 9) # scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) # tiny_weight = 1 / 128 # big_weight = 1.0 From e90eb1a0c7a1b7cc23129734e1593698e27ffda0 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sun, 9 Feb 2025 21:33:02 -0600 Subject: [PATCH 404/554] chore: sep wandb --- webgenie/helpers/weights.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webgenie/helpers/weights.py b/webgenie/helpers/weights.py index d488d6c5..39162cdc 100644 --- a/webgenie/helpers/weights.py +++ b/webgenie/helpers/weights.py @@ -15,9 +15,10 @@ def init_wandb(self): if WANDB_OFF: return wandb.login(key=WANDB_API_KEY) - run_name = f"{self.config.neuron.name}-{self.uid}--{__VERSION__}" + project = f"{WANDB_PROJECT_NAME}-{self.config.neuron.name}" + run_name = f"{self.config.neuron.name}-{self.uid}-{__VERSION__}" run = wandb.init( - project=WANDB_PROJECT_NAME, + project=project, entity=WANDB_ENTITY_NAME, name=run_name, config=self.config, From f8cf6036480ba1c1498455b0de7637f6151b1c6a Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 10 Feb 2025 07:34:53 -0600 Subject: [PATCH 405/554] chore: implement save file to wandb func --- webgenie/helpers/weights.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/webgenie/helpers/weights.py b/webgenie/helpers/weights.py index 39162cdc..9caab406 100644 --- a/webgenie/helpers/weights.py +++ b/webgenie/helpers/weights.py @@ -40,4 +40,13 @@ def log_wandb(data: dict): wandb.log(data) except Exception as e: bt.logging.error(f"Error logging to wandb: {e}") - raise e \ No newline at end of file + raise e + +def save_file_to_wandb(file_path: str): + try: + if WANDB_OFF: + return + wandb.save(file_path) + except Exception as e: + bt.logging.error(f"Error saving file to wandb: {e}") + raise e From 33cd7cbaa5ea31b2d4be000dec1b2be8e69b73a6 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 10 Feb 2025 07:56:44 -0600 Subject: [PATCH 406/554] feat: implement session_resutls state variable --- neurons/validators/score_manager.py | 159 ++++++++++++---------------- 1 file changed, 67 insertions(+), 92 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index cb80114b..9e0870a9 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -2,14 +2,15 @@ import copy import numpy as np +from io import StringIO from rich.console import Console from rich.table import Table from typing import List from webgenie.base.neuron import BaseNeuron -from webgenie.challenges.challenge import Challenge, RESERVED_WEIGHTS -from webgenie.constants import CONSIDERING_SESSION_COUNTS, __STATE_VERSION__ - +from webgenie.challenges.challenge import Challenge +from webgenie.constants import CONSIDERING_SESSION_COUNTS, __STATE_VERSION__, WORK_DIR +from webgenie.helpers.weights import save_file_to_wandb class ScoreManager: def __init__(self, neuron: BaseNeuron): @@ -21,9 +22,8 @@ def __init__(self, neuron: BaseNeuron): self.current_session = -1 self.number_of_tasks = 0 self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.last_set_weights_session = -1 - self.winners = {} + self.session_results = {} def load_scores(self): try: @@ -54,22 +54,18 @@ def load_scores(self): f"total_scores_{__STATE_VERSION__}", np.zeros(self.neuron.metagraph.n, dtype=np.float32), ) - - self.scores = data.get( - f"scores_{__STATE_VERSION__}", - np.zeros(self.neuron.metagraph.n, dtype=np.float32), - ) - self.winners = dict(data.get(f"winners_{__STATE_VERSION__}", np.array({})).item()) + self.session_results = dict( + data.get(f"session_results_{__STATE_VERSION__}", np.array({})).item() + ) except Exception as e: bt.logging.error(f"Error loading state: {e}") self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.current_session = -1 self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - self.scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.last_set_weights_session = -1 self.number_of_tasks = 0 - self.winners = {} + self.session_results = {} def save_scores(self): try: @@ -81,8 +77,7 @@ def save_scores(self): last_set_weights_session=self.last_set_weights_session, number_of_tasks=self.number_of_tasks, **{f"total_scores_{__STATE_VERSION__}": self.total_scores}, - **{f"scores_{__STATE_VERSION__}": self.scores}, - **{f"winners_{__STATE_VERSION__}": self.winners}, + session_results= self.session_results, allow_pickle=True, ) except Exception as e: @@ -96,7 +91,6 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): for uid, hotkey in enumerate(self.hotkeys): if hotkey != new_hotkeys[uid]: self.total_scores[uid] = 0 - self.scores[uid] = 0 # Check to see if the metagraph has changed size. # If so, we need to add new hotkeys and moving averages. @@ -106,11 +100,6 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): new_total_scores[:min_len] = self.total_scores[:min_len] self.total_scores = new_total_scores - new_scores = np.zeros((len(new_hotkeys))) - min_len = min(len(self.hotkeys), len(self.scores)) - new_scores[:min_len] = self.scores[:min_len] - self.scores = new_scores - # Update the hotkeys. self.hotkeys = copy.deepcopy(new_hotkeys) with self.lock: @@ -124,15 +113,54 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen # This is a new session, reset the scores and winners. self.current_session = session self.number_of_tasks = 0 - self.scores = self.total_scores.copy() self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) # Update accumulated scores and track best performer self.number_of_tasks += 1 self.total_scores[uids] += rewards - # Create a rich table to display total scores - + + current_session_results = { + "session": session, + "competition_type": competition_type, + "number_of_tasks": self.number_of_tasks, + "winner": np.argmax(self.total_scores), + "scores": self.total_scores, + } + + self.session_results[session] = current_session_results + for session_number in list(self.session_results.keys()): + if session_number < session - CONSIDERING_SESSION_COUNTS * 2: + self.session_results.pop(session_number) + + with self.lock: + self.save_scores() + + console = Console() + self.print_session_result(session, console) + + def get_scores(self, session_upto: int): + if session_upto in self.session_results: + scores = self.session_results[session_upto]["scores"] + else: + scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + return np.power(scores, 9) + + def print_session_result(self, session_upto: int, console: Console): + session_result = self.session_results[session_upto] + + number_of_tasks = session_result["number_of_tasks"] + session = session_result["session"] + competition_type = session_result["competition_type"] + winner = session_result["winner"] + scores = session_result["scores"] + total_scores_table = Table( - title=f"Total Scores - Session:#{session}, Number of Tasks:#{self.number_of_tasks}", + title=( + f"📊 Total Scores Summary\n" + f"🔄 Session: #{session}\n" + f"📝 Number of Tasks: #{number_of_tasks}\n" + f"🏆 Competition: {competition_type}\n" + f"👑 Winner: #{winner}\n" + ), show_header=True, header_style="bold magenta", title_style="bold blue", @@ -141,78 +169,25 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen total_scores_table.add_column("UID", justify="right", style="cyan", header_style="bold cyan") total_scores_table.add_column("Total Score", justify="right", style="green") total_scores_table.add_column("Average Score", justify="right", style="yellow") - - # Add rows for non-zero scores, sorted by score - scored_uids = [(uid, score) for uid, score in enumerate(self.total_scores) if score > 0] + scored_uids = [(uid, score) for uid, score in enumerate(scores) if score > 0] scored_uids.sort(key=lambda x: x[1], reverse=True) - for uid, score in scored_uids: total_scores_table.add_row( str(uid), f"{score:.4f}", - f"{score / self.number_of_tasks:.4f}", + f"{score / number_of_tasks:.4f}", ) - - console = Console() console.print(total_scores_table) - mask = np.ones_like(self.total_scores, dtype=bool) - if session-1 in self.winners and self.winners[session-1][0] != -1: - mask[self.winners[session-1][0]] = False - masked_scores = np.where(mask, self.total_scores, -np.inf) - current_winner = np.argmax(masked_scores) - - if self.total_scores[current_winner] > 0: - self.winners[session] = (current_winner, competition_type) - else: - self.winners[session] = (-1, competition_type) - - # Remove old winners - for session_number in list(self.winners.keys()): - if session_number < session - CONSIDERING_SESSION_COUNTS * 2: - self.winners.pop(session_number) - - # Create a rich table to display the winners - table = Table( - title="Winners by Session", - show_header=True, - header_style="bold magenta", - title_style="bold blue", - border_style="blue" - ) - table.add_column("Session", justify="right", style="cyan", header_style="bold cyan") - table.add_column("Winner UID", justify="right", style="green") - table.add_column("Competition Type", justify="left") - # Add rows sorted by session number - for session_number in sorted(self.winners.keys()): - winner_uid, competition_type = self.winners[session_number] - table.add_row( - str(session_number), - str(winner_uid), - str(competition_type), - ) - - console = Console() - console.print(table) - with self.lock: - self.save_scores() - - def get_scores(self, session_upto: int): - return np.power(self.scores, 9) - # scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - # tiny_weight = 1 / 128 - # big_weight = 1.0 - # with self.lock: - # for session_number in self.winners: - # if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or - # session_number > session_upto): - # continue - - # winner, _ = self.winners[session_number] - # if winner == -1: - # continue - # if session_number == session_upto: - # scores[winner] += big_weight - # else: - # scores[winner] += tiny_weight - # return scores + def save_session_result_to_file(self, session_upto: int): + try: + log_file_name = f"{WORK_DIR}/session_{session_upto}.txt" + console = Console(file=StringIO(), force_terminal=False) + self.print_session_result(session_upto, console) + table_str = console.file.getvalue() + with open(log_file_name, "w") as f: + f.write(table_str) + save_file_to_wandb(log_file_name) + except Exception as e: + bt.logging.error(f"Error saving session result to file: {e}") + raise e From cce94d202d42cbeb67d5d739021d95a61a481c16 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 10 Feb 2025 07:58:41 -0600 Subject: [PATCH 407/554] chore: save session results to file --- neurons/validators/validator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 3f578359..69d67529 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -179,6 +179,7 @@ def set_weights(self): self.score_manager.last_set_weights_session = current_session - 1 with self.lock: self.score_manager.save_scores() + self.score_manager.save_session_result_to_file(current_session-1) try: bt.logging.info(f"Sending challenge to stats collector for session {current_session-1}") send_challenge_to_stats_collector(self.wallet, current_session-1) From 3181d9294e4077d1c07c165853ed228d94dce343 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 10 Feb 2025 09:12:48 -0600 Subject: [PATCH 408/554] fix: fix bugs in loading session_results --- neurons/validators/score_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 9e0870a9..285a51ab 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -56,7 +56,7 @@ def load_scores(self): ) self.session_results = dict( - data.get(f"session_results_{__STATE_VERSION__}", np.array({})).item() + data.get("session_results", np.array({})).item() ) except Exception as e: bt.logging.error(f"Error loading state: {e}") From f25562d6b936a405dfbb7490f408d13bd336cba0 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 10 Feb 2025 09:16:54 -0600 Subject: [PATCH 409/554] doc: edit changelog --- CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- webgenie/constants.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17a9d4f0..d3d0c00f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,3 +88,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.1.3] - 2025-02-07 ### Changed - Only search for official websites. + +## [1.1.4] - 2025-02-10 +### Added +- Save the results of the previous sessions. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 5504adc0..095167e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.3" +version = "1.1.4" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 95befdfc..0fcffc7f 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.3" # version +__VERSION__ = "1.1.4" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 5a008902f961c2b67916d0189a901c529aa3aaed Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 10 Feb 2025 09:32:19 -0600 Subject: [PATCH 410/554] chore: untruncate html --- neurons/validators/genie_validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 7b2ae572..127a15f3 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -224,7 +224,7 @@ async def score(self): ], "solutions": [ { - "miner_answer": { "html": solution.html[0:100] }, + "miner_answer": { "html": solution.html}, } for solution in solutions ], "scores": [ @@ -236,7 +236,7 @@ async def score(self): } for i in range(len(miner_uids)) ], "challenge": { - "task": challenge.task.ground_truth_html[0:100], + "task": challenge.task.ground_truth_html, "competition_type": challenge.competition_type, "session_number": challenge.session, }, From 3aa008a0b7f8698b94caafdaccf8b6024842ad77 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 18:25:18 -0600 Subject: [PATCH 411/554] feat: implement sync seed --- neurons/validators/genie_validator.py | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 127a15f3..1f6b0245 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -270,6 +270,40 @@ async def synthensize_task(self): except Exception as e: bt.logging.error(f"Error in synthensize_task: {e}") + + + def get_seed(self, session: int, task_index: int, hash_cache: dict = {}): + if session not in hash_cache: + session_start_block = session * SESSION_WINDOW_BLOCKS + subtensor = self.neuron.subtensor + hash_cache[session] = int( + subtensor.get_block_hash(session_start_block), + 16 + ) + return hash_cache[session] + task_index + + async def forward(self): + try: + with self.lock: + session = self.neuron.session + if self.neuron.score_manager.current_session != session: + task_index = 0 + else: + task_index = self.neuron.score_manager.number_of_tasks + if task_index >= 20: + return + + bt.logging.info(f"Forwarding task {task_index}") + seed = self.get_seed(session, task_index) + + bt.logging.info(f"Init random with seed: {seed}") + random.seed(seed) + + await self.synthensize_task() + await self.query_miners() + await self.score() + except Exception as e: + bt.logging.error(f"Error in forward: {e}") async def organic_forward(self, synapse: Union[WebgenieTextSynapse, WebgenieImageSynapse]): if isinstance(synapse, WebgenieTextSynapse): From dc78e0938060d8f1fe1e46f7ee58b201c55bdea3 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 18:28:05 -0600 Subject: [PATCH 412/554] chore: update query logic --- neurons/validators/validator.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 69d67529..d299d33f 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -281,11 +281,18 @@ def query_miners_loop(self): # time.sleep(sleep_blocks * BLOCK_IN_SECONDS) # continue - QUERY_MINERS_TIMEOUT = 60 * 15 + # QUERY_MINERS_TIMEOUT = 60 * 15 + # self.query_miners_event_loop.run_until_complete( + # asyncio.wait_for( + # self.genie_validator.query_miners(), + # timeout=QUERY_MINERS_TIMEOUT + # ) + # ) + FORWARD_TIMEOUT = 60 * 60 * 2 # 2 hours self.query_miners_event_loop.run_until_complete( asyncio.wait_for( - self.genie_validator.query_miners(), - timeout=QUERY_MINERS_TIMEOUT + self.genie_validator.forward(), + timeout=FORWARD_TIMEOUT ) ) except Exception as e: @@ -347,14 +354,14 @@ def run_background_threads(self): self.is_running = True self.should_exit = False - self.synthensize_task_thread = threading.Thread(target=self.synthensize_task_loop, daemon=True) + #self.synthensize_task_thread = threading.Thread(target=self.synthensize_task_loop, daemon=True) self.query_miners_thread = threading.Thread(target=self.query_miners_loop, daemon=True) - self.score_thread = threading.Thread(target=self.score_loop, daemon=True) + #self.score_thread = threading.Thread(target=self.score_loop, daemon=True) self.sync_thread = threading.Thread(target=self.sync_loop, daemon=True) - self.synthensize_task_thread.start() + #self.synthensize_task_thread.start() self.query_miners_thread.start() - self.score_thread.start() + #self.score_thread.start() self.sync_thread.start() start_lighthouse_server_thread() bt.logging.info("Started background threads") @@ -366,15 +373,15 @@ def stop_background_threads(self): self.should_exit = True self.is_running = False - self.synthensize_task_thread.join(5) + #self.synthensize_task_thread.join(5) self.query_miners_thread.join(5) - self.score_thread.join(5) + #self.score_thread.join(5) self.sync_thread.join(5) stop_lighthouse_server() - self.synthensize_task_thread = None + #self.synthensize_task_thread = None self.query_miners_thread = None - self.score_thread = None + #self.score_thread = None self.sync_thread = None bt.logging.info("Stopped background threads") From af4f49c3343efb5702cd040f4d35fe5f9552b718 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 18:29:10 -0600 Subject: [PATCH 413/554] chore: style code --- neurons/validators/genie_validator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 1f6b0245..d69978b6 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -290,7 +290,9 @@ async def forward(self): task_index = 0 else: task_index = self.neuron.score_manager.number_of_tasks - if task_index >= 20: + + MAX_NUMBER_OF_TASKS_PER_SESSION = 20 + if task_index >= MAX_NUMBER_OF_TASKS_PER_SESSION: return bt.logging.info(f"Forwarding task {task_index}") From c41bd49e584f262685e59f067217ddd5c612deb4 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 18:36:50 -0600 Subject: [PATCH 414/554] chore: change score logic --- neurons/validators/score_manager.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 285a51ab..842c7484 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -138,11 +138,28 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen self.print_session_result(session, console) def get_scores(self, session_upto: int): - if session_upto in self.session_results: - scores = self.session_results[session_upto]["scores"] - else: - scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - return np.power(scores, 9) + scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + tiny_weight = 1 / 128 + big_weight = 1.0 + for session_number in self.session_results: + if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or + session_number > session_upto): + continue + + winner = self.session_results[session_number]["winner"] + if winner == -1: + continue + if session_number == session_upto: + scores[winner] += big_weight + else: + scores[winner] += tiny_weight + return scores + + # if session_upto in self.session_results: + # scores = self.session_results[session_upto]["scores"] + # else: + # scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + # return np.power(scores, 9) def print_session_result(self, session_upto: int, console: Console): session_result = self.session_results[session_upto] From 6ddd87eb700ae86499dc1bc6a7a9f7f279ae3b16 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 18:38:40 -0600 Subject: [PATCH 415/554] chore: edit version --- CHANGELOG.md | 6 +++++- pyproject.toml | 2 +- webgenie/constants.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d0c00f..136581a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,4 +91,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.1.4] - 2025-02-10 ### Added -- Save the results of the previous sessions. \ No newline at end of file +- Save the results of the previous sessions. + +## [1.1.5] - 2025-02-12 +### Changed +- Switched to winner-take-all strategy. diff --git a/pyproject.toml b/pyproject.toml index 095167e6..0e754256 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.4" +version = "1.1.5" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 0fcffc7f..474f08ce 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.4" # version +__VERSION__ = "1.1.5" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 15d32506844548d5d6abe75cafec781e33e85ada Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 18:40:28 -0600 Subject: [PATCH 416/554] chore: refactor log --- webgenie/datasets/random_website_dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index e4bb4790..f90148f6 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -146,9 +146,9 @@ async def generate_context(self)->DatasetEntry: website_url = await self.get_random_website_url() if website_url is None: raise Exception("Failed to get a valid website URL") - bt.logging.debug(f"Generated website URL: {website_url}") html = await self.get_rendered_html(website_url) html = await self.shorten_html(html) + bt.logging.info(f"Generated website URL: {website_url}") return DatasetEntry( src="random_website", topic="random_website", From 62af74bf0def0dd514d9c5db0267d147032db586 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 18:41:26 -0600 Subject: [PATCH 417/554] chore: refactor log --- webgenie/helpers/htmls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 1d3a83bb..3ffea784 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -114,7 +114,7 @@ async def html_to_screenshot(html_content: str, page_load_time: int = 1000) -> s await page.close() await browser.close() except Exception as e: - print(f"Failed to take screenshot due to: {e}. Generating a blank image.") + bt.logging.error(f"Failed to take screenshot due to: {e}. Generating a blank image.") # Generate a blank image img = Image.new('RGB', (1280, 960), color = 'white') img.save(png_path) From 4f8472c17d2260b68e928d7c5c3ed617599b880b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 18:42:37 -0600 Subject: [PATCH 418/554] chore: allow only random website --- webgenie/tasks/image_task_generator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index c4e2aa4c..58c00f40 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -36,9 +36,9 @@ def __init__(self): super().__init__() self.datasets = [ - (RandomWebsiteDataset(), 0.8), - (SyntheticDataset(), 0.1), - (HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), 0.1), + (RandomWebsiteDataset(), 1), + #(SyntheticDataset(), 0.1), + #(HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), 0.1), ] self.metrics = { From 564e4a5f92cd9e1f1825c7b457c6870ebd124d67 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 18:44:19 -0600 Subject: [PATCH 419/554] chore: remove too long images --- webgenie/tasks/image_task_generator.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 58c00f40..673f20a3 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -62,7 +62,14 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: if is_empty_html(ground_truth_html): raise ValueError("Empty ground truth html") - base64_image = await html_to_screenshot(ground_truth_html, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) + base64_image = await html_to_screenshot(ground_truth_html, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) + # Check image dimensions ratio + image = base64_to_image(base64_image) + width, height = image.size + aspect_ratio = height / width + if aspect_ratio > 6: # If height is more than 3x the width + raise ValueError(f"Image aspect ratio too extreme: {aspect_ratio:.2f}. Height should not exceed 3x width.") + bt.logging.debug(f"Screenshot generated for {dataset_entry.src}") image_task = ImageTask( base64_image=base64_image, From 10c50af44b6d75c7121e5b667137b094c1e0cc71 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 18:46:07 -0600 Subject: [PATCH 420/554] chore: add rank field --- neurons/validators/score_manager.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 842c7484..8c91594e 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -183,13 +183,16 @@ def print_session_result(self, session_upto: int, console: Console): title_style="bold blue", border_style="blue" ) + + total_scores_table.add_column("Rank", justify="right", style="red", header_style="bold red") total_scores_table.add_column("UID", justify="right", style="cyan", header_style="bold cyan") total_scores_table.add_column("Total Score", justify="right", style="green") total_scores_table.add_column("Average Score", justify="right", style="yellow") scored_uids = [(uid, score) for uid, score in enumerate(scores) if score > 0] scored_uids.sort(key=lambda x: x[1], reverse=True) - for uid, score in scored_uids: + for rank, (uid, score) in enumerate(scored_uids): total_scores_table.add_row( + str(rank + 1), str(uid), f"{score:.4f}", f"{score / number_of_tasks:.4f}", From 4ee0078a8b13dcec42a7ce0078f5759b7015e16e Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 18:58:04 -0600 Subject: [PATCH 421/554] chore: allow at most 3ips --- webgenie/utils/uids.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/webgenie/utils/uids.py b/webgenie/utils/uids.py index a49037c3..bff59ed9 100644 --- a/webgenie/utils/uids.py +++ b/webgenie/utils/uids.py @@ -69,11 +69,18 @@ def get_most_available_uid(self, exclude: List[int] = None) -> int: def get_all_available_uids( self, exclude: List[int] = None ) -> np.ndarray: + ip_count = {} + for uid in range(self.metagraph.n.item()): + ip = self.metagraph.addresses[uid] + ip_count[ip] = ip_count[ip] + 1 if ip in ip_count else 1 + avail_uids = [] for uid in range(self.metagraph.n.item()): uid_is_available = check_uid_availability(self.metagraph, uid) uid_is_not_excluded = exclude is None or uid not in exclude - if uid_is_available and uid_is_not_excluded: + ip = self.metagraph.addresses[uid] + has_too_many_ips = ip_count[ip] > 3 + if uid_is_available and uid_is_not_excluded and not has_too_many_ips: avail_uids.append(uid) return np.array(avail_uids) From a1895ec27c4289d2d2b0c1ac5ad4526aae43c33a Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 19:08:29 -0600 Subject: [PATCH 422/554] chore: fix overflow error --- neurons/validators/genie_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index d69978b6..a5c1cd0c 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -280,7 +280,7 @@ def get_seed(self, session: int, task_index: int, hash_cache: dict = {}): subtensor.get_block_hash(session_start_block), 16 ) - return hash_cache[session] + task_index + return (hash_cache[session] + task_index) % np.iinfo(np.int64).max async def forward(self): try: From 3efd9f2ccce831b27defd76cb3f1373071dd8798 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 19:11:20 -0600 Subject: [PATCH 423/554] fix: fix overflow --- neurons/validators/genie_validator.py | 7 +++--- webgenie/storage/models.py | 8 +++++++ webgenie/storage/utils.py | 31 +++++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index a5c1cd0c..f140cde2 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -276,10 +276,9 @@ def get_seed(self, session: int, task_index: int, hash_cache: dict = {}): if session not in hash_cache: session_start_block = session * SESSION_WINDOW_BLOCKS subtensor = self.neuron.subtensor - hash_cache[session] = int( - subtensor.get_block_hash(session_start_block), - 16 - ) + block_hash = subtensor.get_block_hash(session_start_block) + # Take last 16 digits to avoid integer overflow + hash_cache[session] = int(block_hash[-16:], 16) return (hash_cache[session] + task_index) % np.iinfo(np.int64).max async def forward(self): diff --git a/webgenie/storage/models.py b/webgenie/storage/models.py index 3f486cd2..88ab0877 100644 --- a/webgenie/storage/models.py +++ b/webgenie/storage/models.py @@ -75,4 +75,12 @@ class SolutionEvaluation(Base): score_type: Mapped["EvaluationType"] = relationship(back_populates="solution_scores") solution: Mapped["TaskSolution"] = relationship(back_populates="solution_scores") +class GeneratedTask(Base): + __tablename__ = "generated_tasks" + id: Mapped[int] = mapped_column(primary_key=True) + seed: Mapped[int] + task_html: Mapped[str] + evaluated: Mapped[bool] = mapped_column(default=False) + created_at = Column(DateTime, default=datetime.utcnow) + Base.metadata.create_all(engine) diff --git a/webgenie/storage/utils.py b/webgenie/storage/utils.py index e43ee666..2ea3b872 100644 --- a/webgenie/storage/utils.py +++ b/webgenie/storage/utils.py @@ -1,7 +1,17 @@ import bittensor as bt from io import BufferedReader from .database import Session as DBSession -from .models import Neuron, LeaderboardSession, Competition, Challenge, Judgement, EvaluationType, TaskSolution, SolutionEvaluation +from .models import ( + Challenge, + Competition, + EvaluationType, + Judgement, + LeaderboardSession, + Neuron, + SolutionEvaluation, + TaskSolution, + GeneratedTask +) from datetime import datetime from sqlalchemy import and_ from sqlalchemy.exc import SQLAlchemyError @@ -262,4 +272,21 @@ def send_challenge_to_stats_collector(wallet: "bt.Wallet", session_number: int) if not response.ok: bt.logging.error(f"Failed to send challenge to stats collector | status code: {response.status_code}") else: - bt.logging.success(response.json()) \ No newline at end of file + bt.logging.success(response.json()) + +def store_generated_task(seed: int, task_html: str): + return create_record(session, GeneratedTask, seed=seed, task_html=task_html) + +def get_seed_from_latest_generated_task(): + return session.query(GeneratedTask).order_by(GeneratedTask.id.desc()).first().seed + +def get_generated_task_not_evaluated(last_task_id: int = None): + query = session.query(GeneratedTask).filter_by(evaluated=False) + if last_task_id: + query = query.filter(GeneratedTask.id > last_task_id) + return query.first() + +def set_generated_task_evaluated(task_id: int): + task = session.query(GeneratedTask).filter_by(id=task_id).first() + task.evaluated = True + session.commit() \ No newline at end of file From 19568dbf58d3024669afa74cb755a6db63239539 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 19:20:33 -0600 Subject: [PATCH 424/554] fix: fix type mismatching --- neurons/validators/genie_validator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index f140cde2..88b5157d 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -272,14 +272,13 @@ async def synthensize_task(self): bt.logging.error(f"Error in synthensize_task: {e}") - def get_seed(self, session: int, task_index: int, hash_cache: dict = {}): + def get_seed(self, session: int, task_index: int, hash_cache: dict = {}) -> int: if session not in hash_cache: session_start_block = session * SESSION_WINDOW_BLOCKS subtensor = self.neuron.subtensor block_hash = subtensor.get_block_hash(session_start_block) - # Take last 16 digits to avoid integer overflow - hash_cache[session] = int(block_hash[-16:], 16) - return (hash_cache[session] + task_index) % np.iinfo(np.int64).max + hash_cache[session] = int(block_hash[-15:], 16) + return int(hash_cache[session] + task_index) async def forward(self): try: @@ -297,6 +296,7 @@ async def forward(self): bt.logging.info(f"Forwarding task {task_index}") seed = self.get_seed(session, task_index) + bt.logging.info(f"Session: {session}, Task index: {task_index}, Seed: {seed}, type: {type(seed)}") bt.logging.info(f"Init random with seed: {seed}") random.seed(seed) From c10ee65bcc4d971f7ffe265f1bc01ef562445282 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 19:21:54 -0600 Subject: [PATCH 425/554] chore: style log --- neurons/validators/genie_validator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 88b5157d..b06356d7 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -293,10 +293,9 @@ async def forward(self): if task_index >= MAX_NUMBER_OF_TASKS_PER_SESSION: return - bt.logging.info(f"Forwarding task {task_index}") + bt.logging.info(f"Forwarding task #{task_index} in session #{session}") seed = self.get_seed(session, task_index) - bt.logging.info(f"Session: {session}, Task index: {task_index}, Seed: {seed}, type: {type(seed)}") bt.logging.info(f"Init random with seed: {seed}") random.seed(seed) From 1f38f787191892f917cd2453524bbca057deda87 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 19:24:18 -0600 Subject: [PATCH 426/554] chore: update log --- webgenie/tasks/image_task_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 673f20a3..fca76018 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -67,8 +67,8 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: image = base64_to_image(base64_image) width, height = image.size aspect_ratio = height / width - if aspect_ratio > 6: # If height is more than 3x the width - raise ValueError(f"Image aspect ratio too extreme: {aspect_ratio:.2f}. Height should not exceed 3x width.") + if aspect_ratio > 6: # If height is more than 6x the width + raise ValueError(f"Image aspect ratio too extreme: {aspect_ratio:.2f}. Height should not exceed 6x width.") bt.logging.debug(f"Screenshot generated for {dataset_entry.src}") image_task = ImageTask( From baaa137ee011f1040b364c3e886a6a04d46a6c32 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 19:25:38 -0600 Subject: [PATCH 427/554] chore: update log --- neurons/validators/genie_validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index b06356d7..ed779ac8 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -72,12 +72,12 @@ async def query_miners(self): task, synapse = self.synthetic_tasks.pop(0) - bt.logging.info("querying miners") miner_uids = get_all_available_uids(self.neuron) if len(miner_uids) == 0: bt.logging.warning("No miners available") return - + bt.logging.info(f"querying {len(miner_uids)} miners") + available_challenges_classes = [ AccuracyChallenge, QualityChallenge, From 66e63487aa14bde0c14c3208f66733529813e0bd Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 19:27:08 -0600 Subject: [PATCH 428/554] chore: update available aspect ratio --- webgenie/tasks/image_task_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index fca76018..0fdc7629 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -67,8 +67,8 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: image = base64_to_image(base64_image) width, height = image.size aspect_ratio = height / width - if aspect_ratio > 6: # If height is more than 6x the width - raise ValueError(f"Image aspect ratio too extreme: {aspect_ratio:.2f}. Height should not exceed 6x width.") + if aspect_ratio > 7: # If height is more than 7x the width + raise ValueError(f"Image aspect ratio too extreme: {aspect_ratio:.2f}. Height should not exceed 7x width.") bt.logging.debug(f"Screenshot generated for {dataset_entry.src}") image_task = ImageTask( From 36a2a16c44f4b1477629f4e5355de33356a47df0 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 19:33:26 -0600 Subject: [PATCH 429/554] fix: only ip --- webgenie/utils/uids.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webgenie/utils/uids.py b/webgenie/utils/uids.py index bff59ed9..199d2868 100644 --- a/webgenie/utils/uids.py +++ b/webgenie/utils/uids.py @@ -72,6 +72,7 @@ def get_all_available_uids( ip_count = {} for uid in range(self.metagraph.n.item()): ip = self.metagraph.addresses[uid] + ip = ip.split(":")[0] ip_count[ip] = ip_count[ip] + 1 if ip in ip_count else 1 avail_uids = [] @@ -79,6 +80,7 @@ def get_all_available_uids( uid_is_available = check_uid_availability(self.metagraph, uid) uid_is_not_excluded = exclude is None or uid not in exclude ip = self.metagraph.addresses[uid] + ip = ip.split(":")[0] has_too_many_ips = ip_count[ip] > 3 if uid_is_available and uid_is_not_excluded and not has_too_many_ips: avail_uids.append(uid) From 7f575d280a8fb3883c173ab5c7d16997bb5a7b0d Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 19:41:26 -0600 Subject: [PATCH 430/554] chore: update log --- neurons/validators/genie_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index ed779ac8..04ad6fd7 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -94,7 +94,7 @@ async def query_miners(self): synapse.competition_type = challenge.competition_type synapse.VERSION = __VERSION__ - bt.logging.debug(f"Querying {len(miner_uids)} miners") + bt.logging.debug(f"Querying {len(miner_uids)} miners with task_id: {task.task_id}") query_time = time.time() async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: From 0cb5bf064a576e1ceb402f37ce3a9144bb663dd0 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 19:41:39 -0600 Subject: [PATCH 431/554] chore: lower threshold --- webgenie/challenges/challenge.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py index 92ba527b..fea3ff1b 100644 --- a/webgenie/challenges/challenge.py +++ b/webgenie/challenges/challenge.py @@ -43,7 +43,7 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] seo_scores = scores[SEO_METRIC_NAME] - aggregated_scores = np.where(accuracy_scores > 0.7, seo_scores, 0) + aggregated_scores = np.where(accuracy_scores > 0.4, seo_scores, 0) return aggregated_scores, scores @@ -54,10 +54,9 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] quality_scores = scores[QUALITY_METRIC_NAME] - aggregated_scores = np.where(accuracy_scores > 0.7, quality_scores, 0) + aggregated_scores = np.where(accuracy_scores > 0.4, quality_scores, 0) return aggregated_scores, scores - class BalancedChallenge(Challenge): competition_type: str = Field(default=BALANCED_COMPETITION_TYPE, description="The type of competition") From f9e44342bdb6f5c220f3626329eb0b290b0271d2 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 19:54:41 -0600 Subject: [PATCH 432/554] feat: update miner --- neurons/miners/miner.py | 54 ++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/neurons/miners/miner.py b/neurons/miners/miner.py index 0402f40b..9d450636 100644 --- a/neurons/miners/miner.py +++ b/neurons/miners/miner.py @@ -16,20 +16,20 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. from dotenv import load_dotenv - load_dotenv(".env.miner") load_dotenv(".env") +import bittensor as bt import time - +import os import typing -import bittensor as bt from webgenie.base.miner import BaseMinerNeuron from webgenie.constants import ( TASK_REVEAL_TIME, IMAGE_TASK_TIMEOUT, TEXT_TASK_TIMEOUT, + WORK_DIR, ) from webgenie.helpers.images import image_debug_str from webgenie.helpers.weights import init_wandb @@ -68,21 +68,47 @@ def __init__(self, config=None): self.task_state: typing.Dict[str, typing.Dict[str, typing.Any]] = {} init_wandb(self) - + + def get_saved_answer(self, task_id: str) -> str: + saved_answer_path = f"{WORK_DIR}/answers/{task_id}.html" + if os.path.exists(saved_answer_path): + with open(saved_answer_path, "r") as f: + return f.read() + return None + + def save_answer(self, task_id: str, html: str): + saved_answer_path = f"{WORK_DIR}/answers/{task_id}.html" + os.makedirs(os.path.dirname(saved_answer_path), exist_ok=True) + with open(saved_answer_path, "w") as f: + f.write(html) + async def forward_image( self, synapse: WebgenieImageSynapse ) -> WebgenieImageSynapse: validator_uid = self.metagraph.hotkeys.index(synapse.dendrite.hotkey) + task_hash = f"{synapse.task_id}_{validator_uid}" + task_id = synapse.task_id + bt.logging.debug(f"Validator {validator_uid}'s repo version: {synapse.VERSION}") bt.logging.debug(f"Miner image forward called with image: {image_debug_str(synapse.base64_image)}...") - if synapse.task_id not in self.task_state: - bt.logging.debug(f"Task {synapse.task_id} is not calculated yet.") + if task_hash not in self.task_state: + bt.logging.debug(f"Task {task_hash} is not calculated yet.") create_time = time.time() - synapse = await self.genie_miner.forward_image(synapse) + saved_answer = self.get_saved_answer(task_id) + if saved_answer is not None: + synapse.html = saved_answer + else: + synapse = await self.genie_miner.forward_image(synapse) + saved_answer = self.get_saved_answer(task_id) + if saved_answer is not None: + synapse.html = saved_answer + else: + self.save_answer(task_id, synapse.html) + nonce = add_answer_hash(synapse, self.uid, synapse.html) - self.task_state[synapse.task_id] = { + self.task_state[task_hash] = { "html": synapse.html, "nonce": nonce, "create_time": create_time @@ -92,15 +118,15 @@ async def forward_image( return synapse else: DELTA = 10 - create_time = self.task_state[synapse.task_id]["create_time"] + create_time = self.task_state[task_hash]["create_time"] if time.time() - create_time >= TASK_REVEAL_TIME + IMAGE_TASK_TIMEOUT - DELTA: - bt.logging.debug(f"Task {synapse.task_id} is ready to reveal.") - synapse.html = self.task_state[synapse.task_id]["html"] - synapse.nonce = self.task_state[synapse.task_id]["nonce"] - del self.task_state[synapse.task_id] + bt.logging.debug(f"Task {task_hash} is ready to reveal.") + synapse.html = self.task_state[task_hash]["html"] + synapse.nonce = self.task_state[task_hash]["nonce"] + del self.task_state[task_hash] return synapse else: - bt.logging.warning(f"Task {synapse.task_id} is not ready to reveal yet.") + bt.logging.warning(f"Task {task_hash} is not ready to reveal yet.") return synapse From 261ff338e31583858f6071143b85e0e0e89bb6c0 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 12 Feb 2025 20:01:29 -0600 Subject: [PATCH 433/554] chore: update available uids func --- webgenie/utils/uids.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/webgenie/utils/uids.py b/webgenie/utils/uids.py index 199d2868..3bb0d455 100644 --- a/webgenie/utils/uids.py +++ b/webgenie/utils/uids.py @@ -71,6 +71,12 @@ def get_all_available_uids( ) -> np.ndarray: ip_count = {} for uid in range(self.metagraph.n.item()): + uid_is_available = check_uid_availability(self.metagraph, uid) + if not uid_is_available: + continue + uid_is_not_excluded = exclude is None or uid not in exclude + if not uid_is_not_excluded: + continue ip = self.metagraph.addresses[uid] ip = ip.split(":")[0] ip_count[ip] = ip_count[ip] + 1 if ip in ip_count else 1 @@ -78,12 +84,17 @@ def get_all_available_uids( avail_uids = [] for uid in range(self.metagraph.n.item()): uid_is_available = check_uid_availability(self.metagraph, uid) + if not uid_is_available: + continue uid_is_not_excluded = exclude is None or uid not in exclude + if not uid_is_not_excluded: + continue ip = self.metagraph.addresses[uid] ip = ip.split(":")[0] has_too_many_ips = ip_count[ip] > 3 - if uid_is_available and uid_is_not_excluded and not has_too_many_ips: + if not has_too_many_ips: avail_uids.append(uid) + return np.array(avail_uids) From b0be2c9ffef880cbc91e426f80bfe2257ac99bce Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 07:48:43 -0600 Subject: [PATCH 434/554] chore: fix to return the same url --- webgenie/datasets/random_website_dataset.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index f90148f6..146a1a25 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -1,4 +1,5 @@ import bittensor as bt +import asyncio import nltk import random @@ -26,12 +27,14 @@ def __init__(self , **kwargs): common_words = [word for word, _ in most_common] self.english_words = common_words - async def get_random_website_url(self, retries: int = 3) -> Optional[str]: + def _get_random_website_url(self, retries: int = 3) -> Optional[str]: try: ddg = DDGS() for _ in range(retries): random_words = " ".join(random.sample(self.english_words, 5)) random_words = random_words + " official website" + + bt.logging.info(f"Results: {random_words}") results = list(ddg.text(random_words)) if results: website_url = random.choice(results)["href"] @@ -41,6 +44,22 @@ async def get_random_website_url(self, retries: int = 3) -> Optional[str]: bt.logging.error(f"Failed to get search results from DuckDuckGo: {ex}") return None + async def get_random_website_url(self) -> Optional[str]: + number_of_tries = 10 + urls = [] + for _ in range(number_of_tries): + await asyncio.sleep(1) + url = self._get_random_website_url() + if url is not None: + urls.append(url) + # Return most frequent URL if there are duplicates, otherwise return first URL + if not urls: + return None + url_counts = Counter(urls) + max_freq = max(url_counts.values()) + most_frequent = [url for url, count in url_counts.items() if count == max_freq] + return most_frequent[0] + async def get_rendered_html(self, url): try: async with async_playwright() as p: From e31c9657f837df51938c96268d5c7bce27cd405c Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 07:55:24 -0600 Subject: [PATCH 435/554] fix: the same result for the same key words --- webgenie/datasets/random_website_dataset.py | 40 ++++++++------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index 146a1a25..67408cfd 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -27,39 +27,27 @@ def __init__(self , **kwargs): common_words = [word for word, _ in most_common] self.english_words = common_words - def _get_random_website_url(self, retries: int = 3) -> Optional[str]: + async def get_random_website_url(self, number_of_tries: int = 10) -> Optional[str]: try: ddg = DDGS() - for _ in range(retries): - random_words = " ".join(random.sample(self.english_words, 5)) - random_words = random_words + " official website" - - bt.logging.info(f"Results: {random_words}") + random_words = " ".join(random.sample(self.english_words, 5)) + random_words = random_words + " official website" + urls = [] + for _ in range(number_of_tries): results = list(ddg.text(random_words)) - if results: - website_url = random.choice(results)["href"] - return website_url - + if not results: + continue + website_url = results[0]["href"] + urls.append(website_url) + await asyncio.sleep(1) + if urls: + url_counts = Counter(urls) + most_common_url = url_counts.most_common(1)[0][0] + return most_common_url except Exception as ex: bt.logging.error(f"Failed to get search results from DuckDuckGo: {ex}") return None - async def get_random_website_url(self) -> Optional[str]: - number_of_tries = 10 - urls = [] - for _ in range(number_of_tries): - await asyncio.sleep(1) - url = self._get_random_website_url() - if url is not None: - urls.append(url) - # Return most frequent URL if there are duplicates, otherwise return first URL - if not urls: - return None - url_counts = Counter(urls) - max_freq = max(url_counts.values()) - most_frequent = [url for url, count in url_counts.items() if count == max_freq] - return most_frequent[0] - async def get_rendered_html(self, url): try: async with async_playwright() as p: From d1786773b084b8c5f8cac03cdc0000de45002ffc Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 07:56:40 -0600 Subject: [PATCH 436/554] chore: fix bugs --- neurons/validators/genie_validator.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 04ad6fd7..199278f6 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -299,7 +299,13 @@ async def forward(self): bt.logging.info(f"Init random with seed: {seed}") random.seed(seed) - await self.synthensize_task() + while True: + try: + await self.synthensize_task() + break + except Exception as e: + bt.logging.error(f"Error in synthensize_task: {e}") + await self.query_miners() await self.score() except Exception as e: From ebf58097e4b0a2888d280c98327b29f8de34742d Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 08:00:43 -0600 Subject: [PATCH 437/554] chore: increase key words --- webgenie/datasets/random_website_dataset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index 67408cfd..9e50ae3c 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -30,8 +30,8 @@ def __init__(self , **kwargs): async def get_random_website_url(self, number_of_tries: int = 10) -> Optional[str]: try: ddg = DDGS() - random_words = " ".join(random.sample(self.english_words, 5)) - random_words = random_words + " official website" + random_words = " ".join(random.sample(self.english_words, 7)) + random_words = random_words + " official website with landing page" urls = [] for _ in range(number_of_tries): results = list(ddg.text(random_words)) From be9f6a3b8ca5b961b3cc841ff15f2bc1410ef3d0 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 08:08:13 -0600 Subject: [PATCH 438/554] chore: change task_id --- webgenie/tasks/task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/tasks/task.py b/webgenie/tasks/task.py index c6800db7..9cfc3a93 100644 --- a/webgenie/tasks/task.py +++ b/webgenie/tasks/task.py @@ -4,7 +4,7 @@ class Task(BaseModel): - task_id: str = Field(default_factory=lambda: str(uuid.uuid4())) + task_id: str = Field(default="") timeout: float = Field(default=50) generator: Any = Field(default=None) src: str = Field(default="Unknown", description="The source of the task") From 7e2013c7503c1a7fb38acda828da3a821014d2b4 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 08:12:29 -0600 Subject: [PATCH 439/554] chore: refactor dataset entry --- webgenie/datasets/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/datasets/dataset.py b/webgenie/datasets/dataset.py index 8bf11edc..095230a9 100644 --- a/webgenie/datasets/dataset.py +++ b/webgenie/datasets/dataset.py @@ -3,7 +3,7 @@ class DatasetEntry(BaseModel): src: str = Field(default="", description="The source of the dataset entry") - topic: str = Field(default="", description="The topic of the dataset entry") + url: str = Field(default="", description="The url of the dataset entry") ground_truth_html: str = Field(default="", description="The ground truth html") prompt: str = Field(default="", description="The prompt for the text task") base64_image: str = Field(default="", description="The base64 encoded image") From 2f80b03d07b0dcc51239312ceb7a8d1ed231c54f Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 08:13:18 -0600 Subject: [PATCH 440/554] chore: refactor huggingface_dataset --- webgenie/datasets/huggingface_dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/datasets/huggingface_dataset.py b/webgenie/datasets/huggingface_dataset.py index 9a3bc7d2..6713dfc1 100644 --- a/webgenie/datasets/huggingface_dataset.py +++ b/webgenie/datasets/huggingface_dataset.py @@ -42,7 +42,7 @@ async def generate_context(self)->DatasetEntry: complex_html = await self._make_html_complex(html) return DatasetEntry( src="huggingface", - topic="design2code", + url=f"design2code_{random_index}", ground_truth_html=complex_html, prompt="", base64_image="" From 67ded95a34bfc6d51007b02e636c17468da83af0 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 08:14:11 -0600 Subject: [PATCH 441/554] chore: refactor random_website --- webgenie/datasets/random_website_dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index 9e50ae3c..7f1c2bbf 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -158,7 +158,7 @@ async def generate_context(self)->DatasetEntry: bt.logging.info(f"Generated website URL: {website_url}") return DatasetEntry( src="random_website", - topic="random_website", + url=f"random_website_{website_url}", ground_truth_html=html, prompt="", base64_image="", From c75b956c42f63fd36d5236f24a785365251b110c Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 08:15:21 -0600 Subject: [PATCH 442/554] chore: refactor synthetic_dataset --- webgenie/datasets/synthetic_dataset.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webgenie/datasets/synthetic_dataset.py b/webgenie/datasets/synthetic_dataset.py index 52c7289f..8483d1cc 100644 --- a/webgenie/datasets/synthetic_dataset.py +++ b/webgenie/datasets/synthetic_dataset.py @@ -59,6 +59,7 @@ async def generate_context(self)->DatasetEntry: return DatasetEntry( src="synthetic", + url=f"synthetic_{concept}", prompt=concept, ground_truth_html=ground_truth_html, ) From c34304a7f9cd367f111ffde1d7c1aa4534fecab4 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 08:17:43 -0600 Subject: [PATCH 443/554] chore: update image_task_gen --- webgenie/tasks/image_task_generator.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 0fdc7629..6c351bd8 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -52,7 +52,7 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: dataset, _ = random.choices(self.datasets, weights=[weight for _, weight in self.datasets])[0] dataset_entry = await dataset.generate_context() - bt.logging.debug(f"Generated dataset entry: {dataset_entry.src}") + bt.logging.debug(f"Generated dataset entry: {dataset_entry.url}") ground_truth_html = preprocess_html(dataset_entry.ground_truth_html) bt.logging.info(f"Preprocessed ground truth html") @@ -70,14 +70,16 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: if aspect_ratio > 7: # If height is more than 7x the width raise ValueError(f"Image aspect ratio too extreme: {aspect_ratio:.2f}. Height should not exceed 7x width.") - bt.logging.debug(f"Screenshot generated for {dataset_entry.src}") + bt.logging.debug(f"Screenshot generated for {dataset_entry.url}") image_task = ImageTask( - base64_image=base64_image, + base64_image=base64_image, ground_truth_html=ground_truth_html, - timeout=IMAGE_TASK_TIMEOUT, generator=self, src=dataset_entry.src, + task_id=str(hash(dataset_entry.url)), + timeout=IMAGE_TASK_TIMEOUT, ) + return ( image_task, WebgenieImageSynapse(base64_image=base64_image, task_id=image_task.task_id), From 264dc4e1b994283c1fe99abe5df7301a9443687e Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 08:21:35 -0600 Subject: [PATCH 444/554] chore: update log info --- neurons/validators/genie_validator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 199278f6..4e96dc07 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -76,7 +76,6 @@ async def query_miners(self): if len(miner_uids) == 0: bt.logging.warning("No miners available") return - bt.logging.info(f"querying {len(miner_uids)} miners") available_challenges_classes = [ AccuracyChallenge, @@ -94,7 +93,7 @@ async def query_miners(self): synapse.competition_type = challenge.competition_type synapse.VERSION = __VERSION__ - bt.logging.debug(f"Querying {len(miner_uids)} miners with task_id: {task.task_id}") + bt.logging.info(f"Querying {len(miner_uids)} miners with task_id: {task.task_id}") query_time = time.time() async with bt.dendrite(wallet=self.neuron.wallet) as dendrite: From 745fca105a7fc075e30c7794b9ed77f0e0a1dcf6 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 08:23:47 -0600 Subject: [PATCH 445/554] doc: edit version --- CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- webgenie/constants.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 136581a4..dc427411 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,3 +96,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.1.5] - 2025-02-12 ### Changed - Switched to winner-take-all strategy. + +## [1.1.6] - 2025-02-13 +### Changed +- Resolved an issue with duckduckgo search returning different results for the same query. diff --git a/pyproject.toml b/pyproject.toml index 0e754256..b5b86515 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.5" +version = "1.1.6" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 474f08ce..c4990d31 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.5" # version +__VERSION__ = "1.1.6" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 9bd1e49d2927e8b630e08a6cff9319c70e502f5e Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 08:27:41 -0600 Subject: [PATCH 446/554] chore: fix bugs for task_id --- webgenie/tasks/image_task_generator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 6c351bd8..461c1513 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -1,5 +1,6 @@ import bittensor as bt import numpy as np +import hashlib import random from typing import Tuple, List @@ -76,7 +77,7 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: ground_truth_html=ground_truth_html, generator=self, src=dataset_entry.src, - task_id=str(hash(dataset_entry.url)), + task_id=hashlib.sha256(dataset_entry.url.encode()).hexdigest(), timeout=IMAGE_TASK_TIMEOUT, ) From 11c66b55b307cdc90f2c25e0dd59fbecc017f8fb Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 09:42:02 -0600 Subject: [PATCH 447/554] fix: resolve infinite loop --- neurons/validators/genie_validator.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 4e96dc07..11215d06 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -269,7 +269,7 @@ async def synthensize_task(self): except Exception as e: bt.logging.error(f"Error in synthensize_task: {e}") - + raise e def get_seed(self, session: int, task_index: int, hash_cache: dict = {}) -> int: if session not in hash_cache: @@ -303,7 +303,10 @@ async def forward(self): await self.synthensize_task() break except Exception as e: - bt.logging.error(f"Error in synthensize_task: {e}") + bt.logging.error( + f"Error in synthensize_task: {e}" + f"Retrying..." + ) await self.query_miners() await self.score() From 142c17017628d2cb2efa6b6bb630ea98a4e119a5 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 09:43:57 -0600 Subject: [PATCH 448/554] chore: edit version --- CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- webgenie/constants.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc427411..95b88f47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,3 +100,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.1.6] - 2025-02-13 ### Changed - Resolved an issue with duckduckgo search returning different results for the same query. + +## [1.1.7] - 2025-02-13 +### Fixed +- Fixed an issue of infinite loop in synthensize a task diff --git a/pyproject.toml b/pyproject.toml index b5b86515..398ef5ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.6" +version = "1.1.7" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index c4990d31..2b76f50f 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.6" # version +__VERSION__ = "1.1.7" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 67503ee9d8e6cee607dcf9eab382aca9cbc76272 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 13:35:50 -0600 Subject: [PATCH 449/554] chore: add log for random words --- webgenie/datasets/random_website_dataset.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webgenie/datasets/random_website_dataset.py b/webgenie/datasets/random_website_dataset.py index 7f1c2bbf..f1f2aff7 100644 --- a/webgenie/datasets/random_website_dataset.py +++ b/webgenie/datasets/random_website_dataset.py @@ -32,6 +32,7 @@ async def get_random_website_url(self, number_of_tries: int = 10) -> Optional[st ddg = DDGS() random_words = " ".join(random.sample(self.english_words, 7)) random_words = random_words + " official website with landing page" + bt.logging.info(f"Searching for {random_words}") urls = [] for _ in range(number_of_tries): results = list(ddg.text(random_words)) From 4ee85a93ef052f2ac86a78c407f851a2fdef9609 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 13:37:11 -0600 Subject: [PATCH 450/554] chore: edit version --- CHANGELOG.md | 4 ++++ pyproject.toml | 1 - webgenie/constants.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95b88f47..a6e67f7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,3 +104,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.1.7] - 2025-02-13 ### Fixed - Fixed an issue of infinite loop in synthensize a task + +## [1.1.8] - 2025-02-13 +### Added +- Added logging to the random website dataset. diff --git a/pyproject.toml b/pyproject.toml index 398ef5ca..92280e09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [project] name = "web-genie-ai" -version = "1.1.7" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 2b76f50f..96d3aba0 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.7" # version +__VERSION__ = "1.1.8" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 6ee527af61670763f7da9c2be73df5ed85a8e5df Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 15:21:19 -0600 Subject: [PATCH 451/554] feat: dtao --- pyproject.toml | 6 +- uv.lock | 328 +++++++++++++++++++------------------------------ 2 files changed, 133 insertions(+), 201 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 92280e09..9cd27f9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,17 @@ [project] name = "web-genie-ai" +version = "1.1.7" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" dependencies = [ "ansible-vault==2.1.0", + "async-substrate-interface==1.0.0", "beautifulsoup4==4.12.3", "bert-score==0.3.13", - "bittensor==8.5.2", + "bt-decode==0.5.0a2", + "bittensor==9.0.0", + "bittensor-cli==9.0.0", "clip", "datasets==3.2.0", "ddt==1.6.0", diff --git a/uv.lock b/uv.lock index 0e746371..17ac3b72 100644 --- a/uv.lock +++ b/uv.lock @@ -185,6 +185,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/80/9f608d13b4b3afcebd1dd13baf9551c95fc424d6390e4b1cfd7b1810cd06/async_property-0.2.2-py2.py3-none-any.whl", hash = "sha256:8924d792b5843994537f8ed411165700b27b2bd966cefc4daeefc1253442a9d7", size = 9546 }, ] +[[package]] +name = "async-substrate-interface" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asyncstdlib" }, + { name = "bittensor-wallet" }, + { name = "bt-decode" }, + { name = "scalecodec" }, + { name = "websockets" }, + { name = "wheel" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/ce/34b012bfd118eb58c6dfd29ea30db4424bbe3f7243460768a69ad94c6ab9/async_substrate_interface-1.0.0.tar.gz", hash = "sha256:1b045fa9fb4077bd20dbe5e670efe1a7346a50db1a126380c9fa4e32b03cb5de", size = 59686 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/8b/f3a1281f9b378da724cf5350ab48cf3e685e5682c3b847b334319ce094a2/async_substrate_interface-1.0.0-py3-none-any.whl", hash = "sha256:1df2107e221559c5127e4673c55c42db310bf72a344a7da0aaaa4cde400d28c0", size = 63085 }, +] + [[package]] name = "asyncstdlib" version = "3.13.0" @@ -254,16 +272,15 @@ wheels = [ [[package]] name = "bittensor" -version = "8.5.2" +version = "9.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, - { name = "async-property" }, + { name = "async-substrate-interface" }, { name = "asyncstdlib" }, { name = "bittensor-cli" }, { name = "bittensor-commit-reveal" }, { name = "bittensor-wallet" }, - { name = "bt-decode" }, { name = "colorama" }, { name = "fastapi" }, { name = "msgpack-numpy-opentensor" }, @@ -282,45 +299,46 @@ dependencies = [ { name = "rich" }, { name = "scalecodec" }, { name = "setuptools" }, - { name = "substrate-interface" }, { name = "uvicorn" }, { name = "websockets" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/3b/430a897d6045f430bcb7eeadbadf6c60653a1023c495afb24d50a4a98943/bittensor-8.5.2.tar.gz", hash = "sha256:2adcfd47b016ce499cd9f378f0d5562ca55d40801128ca12418abf6ada14898b", size = 211979 } +sdist = { url = "https://files.pythonhosted.org/packages/b9/e4/8aea55941376f752f5a8074e5b3531579c0686d78c7f9628d92ef65542c7/bittensor-9.0.0.tar.gz", hash = "sha256:64321a5f3acc07dbde41ecb81d753f05d0a8fcd19a22f35cb56e5af8e15e81aa", size = 223046 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/23/84fa9b12eb3dca3a1caf7654123b08c6d926d71d9863a2d05d6dcb276b86/bittensor-8.5.2-py3-none-any.whl", hash = "sha256:ccee3e48c6d0747e7194eb148980c485f9ed857d97b50fed3ae933b9344767ef", size = 260629 }, + { url = "https://files.pythonhosted.org/packages/a0/d5/46f94a11ff179e291b42f9cb770f6cb0facdc8480f8d6326548a7a70a7e9/bittensor-9.0.0-py3-none-any.whl", hash = "sha256:1f5e97d2333b46c535b03e780b7e3d58204e2ed116db6244227433449f6616cc", size = 264260 }, ] [[package]] name = "bittensor-cli" -version = "8.2.0rc8" +version = "9.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "async-property" }, + { name = "async-substrate-interface" }, { name = "backoff" }, { name = "bittensor-wallet" }, - { name = "bt-decode" }, { name = "fuzzywuzzy" }, { name = "gitpython" }, { name = "jinja2" }, { name = "netaddr" }, { name = "numpy" }, + { name = "plotille" }, + { name = "plotly" }, { name = "pycryptodome" }, { name = "pytest" }, { name = "python-levenshtein" }, + { name = "pywry" }, { name = "pyyaml" }, { name = "rich" }, { name = "scalecodec" }, - { name = "substrate-interface" }, { name = "typer" }, { name = "websockets" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/98/bf296a271d460f2912eaad1914197dde5709b88c44ae92784a38e0932b2c/bittensor-cli-8.2.0rc8.tar.gz", hash = "sha256:df7da6ac6344c67a5f4273157c42810ca28dc0564eb7db58160e7d6f754edd01", size = 188330 } +sdist = { url = "https://files.pythonhosted.org/packages/78/7a/d605c7ea004d0f789da0eaf96a96b0ea0f5e75a7843c5c54979cda59a5dc/bittensor-cli-9.0.0.tar.gz", hash = "sha256:f775c24b6acf574c4b64d009d8728f2657c2894fecb15385478fd8ac4501f0fd", size = 178898 } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/95/7d77a3a471e759741d19cb3d94b580942c0b2cd586b89a8ea677ba2015c6/bittensor_cli-8.2.0rc8-py3-none-any.whl", hash = "sha256:2108b4158c8719571f1f990f65333a1d7746b1b3dbfefa8b89da11d8cf7ab305", size = 198315 }, + { url = "https://files.pythonhosted.org/packages/6f/3b/468ed436a02b242197643979e829fa2655f660037e97baf0d036b1611641/bittensor_cli-9.0.0-py3-none-any.whl", hash = "sha256:211334018e878dd4145d1b7998630edd2e3a486ac4e992493b535306e3d0c178", size = 191161 }, ] [[package]] @@ -341,7 +359,7 @@ wheels = [ [[package]] name = "bittensor-wallet" -version = "3.0.0" +version = "3.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, @@ -352,61 +370,63 @@ dependencies = [ { name = "rich" }, { name = "termcolor" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/79/324672b28f7b3ac58baadc92f9a1b9fa74b32c978a7adf3430233103b06f/bittensor_wallet-3.0.0.tar.gz", hash = "sha256:045561e1be2546965a4adecb1515f61c7953b262328809c71d1acdb3aeddc20f", size = 72409 } +sdist = { url = "https://files.pythonhosted.org/packages/a7/fd/d795b144a50e9c213d380d75cce937c6b4d0f0b26f7aac2f80e619986090/bittensor_wallet-3.0.3.tar.gz", hash = "sha256:60e534e1f1c256e4dac788acac4724ec92c445c24058954b7e12a3aca6828e1b", size = 73104 } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/35/c3bf16166c596dcc3b1498e336163cb7622dcdbfa6734c9c6b59c69642c0/bittensor_wallet-3.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7f1744f87d2a859700409d6419448f12475147f8c213b4d1bcc073e11fb8e12d", size = 805856 }, - { url = "https://files.pythonhosted.org/packages/f2/7a/4efc2f566fbecc3a2b2c6b96794620d4f5ce322f6bafe58f82b0ae05ab37/bittensor_wallet-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b5e32c40f1dfa9f6dc30c9525aedb02975fb25ea70692efd31890d02789225", size = 771198 }, - { url = "https://files.pythonhosted.org/packages/56/e5/8235041c2fa655f42d4a3a45720863c5e810aa310a401f081db68c816a85/bittensor_wallet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5beae01cffc7557dc6a68c3ceaf0568f105b25b8b53a4f02db8b39aa364fed4", size = 3165442 }, - { url = "https://files.pythonhosted.org/packages/f7/a3/14a9beec68caffda7660d92835c19fb1bf4d3bf07bc2c805b9682f8caab4/bittensor_wallet-3.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a69271528f81386dd644ca6c03ea902f60b19c5fd9520d86340137cb67241368", size = 2968740 }, - { url = "https://files.pythonhosted.org/packages/92/1f/c0e016333f84bd5cbce6a5855cfb50564f6fcf4d13662f7a5a6ad7c721fc/bittensor_wallet-3.0.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ef66c0b08386c0437fc1ee45c33cc59e5ce713a4f08fd40285c70ccec3129ab5", size = 805237 }, - { url = "https://files.pythonhosted.org/packages/cd/a6/876548ed4833c7340c9880bfe017b49d68e1b2d6ce053a102815678a1514/bittensor_wallet-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f410a21382b128a90b403dd0c0a9b3751c5d76cbd5eb4c12e3629a5a7e6db898", size = 770657 }, - { url = "https://files.pythonhosted.org/packages/2e/02/da36c134dc5b575d606b6d180603fef66a48a8684417bc61f045206a83df/bittensor_wallet-3.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3998ba3f0c2a563dae5605a01f971c421ed4291a0f57f9db5fe6ff4f5acea3eb", size = 3164911 }, - { url = "https://files.pythonhosted.org/packages/2f/fc/9a69465dfdfec846d92b9de5569f4eb62669c45951aceaeb37ca0b4f4385/bittensor_wallet-3.0.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ded68ced27dcaf4473219eef27709c8bd30f2dbc6bbec7656329ab550af234a6", size = 2968063 }, + { url = "https://files.pythonhosted.org/packages/bd/88/20811ef592a1222ff0c75e0bb03e2a117ff12068eb81562eea5f0b1b121c/bittensor_wallet-3.0.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:5e13e08a1e632dcc0a888a85ba15d9beda056ccd22c756ef7419e6d4cea85d32", size = 821469 }, + { url = "https://files.pythonhosted.org/packages/fa/97/d23b678bbbbbbbb7b604219e27748b92a4547b30aefa31564849a1c84192/bittensor_wallet-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7ff0f1bd4dc6f39000a0e70f23bf3bf2830161f0e6ab49a94969a51410962867", size = 771262 }, + { url = "https://files.pythonhosted.org/packages/b4/e2/65894b7c2823f195f6efb53170e155e2bfb156759c06de629eda8513ad17/bittensor_wallet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27a6c6eadb1e96ec3b79a12852c95100e42d4d30c04ca8f19a686ecc60310b5f", size = 3166578 }, + { url = "https://files.pythonhosted.org/packages/46/6b/8236cbd22468b05634bf6467873c8add24223b0aaf74eb843202a445e034/bittensor_wallet-3.0.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fd40bfd51f479747b5264595f6b55ab389b97556040d9b6d4c734eb9bfcc997d", size = 2969786 }, + { url = "https://files.pythonhosted.org/packages/e6/a1/33d9cc74c6704ec62de4ea38925169e9d402e463281b3edbf3ca57a86e04/bittensor_wallet-3.0.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:c8bf959e10a4e1d37f0a00d426475cf9633181a5d061cd65500b3bf5277e9263", size = 821084 }, + { url = "https://files.pythonhosted.org/packages/2c/6f/a2e5a168c7da313fb48ab9135765bcf8951e37d865e015ac93ffcab72e24/bittensor_wallet-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:252efa39b183b1a1de55a86789d8907c422b35d70212259c52519c31871d87af", size = 770756 }, + { url = "https://files.pythonhosted.org/packages/d3/9e/bf23a22faeb02f7e522dac8bc4b60817dc57c6dc413c59162dfda0a56259/bittensor_wallet-3.0.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3fbcda8641a4ecc964b02d418d7e4ef5e345919fa072c1b1e66209e119a9743", size = 3165814 }, + { url = "https://files.pythonhosted.org/packages/fc/6c/56beccb193072687275dd2e64e8e4602134a9d0586d364956981864df933/bittensor_wallet-3.0.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6dc4a4b268004e5b3b93884a9344e2154b2b09f9e0816c3c0237032b63eda753", size = 2969128 }, ] [[package]] name = "bt-decode" -version = "0.4.0" +version = "0.5.0a2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "toml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/a9/7449c1073af4ef57520fc01e587a664591ff0331b694a3ec9c1aff3c3133/bt_decode-0.4.0.tar.gz", hash = "sha256:5c7e6286a4f8b9b704f6a0c263ce0e8854fb95d94da5dff6e8835be6de04d508", size = 3496621 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/29/08/090efa626ad7bb545febf8e47a96dd976effcf6c027ff06cf6e053d83104/bt_decode-0.4.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ee9731ecf76ba4f60e10378b16d15bea826b41183ab208e32a9a7fd86d3b7c21", size = 557364 }, - { url = "https://files.pythonhosted.org/packages/6c/53/7e32ff14583db56a9f1ecc2a506a4af9ca6106e2240928d937b0516e0934/bt_decode-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e0ebd9e6f6e710fce9432d448a6add5b266f19af5ec518a2faf19ddd19ce3dc", size = 542812 }, - { url = "https://files.pythonhosted.org/packages/30/39/835655b931dd4b7734743bf66caf28bd94cd5067a8141f6ce22bb8e2de91/bt_decode-0.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd898558c915dd9374a1860c1aee944cd6acb25f8e0f33f58d18eb989c49fab", size = 604124 }, - { url = "https://files.pythonhosted.org/packages/15/8d/0920fcfa46296fb23093d80554cc305d66a0e66d82b392aea8cd70004dc8/bt_decode-0.4.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f87500550b030c3d265ab6847ef25f1e4f756b455605f1977329a665e41b330", size = 600859 }, - { url = "https://files.pythonhosted.org/packages/6a/86/0a709fb430d157d0be29733a66e56ee78f8354b2dfba42a64feeb54d6e42/bt_decode-0.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59fa64d5eff9fcc00f536e3ef74932f40aeff1335bd75a469bce90c1762451ae", size = 669825 }, - { url = "https://files.pythonhosted.org/packages/d4/83/58495d791a8be3ee5064af3d6e4039f11a0b13dd3b30e8c91dc247405f23/bt_decode-0.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2be0732720588d047b00eb87e234dd83ebbdb717da8d704b8930b9ab580a6c3", size = 708326 }, - { url = "https://files.pythonhosted.org/packages/56/be/ac3f35a7c23929c428a705e872f596a86afc0eae76d3276b79872abb2817/bt_decode-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4107e8b75966c5be0822a5f0525b568c94dbc1faa8d928090fa48daa329b45", size = 614048 }, - { url = "https://files.pythonhosted.org/packages/7e/ee/6b16c47b5ac00cd511da91ab762c3d2353ba9983f205e8d47a77419221f5/bt_decode-0.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:46e09e7c557fe753c20226ec4db887a4a1b520d36dc4d01eb5d2bd2e2846970e", size = 664008 }, - { url = "https://files.pythonhosted.org/packages/04/09/97f411183dd7497edcf5f0d6cbbd1ef56655395b18e614e272698a9d6802/bt_decode-0.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e817fe5e805bc393b266909709660dc14bd34a671712da0087e164a760b928b4", size = 781116 }, - { url = "https://files.pythonhosted.org/packages/71/f8/ec920e1713e24462142f55aa85c1ad6969d826e2cb32d583ccc37fa8ddb4/bt_decode-0.4.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:59f9a61789003c345b423f1728ee0d774f89cc41be0ab2af0f2ad6e2653084b5", size = 862290 }, - { url = "https://files.pythonhosted.org/packages/8b/c7/5b0504f14f1b8c9b60c69a080832f53774f30db181e472944260e0cfbf1c/bt_decode-0.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:905715452ecf4ce204aa937ee8266ea539fc085377f92bd9506ec76dcd874347", size = 819695 }, - { url = "https://files.pythonhosted.org/packages/13/9e/5d2953e4416db004d21f6c480657c8f9b84ee27b48fe5478d2cdba2ec49a/bt_decode-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e85f5f12e6bb00253e194372d90e60f129d613f0ddedae659d3b9a3049a69cf", size = 784116 }, - { url = "https://files.pythonhosted.org/packages/7e/b2/26f374ee94c88a90310569bd5d2f282c105a7ee1ae298e0282d3ee560f50/bt_decode-0.4.0-cp312-cp312-win32.whl", hash = "sha256:ed4c3c4383c9903f371502c0d62ce88ecd2c531044e04deaeb60c827ae45ad8e", size = 390937 }, - { url = "https://files.pythonhosted.org/packages/7e/35/0610ddaf739013a3fff13961edadeefff4be83fff7735bc0592214f0246b/bt_decode-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:68beccbb00f129b75d189d2ffc48fd430bf4eab8a456aab79615b17eec82437d", size = 417431 }, - { url = "https://files.pythonhosted.org/packages/6b/2f/4cdfdf8bd52a38e27b50f36e9b9288085a9bab1d703310cc426e4b4243be/bt_decode-0.4.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:88de7129c3323c36cd6cce28844fb475556a865ec6fc87934ec5deeb95ff2d86", size = 557018 }, - { url = "https://files.pythonhosted.org/packages/43/16/7d29d9f719bab8f3890d6d6dfaaade16aa7616e57bdde8f0114781430134/bt_decode-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:056e6245a2119b391306542134651df54df29569136be892411073fc10840c8e", size = 542668 }, - { url = "https://files.pythonhosted.org/packages/c5/d3/a15421174b9943fd86f2470bfe109b6b6a800a2e9cca414b5bb1b2367752/bt_decode-0.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:faa76d0b8fcb0f9ae2107e8c6ae84ea670de81c0adda4967a52d4b7d1de8c605", size = 603689 }, - { url = "https://files.pythonhosted.org/packages/9e/e7/ef333c2c6c2b2319fef3e28ef9d5a2e82c30b8c7f7f3875b182dae7fc957/bt_decode-0.4.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7a3ff15bfe86d482e642dfaa6e5581b65815e7663f337af7502b422fea2fdcc2", size = 600436 }, - { url = "https://files.pythonhosted.org/packages/7c/4c/3bd5c96dcf2ef09d73f0d35cbdc0d32c1b8f9f0c0d9e10af087405f38e7d/bt_decode-0.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa7687c01c516f84274a2e71ba717898eef095e08ec7125823f7a4e230bd46fe", size = 669460 }, - { url = "https://files.pythonhosted.org/packages/81/d7/df22e559dfe7941edfb33357fbc2dc9f6025ae4fb58740213dc09b1dd53b/bt_decode-0.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d3cf8cfff714600db01c6cd144906fe0a8be85293711e279b8089f6ccaffd71", size = 707396 }, - { url = "https://files.pythonhosted.org/packages/2c/19/0d1eeb47ac8844021e6f7f69c92069c0c80ccee1de1614a9e5dac96da50e/bt_decode-0.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:983972ecc83bd0507e72ae316281960b7e26e31386525c7905f7cdb8fa3e7de1", size = 613845 }, - { url = "https://files.pythonhosted.org/packages/a2/06/308512e5f17e3b3a9472d2271114da0caa394c38523b7d0aa5fc75ee3b89/bt_decode-0.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:32e3950b120b8b59ae5ab70005ba9b5c7560a0e222e805f47878cb259a32ed39", size = 663927 }, - { url = "https://files.pythonhosted.org/packages/a4/53/ec4fc237ffe8b8f7e8e4bd78b54b0c82abad5407f3faed7df0828ba2f0f2/bt_decode-0.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66d906ac225e3cd169dde1e0af21e8d73e8ea7dea3f7e9afcdec501bced3d83a", size = 781071 }, - { url = "https://files.pythonhosted.org/packages/d2/d7/700ddb1280e5aafd0404f445847ec6c4c27f7df949a7d148e8dc3c0f5a3f/bt_decode-0.4.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:58bf09b004dc182748e285b5bc15ac6305af4ab9c318f995c443ba33bb61fbb6", size = 862093 }, - { url = "https://files.pythonhosted.org/packages/53/32/c9d9a5787f793da0ac8a9b5c950f45ad8b2449a751cf5b84ab430c2bc9f7/bt_decode-0.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c202f22152b3186cbc1c319250d6b0ecfe87cf9a4e8e90b19cc9f83786acdf1a", size = 819486 }, - { url = "https://files.pythonhosted.org/packages/3f/94/c182bd002357d68d663a118dc41b95d5f400aac6e9e5074c53693b6de41a/bt_decode-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6dd31b0947b7b15a36f7f9bfdb8ae30ffe3f3f97e0dc4d60bf79b9baf57f4e5", size = 784067 }, - { url = "https://files.pythonhosted.org/packages/29/9c/a17e71aa0e4f674c7a59b5e65b042d2bdf91bebc316e969a1c31c6b51ef1/bt_decode-0.4.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ebb3b72146e7feb08e235d78457b597697708149d7410f184098b73c5ab38aa", size = 600955 }, - { url = "https://files.pythonhosted.org/packages/f4/c6/429323a3c72251c6bc22926995ea3e490db07bb96e608ac4ca9eaa282e62/bt_decode-0.4.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9571680e6b74fab00cbd10dc255594692a9cdf615e33170d5a32112c1da8e3e4", size = 599227 }, - { url = "https://files.pythonhosted.org/packages/15/f4/3495a7d242668d347e851424e95acbbd2916ae70f7827e0533bd3c59e653/bt_decode-0.4.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dec8af1719ced86da6f7b1dcf70e1d480cfb86e2cf7530692d3e66ad1e16067d", size = 666872 }, - { url = "https://files.pythonhosted.org/packages/f1/3a/f0875014848888259f8646f915c1a8046d420799a155ce80d5af10e77044/bt_decode-0.4.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46d2308e13615951f89ff7ba05364a2e3747626b29fd4ee39c085ea56cb5fe", size = 709410 }, - { url = "https://files.pythonhosted.org/packages/be/e5/bc31c0f2a29945c548cda2538c8b5368da722217da7ca0a64eedd4df56a2/bt_decode-0.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0df0436d736544587002e0fa4fe3887b28cec8de4a9036c1ea776c560e966b8d", size = 778135 }, - { url = "https://files.pythonhosted.org/packages/25/48/387fd8cef96a86c39e6716455b493a759fbe9a67bcaa2dfe39c3d3b6b11b/bt_decode-0.4.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:579aba5010a078831af2025cd03df9d429fa35008ec46bc1561e6147e2c9769e", size = 860601 }, - { url = "https://files.pythonhosted.org/packages/12/85/1458d9eaf9a74390ac5e0a1a3be5eaf53550aa4f4c28362fb4f80a94c8a6/bt_decode-0.4.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:039e880688d4c5f2ee090980649811b700593e21eccee520b294c07b85008bce", size = 817941 }, - { url = "https://files.pythonhosted.org/packages/70/72/723265284f71fb95556c5b27c83a370b2e38e02666fd17dbb129856fb1f2/bt_decode-0.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a45173a6f0e48b28b190bfb250b6683984d115d70a6d2ff5102a2421d581de6", size = 783857 }, +sdist = { url = "https://files.pythonhosted.org/packages/db/32/d0ac8e0ffd62f7c3d274926573bfb51b9661367f183fcfee8431b8a4d6b3/bt_decode-0.5.0a2.tar.gz", hash = "sha256:f5f41dd6b7797d58deaed7aec92c651aab4b64f9a08d203543d654cd2ba0cef9", size = 1196580 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/42/e6cc279a83e5c5742bec404bdc3920d50e8edcc593999fcbb76d0eab2b50/bt_decode-0.5.0a2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:63a083dd59d7ad199bc34f5511e9c43a9a68451d74e8618ea726c7bdbdc0f123", size = 589984 }, + { url = "https://files.pythonhosted.org/packages/65/8b/9ef3555db39da72cbf121d3c0ac5d51806c84ffd456092e62f37bff0c3e2/bt_decode-0.5.0a2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b096cfbaf67029c400b49a59b3fdd41481405de121e6b036db52a2fb1c8a2910", size = 577096 }, + { url = "https://files.pythonhosted.org/packages/62/b3/4d23231454c2931cc225e5b5ec905923bac173eee762587118cffd56436f/bt_decode-0.5.0a2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00326c4e8338f5d03579c7e71796da40af548f224216bc9b5354333f13c96325", size = 635772 }, + { url = "https://files.pythonhosted.org/packages/08/d8/b21fdf571c9a23049f0eb9a7792ed7e3c37a2cba6e9d6a3a05960bf9169d/bt_decode-0.5.0a2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3b2537b2166983ee54226b6d6d592fe0255eadecea3aa264b8f5fd9045b2afa6", size = 635973 }, + { url = "https://files.pythonhosted.org/packages/26/0e/b18c4cb8f89b42a519df1b6afde649f4e78f2118ac36513690162a6c1eb6/bt_decode-0.5.0a2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10f9d2d2a92114417899afbcf29e29d456aba9e3239f53e9cd8c8954751e2f03", size = 703793 }, + { url = "https://files.pythonhosted.org/packages/60/30/fc3ef4e46701db97487e106af311d318233568f5ddcf4aa3985dc573abae/bt_decode-0.5.0a2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07035f38ccb6839980c64c0acc6988ada8670366ab87f052c385b511f8694ff0", size = 740602 }, + { url = "https://files.pythonhosted.org/packages/ca/8d/4c68f1b78bb9d8ebc304fc18127f25417757fa95c7399ddbf387c6e48c07/bt_decode-0.5.0a2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20c0535438fb5921232fa3be84177d5fb3d2a8bc4d0cc8497a0f24b8e0dfccab", size = 643033 }, + { url = "https://files.pythonhosted.org/packages/a9/7c/eee0495d0db55db2d1e59a4ad02a44fdcf38884e98299ad0ae52fb83a28f/bt_decode-0.5.0a2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d919dfdaf2407865630481b4d8aab23ac34dc935e19ae5a596d773b1e90db45b", size = 698183 }, + { url = "https://files.pythonhosted.org/packages/0f/94/59ffa41cf163fd79db78702043d6af0a5e7b19b95d90e4e5c3a13515184d/bt_decode-0.5.0a2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bf223d94e20d9c2ad83fd430a812131c927a715254ff42844c114bab8ad73dfa", size = 805359 }, + { url = "https://files.pythonhosted.org/packages/72/39/6e93458954b76cdce708c1153cf66f52aec60fd33c13bfe864f9e1799c50/bt_decode-0.5.0a2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5c253c48ea116e7484415c4577da956b3b3d2a445a5bb901ad9f3e88571ef5da", size = 887592 }, + { url = "https://files.pythonhosted.org/packages/21/27/1705494caff2246cc545e60b46c752b24eef7861333c1eb01dfabe228070/bt_decode-0.5.0a2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ba264ea9424826ac97354e4ae1cb2729a91b6a474f9c462d277e6ba27c544bec", size = 844837 }, + { url = "https://files.pythonhosted.org/packages/63/0a/e10ed03da94b576c1e2acd99371713b7d54a596d1bbc401f33b9382e2d3d/bt_decode-0.5.0a2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52fa7732db915339854cd81d1f1c8ee2db4958260229086568c4c0fcd8af7c31", size = 806971 }, + { url = "https://files.pythonhosted.org/packages/af/3f/f8d6faa6c658feb4a1731167e8d1b00e6a51aa85d1393e4fd61a999034a8/bt_decode-0.5.0a2-cp312-cp312-win32.whl", hash = "sha256:5c7177943af8288556b84cb148962c889075cda7c8ca4655ae067b2cf0d50ac0", size = 412253 }, + { url = "https://files.pythonhosted.org/packages/a1/99/192a42bd205eec697a76be9bcdad16923a1ffe50c9c9307b3b24e4ca50dc/bt_decode-0.5.0a2-cp312-cp312-win_amd64.whl", hash = "sha256:f07f41ab81c3cfa4fccd249e239dc74180ca95d2840a064d4f2ecb88cbc0d6b2", size = 440516 }, + { url = "https://files.pythonhosted.org/packages/21/d5/f08d796e4a3759ba09df3b613a413ed2de5fe0f38dba9b4d3d6908bdf3f0/bt_decode-0.5.0a2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:dc5296ad5be76f189d3d6332dbe4f5ea2fb20057e68ff636ce91f2609f66fb35", size = 589932 }, + { url = "https://files.pythonhosted.org/packages/57/8d/843fb1f0f01c3b59df3ff30cd693046244b2da64f5d60d18461a890fd63e/bt_decode-0.5.0a2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f9688eb67f04d1bb806b675014fb2adaf1441f078344196ff5e1a472c988927a", size = 576895 }, + { url = "https://files.pythonhosted.org/packages/cf/8b/61e71543188c08b18a05f3c8b5d90649359affefc596e16d7bad31201245/bt_decode-0.5.0a2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43dc4dac309cd995d7fccec8f521a91793d72a67609b492e87ef8b2394848660", size = 635775 }, + { url = "https://files.pythonhosted.org/packages/d5/05/a5db18b52f8f72aa7c3335bfe41ac8184ab36fcd6030b4a8085f32449a86/bt_decode-0.5.0a2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c2d5f4303e8b87261ca17bf303821f55edb5b5359fb69ff124ba5c0ab7de7bd", size = 635233 }, + { url = "https://files.pythonhosted.org/packages/78/1e/6bd44c0316d1a594f0cdfea18cc82679fa669ebf91195dbe57db9cb757de/bt_decode-0.5.0a2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176994fedf95340b54ac936f25b42abbb265645a990d02ab6cd551427c1d14f2", size = 703667 }, + { url = "https://files.pythonhosted.org/packages/e2/7a/4bab052db00bb9d6b3d855e5c73daa4cb39b44a0d42008ba3f66191d7819/bt_decode-0.5.0a2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ba059ec2c49eba7c5674d3dadfbc0e904fd2c6da5b3e1a0ce8d1e85407eefa1", size = 740765 }, + { url = "https://files.pythonhosted.org/packages/2a/ea/271b6c0626ddc0bd892ade43d3d92a7a8940dcdc9e766aaddb71aca7ac0c/bt_decode-0.5.0a2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3994ee24fdd724d3ee99fbadd9dbf4aeacd8c46198906633ef9bca9b715b24f", size = 642818 }, + { url = "https://files.pythonhosted.org/packages/92/cf/dc9c51f1645e0039b4b3d0a1ee178c03747240a64fc3c74ba4aa13d54696/bt_decode-0.5.0a2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1b1a5fa89551e6fcace3b6fd4d47b79ee8744ea7632c84a6dfecb8be30594f65", size = 697991 }, + { url = "https://files.pythonhosted.org/packages/e9/ce/82e354750241913e6e1cbca414dcee8dc78c822240bf8d07351f35db182c/bt_decode-0.5.0a2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cae0f21159511066178cfffdeeafab9142d7e933f5d320cafcc9cc6c7702e088", size = 805045 }, + { url = "https://files.pythonhosted.org/packages/ee/ce/f9d2e85cc479b915017b55b1443180b0f0270979ecb6c17dcfcce58f5695/bt_decode-0.5.0a2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:62039a60ce110a26e9d40d534ba9ad60b9e623d6ece0ce47744ffd168168e3f3", size = 887243 }, + { url = "https://files.pythonhosted.org/packages/b8/a5/3a58ad98f959b1f3ec3dab7500701bde949467260dc7e00a855bc2a5e5ba/bt_decode-0.5.0a2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83c864c9427b83f3204df2e302588bc8eb12397acd73a33bdc86c0db11097e6a", size = 844395 }, + { url = "https://files.pythonhosted.org/packages/ce/a6/2e78fe2fb206d5ca5293329ea5f97def807d9b4236178b05addcced157c7/bt_decode-0.5.0a2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d55902248755f7ed557f27bc48706a7d0fdb0faa57fbaf3baa2cabe275f6c022", size = 807006 }, + { url = "https://files.pythonhosted.org/packages/e0/be/2786d9da5e5e07fea4ca9ea22f9e60d2130a508f85e9c0a22cb80cfa3e2d/bt_decode-0.5.0a2-cp313-cp313-win32.whl", hash = "sha256:0bfb120ab3db7340d7aeef873499a3a6e8e7b2b649cd7fc2ee7501094afccbea", size = 412191 }, + { url = "https://files.pythonhosted.org/packages/30/6d/67ec8ccd58072acc1358b580e528f8ab48fbcdcfd38c2a1f36abf8d4b14a/bt_decode-0.5.0a2-cp313-cp313-win_amd64.whl", hash = "sha256:680b4a78b27e7a73cd2837f5cedf4556686ecb8b49b1c38d6ea59f1512860866", size = 440608 }, + { url = "https://files.pythonhosted.org/packages/ca/f2/e089106d596ee574c3bcce2c18378ed014a79a4f91ba8e18978baca0651f/bt_decode-0.5.0a2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:615afa8e147505096cf05943fc8ca5f65de43f6bca5c00231c245eb0c6143409", size = 633809 }, + { url = "https://files.pythonhosted.org/packages/ee/e6/e93636bcb9011e402356c85e03654d15348a1e5ef82fb5400e49b35f2dd2/bt_decode-0.5.0a2-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:446a9ce02725af8ed3a4ce96a26b06010df28945e7e9609cd08fa2e6efd6745c", size = 633795 }, + { url = "https://files.pythonhosted.org/packages/cf/3b/2fb48aa8d796517dbfbd27febff8ea9a1d17425ac3ed0a983cdf94f89964/bt_decode-0.5.0a2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13004a9f0a75f726338ccd6947a20492119fb3d2757aa1a6e6fb469ab0a29e06", size = 700510 }, + { url = "https://files.pythonhosted.org/packages/8d/db/9e457d93956eb5f20d6343312e9878ed5a81b7d9eb23535886c8dfd848bc/bt_decode-0.5.0a2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d3eee3a200d904ce7c8bc179917bbceda935bb989d031f041cf629b1d4c40a68", size = 739709 }, + { url = "https://files.pythonhosted.org/packages/c9/1a/34fe4d7ea1b4e3561f21a97270e949e7759195045245f379962dbd09e1cb/bt_decode-0.5.0a2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ed97cd737bad5c8b908c27664031f67673fc91eef85cb771750f0e76530df75a", size = 804183 }, + { url = "https://files.pythonhosted.org/packages/2c/7e/451630b300480fcbb51afcfae98e844fd9713040fcd503ecc433610b73d1/bt_decode-0.5.0a2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f89ba68fa9291fff5575cd315f5533f544bfd581cbdbca91e8dfe77b87ff0f74", size = 885638 }, + { url = "https://files.pythonhosted.org/packages/af/50/cc0c8c52169f02b4e05d6240702f4c2b757466ec5cef309895ec80771bfc/bt_decode-0.5.0a2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7e94ad44039cd033ded93d390e3926246571c588d05e59b68efd3992e5bbe75c", size = 842597 }, + { url = "https://files.pythonhosted.org/packages/35/f7/0ce061c3ae80927e7b9d4ade5fe3e128a3fb1a8984e8f95f341a081ad477/bt_decode-0.5.0a2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17187ab67e492b901993fde7fe81f6ac0a45e091f1bda980090c44edb65da2a8", size = 807823 }, ] [[package]] @@ -735,18 +755,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/8f/ee72af555cd58feb928ff0fd3977913f4ecd0ce8ad92cf4031c36de91776/duckduckgo_search-7.2.1-py3-none-any.whl", hash = "sha256:72ebbf6ad8759e3c3c79521cd66256e7a4ac741c522fd9342db94de91745ef87", size = 19720 }, ] -[[package]] -name = "ecdsa" -version = "0.19.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5e/d0/ec8ac1de7accdcf18cfe468653ef00afd2f609faf67c423efbd02491051b/ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8", size = 197791 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/e7/ed3243b30d1bec41675b6394a1daae46349dc2b855cb83be846a5a918238/ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a", size = 149266 }, -] - [[package]] name = "einops" version = "0.8.0" @@ -765,19 +773,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/f0/a35e791bd73fa425838d8d0157754150ded141a94cf30d567dfeb9d57316/eth_hash-0.7.0-py3-none-any.whl", hash = "sha256:b8d5a230a2b251f4a291e3164a23a14057c4a6de4b0aa4a16fa4dc9161b57e2f", size = 8650 }, ] -[[package]] -name = "eth-keys" -version = "0.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "eth-typing" }, - { name = "eth-utils" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/4a/aabe0bff4e299858845fba5598c435f2bee0646366b9635750133904e2d8/eth_keys-0.6.0.tar.gz", hash = "sha256:ba33230f851d02c894e83989185b21d76152c49b37e35b61b1d8a6d9f1d20430", size = 28944 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/ee/583612eed5d49f10bd1749d7dda9e93691ab02724b7af84830046e31c64c/eth_keys-0.6.0-py3-none-any.whl", hash = "sha256:b396fdfe048a5bba3ef3990739aec64901eb99901c03921caa774be668b1db6e", size = 21210 }, -] - [[package]] name = "eth-typing" version = "5.1.0" @@ -1654,6 +1649,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/59/7854fbfb59f8ae35483ce93493708be5942ebb6328cd85b3a609df629736/namex-0.0.8-py3-none-any.whl", hash = "sha256:7ddb6c2bb0e753a311b7590f84f6da659dd0c05e65cb89d519d54c0a250c0487", size = 5806 }, ] +[[package]] +name = "narwhals" +version = "1.26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/6f/75929abaac73088fe34c788ecb40db20252174bcd00b8612381aebb954ee/narwhals-1.26.0.tar.gz", hash = "sha256:b9d7605bf1d97a9d87783a69748c39150964e2a1ab0e5a6fef3e59e56772639e", size = 248933 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/fc/420680ad8b0cf81372eee7a213a7b7173ec5a628f0d5b2426047fe55c3b3/narwhals-1.26.0-py3-none-any.whl", hash = "sha256:4af8bbdea9e45638bb9a981568a8dfa880e40eb7dcf740d19fd32aea79223c6f", size = 306574 }, +] + [[package]] name = "nest-asyncio" version = "1.6.0" @@ -2043,6 +2047,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/a9/bd88ac0bd498c91aab3aba2e393d1fa59f72a7243e9265ccbf4861ca4f64/playwright-1.49.1-py3-none-win_amd64.whl", hash = "sha256:47b23cb346283278f5b4d1e1990bcb6d6302f80c0aa0ca93dd0601a1400191df", size = 34060667 }, ] +[[package]] +name = "plotille" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/73/3f342572f7f916e387e546cc502d6cad35e7162ba0bcde203669e15aa3af/plotille-5.0.0.tar.gz", hash = "sha256:99e5ca51a2e4c922ead3a3b0863cc2c6a9a4b3f701944589df10f42ce02ab3dc", size = 53392 } + +[[package]] +name = "plotly" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "narwhals" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/80/761c14012d6daf18e12b6d1e4f6b218e999bcceb694d7a9b180154f9e4db/plotly-6.0.0.tar.gz", hash = "sha256:c4aad38b8c3d65e4a5e7dd308b084143b9025c2cc9d5317fc1f1d30958db87d3", size = 8111782 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/77/a946f38b57fb88e736c71fbdd737a1aebd27b532bda0779c137f357cf5fc/plotly-6.0.0-py3-none-any.whl", hash = "sha256:f708871c3a9349a68791ff943a5781b1ec04de7769ea69068adcd9202e57653a", size = 14805949 }, +] + [[package]] name = "pluggy" version = "1.5.0" @@ -2164,66 +2187,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/86/12/256aa92f70a8bdf2a00dc84f6c75c86abadeca1c990e02c8345933889952/py_bip39_bindings-0.1.11-cp312-none-win_amd64.whl", hash = "sha256:6794187229eb0b04d0770f0fba936f0c5c598f552848a398ed5af9a61638cacb", size = 284888 }, ] -[[package]] -name = "py-ed25519-zebra-bindings" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/51/c5f00db791472d4f6c14b383dca0da621db6b68b8c73bef46bf136cb1c93/py_ed25519_zebra_bindings-1.2.0.tar.gz", hash = "sha256:d9ec63d54b1801d5b5bdef0b3096ed94e2e1a7c870c937682afc7b8b25ffc2fc", size = 11851 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/3f/1cbe6c29d5630ab8b29f6f1d52723f8123331d7a3b1a2a5f8070e2f5bc09/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8d63a447d3adac9b431fecd886cf711a6d44200d8b2497598a8ab44ac897f1fb", size = 290728 }, - { url = "https://files.pythonhosted.org/packages/7f/88/fc2759f89c2d07e594455c2b2442bbf6a5ee223af3f87f452a6369e17fce/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5b1c32414a6da709e84d0614e1ed153a5e1dbcbf6d4d17baa31c493fdbd4da4", size = 266106 }, - { url = "https://files.pythonhosted.org/packages/2d/f6/bba44de332b01b048fd739c242829cef0aac776730df2b96d5da0643cb51/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:780073555571390c4b355b5646c0b59c2a90d3393e354d58c4ad904121a2aee2", size = 296312 }, - { url = "https://files.pythonhosted.org/packages/1b/9b/49ff5ab8fc075f2e9395fe604af587bc2d7bdc123db36657f376a35dd5d6/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:677ade8ab3348604a9e4176b068ff19707cf205fd8ee4f1781614b085628fa45", size = 323178 }, - { url = "https://files.pythonhosted.org/packages/75/3c/5f6e8f56c7d59f67f23f16584ebe34c9cc5cf3593c1bf09c96cfa2f7d3a2/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c19c0cc491bc4999245f9d2e904f611354f442710b6dae6d1d6ebc81666124cc", size = 337000 }, - { url = "https://files.pythonhosted.org/packages/bd/34/e30b63c8bfcfaed3c46a68a1493e255f4adb683999360fd5ad81a50703b9/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1317a8af53c658f1e89b346d361edaf10eccd428c937a17d0684b2192fa77c40", size = 317772 }, - { url = "https://files.pythonhosted.org/packages/60/8e/df7e97ab47e9e522b8babe355d7fb5977bc412d4390b07a8f57accde1a7f/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cdc05ade2608707f6c54701e7425d9c00751ccffa57533a48f68f61b0aada9f1", size = 336488 }, - { url = "https://files.pythonhosted.org/packages/c8/eb/a1dcb632754513d1669dfeeaf3ab1eec582cd55cc92c1805af457e6cb8c4/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec1965ed54fd162da564cc33676377888bd1ad14c15680465463d06e14aac74d", size = 473730 }, - { url = "https://files.pythonhosted.org/packages/a5/5c/1bf76d36a0458708e5a20a5489b77dde12859d6969db4ede3658cbe37291/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7386e7cec522ac50e7d81cfc8488e463fe93902d6ba0f7c79d6f6db0fcf71111", size = 586066 }, - { url = "https://files.pythonhosted.org/packages/d1/30/79ad8283c1d686f34079a5e70dc90bdc41e0bcb0e12a66e43ee22ec91325/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b06102b2be52da075f29f0db907bb5a03af942e2f6fb558065ea5717aa567d32", size = 515271 }, - { url = "https://files.pythonhosted.org/packages/6c/90/ed8850e9c73a0595ce661c56cadc5407fcf7fa5e3bce01cc8427bc1c5ee7/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4237cf821f74126077220d5826448c0b68c8807f40db961b1335bb6a66a83af8", size = 488352 }, - { url = "https://files.pythonhosted.org/packages/06/94/1140e74d213d875e21342bffdcc84003d7a7209cf191d044053a37a4da8f/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-win32.whl", hash = "sha256:fe11223695c94040f31b48a2128f1642a1b689aaaa91b5f8ae018d53b1497409", size = 185890 }, - { url = "https://files.pythonhosted.org/packages/51/37/19ad03c6891fb564a9716409da56a2b5977b49410576eac5ae90cdaef8ee/py_ed25519_zebra_bindings-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:87654379855152770974c045099e488b577d86429af609524903b8029b276417", size = 186788 }, - { url = "https://files.pythonhosted.org/packages/b5/8d/7db18ebddff6cd81cf04cbb072b9d8f03b261816e49bc4b44c5cc1499bfc/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2e10a578c1297a9b12a818c5b874d9830afba1592e8cb9df3a44b2afbc241cf0", size = 290758 }, - { url = "https://files.pythonhosted.org/packages/8e/86/bbf541d3acaf91f230560caf0b06c38120531a4b78c79a1069425c9a865f/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f0edbed9d94f5295c4f360baa38e124626296e36f315d6a19bc91f7d8a61627", size = 266143 }, - { url = "https://files.pythonhosted.org/packages/16/b8/60b80117df4af4194038b09729e2b72f01daae30ad3e31a3cf00c3c12742/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe2d0db5c2d4c0575b91373eb0c33b1d222fbb38664e17d807c8845eab268c16", size = 296165 }, - { url = "https://files.pythonhosted.org/packages/85/7a/ccfb0304fcc2286e3e3ecc681ec26da22e408cf1b55ac931f9d32e91b192/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4b371742adbd9be4a5a813e5d920a1a057fe9013620681651a3e7c84fd1f8d8b", size = 323055 }, - { url = "https://files.pythonhosted.org/packages/4e/01/3669026c7600ac78645ea0250ec9381936a4a05c6c21f72fb27726ff7130/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f82a6ae05ac4feb16d077ce1b4a48396c9685bc2b37d3a1ffbcd16023a4f3b8a", size = 336780 }, - { url = "https://files.pythonhosted.org/packages/d7/82/247c3a5c3d3817905f95fdcb5b28a14235e7a7d776482bf968139ff69235/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:446f26b62311db93205507fedb3fa07dae786ae75822182d44dadd28984d7768", size = 317655 }, - { url = "https://files.pythonhosted.org/packages/c8/d2/14223da5008e65d4ef20b80f267dcc9b770b04852a0ececdf614725b588c/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f76ccb64577bbdfdacc543298355747dca9684e74262f844c3d892bd583e023b", size = 336542 }, - { url = "https://files.pythonhosted.org/packages/1b/0f/1748c84528217a9cdddf5ae54564c7c32d74aa4b6f4381d5ca277e115dbc/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c5c95587f93f9cbf73e3609e8befe2b36c488bcf96ccc1c8c63b257212e1b9df", size = 473517 }, - { url = "https://files.pythonhosted.org/packages/5e/e3/4575b55a859933d7819b51d2ac18f4fadfbda3daed8cece11afba68256ef/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f157f87844d5e395380eaf03d9baa2108126ad276088c7edb55869683cc2cfc", size = 585960 }, - { url = "https://files.pythonhosted.org/packages/ec/9c/7dab2229cfcdedf95dfbc65088821f4013d5bd7e7259abb031959d9c4ef9/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:022499a21096d03d90654af2203a5475f6c3c5572245b7bc6a1bbeeb4e42c319", size = 515244 }, - { url = "https://files.pythonhosted.org/packages/37/d4/1aff446495187df2bace76c0a88653b9f9428ac809938841642409d8905a/py_ed25519_zebra_bindings-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7b466ec2de929e38e6f441156a3e108a3c090dbc6b624864f6c1b300cc329f8d", size = 488348 }, - { url = "https://files.pythonhosted.org/packages/3a/25/445680dc6fe7cb4bb8a45219d312b0bee1b63b5cc3467dd0e4fa14e244c3/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:998b5d9c4db1053156a55e8edf06a5dce68ddaa3e928e2861f8ba9a5fe5b6119", size = 296264 }, - { url = "https://files.pythonhosted.org/packages/0d/ca/60e217a0fd3e160f1ed32211a19c93425292fce2d3818a21d2781c547534/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a0fe34c20032f406a78c865c308b49fe3c79c9e1642f6471228cfbc6c513348", size = 322758 }, - { url = "https://files.pythonhosted.org/packages/63/df/5970fab50ce04026c780d48838d5a2c3f96a4e46c69ba81069a24941e18e/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7e3273d73148d983a5e7f9ed3e8b53824dcb7833393aa09dd969dd3e7a1f3c1", size = 337049 }, - { url = "https://files.pythonhosted.org/packages/55/4b/69f7b03c4edd5a8dfa6a64f33c5c99ffbbf63419ec0fe775418be4f930da/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cb5858f54ebd7d37c9d21c6dd80367d0031dbda7bd91b333018c0f243e1284f5", size = 473442 }, - { url = "https://files.pythonhosted.org/packages/30/7a/0d5073188f94fd3b22a836e867e32fae0e26f3b39f734314e3eff5b530f6/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:4fd00c8686b17e31ec29d8e4e7ce97f465fe26227f12c9e111e012b9d0dff4b9", size = 585681 }, - { url = "https://files.pythonhosted.org/packages/ab/99/add86df518d799a17c91763eebf756de68b1a858a5c7977de1b335e886cc/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e4e55fc5be4ba0c723d424cefdbb8d863e74d2ff25fbeadca9539ca60d78cc0f", size = 514835 }, - { url = "https://files.pythonhosted.org/packages/e7/fc/bf32dc80a597501fc7ef8b18638f78e5ee672b0b43cc02373075f9b1f8d4/py_ed25519_zebra_bindings-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:91816ed4cef90d4d08fa9f55fa0c5687c5eba601dc1a44f211adcf1c20d96cc3", size = 488524 }, -] - -[[package]] -name = "py-sr25519-bindings" -version = "0.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/55/e5c27d1387f6cb3a6bf7714e1e0c4a62edc3b006710e2d081e8bdfa4123f/py_sr25519_bindings-0.2.1.tar.gz", hash = "sha256:1b96d3dde43adcf86ab427a9fd72b2c6291dca36eb40747df631588c16f01c1a", size = 18439 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/6f/5dca831fe2617075237d49868d1bd4f025d0dbd23676d7dec3aaf39642cd/py_sr25519_bindings-0.2.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:01ef73c0b3d3f703b54ee69c0f5ff4aa54b4233212c466fd497c7a84d170963a", size = 330633 }, - { url = "https://files.pythonhosted.org/packages/3e/86/569b69e01a962e0c3cd63465e5faad589e54f0c27bfaed5436fef283d56c/py_sr25519_bindings-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7ce8ac85e5ea82825a863f3f6f071e5ead610d7675820eb8ffe772267445ec0b", size = 306030 }, - { url = "https://files.pythonhosted.org/packages/a1/ae/ad0d1fff92966b4ca020abc3ea12e3e1f34c3a937bab28fa0e6bf893d587/py_sr25519_bindings-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f59ac8c03c8ef819db063627f4a8247aab0db11d88b21562abbe371612cf66ab", size = 340266 }, - { url = "https://files.pythonhosted.org/packages/b0/7e/93903b1a0789fe1e7f2bb17f4992b55549dfbc8dd8dc3fa4d57c08b72250/py_sr25519_bindings-0.2.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2c11fc77b57308e3ada9a40e7c343027129b582d3091ebd992c99b1832ac8c1", size = 367790 }, - { url = "https://files.pythonhosted.org/packages/f4/79/842a46cc48c33ff0d08f95db6b327fdd5972fd68d733634322762dd74702/py_sr25519_bindings-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92af2831d6896f0b3fef792d1f2da780fabf6c78dac12535b394cbdb51c0d257", size = 383790 }, - { url = "https://files.pythonhosted.org/packages/0d/33/aeeacf174483ae6163bfb8993c0dabdb15875272e59658123d2dcf55f39a/py_sr25519_bindings-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc99f7f310b7641e510810c1d6a6b51792ab2ccefac3ab288445a9fcbc9a8265", size = 365962 }, - { url = "https://files.pythonhosted.org/packages/85/bb/c41e0115115336acad5b05d577bf463fa69975ed84dcf50011ac4e07eb89/py_sr25519_bindings-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1dc4995a352a6e5851a41cb0ea37d8c9083d173515b7fd2f381b014f57dc1cda", size = 386028 }, - { url = "https://files.pythonhosted.org/packages/cd/d0/48744d7ec55853dc7ec6889f7b85b4f9d21349f09a9ccc8fd988a67f0a46/py_sr25519_bindings-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f103dc5c420057c4447bd6ebf28b2b68ff3ab8da85a5f7ff39c405293de80c78", size = 524320 }, - { url = "https://files.pythonhosted.org/packages/50/4f/9462c0525bd64417c56e788b9543a34c08583bf7eabf81797bf5545b924d/py_sr25519_bindings-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:902ee675497b8d356a2abe2abc4278cd76c503f76d06ef2bcd797c1df59e84b7", size = 628052 }, - { url = "https://files.pythonhosted.org/packages/a7/2a/873f8e7425fd424f9d4aa6eddbbe767889d2aee639372fd9516d6b352c93/py_sr25519_bindings-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5dd9748f4bd9a3bc4d5c1245f6edcc723075b1470b4c36add4474df4c53604e8", size = 552273 }, - { url = "https://files.pythonhosted.org/packages/0e/e2/bb29457851816c1637bdd7176ac419073faeecf452dcfae54b50ddb81bc1/py_sr25519_bindings-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c24bc55699d12948571969c26e65138a942bdaca062171288c40c44b9a4f266", size = 530013 }, - { url = "https://files.pythonhosted.org/packages/4b/70/21d32090ca207738a3979620865e2a48ccbed64871cffafb24c6febe234d/py_sr25519_bindings-0.2.1-cp312-none-win32.whl", hash = "sha256:d4799c9a8f280abdfe564d397bad45da380275c8d22604e059bd7b3d5af404b5", size = 218181 }, - { url = "https://files.pythonhosted.org/packages/bb/df/06a61ef52a6889d6879bfa8a5877688f62854c8eab491ad7af60e797a3ef/py_sr25519_bindings-0.2.1-cp312-none-win_amd64.whl", hash = "sha256:0746befd71d1766d8747910cfeb2cec2be2c859c3b3618eda1dc3cb4a1b85175", size = 224095 }, -] - [[package]] name = "pyarrow" version = "18.1.0" @@ -2338,26 +2301,6 @@ 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 = "pynacl" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a7/22/27582568be639dfe22ddb3902225f91f2f17ceff88ce80e4db396c8986da/PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba", size = 3392854 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/75/0b8ede18506041c0bf23ac4d8e2971b4161cd6ce630b177d0a08eb0d8857/PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1", size = 349920 }, - { url = "https://files.pythonhosted.org/packages/59/bb/fddf10acd09637327a97ef89d2a9d621328850a72f1fdc8c08bdf72e385f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92", size = 601722 }, - { url = "https://files.pythonhosted.org/packages/5d/70/87a065c37cca41a75f2ce113a5a2c2aa7533be648b184ade58971b5f7ccc/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394", size = 680087 }, - { url = "https://files.pythonhosted.org/packages/ee/87/f1bb6a595f14a327e8285b9eb54d41fef76c585a0edef0a45f6fc95de125/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d", size = 856678 }, - { url = "https://files.pythonhosted.org/packages/66/28/ca86676b69bf9f90e710571b67450508484388bfce09acf8a46f0b8c785f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858", size = 1133660 }, - { url = "https://files.pythonhosted.org/packages/3d/85/c262db650e86812585e2bc59e497a8f59948a005325a11bbbc9ecd3fe26b/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b", size = 663824 }, - { url = "https://files.pythonhosted.org/packages/fd/1a/cc308a884bd299b651f1633acb978e8596c71c33ca85e9dc9fa33a5399b9/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff", size = 1117912 }, - { url = "https://files.pythonhosted.org/packages/25/2d/b7df6ddb0c2a33afdb358f8af6ea3b8c4d1196ca45497dd37a56f0c122be/PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543", size = 204624 }, - { url = "https://files.pythonhosted.org/packages/5e/22/d3db169895faaf3e2eda892f005f433a62db2decbcfbc2f61e6517adfa87/PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", size = 212141 }, -] - [[package]] name = "pyparsing" version = "3.2.1" @@ -2433,6 +2376,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, ] +[[package]] +name = "pywry" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setproctitle" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/df/2bd468f465011fb021f45cbe5cc9f1cfe15872c61e1cab2a7962bd4f4860/pywry-0.6.2.tar.gz", hash = "sha256:9bd88c36ab0860728d9e64360010f8abcede43645656030e4a63e69e81a98c95", size = 38983 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/0e/0a4b6436433678d49790683ea869e40cca6ecc36f6abdaf01a489298a8f8/pywry-0.6.2-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:45d6bb827bf76b2532a9d70b539209d70f37dfb13e9862549b7bff8500ad2495", size = 2303900 }, + { url = "https://files.pythonhosted.org/packages/02/cc/00c1c93ff5df28cb95a1838f2405f7943e1c0c1a965a6a14670ca3ea9745/pywry-0.6.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:1d9ffd826a3a08c132843340e6d896efb7b972b301d045e3239a7dc08d9cac2f", size = 66838493 }, + { url = "https://files.pythonhosted.org/packages/19/09/d33a4fedf333af8cb208bb9b9a974fbd025c654c6b231b77e22766591ed1/pywry-0.6.2-py3-none-win_amd64.whl", hash = "sha256:4f0e5b502555ee8b8e799baeaebe63243a84b7ce51df01a1c439dbc4e8227b9e", size = 868356 }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -2878,32 +2835,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/18/31fa32ed6c68ba66220204ef0be798c349d0a20c1901f9d4a794e08c76d8/starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee", size = 71908 }, ] -[[package]] -name = "substrate-interface" -version = "1.7.11" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "base58" }, - { name = "certifi" }, - { name = "ecdsa" }, - { name = "eth-keys" }, - { name = "eth-utils" }, - { name = "idna" }, - { name = "py-bip39-bindings" }, - { name = "py-ed25519-zebra-bindings" }, - { name = "py-sr25519-bindings" }, - { name = "pycryptodome" }, - { name = "pynacl" }, - { name = "requests" }, - { name = "scalecodec" }, - { name = "websocket-client" }, - { name = "xxhash" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/44/825433c906bdb69ab66fd3967c11fcfbcd953241e9d6257fd6a21c4cdc76/substrate-interface-1.7.11.tar.gz", hash = "sha256:4caa5eacb9996edbe76ad12249521b3542bbd8d9d69b96734087201db1fef8f6", size = 79221 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/e1/37344b7acd260cbfed13563dcbab391c7c4b0c9eca5ec59aba138c5dca9e/substrate_interface-1.7.11-py3-none-any.whl", hash = "sha256:ce19bc97481769238ed23c752db985a3058637918693f2db6aeed2fab3756075", size = 60273 }, -] - [[package]] name = "sympy" version = "1.13.1" @@ -3277,13 +3208,16 @@ wheels = [ [[package]] name = "web-genie-ai" -version = "1.0.0" +version = "1.1.7" source = { virtual = "." } dependencies = [ { name = "ansible-vault" }, + { name = "async-substrate-interface" }, { name = "beautifulsoup4" }, { name = "bert-score" }, { name = "bittensor" }, + { name = "bittensor-cli" }, + { name = "bt-decode" }, { name = "clip" }, { name = "colormath" }, { name = "datasets" }, @@ -3316,9 +3250,12 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "ansible-vault", specifier = "==2.1.0" }, + { name = "async-substrate-interface", specifier = "==1.0.0" }, { name = "beautifulsoup4", specifier = "==4.12.3" }, { name = "bert-score", specifier = "==0.3.13" }, - { name = "bittensor", specifier = "==8.5.2" }, + { name = "bittensor", specifier = "==9.0.0" }, + { name = "bittensor-cli", specifier = "==9.0.0" }, + { name = "bt-decode", specifier = "==0.5.0a2" }, { name = "clip", git = "https://github.com/openai/CLIP.git" }, { name = "colormath", specifier = ">=3.0.0" }, { name = "datasets" }, @@ -3358,15 +3295,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, ] -[[package]] -name = "websocket-client" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826 }, -] - [[package]] name = "websockets" version = "14.1" From 2416bd0c2906f8fad983e43078d1e27caf76d2b3 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 15:25:58 -0600 Subject: [PATCH 452/554] chore: edit version --- CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- webgenie/constants.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6e67f7d..986e250b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,3 +108,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.1.8] - 2025-02-13 ### Added - Added logging to the random website dataset. + +## [1.1.9] - 2025-02-13 +### Updated +- Updated the version of bittensor to 9.0.0 for dynamic TAO. diff --git a/pyproject.toml b/pyproject.toml index 9cd27f9d..009a00e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.7" +version = "1.1.9" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 96d3aba0..f62dab43 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.8" # version +__VERSION__ = "1.1.9" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 8c126872859cb052a5582e76c6c71b7dc128c8ac Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 18:21:53 -0600 Subject: [PATCH 453/554] feat: sync tasks --- webgenie/helpers/llms.py | 2 +- webgenie/tasks/image_task_generator.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/webgenie/helpers/llms.py b/webgenie/helpers/llms.py index 7e6f5733..b0e45f87 100644 --- a/webgenie/helpers/llms.py +++ b/webgenie/helpers/llms.py @@ -16,7 +16,7 @@ base_url=LLM_MODEL_URL, ) -async def openai_call(messages, response_format, deterministic=False, retries=3): +async def openai_call(messages, response_format, deterministic=True, retries=3): for _ in range(retries): try: if deterministic: diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 461c1513..05b05a18 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -37,9 +37,9 @@ def __init__(self): super().__init__() self.datasets = [ - (RandomWebsiteDataset(), 1), - #(SyntheticDataset(), 0.1), - #(HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), 0.1), + #(RandomWebsiteDataset(), 1), + (SyntheticDataset(), 0.5), + (HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), 0.5), ] self.metrics = { From 8ffcc1f98aa83ef74e9a6ede396f34753e21b0a9 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 13 Feb 2025 20:43:27 -0600 Subject: [PATCH 454/554] chore: set seed --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index f62dab43..5af701ac 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.9" # version +__VERSION__ = "1.1.10" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 37ef646c821e6191672f16c9acf5bc4bddc07a68 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 14 Feb 2025 05:20:07 -0600 Subject: [PATCH 455/554] chore: set openai seed --- neurons/validators/genie_validator.py | 2 ++ webgenie/helpers/llms.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 11215d06..c807c539 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -28,6 +28,7 @@ ) from webgenie.helpers.htmls import preprocess_html, is_valid_resources from webgenie.helpers.images import image_debug_str +from webgenie.helpers.llms import set_seed from webgenie.protocol import ( WebgenieImageSynapse, WebgenieTextSynapse, @@ -297,6 +298,7 @@ async def forward(self): bt.logging.info(f"Init random with seed: {seed}") random.seed(seed) + set_seed(seed) while True: try: diff --git a/webgenie/helpers/llms.py b/webgenie/helpers/llms.py index b0e45f87..b33652be 100644 --- a/webgenie/helpers/llms.py +++ b/webgenie/helpers/llms.py @@ -16,6 +16,15 @@ base_url=LLM_MODEL_URL, ) + +SEED = 42 + + +def set_seed(seed): + global SEED + SEED = seed + + async def openai_call(messages, response_format, deterministic=True, retries=3): for _ in range(retries): try: @@ -25,6 +34,7 @@ async def openai_call(messages, response_format, deterministic=True, retries=3): messages= messages, response_format=response_format, temperature=0, + seed=SEED, ) else: completion = await client.beta.chat.completions.parse( From 30a1ae4ba7a4a11b857ec68165ec894ad5667158 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 14 Feb 2025 05:21:27 -0600 Subject: [PATCH 456/554] chore: edit version --- pyproject.toml | 2 +- webgenie/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 009a00e9..2323731f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.9" +version = "1.1.11" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 5af701ac..3d16a110 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.10" # version +__VERSION__ = "1.1.11" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 2d63a0a32a261a688eaae29e1ebc51b4678d469a Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 14 Feb 2025 06:46:03 -0600 Subject: [PATCH 457/554] feat: disable synthetic dataset for vtrust error --- webgenie/tasks/image_task_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 05b05a18..16a45130 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -38,8 +38,8 @@ def __init__(self): self.datasets = [ #(RandomWebsiteDataset(), 1), - (SyntheticDataset(), 0.5), - (HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), 0.5), + #(SyntheticDataset(), 0.5), + (HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), 1), ] self.metrics = { From 7b169edd8f0394a104d36d8c0ad7feebc6a57d76 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 14 Feb 2025 06:47:00 -0600 Subject: [PATCH 458/554] chore: edit version --- pyproject.toml | 2 +- webgenie/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2323731f..9fb9ce51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.11" +version = "1.1.12" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 3d16a110..74521b9c 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.11" # version +__VERSION__ = "1.1.12" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 55bafa23ec6ada86729f720fb2e2e2a425de6491 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Sat, 15 Feb 2025 12:57:03 -0600 Subject: [PATCH 459/554] chore: increase threshold --- pyproject.toml | 2 +- webgenie/challenges/challenge.py | 4 ++-- webgenie/constants.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9fb9ce51..232cf67d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.12" +version = "1.1.13" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py index fea3ff1b..65a66819 100644 --- a/webgenie/challenges/challenge.py +++ b/webgenie/challenges/challenge.py @@ -43,7 +43,7 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] seo_scores = scores[SEO_METRIC_NAME] - aggregated_scores = np.where(accuracy_scores > 0.4, seo_scores, 0) + aggregated_scores = np.where(accuracy_scores > 0.9, seo_scores, 0) return aggregated_scores, scores @@ -54,7 +54,7 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] quality_scores = scores[QUALITY_METRIC_NAME] - aggregated_scores = np.where(accuracy_scores > 0.4, quality_scores, 0) + aggregated_scores = np.where(accuracy_scores > 0.9, quality_scores, 0) return aggregated_scores, scores class BalancedChallenge(Challenge): diff --git a/webgenie/constants.py b/webgenie/constants.py index 74521b9c..ab324cde 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.12" # version +__VERSION__ = "1.1.13" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 42e02f015b3d750064653d663285f2a936e1f020 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 17 Feb 2025 07:23:55 -0600 Subject: [PATCH 460/554] chore: catch rate limit error --- webgenie/helpers/llms.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/webgenie/helpers/llms.py b/webgenie/helpers/llms.py index b33652be..6b489c52 100644 --- a/webgenie/helpers/llms.py +++ b/webgenie/helpers/llms.py @@ -1,6 +1,6 @@ import bittensor as bt -from openai import AsyncOpenAI +from openai import AsyncOpenAI, OpenAIError from webgenie.constants import ( LLM_MODEL_ID, @@ -44,6 +44,9 @@ async def openai_call(messages, response_format, deterministic=True, retries=3): temperature=0.7, ) return completion.choices[0].message.parsed + except OpenAIError as e: + if "as the length limit was reached" in str(e): + raise e except Exception as e: bt.logging.warning(f"Error calling OpenAI: {e}") continue From 40acc7a9794b4a97f8e5e64ec0952fc9c80e12c4 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 17 Feb 2025 07:25:31 -0600 Subject: [PATCH 461/554] chore: decrease number of task --- neurons/validators/genie_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index c807c539..213abaf5 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -289,7 +289,7 @@ async def forward(self): else: task_index = self.neuron.score_manager.number_of_tasks - MAX_NUMBER_OF_TASKS_PER_SESSION = 20 + MAX_NUMBER_OF_TASKS_PER_SESSION = 10 if task_index >= MAX_NUMBER_OF_TASKS_PER_SESSION: return From aab88549cf4347780f0db257cac032f97f71f51c Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 17 Feb 2025 07:35:32 -0600 Subject: [PATCH 462/554] chore: edit version --- pyproject.toml | 2 +- webgenie/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 232cf67d..bb4529c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.13" +version = "1.1.14" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index ab324cde..3c206d1d 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.13" # version +__VERSION__ = "1.1.14" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 7009681ff94b985a7a0e4c574e3ad2d5c2e4962b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 17 Feb 2025 08:27:13 -0600 Subject: [PATCH 463/554] chore: update llm code --- webgenie/helpers/llms.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/webgenie/helpers/llms.py b/webgenie/helpers/llms.py index 6b489c52..3f7b1c20 100644 --- a/webgenie/helpers/llms.py +++ b/webgenie/helpers/llms.py @@ -46,7 +46,13 @@ async def openai_call(messages, response_format, deterministic=True, retries=3): return completion.choices[0].message.parsed except OpenAIError as e: if "as the length limit was reached" in str(e): + bt.logging.error(f"Error calling OpenAI: {e}") raise e + if "check your billing details" in str(e): + bt.logging.error(f"Error calling OpenAI: {e}") + raise e + bt.logging.warning(f"Error calling OpenAI: {e}") + continue except Exception as e: bt.logging.warning(f"Error calling OpenAI: {e}") continue From 0da5628c04922c3d17057d9a48cfd78373ade9e5 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 17 Feb 2025 10:33:59 -0600 Subject: [PATCH 464/554] chore: increase task_count --- neurons/validators/genie_validator.py | 2 +- webgenie/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 213abaf5..c807c539 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -289,7 +289,7 @@ async def forward(self): else: task_index = self.neuron.score_manager.number_of_tasks - MAX_NUMBER_OF_TASKS_PER_SESSION = 10 + MAX_NUMBER_OF_TASKS_PER_SESSION = 20 if task_index >= MAX_NUMBER_OF_TASKS_PER_SESSION: return diff --git a/webgenie/constants.py b/webgenie/constants.py index 3c206d1d..172f4afa 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.14" # version +__VERSION__ = "1.1.15" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 43d271275f4e4bb15ede4ff356f2abd3b5b26430 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 17 Feb 2025 19:26:06 -0600 Subject: [PATCH 465/554] chore: adjust task count --- neurons/validators/genie_validator.py | 2 +- webgenie/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index c807c539..93ce3941 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -289,7 +289,7 @@ async def forward(self): else: task_index = self.neuron.score_manager.number_of_tasks - MAX_NUMBER_OF_TASKS_PER_SESSION = 20 + MAX_NUMBER_OF_TASKS_PER_SESSION = 18 if task_index >= MAX_NUMBER_OF_TASKS_PER_SESSION: return diff --git a/webgenie/constants.py b/webgenie/constants.py index 172f4afa..e0834cad 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.15" # version +__VERSION__ = "1.1.16" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 43ad5b10726c4db19c884bad28042e7015fd4cce Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 04:53:02 -0600 Subject: [PATCH 466/554] chore: add constant --- neurons/validators/genie_validator.py | 2 +- webgenie/constants.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 93ce3941..79561c07 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -40,6 +40,7 @@ ACCURACY_METRIC_NAME, QUALITY_METRIC_NAME, SEO_METRIC_NAME, + MAX_NUMBER_OF_TASKS_PER_SESSION, ) from webgenie.tasks.image_task_generator import ImageTaskGenerator from webgenie.utils.uids import get_all_available_uids @@ -289,7 +290,6 @@ async def forward(self): else: task_index = self.neuron.score_manager.number_of_tasks - MAX_NUMBER_OF_TASKS_PER_SESSION = 18 if task_index >= MAX_NUMBER_OF_TASKS_PER_SESSION: return diff --git a/webgenie/constants.py b/webgenie/constants.py index e0834cad..483674f3 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -87,3 +87,4 @@ NEURON_EPOCH_LENGTH = int(os.getenv("NEURON_EPOCH_LENGTH", 25)) # neuron epoch length +MAX_NUMBER_OF_TASKS_PER_SESSION = 18 # max number of tasks per session From 9bd1af26014253cbb68b66b45c9ddd33c4e0b373 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 04:59:55 -0600 Subject: [PATCH 467/554] chore: add avg_score --- neurons/validators/score_manager.py | 41 ++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 8c91594e..efa292ad 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -50,6 +50,11 @@ def load_scores(self): -1 ) + self.solved_tasks = data.get( + f"solved_tasks", + np.zeros(self.neuron.metagraph.n, dtype=np.float32), + ) + self.total_scores = data.get( f"total_scores_{__STATE_VERSION__}", np.zeros(self.neuron.metagraph.n, dtype=np.float32), @@ -62,6 +67,7 @@ def load_scores(self): bt.logging.error(f"Error loading state: {e}") self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.current_session = -1 + self.solved_tasks = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.last_set_weights_session = -1 self.number_of_tasks = 0 @@ -76,6 +82,7 @@ def save_scores(self): **{f"current_session": self.current_session}, last_set_weights_session=self.last_set_weights_session, number_of_tasks=self.number_of_tasks, + solved_tasks=self.solved_tasks, **{f"total_scores_{__STATE_VERSION__}": self.total_scores}, session_results= self.session_results, allow_pickle=True, @@ -91,6 +98,7 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): for uid, hotkey in enumerate(self.hotkeys): if hotkey != new_hotkeys[uid]: self.total_scores[uid] = 0 + self.solved_tasks[uid] = 0 # Check to see if the metagraph has changed size. # If so, we need to add new hotkeys and moving averages. @@ -100,6 +108,11 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): new_total_scores[:min_len] = self.total_scores[:min_len] self.total_scores = new_total_scores + new_solved_tasks = np.zeros((len(new_hotkeys))) + min_len = min(len(self.hotkeys), len(self.solved_tasks)) + new_solved_tasks[:min_len] = self.solved_tasks[:min_len] + self.solved_tasks = new_solved_tasks + # Update the hotkeys. self.hotkeys = copy.deepcopy(new_hotkeys) with self.lock: @@ -113,16 +126,26 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen # This is a new session, reset the scores and winners. self.current_session = session self.number_of_tasks = 0 + self.solved_tasks = np.zeros(self.neuron.metagraph.n, dtype=np.float32) self.total_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) # Update accumulated scores and track best performer self.number_of_tasks += 1 self.total_scores[uids] += rewards - + self.solved_tasks[uids] += 1 + + # Calculate average scores for each UID + avg_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + for uid in range(self.neuron.metagraph.n): + if self.solved_tasks[uid] > 0: + avg_scores[uid] = self.total_scores[uid] / self.solved_tasks[uid] + winner = np.argmax(avg_scores) if max(avg_scores) > 0 else -1 + current_session_results = { "session": session, "competition_type": competition_type, "number_of_tasks": self.number_of_tasks, - "winner": np.argmax(self.total_scores), + "winner": winner, + "solved_tasks": self.solved_tasks, "scores": self.total_scores, } @@ -169,7 +192,15 @@ def print_session_result(self, session_upto: int, console: Console): competition_type = session_result["competition_type"] winner = session_result["winner"] scores = session_result["scores"] - + solved_tasks = session_result["solved_tasks"] + + avg_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + for uid in range(self.neuron.metagraph.n): + if solved_tasks[uid] > 0: + avg_scores[uid] = scores[uid] / solved_tasks[uid] + else: + avg_scores[uid] = 0 + total_scores_table = Table( title=( f"📊 Total Scores Summary\n" @@ -186,16 +217,14 @@ def print_session_result(self, session_upto: int, console: Console): total_scores_table.add_column("Rank", justify="right", style="red", header_style="bold red") total_scores_table.add_column("UID", justify="right", style="cyan", header_style="bold cyan") - total_scores_table.add_column("Total Score", justify="right", style="green") total_scores_table.add_column("Average Score", justify="right", style="yellow") - scored_uids = [(uid, score) for uid, score in enumerate(scores) if score > 0] + scored_uids = [(uid, avg_scores[uid]) for uid in range(self.neuron.metagraph.n) if avg_scores[uid] > 0] scored_uids.sort(key=lambda x: x[1], reverse=True) for rank, (uid, score) in enumerate(scored_uids): total_scores_table.add_row( str(rank + 1), str(uid), f"{score:.4f}", - f"{score / number_of_tasks:.4f}", ) console.print(total_scores_table) From 29dc3f203e7f9ca8bea440ba312b46031b3f4e70 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 05:37:53 -0600 Subject: [PATCH 468/554] chore: update score logic --- neurons/validators/score_manager.py | 93 +++++++++++++++-------------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index efa292ad..35b6aa5e 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -132,12 +132,13 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen self.number_of_tasks += 1 self.total_scores[uids] += rewards self.solved_tasks[uids] += 1 - - # Calculate average scores for each UID + avg_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) for uid in range(self.neuron.metagraph.n): - if self.solved_tasks[uid] > 0: + if self.solved_tasks[uid] >= max(1, self.number_of_tasks / 2): avg_scores[uid] = self.total_scores[uid] / self.solved_tasks[uid] + else: + avg_scores[uid] = 0 winner = np.argmax(avg_scores) if max(avg_scores) > 0 else -1 current_session_results = { @@ -185,48 +186,51 @@ def get_scores(self, session_upto: int): # return np.power(scores, 9) def print_session_result(self, session_upto: int, console: Console): - session_result = self.session_results[session_upto] - - number_of_tasks = session_result["number_of_tasks"] - session = session_result["session"] - competition_type = session_result["competition_type"] - winner = session_result["winner"] - scores = session_result["scores"] - solved_tasks = session_result["solved_tasks"] - - avg_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - for uid in range(self.neuron.metagraph.n): - if solved_tasks[uid] > 0: - avg_scores[uid] = scores[uid] / solved_tasks[uid] - else: - avg_scores[uid] = 0 - - total_scores_table = Table( - title=( - f"📊 Total Scores Summary\n" - f"🔄 Session: #{session}\n" - f"📝 Number of Tasks: #{number_of_tasks}\n" - f"🏆 Competition: {competition_type}\n" - f"👑 Winner: #{winner}\n" - ), - show_header=True, - header_style="bold magenta", - title_style="bold blue", - border_style="blue" - ) - - total_scores_table.add_column("Rank", justify="right", style="red", header_style="bold red") - total_scores_table.add_column("UID", justify="right", style="cyan", header_style="bold cyan") - total_scores_table.add_column("Average Score", justify="right", style="yellow") - scored_uids = [(uid, avg_scores[uid]) for uid in range(self.neuron.metagraph.n) if avg_scores[uid] > 0] - scored_uids.sort(key=lambda x: x[1], reverse=True) - for rank, (uid, score) in enumerate(scored_uids): - total_scores_table.add_row( - str(rank + 1), - str(uid), - f"{score:.4f}", + try: + session_result = self.session_results[session_upto] + + number_of_tasks = session_result["number_of_tasks"] + session = session_result["session"] + competition_type = session_result["competition_type"] + winner = session_result["winner"] + scores = session_result["scores"] + solved_tasks = session_result["solved_tasks"] + + avg_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + for uid in range(self.neuron.metagraph.n): + if solved_tasks[uid] >= max(1, number_of_tasks / 2): + avg_scores[uid] = scores[uid] / solved_tasks[uid] + else: + avg_scores[uid] = 0 + + total_scores_table = Table( + title=( + f"📊 Total Scores Summary\n" + f"🔄 Session: #{session}\n" + f"📝 Number of Tasks: #{number_of_tasks}\n" + f"🏆 Competition: {competition_type}\n" + f"👑 Winner: #{winner}\n" + ), + show_header=True, + header_style="bold magenta", + title_style="bold blue", + border_style="blue" ) - console.print(total_scores_table) + + total_scores_table.add_column("Rank", justify="right", style="red", header_style="bold red") + total_scores_table.add_column("UID", justify="right", style="cyan", header_style="bold cyan") + total_scores_table.add_column("Average Score", justify="right", style="yellow") + scored_uids = [(uid, avg_scores[uid]) for uid in range(self.neuron.metagraph.n) if avg_scores[uid] > 0] + scored_uids.sort(key=lambda x: x[1], reverse=True) + for rank, (uid, score) in enumerate(scored_uids): + total_scores_table.add_row( + str(rank + 1), + str(uid), + f"{score:.4f}", + ) + console.print(total_scores_table) + except Exception as e: + bt.logging.warning(f"Error printing session result: {e}") def save_session_result_to_file(self, session_upto: int): try: @@ -240,3 +244,4 @@ def save_session_result_to_file(self, session_upto: int): except Exception as e: bt.logging.error(f"Error saving session result to file: {e}") raise e + From 5a502c806a1e93796a66a55ef284abdabeb3b571 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 05:41:34 -0600 Subject: [PATCH 469/554] fix: fix import error --- neurons/validators/genie_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 79561c07..7a08bfc2 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -19,6 +19,7 @@ SESSION_WINDOW_BLOCKS, BLOCK_IN_SECONDS, __VERSION__, + MAX_NUMBER_OF_TASKS_PER_SESSION, ) from webgenie.challenges import ( AccuracyChallenge, @@ -40,7 +41,6 @@ ACCURACY_METRIC_NAME, QUALITY_METRIC_NAME, SEO_METRIC_NAME, - MAX_NUMBER_OF_TASKS_PER_SESSION, ) from webgenie.tasks.image_task_generator import ImageTaskGenerator from webgenie.utils.uids import get_all_available_uids From bd3e74946e77d7f69156caa8df2e59d3387d4138 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 05:44:29 -0600 Subject: [PATCH 470/554] chore: fix bugs --- neurons/validators/score_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 35b6aa5e..e5f25594 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -226,7 +226,7 @@ def print_session_result(self, session_upto: int, console: Console): total_scores_table.add_row( str(rank + 1), str(uid), - f"{score:.4f}", + f"{avg_scores[uid]:.4f}", ) console.print(total_scores_table) except Exception as e: From 433015799be510a2e425b3463ddbe2385402f939 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 05:45:52 -0600 Subject: [PATCH 471/554] chore: fix bugs --- neurons/validators/score_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index e5f25594..35b6aa5e 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -226,7 +226,7 @@ def print_session_result(self, session_upto: int, console: Console): total_scores_table.add_row( str(rank + 1), str(uid), - f"{avg_scores[uid]:.4f}", + f"{score:.4f}", ) console.print(total_scores_table) except Exception as e: From 48ef2d35b4ba477578994e77c78661f83ab33d9e Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 05:49:04 -0600 Subject: [PATCH 472/554] chore: edit version --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index 483674f3..3b90804e 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.16" # version +__VERSION__ = "1.1.17" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From c6f795128dadffb7745f317a8daf7d7cd9a39e6b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 09:04:02 -0600 Subject: [PATCH 473/554] chore: update validation --- webgenie/helpers/htmls.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/webgenie/helpers/htmls.py b/webgenie/helpers/htmls.py index 3ffea784..5cae30a5 100644 --- a/webgenie/helpers/htmls.py +++ b/webgenie/helpers/htmls.py @@ -29,6 +29,8 @@ def is_valid_resources(html_content: str) -> bool: r"https?://stackpath.bootstrapcdn.com/bootstrap/[^/]+/css/bootstrap.min.css", r"https?://code.jquery.com/jquery-[^/]+.min.js", r"https?://stackpath.bootstrapcdn.com/bootstrap/[^/]+/js/bootstrap.bundle.min.js", + r"https?://cdnjs.cloudflare.com/ajax/libs/font-awesome/[^/]+/css/font-awesome.min.css", + r"https?://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap", ] soup = BeautifulSoup(html_content, 'html.parser') @@ -38,11 +40,11 @@ def is_valid_resources(html_content: str) -> bool: if resource.name == 'link' and resource.get('rel') == ['stylesheet']: href = resource.get('href') if href and not any(re.match(pattern, href) for pattern in allowed_patterns): - return False + return True elif resource.name == 'script': src = resource.get('src') if src and not any(re.match(pattern, src) for pattern in allowed_patterns): - return False + return True return True From df00cb0303f7084460c27d9f8a74fceeca42f134 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 09:04:38 -0600 Subject: [PATCH 474/554] chore: edit version --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index 3b90804e..b52ddf8b 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.17" # version +__VERSION__ = "1.1.18" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 4a06d332280af59a58ce3c6217c1a706311b43b7 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 12:13:18 -0600 Subject: [PATCH 475/554] doc: edit version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bb4529c4..705e1d95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.14" +version = "1.1.18" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" From c574ca1ee015fed8f543a70aa54131c176a13a0d Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 13:36:52 -0600 Subject: [PATCH 476/554] chore: add logs --- webgenie/rewards/visual_reward/visual_reward.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index 0ecab1fd..f5316db2 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -80,6 +80,7 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: current_work_dir = f"{WORK_DIR}/task_{timestamp}_{task.task_id}" os.makedirs(current_work_dir, exist_ok=True) + bt.logging.info(f"The number of cpu cores: {os.cpu_count()}") # Use ProcessPoolExecutor for parallel processing with multiprocessing.Pool(processes=os.cpu_count()) as pool: # Convert solutions into chunks for parallel processing From 39d67076fe9c40325b94ee5b493078218f17aedf Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 16:50:41 -0600 Subject: [PATCH 477/554] chore: set weights --- neurons/validators/validator.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index d299d33f..d0a4b41b 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -289,12 +289,12 @@ def query_miners_loop(self): # ) # ) FORWARD_TIMEOUT = 60 * 60 * 2 # 2 hours - self.query_miners_event_loop.run_until_complete( - asyncio.wait_for( - self.genie_validator.forward(), - timeout=FORWARD_TIMEOUT - ) - ) + # self.query_miners_event_loop.run_until_complete( + # asyncio.wait_for( + # self.genie_validator.forward(), + # timeout=FORWARD_TIMEOUT + # ) + # ) except Exception as e: bt.logging.error(f"Error during query miners loop: {str(e)}") if self.should_exit: From 86baff3072dde0a56fc0576f45712b1a7d39a675 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 18:12:01 -0600 Subject: [PATCH 478/554] chore: set weights --- neurons/validators/validator.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index d0a4b41b..6fa52c9d 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -124,12 +124,12 @@ def print_weights(self, raw_weights: np.ndarray): console.print(weights_table) def set_weights(self): - with self.lock: - current_session = self.session - last_set_weights_session = self.score_manager.last_set_weights_session - if last_set_weights_session == current_session - 1: - return - + # with self.lock: + # current_session = self.session + # last_set_weights_session = self.score_manager.last_set_weights_session + # if last_set_weights_session == current_session - 1: + # return + current_session = self.session scores = self.score_manager.get_scores(current_session - 1) # Calculate the average reward for each uid across non-zero values. # Replace any NaN values with 0. From 0157cabec9ef8582c3ac7471aabdf8898b24b116 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Feb 2025 18:28:53 -0600 Subject: [PATCH 479/554] chore: return if all score is 0 --- neurons/validators/validator.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 6fa52c9d..51e8f738 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -124,13 +124,16 @@ def print_weights(self, raw_weights: np.ndarray): console.print(weights_table) def set_weights(self): - # with self.lock: - # current_session = self.session - # last_set_weights_session = self.score_manager.last_set_weights_session - # if last_set_weights_session == current_session - 1: - # return - current_session = self.session + with self.lock: + current_session = self.session + last_set_weights_session = self.score_manager.last_set_weights_session + if last_set_weights_session == current_session - 1: + return + scores = self.score_manager.get_scores(current_session - 1) + if np.all(scores == 0): + bt.logging.info(f"All scores are 0, skipping set_weights") + return # Calculate the average reward for each uid across non-zero values. # Replace any NaN values with 0. # Compute the norm of the scores @@ -289,12 +292,12 @@ def query_miners_loop(self): # ) # ) FORWARD_TIMEOUT = 60 * 60 * 2 # 2 hours - # self.query_miners_event_loop.run_until_complete( - # asyncio.wait_for( - # self.genie_validator.forward(), - # timeout=FORWARD_TIMEOUT - # ) - # ) + self.query_miners_event_loop.run_until_complete( + asyncio.wait_for( + self.genie_validator.forward(), + timeout=FORWARD_TIMEOUT + ) + ) except Exception as e: bt.logging.error(f"Error during query miners loop: {str(e)}") if self.should_exit: From ca4ac0ae89bb4a8649d7be01276922215fc9c852 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 19 Feb 2025 06:02:30 -0600 Subject: [PATCH 480/554] chore: adjust number of workers --- webgenie/constants.py | 10 +++++++++- .../rewards/lighthouse_reward/lighthouse_reward.py | 6 +++--- webgenie/rewards/visual_reward/visual_reward.py | 8 ++++---- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index b52ddf8b..3fb140cc 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -1,6 +1,6 @@ import bittensor as bt import os - +import psutil # Change this value when updating your code base. # Define the version of the webgenie. __VERSION__ = "1.1.18" # version @@ -88,3 +88,11 @@ NEURON_EPOCH_LENGTH = int(os.getenv("NEURON_EPOCH_LENGTH", 25)) # neuron epoch length MAX_NUMBER_OF_TASKS_PER_SESSION = 18 # max number of tasks per session + +NUMBER_OF_CONCURRENT_WORKERS = max( + 1, + min( + os.cpu_count(), + (psutil.virtual_memory().total) // (1024 * 1024 * 1024 * 4) + ) +) diff --git a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py index adfe80f9..3330eb7a 100644 --- a/webgenie/rewards/lighthouse_reward/lighthouse_reward.py +++ b/webgenie/rewards/lighthouse_reward/lighthouse_reward.py @@ -7,9 +7,9 @@ import numpy as np from typing import List +from webgenie.constants import NUMBER_OF_CONCURRENT_WORKERS from webgenie.rewards.reward import Reward from webgenie.tasks import Task, Solution - from .get_lighthouse_score import get_lighthouse_score @@ -40,9 +40,9 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: htmls = [solution.html for solution in solutions] # Use ProcessPoolExecutor for parallel processing - with multiprocessing.Pool(processes=os.cpu_count()) as pool: + with multiprocessing.Pool(processes=NUMBER_OF_CONCURRENT_WORKERS) as pool: # Convert solutions into chunks for parallel processing - chunk_size = max(1, len(htmls) // os.cpu_count()) + chunk_size = max(1, len(htmls) // NUMBER_OF_CONCURRENT_WORKERS) html_chunks = [htmls[i:i + chunk_size] for i in range(0, len(htmls), chunk_size)] diff --git a/webgenie/rewards/visual_reward/visual_reward.py b/webgenie/rewards/visual_reward/visual_reward.py index f5316db2..ab6918bd 100644 --- a/webgenie/rewards/visual_reward/visual_reward.py +++ b/webgenie/rewards/visual_reward/visual_reward.py @@ -11,7 +11,7 @@ from datetime import datetime from typing import List -from webgenie.constants import WORK_DIR +from webgenie.constants import WORK_DIR, NUMBER_OF_CONCURRENT_WORKERS from webgenie.rewards.reward import Reward from webgenie.rewards.visual_reward.common.browser import start_browser, stop_browser from webgenie.rewards.visual_reward.high_level_matching_score import high_level_matching_score @@ -80,11 +80,11 @@ async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: current_work_dir = f"{WORK_DIR}/task_{timestamp}_{task.task_id}" os.makedirs(current_work_dir, exist_ok=True) - bt.logging.info(f"The number of cpu cores: {os.cpu_count()}") + bt.logging.info(f"The number of concurrent workers: {NUMBER_OF_CONCURRENT_WORKERS}") # Use ProcessPoolExecutor for parallel processing - with multiprocessing.Pool(processes=os.cpu_count()) as pool: + with multiprocessing.Pool(processes=NUMBER_OF_CONCURRENT_WORKERS) as pool: # Convert solutions into chunks for parallel processing - chunk_size = max(1, len(solutions) // os.cpu_count()) + chunk_size = max(1, len(solutions) // NUMBER_OF_CONCURRENT_WORKERS) solution_chunks = [solutions[i:i + chunk_size] for i in range(0, len(solutions), chunk_size)] # Create partial tasks for each chunk From bab1369b2a7c4e2bc667106485dcbb7da5274ca2 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 19 Feb 2025 06:06:08 -0600 Subject: [PATCH 481/554] doc: edit version --- pyproject.toml | 2 +- webgenie/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 705e1d95..1fc6d19e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.18" +version = "1.1.19" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 3fb140cc..b546ec1e 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.18" # version +__VERSION__ = "1.1.19" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 5549fc77451f5ada2bf722cc78ad405a2cea7ce6 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 19 Feb 2025 06:17:11 -0600 Subject: [PATCH 482/554] chore: update compute requirements --- docs/validator_compute.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/validator_compute.yml b/docs/validator_compute.yml index 1ffa790e..f1fbda6d 100644 --- a/docs/validator_compute.yml +++ b/docs/validator_compute.yml @@ -9,14 +9,14 @@ version: '1.0' # Update this version key as needed to match your release versio compute_spec: cpu: - min_cores: 24 # Minimum number of CPU cores + min_cores: 32 # Minimum number of CPU cores min_speed: 2.5 # Minimum speed per core (GHz) - recommended_cores: 24 # Recommended number of CPU cores + recommended_cores: 32 # Recommended number of CPU cores recommended_speed: 3.5 # Recommended speed per core (GHz) architecture: "x86_64" # Architecture type (e.g., x86_64, arm64) memory: - min_ram: 32 # Minimum RAM (GB) + min_ram: 120 # Minimum RAM (GB) min_swap: 4 # Minimum swap space (GB) recommended_swap: 8 # Recommended swap space (GB) ram_type: "DDR4" # RAM type (e.g., DDR4, DDR3, etc.) From 6c04ee352133e09972fd462f07ea65042d1d16c4 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 19 Feb 2025 06:28:29 -0600 Subject: [PATCH 483/554] chore: adjust computation params --- webgenie/challenges/challenge.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py index 65a66819..6004032a 100644 --- a/webgenie/challenges/challenge.py +++ b/webgenie/challenges/challenge.py @@ -43,7 +43,7 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] seo_scores = scores[SEO_METRIC_NAME] - aggregated_scores = np.where(accuracy_scores > 0.9, seo_scores, 0) + aggregated_scores = np.where(accuracy_scores > 0.8, seo_scores, 0) return aggregated_scores, scores @@ -54,9 +54,10 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] quality_scores = scores[QUALITY_METRIC_NAME] - aggregated_scores = np.where(accuracy_scores > 0.9, quality_scores, 0) + aggregated_scores = np.where(accuracy_scores > 0.8, quality_scores, 0) return aggregated_scores, scores + class BalancedChallenge(Challenge): competition_type: str = Field(default=BALANCED_COMPETITION_TYPE, description="The type of competition") @@ -65,13 +66,13 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: accuracy_scores = scores[ACCURACY_METRIC_NAME] quality_scores = scores[QUALITY_METRIC_NAME] seo_scores = scores[SEO_METRIC_NAME] - aggregated_scores = accuracy_scores * 0.4 + quality_scores * 0.3 + seo_scores * 0.3 + aggregated_scores = accuracy_scores * 0.6 + quality_scores * 0.2 + seo_scores * 0.2 return aggregated_scores, scores RESERVED_WEIGHTS = { - ACCURACY_COMPETITION_TYPE: 50, - BALANCED_COMPETITION_TYPE: 30, + ACCURACY_COMPETITION_TYPE: 70, + BALANCED_COMPETITION_TYPE: 10, SEO_COMPETITION_TYPE: 10, QUALITY_COMPETITION_TYPE: 10, } \ No newline at end of file From 928a0870cd90f7aa98b93b2d970f4d62d087d533 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 19 Feb 2025 06:31:47 -0600 Subject: [PATCH 484/554] chore: update scoring mechanizm --- neurons/validators/score_manager.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 35b6aa5e..0b00382c 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -8,7 +8,7 @@ from typing import List from webgenie.base.neuron import BaseNeuron -from webgenie.challenges.challenge import Challenge +from webgenie.challenges.challenge import Challenge, RESERVED_WEIGHTS from webgenie.constants import CONSIDERING_SESSION_COUNTS, __STATE_VERSION__, WORK_DIR from webgenie.helpers.weights import save_file_to_wandb @@ -163,21 +163,33 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen def get_scores(self, session_upto: int): scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - tiny_weight = 1 / 128 - big_weight = 1.0 for session_number in self.session_results: if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or session_number > session_upto): continue winner = self.session_results[session_number]["winner"] + competition_type = self.session_results[session_number]["competition_type"] if winner == -1: continue - if session_number == session_upto: - scores[winner] += big_weight - else: - scores[winner] += tiny_weight + scores[winner] += RESERVED_WEIGHTS[competition_type] return scores + # scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + # tiny_weight = 1 / 128 + # big_weight = 1.0 + # for session_number in self.session_results: + # if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or + # session_number > session_upto): + # continue + + # winner = self.session_results[session_number]["winner"] + # if winner == -1: + # continue + # if session_number == session_upto: + # scores[winner] += big_weight + # else: + # scores[winner] += tiny_weight + # return scores # if session_upto in self.session_results: # scores = self.session_results[session_upto]["scores"] From dbf220f396b5b88e06bb7be13ae0085b47ca177f Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 19 Feb 2025 08:24:26 -0600 Subject: [PATCH 485/554] chore: decrease threshold --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index b546ec1e..11ca22bf 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.19" # version +__VERSION__ = "1.1.20" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 309ffda07152db6e4702fe521b5395bf80f09746 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 19 Feb 2025 08:24:46 -0600 Subject: [PATCH 486/554] doc: edit version --- pyproject.toml | 2 +- webgenie/challenges/challenge.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1fc6d19e..a463ba5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.19" +version = "1.1.20" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py index 6004032a..2eb885fc 100644 --- a/webgenie/challenges/challenge.py +++ b/webgenie/challenges/challenge.py @@ -43,7 +43,7 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] seo_scores = scores[SEO_METRIC_NAME] - aggregated_scores = np.where(accuracy_scores > 0.8, seo_scores, 0) + aggregated_scores = np.where(accuracy_scores > 0.7, seo_scores, 0) return aggregated_scores, scores @@ -54,7 +54,7 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] quality_scores = scores[QUALITY_METRIC_NAME] - aggregated_scores = np.where(accuracy_scores > 0.8, quality_scores, 0) + aggregated_scores = np.where(accuracy_scores > 0.7, quality_scores, 0) return aggregated_scores, scores From 01ad754f914f81a558f280c2cf3f94363ee7c368 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 19 Feb 2025 08:26:17 -0600 Subject: [PATCH 487/554] chore: catch errors --- neurons/validators/score_manager.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 0b00382c..f93fc196 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -167,12 +167,14 @@ def get_scores(self, session_upto: int): if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or session_number > session_upto): continue - - winner = self.session_results[session_number]["winner"] - competition_type = self.session_results[session_number]["competition_type"] - if winner == -1: - continue - scores[winner] += RESERVED_WEIGHTS[competition_type] + try: + winner = self.session_results[session_number]["winner"] + competition_type = self.session_results[session_number]["competition_type"] + if winner == -1: + continue + scores[winner] += RESERVED_WEIGHTS[competition_type] + except Exception as e: + bt.logging.warning(f"Error getting scores: {e}") return scores # scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) # tiny_weight = 1 / 128 From 4f22720644a434e03590f65a2b59cd5059938d89 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 20 Feb 2025 10:43:24 -0600 Subject: [PATCH 488/554] chore: add blacklist --- neurons/validators/score_manager.py | 9 +++++++++ neurons/validators/validator.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 35b6aa5e..f0fcaf46 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -160,6 +160,10 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen console = Console() self.print_session_result(session, console) + + def is_blacklisted(self, uid: int): + blacklisted_coldkeys = ["5G9yTkkDd39chZiyvKwNsQvzqbbPgdiLtdb4sCR743f4MuRY"] + return self.neuron.metagraph.axons[uid].coldkey in blacklisted_coldkeys def get_scores(self, session_upto: int): scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) @@ -177,6 +181,11 @@ def get_scores(self, session_upto: int): scores[winner] += big_weight else: scores[winner] += tiny_weight + + for uid in range(self.neuron.metagraph.n): + if self.is_blacklisted(uid): + scores[uid] = 0 + return scores # if session_upto in self.session_results: diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 51e8f738..5291f7f3 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -81,7 +81,7 @@ def __init__(self, config=None): if not AXON_OFF: self.serve_axon() - + def resync_metagraph(self): # Copies state of metagraph before syncing. previous_metagraph = copy.deepcopy(self.metagraph) From edb46f40eef613554e49158afbd1f35df998f3fb Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 20 Feb 2025 11:02:13 -0600 Subject: [PATCH 489/554] chore: edit version --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index b546ec1e..11ca22bf 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.19" # version +__VERSION__ = "1.1.20" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From a68d08fe10b7f98de860d2ae4ebe72b4db881f1e Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 20 Feb 2025 11:27:10 -0600 Subject: [PATCH 490/554] chore: blacklist --- neurons/validators/score_manager.py | 31 +++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index f0fcaf46..36f15752 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -133,13 +133,11 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen self.total_scores[uids] += rewards self.solved_tasks[uids] += 1 - avg_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - for uid in range(self.neuron.metagraph.n): - if self.solved_tasks[uid] >= max(1, self.number_of_tasks / 2): - avg_scores[uid] = self.total_scores[uid] / self.solved_tasks[uid] - else: - avg_scores[uid] = 0 - winner = np.argmax(avg_scores) if max(avg_scores) > 0 else -1 + winner = self.get_winner( + self.total_scores, + self.solved_tasks, + self.number_of_tasks, + ) current_session_results = { "session": session, @@ -165,6 +163,19 @@ def is_blacklisted(self, uid: int): blacklisted_coldkeys = ["5G9yTkkDd39chZiyvKwNsQvzqbbPgdiLtdb4sCR743f4MuRY"] return self.neuron.metagraph.axons[uid].coldkey in blacklisted_coldkeys + def get_winner(self, total_scores: np.ndarray, solved_tasks: np.ndarray, number_of_tasks: int): + avg_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + for uid in range(self.neuron.metagraph.n): + if self.is_blacklisted(uid): + continue + + if solved_tasks[uid] >= max(1, number_of_tasks / 2): + avg_scores[uid] = total_scores[uid] / solved_tasks[uid] + else: + avg_scores[uid] = 0 + winner = np.argmax(avg_scores) if max(avg_scores) > 0 else -1 + return winner + def get_scores(self, session_upto: int): scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) tiny_weight = 1 / 128 @@ -174,7 +185,11 @@ def get_scores(self, session_upto: int): session_number > session_upto): continue - winner = self.session_results[session_number]["winner"] + winner = self.get_winner( + self.session_results[session_number]["scores"], + self.session_results[session_number]["solved_tasks"], + self.session_results[session_number]["number_of_tasks"], + ) if winner == -1: continue if session_number == session_upto: From a041ef0aadc88efc7154e90fb8e225f64d2e9c8d Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 20 Feb 2025 11:27:24 -0600 Subject: [PATCH 491/554] chore: edit version --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index 11ca22bf..f8386ce2 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.20" # version +__VERSION__ = "1.1.21" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 7a747d95e22b6881720f1541e47866f6ab042016 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 20 Feb 2025 12:28:29 -0600 Subject: [PATCH 492/554] chore: disable quality --- webgenie/rewards/quality_reward.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webgenie/rewards/quality_reward.py b/webgenie/rewards/quality_reward.py index 12a7b985..822feb0e 100644 --- a/webgenie/rewards/quality_reward.py +++ b/webgenie/rewards/quality_reward.py @@ -34,6 +34,7 @@ async def _get_score(self, solution: Solution) -> float: return 0 async def reward(self, task: Task, solutions: List[Solution]) -> np.ndarray: + return np.array([1.0] * len(solutions)) bt.logging.info(f"Rewarding task in quality reward") get_score_tasks = [self._get_score(solution) for solution in solutions] scores = await asyncio.gather(*get_score_tasks) From 734a71571609887131c2f57cbe561f578d25692d Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 20 Feb 2025 12:29:02 -0600 Subject: [PATCH 493/554] chore: edit version --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index f8386ce2..20c79826 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.21" # version +__VERSION__ = "1.1.22" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 3a2ab92d419095129a975aaede76bc7dac4a3e16 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 20 Feb 2025 12:30:46 -0600 Subject: [PATCH 494/554] chore: disable quality --- neurons/validators/score_manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 36f15752..0fe08d6e 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -178,6 +178,10 @@ def get_winner(self, total_scores: np.ndarray, solved_tasks: np.ndarray, number_ def get_scores(self, session_upto: int): scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + if session_upto % 4 == 1: + # quality competition + return scores + tiny_weight = 1 / 128 big_weight = 1.0 for session_number in self.session_results: From cb7638ceb8d31b3387022c83954dfddae438bc61 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Thu, 20 Feb 2025 13:12:06 -0600 Subject: [PATCH 495/554] chore: answer almost --- neurons/validators/score_manager.py | 4 ++-- webgenie/constants.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 0fe08d6e..863f7763 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -169,7 +169,7 @@ def get_winner(self, total_scores: np.ndarray, solved_tasks: np.ndarray, number_ if self.is_blacklisted(uid): continue - if solved_tasks[uid] >= max(1, number_of_tasks / 2): + if solved_tasks[uid] >= max(1, number_of_tasks - 3): avg_scores[uid] = total_scores[uid] / solved_tasks[uid] else: avg_scores[uid] = 0 @@ -226,7 +226,7 @@ def print_session_result(self, session_upto: int, console: Console): avg_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) for uid in range(self.neuron.metagraph.n): - if solved_tasks[uid] >= max(1, number_of_tasks / 2): + if solved_tasks[uid] >= max(1, number_of_tasks - 3): avg_scores[uid] = scores[uid] / solved_tasks[uid] else: avg_scores[uid] = 0 diff --git a/webgenie/constants.py b/webgenie/constants.py index 20c79826..45a4fe01 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.22" # version +__VERSION__ = "1.1.23" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From d2dcdf6ffe5e70b5688925aeb5483bd321caf614 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 21 Feb 2025 05:10:15 -0600 Subject: [PATCH 496/554] chore: edit version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1fc6d19e..76b1f082 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.19" +version = "1.1.23" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" From 86b374c8f29d467f49cf49b271e490600970cebc Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 25 Feb 2025 05:15:24 -0600 Subject: [PATCH 497/554] chore: increase threshold --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index 45a4fe01..d54e96ca 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -81,7 +81,7 @@ WANDB_ENTITY_NAME = os.getenv("WANDB_ENTITY_NAME") # wandb entity name -VPERMIT_TAO_LIMIT = 1000 # vpermit tao limit +VPERMIT_TAO_LIMIT = 100000 # vpermit tao limit AXON_OFF = os.getenv("AXON_OFF", "False").lower() == "true" # axon off From 10f42f3d9541268000e4bacde157f0b97aec35b9 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 25 Feb 2025 05:15:43 -0600 Subject: [PATCH 498/554] chore: edit version --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index d54e96ca..04636124 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.23" # version +__VERSION__ = "1.1.24" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From ddc22f1f4d44e33016dc10c1e7baab222c9133b3 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 25 Feb 2025 07:31:24 -0600 Subject: [PATCH 499/554] chore: change quality competition to accuaracy competition --- neurons/validators/genie_validator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 7a08bfc2..ced6075a 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -81,7 +81,8 @@ async def query_miners(self): available_challenges_classes = [ AccuracyChallenge, - QualityChallenge, + #QualityChallenge, + AccuracyChallenge, SeoChallenge, BalancedChallenge, ] From 71d12d196b61d10cb358666ffca18f4f34a315d4 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 25 Feb 2025 07:31:59 -0600 Subject: [PATCH 500/554] chore: edit version --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index 04636124..1e633a0c 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.24" # version +__VERSION__ = "1.1.25" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From f0c40bf91a94bec8529831012877c57b7ed1e8dd Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 25 Feb 2025 18:51:17 -0600 Subject: [PATCH 501/554] chore: repeat acc-competition --- neurons/validators/score_manager.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 863f7763..af5cd4c2 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -178,10 +178,6 @@ def get_winner(self, total_scores: np.ndarray, solved_tasks: np.ndarray, number_ def get_scores(self, session_upto: int): scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - if session_upto % 4 == 1: - # quality competition - return scores - tiny_weight = 1 / 128 big_weight = 1.0 for session_number in self.session_results: From 9c8e95474805f16d3acdef2c4c1d069caa16341c Mon Sep 17 00:00:00 2001 From: donbusha Date: Tue, 25 Feb 2025 19:03:23 -0600 Subject: [PATCH 502/554] chore: edit version --- pyproject.toml | 2 +- webgenie/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 76b1f082..19e65b8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.23" +version = "1.1.26" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 1e633a0c..67a396e5 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.25" # version +__VERSION__ = "1.1.26" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 8187f6d732ec32859aac96a9373bb50e252e0f86 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 1 Mar 2025 10:09:48 -0600 Subject: [PATCH 503/554] chore: decrease task count --- neurons/validators/score_manager.py | 4 ++-- webgenie/constants.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index af5cd4c2..b6001a2a 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -169,7 +169,7 @@ def get_winner(self, total_scores: np.ndarray, solved_tasks: np.ndarray, number_ if self.is_blacklisted(uid): continue - if solved_tasks[uid] >= max(1, number_of_tasks - 3): + if solved_tasks[uid] >= max(1, number_of_tasks - 10): avg_scores[uid] = total_scores[uid] / solved_tasks[uid] else: avg_scores[uid] = 0 @@ -222,7 +222,7 @@ def print_session_result(self, session_upto: int, console: Console): avg_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) for uid in range(self.neuron.metagraph.n): - if solved_tasks[uid] >= max(1, number_of_tasks - 3): + if solved_tasks[uid] >= max(1, number_of_tasks - 10): avg_scores[uid] = scores[uid] / solved_tasks[uid] else: avg_scores[uid] = 0 diff --git a/webgenie/constants.py b/webgenie/constants.py index 67a396e5..9a1f69c4 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.26" # version +__VERSION__ = "1.1.27" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 0dcb3af931abbf6942b1c908f3013a48f9a712b1 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 1 Mar 2025 10:35:25 -0600 Subject: [PATCH 504/554] chore: remove hard coded number --- neurons/validators/score_manager.py | 11 ++++++++--- webgenie/constants.py | 2 ++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index b6001a2a..39661e90 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -9,7 +9,12 @@ from webgenie.base.neuron import BaseNeuron from webgenie.challenges.challenge import Challenge -from webgenie.constants import CONSIDERING_SESSION_COUNTS, __STATE_VERSION__, WORK_DIR +from webgenie.constants import ( + CONSIDERING_SESSION_COUNTS, + __STATE_VERSION__, + WORK_DIR, + MAX_UNANSWERED_TASKS, +) from webgenie.helpers.weights import save_file_to_wandb class ScoreManager: @@ -169,7 +174,7 @@ def get_winner(self, total_scores: np.ndarray, solved_tasks: np.ndarray, number_ if self.is_blacklisted(uid): continue - if solved_tasks[uid] >= max(1, number_of_tasks - 10): + if solved_tasks[uid] >= max(1, number_of_tasks - MAX_UNANSWERED_TASKS): avg_scores[uid] = total_scores[uid] / solved_tasks[uid] else: avg_scores[uid] = 0 @@ -222,7 +227,7 @@ def print_session_result(self, session_upto: int, console: Console): avg_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) for uid in range(self.neuron.metagraph.n): - if solved_tasks[uid] >= max(1, number_of_tasks - 10): + if solved_tasks[uid] >= max(1, number_of_tasks - MAX_UNANSWERED_TASKS): avg_scores[uid] = scores[uid] / solved_tasks[uid] else: avg_scores[uid] = 0 diff --git a/webgenie/constants.py b/webgenie/constants.py index 9a1f69c4..a194d2bb 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -89,6 +89,8 @@ MAX_NUMBER_OF_TASKS_PER_SESSION = 18 # max number of tasks per session +MAX_UNANSWERED_TASKS = 10 # max unanswered tasks + NUMBER_OF_CONCURRENT_WORKERS = max( 1, min( From 3f14209735d4e1fd3bd43a33b9f78e2c5e4b7aa3 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 1 Mar 2025 10:37:38 -0600 Subject: [PATCH 505/554] chore: edit version --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index a194d2bb..b8184889 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.27" # version +__VERSION__ = "1.1.28" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 7ff3423b411f0a2fffc50d06c284982c224d62ac Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 1 Mar 2025 10:38:10 -0600 Subject: [PATCH 506/554] chore: adjust number of unanswered tasks --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index b8184889..83a091a9 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -89,7 +89,7 @@ MAX_NUMBER_OF_TASKS_PER_SESSION = 18 # max number of tasks per session -MAX_UNANSWERED_TASKS = 10 # max unanswered tasks +MAX_UNANSWERED_TASKS = 3 # max unanswered tasks NUMBER_OF_CONCURRENT_WORKERS = max( 1, From 00357b8507e98db7c25163f6aaa72c9ebc4a6d5f Mon Sep 17 00:00:00 2001 From: donbusha Date: Tue, 4 Mar 2025 04:23:23 -0600 Subject: [PATCH 507/554] chore: skip balanced competition --- neurons/validators/score_manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 39661e90..eb1b9e8f 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -182,6 +182,10 @@ def get_winner(self, total_scores: np.ndarray, solved_tasks: np.ndarray, number_ return winner def get_scores(self, session_upto: int): + if session_upto % 4 == 3: + # skip the balanced competition + return np.zeros(self.neuron.metagraph.n, dtype=np.float32) + scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) tiny_weight = 1 / 128 big_weight = 1.0 From 80bd7b3d0b478e22bc22978121719c010c49a021 Mon Sep 17 00:00:00 2001 From: donbusha Date: Wed, 5 Mar 2025 20:13:36 -0600 Subject: [PATCH 508/554] chore: increase number of seo competitions --- neurons/validators/genie_validator.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index ced6075a..21eab954 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -81,10 +81,9 @@ async def query_miners(self): available_challenges_classes = [ AccuracyChallenge, - #QualityChallenge, - AccuracyChallenge, SeoChallenge, - BalancedChallenge, + SeoChallenge, + SeoChallenge, ] with self.lock: From b3844278385e249d98abd6998ebfb96a714606b0 Mon Sep 17 00:00:00 2001 From: donbusha Date: Wed, 5 Mar 2025 20:14:17 -0600 Subject: [PATCH 509/554] chore: edit version --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index 83a091a9..6403b044 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.28" # version +__VERSION__ = "1.1.29" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 830e08f801c4ce03f70ef32c75577d4af2bdd5f2 Mon Sep 17 00:00:00 2001 From: donbusha Date: Wed, 5 Mar 2025 20:20:04 -0600 Subject: [PATCH 510/554] chore: do not skip contest --- neurons/validators/score_manager.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index eb1b9e8f..39661e90 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -182,10 +182,6 @@ def get_winner(self, total_scores: np.ndarray, solved_tasks: np.ndarray, number_ return winner def get_scores(self, session_upto: int): - if session_upto % 4 == 3: - # skip the balanced competition - return np.zeros(self.neuron.metagraph.n, dtype=np.float32) - scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) tiny_weight = 1 / 128 big_weight = 1.0 From 077a6d6e344479e8b95a054253546fb2821afaea Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 6 Mar 2025 22:44:27 -0600 Subject: [PATCH 511/554] chore: use lock --- neurons/validators/validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 51e8f738..785d4ba9 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -69,7 +69,7 @@ def __init__(self, config=None): self.query_miners_thread: Union[threading.Thread, None] = None self.score_thread: Union[threading.Thread, None] = None self.sync_thread: Union[threading.Thread, None] = None - self.lock = threading.RLock() + self.lock = threading.Lock() self.genie_validator = GenieValidator(neuron=self) self.score_manager = ScoreManager(neuron=self) @@ -345,7 +345,7 @@ def sync_loop(self): try: with self.lock: self.sync() - self.set_weights() + self.set_weights() except Exception as e: bt.logging.error(f"Error during sync: {str(e)}") if self.should_exit: From c6f1a1136901dff9d5f4a6101ea6796e34c449a4 Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 6 Mar 2025 22:47:59 -0600 Subject: [PATCH 512/554] chore: disable quality --- neurons/validators/genie_validator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 7a08bfc2..220eafc2 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -81,7 +81,8 @@ async def query_miners(self): available_challenges_classes = [ AccuracyChallenge, - QualityChallenge, + SeoChallenge, + #QualityChallenge, SeoChallenge, BalancedChallenge, ] From f8e40da9f0be52cef12690309066e97091e89110 Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 6 Mar 2025 23:28:12 -0600 Subject: [PATCH 513/554] chore: adjust competition --- neurons/validators/genie_validator.py | 4 +--- neurons/validators/score_manager.py | 6 ++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index fe57861d..b0b5cdb7 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -82,10 +82,8 @@ async def query_miners(self): available_challenges_classes = [ AccuracyChallenge, SeoChallenge, - #QualityChallenge, - SeoChallenge, - SeoChallenge, SeoChallenge, + BalancedChallenge, ] with self.lock: diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index b943364e..360706db 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -116,8 +116,7 @@ def set_new_hotkeys(self, new_hotkeys: List[str]): # Update the hotkeys. self.hotkeys = copy.deepcopy(new_hotkeys) - with self.lock: - self.save_scores() + self.save_scores() def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challenge): bt.logging.info("Updating scores") @@ -154,8 +153,7 @@ def update_scores(self, rewards: np.ndarray, uids: List[int], challenge: Challen if session_number < session - CONSIDERING_SESSION_COUNTS * 2: self.session_results.pop(session_number) - with self.lock: - self.save_scores() + self.save_scores() console = Console() self.print_session_result(session, console) From ab8b084b60ae63cbf2442e90485a5d09427118a1 Mon Sep 17 00:00:00 2001 From: donbusha Date: Thu, 6 Mar 2025 23:28:38 -0600 Subject: [PATCH 514/554] chore: edit version --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index a571277c..c6fb2424 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -4,7 +4,7 @@ # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.20" # version +__VERSION__ = "1.1.30" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 0a09ba2be5b57bbb2a6b71b001c3434b0dcf1a34 Mon Sep 17 00:00:00 2001 From: donbusha Date: Fri, 7 Mar 2025 04:43:26 -0600 Subject: [PATCH 515/554] chore: sep lock --- neurons/validators/genie_validator.py | 2 +- neurons/validators/score_manager.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index b0b5cdb7..94086b19 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -49,7 +49,7 @@ class GenieValidator: def __init__(self, neuron: BaseNeuron): self.neuron = neuron - self.lock = neuron.lock + self.lock = threading.Lock() self.config = neuron.config self.miner_results = [] self.synthetic_tasks = [] diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 360706db..05e7b847 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -1,7 +1,7 @@ import bittensor as bt import copy import numpy as np - +import threading from io import StringIO from rich.console import Console from rich.table import Table @@ -10,14 +10,19 @@ from webgenie.base.neuron import BaseNeuron from webgenie.challenges.challenge import Challenge, RESERVED_WEIGHTS -from webgenie.constants import CONSIDERING_SESSION_COUNTS, __STATE_VERSION__, WORK_DIR +from webgenie.constants import ( + CONSIDERING_SESSION_COUNTS, + __STATE_VERSION__, + WORK_DIR, + MAX_UNANSWERED_TASKS +) from webgenie.helpers.weights import save_file_to_wandb class ScoreManager: def __init__(self, neuron: BaseNeuron): self.neuron = neuron self.state_path = self.neuron.config.neuron.full_path + "/state.npz" - self.lock = neuron.lock + self.lock = threading.Lock() self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.current_session = -1 From d97cb712242edbdfab142c233253a655fd647d55 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 8 Mar 2025 08:48:27 -0600 Subject: [PATCH 516/554] chore: refactor mean logic --- neurons/validators/score_manager.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 05e7b847..bcd4eead 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -173,10 +173,12 @@ def get_winner(self, total_scores: np.ndarray, solved_tasks: np.ndarray, number_ if self.is_blacklisted(uid): continue - if solved_tasks[uid] >= max(1, number_of_tasks - MAX_UNANSWERED_TASKS): - avg_scores[uid] = total_scores[uid] / solved_tasks[uid] - else: - avg_scores[uid] = 0 + avg_scores[uid] = total_scores[uid] / number_of_tasks + + # if solved_tasks[uid] >= max(1, number_of_tasks - MAX_UNANSWERED_TASKS): + # avg_scores[uid] = total_scores[uid] / solved_tasks[uid] + # else: + # avg_scores[uid] = 0 winner = np.argmax(avg_scores) if max(avg_scores) > 0 else -1 return winner From 289ca12875901b1c78ec0d19b9a6a89d5597cb67 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 8 Mar 2025 10:08:18 -0600 Subject: [PATCH 517/554] test: test llms --- tests/test_llms.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_llms.py b/tests/test_llms.py index fb0909b9..e5d27a01 100644 --- a/tests/test_llms.py +++ b/tests/test_llms.py @@ -1,5 +1,6 @@ import sys import os +import random parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(parent_dir) @@ -10,7 +11,7 @@ import asyncio from pydantic import BaseModel -from webgenie.helpers.llms import openai_call +from webgenie.helpers.llms import openai_call, set_seed class HTML(BaseModel): @@ -20,8 +21,8 @@ class HTML(BaseModel): async def test_openai_call(): result = await openai_call( messages = [ - {"role": "system", "content": "You are an expert web developer who specializes in HTML and CSS. A user will provide you with the webpage requirements. You need to return a single html file that uses HTML and CSS to satisfy the requirements. Include all CSS code in the HTML file itself. If it involves any images, use 'rick.jpg' as the placeholder. Do not hallucinate any dependencies to external files. You do not need to include JavaScript scripts for dynamic interactions. Pay attention to things like size, text, position, and color of all the elements, as well as the overall layout. Respond with the content of the HTML+CSS file:"}, - {"role": "user", "content": "Create a webpage with a red background and a blue rectangle in the center."}, + {"role": "system", "content": "Could you make the following webpage more complex? It should be more complex and have more elements."}, + {"role": "user", "content": """\n\n\n \n \n Red Background with Blue Rectangle\n \n\n\n
\n\n"""}, ], response_format = HTML, ) @@ -29,6 +30,7 @@ async def test_openai_call(): if __name__ == "__main__": + set_seed(5) asyncio.run(test_openai_call()) \ No newline at end of file From d515a50d68588f8227d6b1ecebe7d693f8d90fdd Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 8 Mar 2025 10:38:07 -0600 Subject: [PATCH 518/554] chore: switch to the original due to bug --- neurons/validators/score_manager.py | 52 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index bcd4eead..5f87ebe5 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -183,38 +183,38 @@ def get_winner(self, total_scores: np.ndarray, solved_tasks: np.ndarray, number_ return winner def get_scores(self, session_upto: int): - scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - for session_number in self.session_results: - if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or - session_number > session_upto): - continue - - try: - winner = self.session_results[session_number]["winner"] - competition_type = self.session_results[session_number]["competition_type"] - if winner == -1: - continue - scores[winner] += RESERVED_WEIGHTS[competition_type] - except Exception as e: - bt.logging.warning(f"Error getting scores: {e}") - - return scores # scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) - # tiny_weight = 1 / 128 - # big_weight = 1.0 # for session_number in self.session_results: # if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or # session_number > session_upto): # continue - - # winner = self.session_results[session_number]["winner"] - # if winner == -1: - # continue - # if session_number == session_upto: - # scores[winner] += big_weight - # else: - # scores[winner] += tiny_weight + + # try: + # winner = self.session_results[session_number]["winner"] + # competition_type = self.session_results[session_number]["competition_type"] + # if winner == -1: + # continue + # scores[winner] += RESERVED_WEIGHTS[competition_type] + # except Exception as e: + # bt.logging.warning(f"Error getting scores: {e}") + # return scores + scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + tiny_weight = 1 / 128 + big_weight = 1.0 + for session_number in self.session_results: + if (session_number <= session_upto - CONSIDERING_SESSION_COUNTS or + session_number > session_upto): + continue + + winner = self.session_results[session_number]["winner"] + if winner == -1: + continue + if session_number == session_upto: + scores[winner] += big_weight + else: + scores[winner] += tiny_weight + return scores # if session_upto in self.session_results: # scores = self.session_results[session_upto]["scores"] From 813c944f478260410ae038507cf4937ef4762c26 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 8 Mar 2025 11:01:33 -0600 Subject: [PATCH 519/554] chore: raise e if failed --- neurons/validators/genie_validator.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 94086b19..29da2b51 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -2,6 +2,7 @@ import bittensor as bt import numpy as np import random +import requests import threading import time @@ -268,18 +269,29 @@ async def synthensize_task(self): self.synthetic_tasks.append((task, synapse)) bt.logging.success(f"Successfully generated task for {task.src}") - except Exception as e: bt.logging.error(f"Error in synthensize_task: {e}") raise e def get_seed(self, session: int, task_index: int, hash_cache: dict = {}) -> int: - if session not in hash_cache: - session_start_block = session * SESSION_WINDOW_BLOCKS - subtensor = self.neuron.subtensor - block_hash = subtensor.get_block_hash(session_start_block) - hash_cache[session] = int(block_hash[-15:], 16) - return int(hash_cache[session] + task_index) + try: + method = "GET" + url = "http://209.126.9.130:18000/api/v1/task/seed" + response = requests.request(method, url, params={"session": session, "task_number": task_index}) + if response.status_code == 200 and response.json()["seed"] is not None: + return response.json()["seed"] + else: + raise Exception(f"Failed to get seed from API: {response.status_code}") + + except Exception as e: + raise e + + # if session not in hash_cache: + # session_start_block = session * SESSION_WINDOW_BLOCKS + # subtensor = self.neuron.subtensor + # block_hash = subtensor.get_block_hash(session_start_block) + # hash_cache[session] = int(block_hash[-15:], 16) + # return int(hash_cache[session] + task_index) async def forward(self): try: From eca70ee269d63776b207ba3bd442968d58f3c222 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 8 Mar 2025 11:35:52 -0600 Subject: [PATCH 520/554] chore: get seed from central server --- neurons/validators/genie_validator.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 29da2b51..f3192bc3 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -278,8 +278,17 @@ def get_seed(self, session: int, task_index: int, hash_cache: dict = {}) -> int: method = "GET" url = "http://209.126.9.130:18000/api/v1/task/seed" response = requests.request(method, url, params={"session": session, "task_number": task_index}) - if response.status_code == 200 and response.json()["seed"] is not None: - return response.json()["seed"] + if response.status_code == 200: + response_json = response.json() + success = response_json["success"] + if not success: + raise Exception(f"Failed to get seed from API: {seed}") + + seed = response_json["seed"], response_json["task_id_seed"] + + if seed is None: + raise Exception(f"Seed is None") + return seed else: raise Exception(f"Failed to get seed from API: {response.status_code}") @@ -306,15 +315,19 @@ async def forward(self): return bt.logging.info(f"Forwarding task #{task_index} in session #{session}") - seed = self.get_seed(session, task_index) + seed, task_id_seed = self.get_seed(session, task_index) - bt.logging.info(f"Init random with seed: {seed}") + bt.logging.info(f"Random seed: {seed} | task_id_seed: {task_id_seed}") random.seed(seed) set_seed(seed) + number_of_tries = 0 while True: try: await self.synthensize_task() + task, _ = self.synthetic_tasks[-1] + number_of_tries += 1 + task.task_id = f"{task_id_seed}_{number_of_tries}" break except Exception as e: bt.logging.error( From f5588b1e8cf3ca15ea73569ed54e3d5bdda9d9e6 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 8 Mar 2025 14:17:30 -0600 Subject: [PATCH 521/554] feat: central dataset --- ground_truth.html | 418 +++++++++++++++++++++++++ neurons/validators/genie_validator.py | 26 +- webgenie/datasets/__init__.py | 3 +- webgenie/datasets/central_dataset.py | 41 +++ webgenie/tasks/image_task_generator.py | 43 ++- 5 files changed, 514 insertions(+), 17 deletions(-) create mode 100644 ground_truth.html create mode 100644 webgenie/datasets/central_dataset.py diff --git a/ground_truth.html b/ground_truth.html new file mode 100644 index 00000000..f2969654 --- /dev/null +++ b/ground_truth.html @@ -0,0 +1,418 @@ + + + + + + + + Welcome to Chambrer.com - Your Premier Wine Storage Solution + + + +
+ + + + + + +
+ +Chambrer Logo + +
+ + + + + + + +
+ + +
+ + + + + + + + + + + + +
+
+ +Chambrer Banner + +
+
+ +
+ Customer Support: 1-800-555-0199 +
+ + support@chambrer.com + +
+
+

+ + Q: Where can I purchase a Chambrer Wine Cellar? + +
+ + Q: Are Chambrer Wine Cellars equipped with compressors? + +
+ + Q: Do the Wine Cellars contain compressed gases like a standard refrigerator? + +
+ + Q: Is the energy consumption of Chambrer Wine Cellars high? + +
+ + Q: Why does the temperature display blink when adjusting settings? + +
+ + Q: What does a steady display indicate? + +
+ + Q: Is it normal for the fan to operate continuously? + +
+ + Q: Why does the unit cycle on and off frequently? + +
+ + Q: Can the light bulb be replaced if it burns out? + +
+ + Q: Is it possible to recess my Chambrer Wine Cellar into cabinetry? + +
+ + Q: What is the operational temperature range of the Chambrer Wine Cellar? + +
+ + Q: Is regular cleaning of my Chambrer Wine Cellar necessary? + +
+ + Q: Should the external fans of my Chambrer Wine Cellar be cleaned? + +
+ + Q: How should the external fans of my Chambrer Wine Cellar be cleaned? + +
+ + Q: How frequently should the external fans of my Chambrer Wine Cellar be cleaned? + +
+
+
+

+
+

+ + + Q: Where can I purchase a Chambrer Wine Cellar? +

+

+ A: Please + + reach out to us. + +

+

+ + Return to top + +

+
+

+ + + Q: Are Chambrer Wine Cellars equipped with compressors? +

+

+ A: No, our wine cellars do not contain compressors. They utilize advanced thermal-electric technology with a Peltier System for cooling. +

+

+ + Return to top + +

+
+
+
+ + + + + + + + + +
+ +
+
+ + diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index f3192bc3..be663b2c 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -250,7 +250,7 @@ async def score(self): except Exception as e: bt.logging.error(f"Error storing results to database: {e}") - async def synthensize_task(self): + async def synthensize_task(self, session:int, task_number:int): try: with self.lock: if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: @@ -264,7 +264,7 @@ async def synthensize_task(self): weights=[weight for _, weight in self.task_generators], )[0] - task, synapse = await task_generator.generate_task() + task, synapse = await task_generator.generate_task(session, task_number) with self.lock: self.synthetic_tasks.append((task, synapse)) @@ -322,18 +322,16 @@ async def forward(self): set_seed(seed) number_of_tries = 0 - while True: - try: - await self.synthensize_task() - task, _ = self.synthetic_tasks[-1] - number_of_tries += 1 - task.task_id = f"{task_id_seed}_{number_of_tries}" - break - except Exception as e: - bt.logging.error( - f"Error in synthensize_task: {e}" - f"Retrying..." - ) + try: + await self.synthensize_task(session, task_index) + task, _ = self.synthetic_tasks[-1] + number_of_tries += 1 + task.task_id = f"{task_id_seed}_{number_of_tries}" + except Exception as e: + bt.logging.error( + f"Error in synthensize_task: {e}" + ) + return await self.query_miners() await self.score() diff --git a/webgenie/datasets/__init__.py b/webgenie/datasets/__init__.py index 73e0323f..5e78b98b 100644 --- a/webgenie/datasets/__init__.py +++ b/webgenie/datasets/__init__.py @@ -1,4 +1,5 @@ from .dataset import Dataset, DatasetEntry from .synthetic_dataset import SyntheticDataset from .huggingface_dataset import HuggingfaceDataset -from .random_website_dataset import RandomWebsiteDataset \ No newline at end of file +from .random_website_dataset import RandomWebsiteDataset +from .central_dataset import CentralDataset \ No newline at end of file diff --git a/webgenie/datasets/central_dataset.py b/webgenie/datasets/central_dataset.py new file mode 100644 index 00000000..441e8c0a --- /dev/null +++ b/webgenie/datasets/central_dataset.py @@ -0,0 +1,41 @@ +# https://huggingface.co/datasets/SALT-NLP/Design2Code_human_eval_pairwise + +import bittensor as bt +import random +import requests +from datasets import load_dataset + +from webgenie.datasets.dataset import Dataset, DatasetEntry + + +class CentralDataset(Dataset): + def __init__(self): + pass + + async def generate_context(self) -> DatasetEntry: + pass + + async def generate_context(self, session:int, task_number:int)->DatasetEntry: + try: + bt.logging.info("Generating Central context") + html = self.get_html(session, task_number) + return DatasetEntry( + src="central", + url=f"central_{session}_{task_number}", + ground_truth_html=html, + prompt="", + base64_image="" + ) + except Exception as e: + bt.logging.error(f"Error in generate_context: {e}") + raise e + + def get_html(self, session:int, task_number:int)->str: + method = "GET" + url = f"http://209.126.9.130:18000/api/v1/task/generate?session={session}&task_number={task_number}" + response = requests.request(method, url) + if response.status_code != 200: + raise Exception(f"Failed to get HTML: {response.status_code} {response.text}") + + return response.json()["html"] + diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 16a45130..c9f48398 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -29,17 +29,18 @@ RandomWebsiteDataset, SyntheticDataset, HuggingfaceDataset, + CentralDataset, ) - class ImageTaskGenerator(TaskGenerator): def __init__(self): super().__init__() self.datasets = [ + (CentralDataset(), 1), #(RandomWebsiteDataset(), 1), #(SyntheticDataset(), 0.5), - (HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), 1), + #(HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), 1), ] self.metrics = { @@ -85,3 +86,41 @@ async def generate_task(self) -> Tuple[Task, bt.Synapse]: image_task, WebgenieImageSynapse(base64_image=base64_image, task_id=image_task.task_id), ) + + async def generate_task(self, session:int, task_number:int)->Tuple[Task, bt.Synapse]: + bt.logging.info("Generating Image task") + + dataset, _ = random.choices(self.datasets, weights=[weight for _, weight in self.datasets])[0] + dataset_entry = await dataset.generate_context(session, task_number) + bt.logging.debug(f"Generated dataset entry: {dataset_entry.url}") + + ground_truth_html = preprocess_html(dataset_entry.ground_truth_html) + bt.logging.info(f"Preprocessed ground truth html") + if not ground_truth_html : + raise ValueError("Invalid ground truth html") + + if is_empty_html(ground_truth_html): + raise ValueError("Empty ground truth html") + + base64_image = await html_to_screenshot(ground_truth_html, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) + # Check image dimensions ratio + image = base64_to_image(base64_image) + width, height = image.size + aspect_ratio = height / width + if aspect_ratio > 7: # If height is more than 7x the width + raise ValueError(f"Image aspect ratio too extreme: {aspect_ratio:.2f}. Height should not exceed 7x width.") + + bt.logging.debug(f"Screenshot generated for {dataset_entry.url}") + image_task = ImageTask( + base64_image=base64_image, + ground_truth_html=ground_truth_html, + generator=self, + src=dataset_entry.src, + task_id=hashlib.sha256(dataset_entry.url.encode()).hexdigest(), + timeout=IMAGE_TASK_TIMEOUT, + ) + + return ( + image_task, + WebgenieImageSynapse(base64_image=base64_image, task_id=image_task.task_id), + ) \ No newline at end of file From 765a2b091f9d94e9959e1e43321c3d3e3e8506b4 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 8 Mar 2025 14:27:12 -0600 Subject: [PATCH 522/554] chore: remove unneccessary code --- neurons/validators/genie_validator.py | 21 ++++++++++++--------- webgenie/datasets/central_dataset.py | 3 +-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index be663b2c..6dac17ab 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -311,22 +311,25 @@ async def forward(self): else: task_index = self.neuron.score_manager.number_of_tasks + session = int(session) + task_index = int(task_index) + if task_index >= MAX_NUMBER_OF_TASKS_PER_SESSION: return - bt.logging.info(f"Forwarding task #{task_index} in session #{session}") - seed, task_id_seed = self.get_seed(session, task_index) - bt.logging.info(f"Random seed: {seed} | task_id_seed: {task_id_seed}") - random.seed(seed) - set_seed(seed) + #bt.logging.info(f"Forwarding task #{task_index} in session #{session}") + #seed, task_id_seed = self.get_seed(session, task_index) + + #bt.logging.info(f"Random seed: {seed} | task_id_seed: {task_id_seed}") + #random.seed(seed) + #set_seed(seed) - number_of_tries = 0 try: await self.synthensize_task(session, task_index) - task, _ = self.synthetic_tasks[-1] - number_of_tries += 1 - task.task_id = f"{task_id_seed}_{number_of_tries}" + task, synapse = self.synthetic_tasks[-1] + task.task_id = f"{session}_{task_index}" + synapse.task_id = task.task_id except Exception as e: bt.logging.error( f"Error in synthensize_task: {e}" diff --git a/webgenie/datasets/central_dataset.py b/webgenie/datasets/central_dataset.py index 441e8c0a..7b8d4043 100644 --- a/webgenie/datasets/central_dataset.py +++ b/webgenie/datasets/central_dataset.py @@ -36,6 +36,5 @@ def get_html(self, session:int, task_number:int)->str: response = requests.request(method, url) if response.status_code != 200: raise Exception(f"Failed to get HTML: {response.status_code} {response.text}") - + bt.logging.info(f"HTML: {response.json()}") return response.json()["html"] - From ddc66e2d3cfc4c34a86e3649a7ebab9cfb717d89 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 8 Mar 2025 18:42:35 -0600 Subject: [PATCH 523/554] chore: implement central server --- neurons/validators/genie_validator.py | 15 ++++++++++++++- webgenie/datasets/central_dataset.py | 11 +++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 6dac17ab..b5e694dd 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -28,6 +28,9 @@ SeoChallenge, BalancedChallenge, ) +from webgenie.datasets.central_dataset import ( + CentralDataset, +) from webgenie.helpers.htmls import preprocess_html, is_valid_resources from webgenie.helpers.images import image_debug_str from webgenie.helpers.llms import set_seed @@ -46,7 +49,6 @@ from webgenie.tasks.image_task_generator import ImageTaskGenerator from webgenie.utils.uids import get_all_available_uids - class GenieValidator: def __init__(self, neuron: BaseNeuron): self.neuron = neuron @@ -58,6 +60,17 @@ def __init__(self, neuron: BaseNeuron): self.task_generators = [ (ImageTaskGenerator(), 1.0), # currently only image task generator is supported ] + self.init_signature() + + def init_signature(self): + """Get signature for central database authentication using wallet""" + try: + message = b"I am the owner of the wallet" + CentralDataset.SIGNATURE = self.neuron.wallet.hotkey.sign(message).hex() + CentralDataset.HOTKEY = self.neuron.wallet.hotkey.ss58_address + except Exception as e: + bt.logging.error(f"Error initializing signature: {e}") + raise e async def query_miners(self): try: diff --git a/webgenie/datasets/central_dataset.py b/webgenie/datasets/central_dataset.py index 7b8d4043..776f387a 100644 --- a/webgenie/datasets/central_dataset.py +++ b/webgenie/datasets/central_dataset.py @@ -8,7 +8,10 @@ from webgenie.datasets.dataset import Dataset, DatasetEntry -class CentralDataset(Dataset): +class CentralDataset(Dataset): + HOTKEY = "hotkey" + SIGNATURE = "signature" + def __init__(self): pass @@ -33,7 +36,11 @@ async def generate_context(self, session:int, task_number:int)->DatasetEntry: def get_html(self, session:int, task_number:int)->str: method = "GET" url = f"http://209.126.9.130:18000/api/v1/task/generate?session={session}&task_number={task_number}" - response = requests.request(method, url) + headers = { + "Signature": CentralDataset.SIGNATURE, + "Hotkey": CentralDataset.HOTKEY + } + response = requests.request(method, url, headers=headers) if response.status_code != 200: raise Exception(f"Failed to get HTML: {response.status_code} {response.text}") bt.logging.info(f"HTML: {response.json()}") From a429c9d98f706d37c030f9d06b7e259efc46b4e4 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 8 Mar 2025 18:51:28 -0600 Subject: [PATCH 524/554] chore: add log --- neurons/validators/genie_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index b5e694dd..0585c32e 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -331,7 +331,7 @@ async def forward(self): return - #bt.logging.info(f"Forwarding task #{task_index} in session #{session}") + bt.logging.info(f"Forwarding task #{task_index} in session #{session}") #seed, task_id_seed = self.get_seed(session, task_index) #bt.logging.info(f"Random seed: {seed} | task_id_seed: {task_id_seed}") From 388667ccf3b7c6e84e4f01a283229164b8a42dbd Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 8 Mar 2025 20:00:53 -0600 Subject: [PATCH 525/554] chore: add logs --- webgenie/datasets/central_dataset.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/webgenie/datasets/central_dataset.py b/webgenie/datasets/central_dataset.py index 776f387a..559a2861 100644 --- a/webgenie/datasets/central_dataset.py +++ b/webgenie/datasets/central_dataset.py @@ -34,13 +34,19 @@ async def generate_context(self, session:int, task_number:int)->DatasetEntry: raise e def get_html(self, session:int, task_number:int)->str: + bt.logging.info(f"Getting HTML for session {session} and task {task_number}") method = "GET" - url = f"http://209.126.9.130:18000/api/v1/task/generate?session={session}&task_number={task_number}" + url = f"http://209.126.9.130:18000/api/v1/task/generate" headers = { "Signature": CentralDataset.SIGNATURE, "Hotkey": CentralDataset.HOTKEY } - response = requests.request(method, url, headers=headers) + params = { + "session": int(session), + "task_number": int(task_number) + } + response = requests.request(method, url, headers=headers, params=params) + if response.status_code != 200: raise Exception(f"Failed to get HTML: {response.status_code} {response.text}") bt.logging.info(f"HTML: {response.json()}") From 4930b40697416ff75a1d8250f41ee73d8a0c680a Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 8 Mar 2025 20:17:37 -0600 Subject: [PATCH 526/554] chore: remove log file --- ground_truth.html | 418 ---------------------------------------------- 1 file changed, 418 deletions(-) delete mode 100644 ground_truth.html diff --git a/ground_truth.html b/ground_truth.html deleted file mode 100644 index f2969654..00000000 --- a/ground_truth.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - - Welcome to Chambrer.com - Your Premier Wine Storage Solution - - - -
- - - - - - -
- -Chambrer Logo - -
- - - - - - - -
- - -
- - - - - - - - - - - - -
-
- -Chambrer Banner - -
-
- -
- Customer Support: 1-800-555-0199 -
- - support@chambrer.com - -
-
-

- - Q: Where can I purchase a Chambrer Wine Cellar? - -
- - Q: Are Chambrer Wine Cellars equipped with compressors? - -
- - Q: Do the Wine Cellars contain compressed gases like a standard refrigerator? - -
- - Q: Is the energy consumption of Chambrer Wine Cellars high? - -
- - Q: Why does the temperature display blink when adjusting settings? - -
- - Q: What does a steady display indicate? - -
- - Q: Is it normal for the fan to operate continuously? - -
- - Q: Why does the unit cycle on and off frequently? - -
- - Q: Can the light bulb be replaced if it burns out? - -
- - Q: Is it possible to recess my Chambrer Wine Cellar into cabinetry? - -
- - Q: What is the operational temperature range of the Chambrer Wine Cellar? - -
- - Q: Is regular cleaning of my Chambrer Wine Cellar necessary? - -
- - Q: Should the external fans of my Chambrer Wine Cellar be cleaned? - -
- - Q: How should the external fans of my Chambrer Wine Cellar be cleaned? - -
- - Q: How frequently should the external fans of my Chambrer Wine Cellar be cleaned? - -
-
-
-

-
-

- - - Q: Where can I purchase a Chambrer Wine Cellar? -

-

- A: Please - - reach out to us. - -

-

- - Return to top - -

-
-

- - - Q: Are Chambrer Wine Cellars equipped with compressors? -

-

- A: No, our wine cellars do not contain compressors. They utilize advanced thermal-electric technology with a Peltier System for cooling. -

-

- - Return to top - -

-
-
-
- - - - - - - - - -
- -
-
- - From cf88bd7b89563848bea7a944caeddb7c6108e460 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sat, 8 Mar 2025 20:21:44 -0600 Subject: [PATCH 527/554] chore: reconfig contest --- neurons/validators/genie_validator.py | 2 +- webgenie/challenges/challenge.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 0585c32e..8ec2712b 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -96,8 +96,8 @@ async def query_miners(self): available_challenges_classes = [ AccuracyChallenge, SeoChallenge, + AccuracyChallenge, SeoChallenge, - BalancedChallenge, ] with self.lock: diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py index 2eb885fc..c09c1b37 100644 --- a/webgenie/challenges/challenge.py +++ b/webgenie/challenges/challenge.py @@ -43,7 +43,7 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] seo_scores = scores[SEO_METRIC_NAME] - aggregated_scores = np.where(accuracy_scores > 0.7, seo_scores, 0) + aggregated_scores = np.where(accuracy_scores > 0.9, seo_scores, 0) return aggregated_scores, scores @@ -54,7 +54,7 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] quality_scores = scores[QUALITY_METRIC_NAME] - aggregated_scores = np.where(accuracy_scores > 0.7, quality_scores, 0) + aggregated_scores = np.where(accuracy_scores > 0.9, quality_scores, 0) return aggregated_scores, scores From 5498460da391dc982e6134342a79edbbe3cd5a15 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sun, 9 Mar 2025 01:04:52 -0600 Subject: [PATCH 528/554] feat: implement central dataset --- neurons/validators/genie_validator.py | 44 +++++++++++++-------- webgenie/constants.py | 2 +- webgenie/datasets/__init__.py | 3 +- webgenie/datasets/central_dataset.py | 55 ++++++++++++++++++++++++++ webgenie/tasks/image_task_generator.py | 9 +++-- webgenie/tasks/task_generator.py | 2 +- 6 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 webgenie/datasets/central_dataset.py diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index ced6075a..806d185d 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -27,6 +27,7 @@ SeoChallenge, BalancedChallenge, ) +from webgenie.datasets import CentralDataset from webgenie.helpers.htmls import preprocess_html, is_valid_resources from webgenie.helpers.images import image_debug_str from webgenie.helpers.llms import set_seed @@ -57,6 +58,17 @@ def __init__(self, neuron: BaseNeuron): self.task_generators = [ (ImageTaskGenerator(), 1.0), # currently only image task generator is supported ] + self.init_signature() + + def init_signature(self): + """Get signature for central database authentication using wallet""" + try: + message = b"I am the owner of the wallet" + CentralDataset.SIGNATURE = self.neuron.wallet.hotkey.sign(message).hex() + CentralDataset.HOTKEY = self.neuron.wallet.hotkey.ss58_address + except Exception as e: + bt.logging.error(f"Error initializing signature: {e}") + raise e async def query_miners(self): try: @@ -250,7 +262,7 @@ async def score(self): except Exception as e: bt.logging.error(f"Error storing results to database: {e}") - async def synthensize_task(self): + async def synthensize_task(self, session:int, task_index:int): try: with self.lock: if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: @@ -264,7 +276,7 @@ async def synthensize_task(self): weights=[weight for _, weight in self.task_generators], )[0] - task, synapse = await task_generator.generate_task() + task, synapse = await task_generator.generate_task(session=session, task_index=task_index) with self.lock: self.synthetic_tasks.append((task, synapse)) @@ -295,22 +307,22 @@ async def forward(self): return bt.logging.info(f"Forwarding task #{task_index} in session #{session}") - seed = self.get_seed(session, task_index) + # seed = self.get_seed(session, task_index) - bt.logging.info(f"Init random with seed: {seed}") - random.seed(seed) - set_seed(seed) - - while True: - try: - await self.synthensize_task() - break - except Exception as e: - bt.logging.error( - f"Error in synthensize_task: {e}" - f"Retrying..." - ) + # bt.logging.info(f"Init random with seed: {seed}") + # random.seed(seed) + # set_seed(seed) + try: + task, synapse = await self.synthensize_task(session, task_index) + task.task_id = f"{session}_{task_index}" + synapse.task_id = task.task_id + except Exception as e: + bt.logging.error( + f"Error in synthensize_task: {e}" + f"Retrying..." + ) + await self.query_miners() await self.score() except Exception as e: diff --git a/webgenie/constants.py b/webgenie/constants.py index 83a091a9..d25004a5 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.1.28" # version +__VERSION__ = "1.2.0" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) diff --git a/webgenie/datasets/__init__.py b/webgenie/datasets/__init__.py index 73e0323f..5e78b98b 100644 --- a/webgenie/datasets/__init__.py +++ b/webgenie/datasets/__init__.py @@ -1,4 +1,5 @@ from .dataset import Dataset, DatasetEntry from .synthetic_dataset import SyntheticDataset from .huggingface_dataset import HuggingfaceDataset -from .random_website_dataset import RandomWebsiteDataset \ No newline at end of file +from .random_website_dataset import RandomWebsiteDataset +from .central_dataset import CentralDataset \ No newline at end of file diff --git a/webgenie/datasets/central_dataset.py b/webgenie/datasets/central_dataset.py new file mode 100644 index 00000000..8f4fb867 --- /dev/null +++ b/webgenie/datasets/central_dataset.py @@ -0,0 +1,55 @@ +# https://huggingface.co/datasets/SALT-NLP/Design2Code_human_eval_pairwise + +import bittensor as bt +import random +import requests +from datasets import load_dataset + +from webgenie.datasets.dataset import Dataset, DatasetEntry + + +class CentralDataset(Dataset): + HOTKEY = "hotkey" + SIGNATURE = "signature" + + def __init__(self): + pass + + async def generate_context(self) -> DatasetEntry: + pass + + async def generate_context(self, **kwargs)->DatasetEntry: + try: + bt.logging.info("Generating Central context") + session = kwargs.get("session") + task_number = kwargs.get("task_number") + html = self.get_html(session, task_number) + return DatasetEntry( + src="central", + url=f"central_{session}_{task_number}", + ground_truth_html=html, + prompt="", + base64_image="" + ) + except Exception as e: + bt.logging.error(f"Error in generate_context: {e}") + raise e + + def get_html(self, session:int, task_number:int)->str: + bt.logging.info(f"Getting HTML for session {session} and task {task_number}") + method = "GET" + url = f"http://209.126.9.130:18000/api/v1/task/generate" + headers = { + "Signature": CentralDataset.SIGNATURE, + "Hotkey": CentralDataset.HOTKEY + } + params = { + "session": int(session), + "task_number": int(task_number) + } + response = requests.request(method, url, headers=headers, params=params) + + if response.status_code != 200: + raise Exception(f"Failed to get HTML: {response.status_code} {response.text}") + bt.logging.info(f"HTML: {response.json()}") + return response.json()["html"] diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 16a45130..ebfb5c1d 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -29,17 +29,18 @@ RandomWebsiteDataset, SyntheticDataset, HuggingfaceDataset, + CentralDataset, ) - class ImageTaskGenerator(TaskGenerator): def __init__(self): super().__init__() self.datasets = [ + (CentralDataset(), 1), #(RandomWebsiteDataset(), 1), #(SyntheticDataset(), 0.5), - (HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), 1), + #(HuggingfaceDataset(dataset_name="SALT-NLP/Design2Code-hf", split="train", html_column="text"), 1), ] self.metrics = { @@ -48,11 +49,11 @@ def __init__(self): QUALITY_METRIC_NAME: QualityReward(), } - async def generate_task(self) -> Tuple[Task, bt.Synapse]: + async def generate_task(self, **kwargs) -> Tuple[Task, bt.Synapse]: bt.logging.info("Generating Image task") dataset, _ = random.choices(self.datasets, weights=[weight for _, weight in self.datasets])[0] - dataset_entry = await dataset.generate_context() + dataset_entry = await dataset.generate_context(**kwargs) bt.logging.debug(f"Generated dataset entry: {dataset_entry.url}") ground_truth_html = preprocess_html(dataset_entry.ground_truth_html) diff --git a/webgenie/tasks/task_generator.py b/webgenie/tasks/task_generator.py index e9241842..4465a9cc 100644 --- a/webgenie/tasks/task_generator.py +++ b/webgenie/tasks/task_generator.py @@ -11,7 +11,7 @@ class TaskGenerator: def __init__(self): self.metrics: dict[str, Reward] = {} - async def generate_task(self) -> Tuple[Task, bt.Synapse]: + async def generate_task(self, **kwargs) -> Tuple[Task, bt.Synapse]: pass async def calculate_scores(self, task: Task, solutions: List[Solution]) -> dict[str, np.ndarray]: From 794de70f4d819536412bb4c4e226cdbf8bfede6f Mon Sep 17 00:00:00 2001 From: donbusha Date: Sun, 9 Mar 2025 01:06:27 -0600 Subject: [PATCH 529/554] chore: disable set_weights temporarily --- neurons/validators/validator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 5291f7f3..0382a9c3 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -148,6 +148,7 @@ def set_weights(self): self.print_weights(raw_weights) + return with self.lock: # Process the raw weights to final_weights via subtensor limitations. ( From a47cdcc0f1abb6fa2a9748478b2a62c893958a96 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sun, 9 Mar 2025 01:10:10 -0600 Subject: [PATCH 530/554] hotfix: fix kwarg errors --- neurons/validators/genie_validator.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 806d185d..575556da 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -262,7 +262,7 @@ async def score(self): except Exception as e: bt.logging.error(f"Error storing results to database: {e}") - async def synthensize_task(self, session:int, task_index:int): + async def synthensize_task(self, session:int, task_number:int): try: with self.lock: if len(self.synthetic_tasks) > MAX_SYNTHETIC_TASK_SIZE: @@ -276,7 +276,7 @@ async def synthensize_task(self, session:int, task_index:int): weights=[weight for _, weight in self.task_generators], )[0] - task, synapse = await task_generator.generate_task(session=session, task_index=task_index) + task, synapse = await task_generator.generate_task(session=session, task_number=task_number) with self.lock: self.synthetic_tasks.append((task, synapse)) @@ -286,36 +286,36 @@ async def synthensize_task(self, session:int, task_index:int): bt.logging.error(f"Error in synthensize_task: {e}") raise e - def get_seed(self, session: int, task_index: int, hash_cache: dict = {}) -> int: + def get_seed(self, session: int, task_number: int, hash_cache: dict = {}) -> int: if session not in hash_cache: session_start_block = session * SESSION_WINDOW_BLOCKS subtensor = self.neuron.subtensor block_hash = subtensor.get_block_hash(session_start_block) hash_cache[session] = int(block_hash[-15:], 16) - return int(hash_cache[session] + task_index) + return int(hash_cache[session] + task_number) async def forward(self): try: with self.lock: session = self.neuron.session if self.neuron.score_manager.current_session != session: - task_index = 0 + task_number = 0 else: - task_index = self.neuron.score_manager.number_of_tasks + task_number = self.neuron.score_manager.number_of_tasks - if task_index >= MAX_NUMBER_OF_TASKS_PER_SESSION: + if task_number >= MAX_NUMBER_OF_TASKS_PER_SESSION: return - bt.logging.info(f"Forwarding task #{task_index} in session #{session}") - # seed = self.get_seed(session, task_index) + bt.logging.info(f"Forwarding task #{task_number} in session #{session}") + # seed = self.get_seed(session, task_number) # bt.logging.info(f"Init random with seed: {seed}") # random.seed(seed) # set_seed(seed) try: - task, synapse = await self.synthensize_task(session, task_index) - task.task_id = f"{session}_{task_index}" + task, synapse = await self.synthensize_task(session, task_number) + task.task_id = f"{session}_{task_number}" synapse.task_id = task.task_id except Exception as e: bt.logging.error( From 1d2ef7649f866ce4848650d1e26762f925dcb7d3 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sun, 9 Mar 2025 01:12:25 -0600 Subject: [PATCH 531/554] fix: fix bugs func return params --- neurons/validators/genie_validator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 575556da..5cd3e7da 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -314,7 +314,8 @@ async def forward(self): # set_seed(seed) try: - task, synapse = await self.synthensize_task(session, task_number) + await self.synthensize_task(session, task_number) + task, synapse = self.synthetic_tasks[-1] task.task_id = f"{session}_{task_number}" synapse.task_id = task.task_id except Exception as e: From 188bc7619ad94ea6de81ae17df173fadc823fbd3 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sun, 9 Mar 2025 01:27:02 -0600 Subject: [PATCH 532/554] chore: remove conflit func --- webgenie/datasets/central_dataset.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/webgenie/datasets/central_dataset.py b/webgenie/datasets/central_dataset.py index 8f4fb867..e08b4365 100644 --- a/webgenie/datasets/central_dataset.py +++ b/webgenie/datasets/central_dataset.py @@ -14,9 +14,6 @@ class CentralDataset(Dataset): def __init__(self): pass - - async def generate_context(self) -> DatasetEntry: - pass async def generate_context(self, **kwargs)->DatasetEntry: try: From d3cded8a8e6dea6da575e56c40d9dce29c446dac Mon Sep 17 00:00:00 2001 From: donbusha Date: Sun, 9 Mar 2025 01:28:59 -0600 Subject: [PATCH 533/554] chore: remove conflict func --- webgenie/tasks/image_task_generator.py | 38 -------------------------- 1 file changed, 38 deletions(-) diff --git a/webgenie/tasks/image_task_generator.py b/webgenie/tasks/image_task_generator.py index 29803504..89786294 100644 --- a/webgenie/tasks/image_task_generator.py +++ b/webgenie/tasks/image_task_generator.py @@ -82,44 +82,6 @@ async def generate_task(self, **kwargs) -> Tuple[Task, bt.Synapse]: timeout=IMAGE_TASK_TIMEOUT, ) - return ( - image_task, - WebgenieImageSynapse(base64_image=base64_image, task_id=image_task.task_id), - ) - - async def generate_task(self, session:int, task_number:int)->Tuple[Task, bt.Synapse]: - bt.logging.info("Generating Image task") - - dataset, _ = random.choices(self.datasets, weights=[weight for _, weight in self.datasets])[0] - dataset_entry = await dataset.generate_context(session, task_number) - bt.logging.debug(f"Generated dataset entry: {dataset_entry.url}") - - ground_truth_html = preprocess_html(dataset_entry.ground_truth_html) - bt.logging.info(f"Preprocessed ground truth html") - if not ground_truth_html : - raise ValueError("Invalid ground truth html") - - if is_empty_html(ground_truth_html): - raise ValueError("Empty ground truth html") - - base64_image = await html_to_screenshot(ground_truth_html, page_load_time=GROUND_TRUTH_HTML_LOAD_TIME) - # Check image dimensions ratio - image = base64_to_image(base64_image) - width, height = image.size - aspect_ratio = height / width - if aspect_ratio > 7: # If height is more than 7x the width - raise ValueError(f"Image aspect ratio too extreme: {aspect_ratio:.2f}. Height should not exceed 7x width.") - - bt.logging.debug(f"Screenshot generated for {dataset_entry.url}") - image_task = ImageTask( - base64_image=base64_image, - ground_truth_html=ground_truth_html, - generator=self, - src=dataset_entry.src, - task_id=hashlib.sha256(dataset_entry.url.encode()).hexdigest(), - timeout=IMAGE_TASK_TIMEOUT, - ) - return ( image_task, WebgenieImageSynapse(base64_image=base64_image, task_id=image_task.task_id), From b3336f367f0e996555e5fca22b186acea6a765dc Mon Sep 17 00:00:00 2001 From: donbusha Date: Sun, 9 Mar 2025 01:30:03 -0600 Subject: [PATCH 534/554] doc: update version --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index d25004a5..b7e44865 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.2.0" # version +__VERSION__ = "1.2.1" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 6ee1748462dd7828e904a0165ab3dbb6ccc70a8b Mon Sep 17 00:00:00 2001 From: donbusha Date: Sun, 9 Mar 2025 01:46:42 -0600 Subject: [PATCH 535/554] hotfix: resolve thread issue --- neurons/validators/genie_validator.py | 2 +- neurons/validators/score_manager.py | 2 +- neurons/validators/validator.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/neurons/validators/genie_validator.py b/neurons/validators/genie_validator.py index 670b5701..71449877 100644 --- a/neurons/validators/genie_validator.py +++ b/neurons/validators/genie_validator.py @@ -50,7 +50,7 @@ class GenieValidator: def __init__(self, neuron: BaseNeuron): self.neuron = neuron - self.lock = threading.Lock() + self.lock = self.neuron.lock self.config = neuron.config self.miner_results = [] self.synthetic_tasks = [] diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 5f87ebe5..2b907ae7 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -22,7 +22,7 @@ class ScoreManager: def __init__(self, neuron: BaseNeuron): self.neuron = neuron self.state_path = self.neuron.config.neuron.full_path + "/state.npz" - self.lock = threading.Lock() + self.lock = self.neuron.lock self.hotkeys = copy.deepcopy(self.neuron.metagraph.hotkeys) self.current_session = -1 diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 24e381c8..b358dbd8 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -69,7 +69,7 @@ def __init__(self, config=None): self.query_miners_thread: Union[threading.Thread, None] = None self.score_thread: Union[threading.Thread, None] = None self.sync_thread: Union[threading.Thread, None] = None - self.lock = threading.Lock() + self.lock = threading.RLock() self.genie_validator = GenieValidator(neuron=self) self.score_manager = ScoreManager(neuron=self) From 6b23b365fa091cf38be78cfa396c1e478fc29834 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sun, 9 Mar 2025 05:14:37 -0500 Subject: [PATCH 536/554] chore: enable set weights --- neurons/validators/validator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index b358dbd8..3868ecb7 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -148,7 +148,6 @@ def set_weights(self): self.print_weights(raw_weights) - return with self.lock: # Process the raw weights to final_weights via subtensor limitations. ( From c2db4955060cc0c1de20763062ef4b49deaee939 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sun, 9 Mar 2025 05:15:32 -0500 Subject: [PATCH 537/554] chore: upgrade version --- webgenie/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/constants.py b/webgenie/constants.py index b7e44865..d1b35ec2 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.2.1" # version +__VERSION__ = "1.2.2" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 27f0cdd85e66432f45b4281f4dd5504e277550b4 Mon Sep 17 00:00:00 2001 From: donbusha Date: Sun, 16 Mar 2025 20:53:30 -0500 Subject: [PATCH 538/554] feat: implement dashboard --- .gitignore | 3 ++- neurons/validators/score_manager.py | 37 +++++++++++++++++++++++++- neurons/validators/validator.py | 11 ++++---- tests/test_submit_results.py | 40 +++++++++++++++++++++++++++++ uv.lock | 3 ++- webgenie/constants.py | 4 +++ webgenie/storage/__init__.py | 1 + 7 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 tests/test_submit_results.py diff --git a/.gitignore b/.gitignore index c024c3f4..e12b864a 100644 --- a/.gitignore +++ b/.gitignore @@ -191,4 +191,5 @@ run_validator.sh # developer doc developer_doc.md -debug_images/ \ No newline at end of file +debug_images/ +submit_results.py diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 2b907ae7..b4131c1f 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -17,7 +17,7 @@ MAX_UNANSWERED_TASKS ) from webgenie.helpers.weights import save_file_to_wandb - +from webgenie.storage import submit_results class ScoreManager: def __init__(self, neuron: BaseNeuron): self.neuron = neuron @@ -282,3 +282,38 @@ def save_session_result_to_file(self, session_upto: int): bt.logging.error(f"Error saving session result to file: {e}") raise e + def submit_results_to_dashboard(self, session_upto: int): + try: + session_result = self.session_results[session_upto] + + number_of_tasks = session_result["number_of_tasks"] + session = session_result["session"] + competition_type = session_result["competition_type"] + scores = session_result["scores"] + solved_tasks = session_result["solved_tasks"] + competition = { + "session_number": session, + "competition_type": competition_type, + } + + submissions = [] + avg_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) + for uid in range(self.neuron.metagraph.n): + if solved_tasks[uid] >= max(1, number_of_tasks - MAX_UNANSWERED_TASKS): + avg_scores[uid] = scores[uid] / solved_tasks[uid] + else: + avg_scores[uid] = 0 + submissions.append({ + "neuron": { + "hotkey": self.neuron.metagraph.hotkeys[uid], + }, + "score": avg_scores[uid], + }) + + submit_results({ + "competition": competition, + "submissions": submissions, + }) + except Exception as e: + bt.logging.error(f"Error submitting results to dashboard: {e}") + raise e diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 3868ecb7..323b82db 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -33,7 +33,10 @@ ) from webgenie.protocol import WebgenieTextSynapse, WebgenieImageSynapse from webgenie.rewards.lighthouse_reward import start_lighthouse_server_thread, stop_lighthouse_server -from webgenie.storage import send_challenge_to_stats_collector +from webgenie.storage import ( + send_challenge_to_stats_collector, + submit_results, +) from webgenie.utils.uids import get_validator_index from neurons.validators.genie_validator import GenieValidator @@ -183,11 +186,7 @@ def set_weights(self): with self.lock: self.score_manager.save_scores() self.score_manager.save_session_result_to_file(current_session-1) - try: - bt.logging.info(f"Sending challenge to stats collector for session {current_session-1}") - send_challenge_to_stats_collector(self.wallet, current_session-1) - except Exception as e: - bt.logging.error(f"Error sending challenge to stats collector: {e}") + self.score_manager.submit_results_to_dashboard(current_session-1) bt.logging.success("set_weights on chain successfully!") else: diff --git a/tests/test_submit_results.py b/tests/test_submit_results.py new file mode 100644 index 00000000..7648bc8c --- /dev/null +++ b/tests/test_submit_results.py @@ -0,0 +1,40 @@ +import dotenv +dotenv.load_dotenv(".env.validator") + +from webgenie.storage import submit_results + + +submit_results( + miner_submissions_request={ + "competition": { + "session_number": 12, + "competition_type": "seo_competition", + }, + "submissions": [ + { + "neuron":{ + "hotkey": "sdasfasdfd1234567890", + }, + "score": "0.92" + }, + { + "neuron":{ + "hotkey": "sdasfasdfd1234567890", + }, + "score": "0.91" + }, + { + "neuron":{ + "hotkey": "sdasfasdfd1234567890", + }, + "score": "0.90" + }, + { + "neuron":{ + "hotkey": "sdasfasdfd1234567890", + }, + "score": "0.89" + }, + ] + } +) diff --git a/uv.lock b/uv.lock index 17ac3b72..a6c33de5 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,5 @@ version = 1 +revision = 1 requires-python = ">=3.12.4" resolution-markers = [ "sys_platform == 'darwin'", @@ -3208,7 +3209,7 @@ wheels = [ [[package]] name = "web-genie-ai" -version = "1.1.7" +version = "1.1.20" source = { virtual = "." } dependencies = [ { name = "ansible-vault" }, diff --git a/webgenie/constants.py b/webgenie/constants.py index d1b35ec2..c64bbf2e 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -98,3 +98,7 @@ (psutil.virtual_memory().total) // (1024 * 1024 * 1024 * 4) ) ) + +DASHBOARD_BACKEND_URL = os.getenv("DASHBOARD_BACKEND_URL", "http://209.126.9.130:19000") # dashboard backend url + +API_TOKEN = os.getenv("API_TOKEN", "api_token") # api token diff --git a/webgenie/storage/__init__.py b/webgenie/storage/__init__.py index 3967d0a3..c539bf5e 100644 --- a/webgenie/storage/__init__.py +++ b/webgenie/storage/__init__.py @@ -2,4 +2,5 @@ store_results_to_database, send_challenge_to_stats_collector, ) +from .submit_results import submit_results From 8b09909bb9e1975158ff352389d658fae5424b3c Mon Sep 17 00:00:00 2001 From: donbusha Date: Mon, 17 Mar 2025 05:36:08 -0500 Subject: [PATCH 539/554] hotfix: fix bugs --- neurons/validators/score_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index b4131c1f..de765024 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -292,7 +292,7 @@ def submit_results_to_dashboard(self, session_upto: int): scores = session_result["scores"] solved_tasks = session_result["solved_tasks"] competition = { - "session_number": session, + "session_number": session_upto, "competition_type": competition_type, } From 76a97ac2edfd2a2dd8d84d31e3fbf20889b6b382 Mon Sep 17 00:00:00 2001 From: donbusha Date: Mon, 17 Mar 2025 05:37:31 -0500 Subject: [PATCH 540/554] hotfix: fix bugs --- neurons/validators/score_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index de765024..b4131c1f 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -292,7 +292,7 @@ def submit_results_to_dashboard(self, session_upto: int): scores = session_result["scores"] solved_tasks = session_result["solved_tasks"] competition = { - "session_number": session_upto, + "session_number": session, "competition_type": competition_type, } From e07bcf198cf6df0fbfb6d7f906536ac113d6e987 Mon Sep 17 00:00:00 2001 From: donbusha Date: Mon, 17 Mar 2025 09:15:53 -0500 Subject: [PATCH 541/554] fix: fix json serialize error --- neurons/validators/score_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index b4131c1f..77734bd0 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -292,7 +292,7 @@ def submit_results_to_dashboard(self, session_upto: int): scores = session_result["scores"] solved_tasks = session_result["solved_tasks"] competition = { - "session_number": session, + "session_number": int(session), "competition_type": competition_type, } @@ -307,7 +307,7 @@ def submit_results_to_dashboard(self, session_upto: int): "neuron": { "hotkey": self.neuron.metagraph.hotkeys[uid], }, - "score": avg_scores[uid], + "score": float(avg_scores[uid]), }) submit_results({ From 18741f583eed15b3f6a8f76dd50809d45f23c23f Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 17 Mar 2025 11:34:29 -0500 Subject: [PATCH 542/554] chore: update dependencies --- pyproject.toml | 9 +- uv.lock | 233 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 196 insertions(+), 46 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a463ba5c..a45a260e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,12 +6,12 @@ readme = "README.md" requires-python = ">=3.12.4" dependencies = [ "ansible-vault==2.1.0", - "async-substrate-interface==1.0.0", + "async-substrate-interface==1.0.3", "beautifulsoup4==4.12.3", "bert-score==0.3.13", "bt-decode==0.5.0a2", - "bittensor==9.0.0", - "bittensor-cli==9.0.0", + "bittensor==9.0.3", + "bittensor-cli==9.0.3", "clip", "datasets==3.2.0", "ddt==1.6.0", @@ -26,7 +26,7 @@ dependencies = [ "peft", "pip-chill==1.0.3", "playwright==1.49.1", - "pydantic==2.6", + "pydantic==2.10.6", "python-dotenv==1.0.1", "scikit-learn==1.6.0", "shtab==1.6.5", @@ -40,6 +40,7 @@ dependencies = [ "tensorflow>=2.18.0", "tf-keras", "uvicorn", + "bt-ddos-shield-client>=0.1.3", ] [project.urls] diff --git a/uv.lock b/uv.lock index a6c33de5..ba37851a 100644 --- a/uv.lock +++ b/uv.lock @@ -188,7 +188,7 @@ wheels = [ [[package]] name = "async-substrate-interface" -version = "1.0.0" +version = "1.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asyncstdlib" }, @@ -199,9 +199,9 @@ dependencies = [ { name = "wheel" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/ce/34b012bfd118eb58c6dfd29ea30db4424bbe3f7243460768a69ad94c6ab9/async_substrate_interface-1.0.0.tar.gz", hash = "sha256:1b045fa9fb4077bd20dbe5e670efe1a7346a50db1a126380c9fa4e32b03cb5de", size = 59686 } +sdist = { url = "https://files.pythonhosted.org/packages/da/d7/71421a4f2ecb82a0141e61dab83770c0f9b2257543ad09f8a7eb4ed3017e/async_substrate_interface-1.0.3.tar.gz", hash = "sha256:da944b42aad508a6681a58b3223801013fbabc2f3d63c1e599eada4ecb53a2e9", size = 61528 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/8b/f3a1281f9b378da724cf5350ab48cf3e685e5682c3b847b334319ce094a2/async_substrate_interface-1.0.0-py3-none-any.whl", hash = "sha256:1df2107e221559c5127e4673c55c42db310bf72a344a7da0aaaa4cde400d28c0", size = 63085 }, + { url = "https://files.pythonhosted.org/packages/5d/a9/6c36e68b0b7cce2ff2a35a3a76c19e0dd2a1a6dedcf72189f6dd52b5d4a3/async_substrate_interface-1.0.3-py3-none-any.whl", hash = "sha256:d51db230fa0c0de6f9960089faad806da773c7500727096a2659c0fd398edc92", size = 64397 }, ] [[package]] @@ -273,13 +273,12 @@ wheels = [ [[package]] name = "bittensor" -version = "9.0.0" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "async-substrate-interface" }, { name = "asyncstdlib" }, - { name = "bittensor-cli" }, { name = "bittensor-commit-reveal" }, { name = "bittensor-wallet" }, { name = "colorama" }, @@ -304,14 +303,14 @@ dependencies = [ { name = "websockets" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/e4/8aea55941376f752f5a8074e5b3531579c0686d78c7f9628d92ef65542c7/bittensor-9.0.0.tar.gz", hash = "sha256:64321a5f3acc07dbde41ecb81d753f05d0a8fcd19a22f35cb56e5af8e15e81aa", size = 223046 } +sdist = { url = "https://files.pythonhosted.org/packages/66/51/b05459ff0f8910c7617cd098d0917029015a70613757941163a22d514e1e/bittensor-9.0.3.tar.gz", hash = "sha256:54b13149d86c3de5e71b587e8474e49dffbb4ee864fb66f27e1efdc9e107504b", size = 223737 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/d5/46f94a11ff179e291b42f9cb770f6cb0facdc8480f8d6326548a7a70a7e9/bittensor-9.0.0-py3-none-any.whl", hash = "sha256:1f5e97d2333b46c535b03e780b7e3d58204e2ed116db6244227433449f6616cc", size = 264260 }, + { url = "https://files.pythonhosted.org/packages/ab/55/03618fe9200575828401433d93f23037b9f8522d0e1d8018eda95d7f0bd8/bittensor-9.0.3-py3-none-any.whl", hash = "sha256:00d46eb2a61bb06f9c5f0a5ad551f8380db7e3c28441a39899f284b96edccbe6", size = 264616 }, ] [[package]] name = "bittensor-cli" -version = "9.0.0" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -337,9 +336,9 @@ dependencies = [ { name = "websockets" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/7a/d605c7ea004d0f789da0eaf96a96b0ea0f5e75a7843c5c54979cda59a5dc/bittensor-cli-9.0.0.tar.gz", hash = "sha256:f775c24b6acf574c4b64d009d8728f2657c2894fecb15385478fd8ac4501f0fd", size = 178898 } +sdist = { url = "https://files.pythonhosted.org/packages/a9/65/a3f4db7e4474e27c81f061c6e8c6aec0109c4c4cc1c3c326dc4309e86ddf/bittensor-cli-9.0.3.tar.gz", hash = "sha256:6fa14b18276c34dd30d06a0bf26b1e6e78cb33cbf332de9453d9d8557ee868f9", size = 181447 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/3b/468ed436a02b242197643979e829fa2655f660037e97baf0d036b1611641/bittensor_cli-9.0.0-py3-none-any.whl", hash = "sha256:211334018e878dd4145d1b7998630edd2e3a486ac4e992493b535306e3d0c178", size = 191161 }, + { url = "https://files.pythonhosted.org/packages/28/41/3de43d1897904ace9153bb71d9cb137050a2b6253548b10b8acaed898fff/bittensor_cli-9.0.3-py3-none-any.whl", hash = "sha256:171d2cc2b7ff4d9563376ba00aa0310e8dbb7e3d327bf6cb97e2b1a0bb215a14", size = 193851 }, ] [[package]] @@ -360,7 +359,7 @@ wheels = [ [[package]] name = "bittensor-wallet" -version = "3.0.3" +version = "3.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, @@ -371,16 +370,63 @@ dependencies = [ { name = "rich" }, { name = "termcolor" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a7/fd/d795b144a50e9c213d380d75cce937c6b4d0f0b26f7aac2f80e619986090/bittensor_wallet-3.0.3.tar.gz", hash = "sha256:60e534e1f1c256e4dac788acac4724ec92c445c24058954b7e12a3aca6828e1b", size = 73104 } +sdist = { url = "https://files.pythonhosted.org/packages/5d/dc/a0583fd044f93bd19c23253c245fa83bdd28f225f25e3a171c16f0b64f45/bittensor_wallet-3.0.4.tar.gz", hash = "sha256:f105239b7b290998dcc0b59260507aa69f6f287dbe2cb814e32b985fbe84e7a3", size = 73529 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/14/dc3b8216ceb9dcdb30b9a36ec68bcf6e5c6eb2414216f74d6ea65aeee29c/bittensor_wallet-3.0.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e5993a25d1ed3d640502fa141f33e10120cc779599d27c52f8035d8494c339de", size = 822727 }, + { url = "https://files.pythonhosted.org/packages/a5/40/4122434df63d16fa1c7bfd138021fd1eec3c139a59913735205a567127bc/bittensor_wallet-3.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9101ba84f00b38249766d5516bd40d21eb01f0ab44921320003bcc985c89ce7b", size = 771578 }, + { url = "https://files.pythonhosted.org/packages/01/09/7a2c8642a946714ecdc92336441ae7e71377e6953a260e0741ef7237dedf/bittensor_wallet-3.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76c985b53a03b36396153926cf88239aeca0cd3da778f18d15cb7be2da8e66", size = 3168611 }, + { url = "https://files.pythonhosted.org/packages/38/3f/bbdf0cf96e319c8cac6a58e7b66d81b364aeed6e29c5596a8b5c20a2deb4/bittensor_wallet-3.0.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05e1fa04de5636686504e2bb700811dc5f0d826888b7783a400c384218048064", size = 2971398 }, + { url = "https://files.pythonhosted.org/packages/b1/2b/f892285d13ee80cd96fd644208d75d6eea0e7dc1fdb666a91d21b9ad5ed7/bittensor_wallet-3.0.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:45d22e7a055013c94a16301268877763e1668d6bf52378b857c792cca7cd6e72", size = 822242 }, + { url = "https://files.pythonhosted.org/packages/d4/c6/a7d284d06bebb2913d5c25c6106b39241495e18ab2308db3952e66f86211/bittensor_wallet-3.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cdb0148d75565654da9f867154add84d5f29d139e69dafc63aa88477fb656ff6", size = 771310 }, + { url = "https://files.pythonhosted.org/packages/ed/f0/d23459b06ed5df88e5be78435f5e12903d9516d19dbea913fef6581c2322/bittensor_wallet-3.0.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cb3f12ee2ff73a43f0310abae23db782803789ed960328a50398b139a95a667", size = 3167976 }, + { url = "https://files.pythonhosted.org/packages/d9/bc/d0fd995cef20247fa944d98942a3de65da34ffadd7d802fef553b5de2700/bittensor_wallet-3.0.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a349b0b9d95e3e2c812414422f55e5dd8c63810d94b0d286ec1f55c5875bff4", size = 2970290 }, +] + +[[package]] +name = "boto3" +version = "1.36.26" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/af/2082fde2cbd81f8b60fd46e3ac07a0f841abfdb9818b818d560e42b5c444/boto3-1.36.26.tar.gz", hash = "sha256:523b69457eee55ac15aa707c0e768b2a45ca1521f95b2442931090633ec72458", size = 111027 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/88/20811ef592a1222ff0c75e0bb03e2a117ff12068eb81562eea5f0b1b121c/bittensor_wallet-3.0.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:5e13e08a1e632dcc0a888a85ba15d9beda056ccd22c756ef7419e6d4cea85d32", size = 821469 }, - { url = "https://files.pythonhosted.org/packages/fa/97/d23b678bbbbbbbb7b604219e27748b92a4547b30aefa31564849a1c84192/bittensor_wallet-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7ff0f1bd4dc6f39000a0e70f23bf3bf2830161f0e6ab49a94969a51410962867", size = 771262 }, - { url = "https://files.pythonhosted.org/packages/b4/e2/65894b7c2823f195f6efb53170e155e2bfb156759c06de629eda8513ad17/bittensor_wallet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27a6c6eadb1e96ec3b79a12852c95100e42d4d30c04ca8f19a686ecc60310b5f", size = 3166578 }, - { url = "https://files.pythonhosted.org/packages/46/6b/8236cbd22468b05634bf6467873c8add24223b0aaf74eb843202a445e034/bittensor_wallet-3.0.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fd40bfd51f479747b5264595f6b55ab389b97556040d9b6d4c734eb9bfcc997d", size = 2969786 }, - { url = "https://files.pythonhosted.org/packages/e6/a1/33d9cc74c6704ec62de4ea38925169e9d402e463281b3edbf3ca57a86e04/bittensor_wallet-3.0.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:c8bf959e10a4e1d37f0a00d426475cf9633181a5d061cd65500b3bf5277e9263", size = 821084 }, - { url = "https://files.pythonhosted.org/packages/2c/6f/a2e5a168c7da313fb48ab9135765bcf8951e37d865e015ac93ffcab72e24/bittensor_wallet-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:252efa39b183b1a1de55a86789d8907c422b35d70212259c52519c31871d87af", size = 770756 }, - { url = "https://files.pythonhosted.org/packages/d3/9e/bf23a22faeb02f7e522dac8bc4b60817dc57c6dc413c59162dfda0a56259/bittensor_wallet-3.0.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3fbcda8641a4ecc964b02d418d7e4ef5e345919fa072c1b1e66209e119a9743", size = 3165814 }, - { url = "https://files.pythonhosted.org/packages/fc/6c/56beccb193072687275dd2e64e8e4602134a9d0586d364956981864df933/bittensor_wallet-3.0.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6dc4a4b268004e5b3b93884a9344e2154b2b09f9e0816c3c0237032b63eda753", size = 2969128 }, + { url = "https://files.pythonhosted.org/packages/b1/a7/9081049e432f5130c6bd4d86f4db7a7729f812ebceda59baff69a06b19a5/boto3-1.36.26-py3-none-any.whl", hash = "sha256:f67d014a7c5a3cd540606d64d7cb9eec3600cf42acab1ac0518df9751ae115e2", size = 139178 }, +] + +[[package]] +name = "botocore" +version = "1.36.26" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/db/caa8778cf98ecbe0ad0efd7fbf673e2d036373386582e15dffff80bf16e1/botocore-1.36.26.tar.gz", hash = "sha256:4a63bcef7ecf6146fd3a61dc4f9b33b7473b49bdaf1770e9aaca6eee0c9eab62", size = 13574958 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/0c/a3eeca35b22ac8f441d412881582a5f3b8665de0269baf9fdeb8e86d7f1c/botocore-1.36.26-py3-none-any.whl", hash = "sha256:4e3f19913887a58502e71ef8d696fe7eaa54de7813ff73390cd5883f837dfa6e", size = 13360675 }, +] + +[[package]] +name = "bt-ddos-shield-client" +version = "0.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bittensor" }, + { name = "boto3" }, + { name = "eciespy" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-dotenv" }, + { name = "route53" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/9f/13edb2546caaf7d6266ea403ca7a053204073b3da37bf17407778d9d2a94/bt_ddos_shield_client-0.1.3.tar.gz", hash = "sha256:43ef9a0739ac87f4b8493bd0065a04ada8af945134ba3efd5c5396618358d0ad", size = 45474 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/bb/cd27b012e7562232d975a5dc4ed86cc343789cfabd7784bf4d741d275941/bt_ddos_shield_client-0.1.3-py3-none-any.whl", hash = "sha256:0fe26640404dadaa9c12fa463d142d76bb5b591cabf461ea2e6d217c4e4e62e0", size = 36890 }, ] [[package]] @@ -532,6 +578,34 @@ dependencies = [ { name = "tqdm" }, ] +[[package]] +name = "coincurve" +version = "21.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/a2/f2a38eb05b747ed3e54e1be33be339d4a14c1f5cc6a6e2b342b5e8160d51/coincurve-21.0.0.tar.gz", hash = "sha256:8b37ce4265a82bebf0e796e21a769e56fdbf8420411ccbe3fafee4ed75b6a6e5", size = 128986 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/61/a2d9e109f99b6f5e65e653ac998b0944c5b82c568ac142fcbb381a4803be/coincurve-21.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f60ad56113f08e8c540bb89f4f35f44d434311433195ffff22893ccfa335070c", size = 1391948 }, + { url = "https://files.pythonhosted.org/packages/24/5a/2da75ee00a722ef1fa068ada3bc34c564595ead86fef573434e2f0cb0a5c/coincurve-21.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1cb1cd19fb0be22e68ecb60ad950b41f18b9b02eebeffaac9391dc31f74f08f2", size = 1384958 }, + { url = "https://files.pythonhosted.org/packages/dc/50/6bf0bf7e8a9a9dd419ecc1e479dcb9fbfe657029276ad703806a25a2bef2/coincurve-21.0.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05d7e255a697b3475d7ae7640d3bdef3d5bc98ce9ce08dd387f780696606c33b", size = 1606576 }, + { url = "https://files.pythonhosted.org/packages/bd/ab/9e89908fdd09ad522938085587aaa821b022f4def16c286c5580cfc85811/coincurve-21.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a366c314df7217e3357bb8c7d2cda540b0bce180705f7a0ce2d1d9e28f62ad4", size = 1613642 }, + { url = "https://files.pythonhosted.org/packages/b7/75/050b6fd08978de85a7b480f0f220ab6a30967c0910119f3096a8dd40befc/coincurve-21.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b04778b75339c6e46deb9ae3bcfc2250fbe48d1324153e4310fc4996e135715", size = 1616974 }, + { url = "https://files.pythonhosted.org/packages/d7/62/2740ba0cafebf45708633635fecadcbe582d7a3ed1ce8b4637921feceaf8/coincurve-21.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8efcbdcd50cc219989a2662e6c6552f455efc000a15dd6ab3ebf4f9b187f41a3", size = 1644133 }, + { url = "https://files.pythonhosted.org/packages/94/14/1f27c3048c4084fa85ef65f42a4ca631f2b184336e6d9446fecec20e0a7f/coincurve-21.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6df44b4e3b7acdc1453ade52a52e3f8a5b53ecdd5a06bd200f1ec4b4e250f7d9", size = 1619918 }, + { url = "https://files.pythonhosted.org/packages/ca/22/7ec3ec4c8e7764daa25767d6674cb5741ea2d9b39ff758e9918d22a4b49b/coincurve-21.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bcc0831f07cb75b91c35c13b1362e7b9dc76c376b27d01ff577bec52005e22a8", size = 1645797 }, + { url = "https://files.pythonhosted.org/packages/fb/60/87982b7499943ab12605df7b14f6001fff331aca0881b260682461e2309d/coincurve-21.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:5dd7b66b83b143f3ad3861a68fc0279167a0bae44fe3931547400b7a200e90b1", size = 1329255 }, + { url = "https://files.pythonhosted.org/packages/62/c0/65b60b371579570931daca8a3f67debfc1482908b8ed03432297274a27da/coincurve-21.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:78dbe439e8cb22389956a4f2f2312813b4bd0531a0b691d4f8e868c7b366555d", size = 1325973 }, + { url = "https://files.pythonhosted.org/packages/b3/40/cce55adaec37a588eb24b67da8eb68926546458e12ed2c4c2a21deb93d4c/coincurve-21.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9df5ceb5de603b9caf270629996710cf5ed1d43346887bc3895a11258644b65b", size = 1391762 }, + { url = "https://files.pythonhosted.org/packages/ca/7a/628a30281d246ce98aea56592e0c8e79b03a93ee8b85d688db3388130c2d/coincurve-21.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:154467858d23c48f9e5ab380433bc2625027b50617400e2984cc16f5799ab601", size = 1384921 }, + { url = "https://files.pythonhosted.org/packages/61/cc/719c5da31e6ba07e438abcf962f7a365eb69a06a0621ca4f2a484f344e09/coincurve-21.0.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f57f07c44d14d939bed289cdeaba4acb986bba9f729a796b6a341eab1661eedc", size = 1606559 }, + { url = "https://files.pythonhosted.org/packages/b2/ee/dd14237013d732e7fc3248c0c33a1d36b88b5378dfa3e624a50a23fb6f19/coincurve-21.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fb03e3a388a93d31ed56a442bdec7983ea404490e21e12af76fb1dbf097082a", size = 1613684 }, + { url = "https://files.pythonhosted.org/packages/f0/05/eaa7f36a03376ced1c19e0cb563341cc83fe48f5734b2effe8f16d0ee0ab/coincurve-21.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d09ba4fd9d26b00b06645fcd768c5ad44832a1fa847ebe8fb44970d3204c3cb7", size = 1617001 }, + { url = "https://files.pythonhosted.org/packages/39/32/fc75f1dd914ac95eb2704425c7ca1a9f509f982e15d05e0ca895b9e6ea9c/coincurve-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1a1e7ee73bc1b3bcf14c7b0d1f44e6485785d3b53ef7b16173c36d3cefa57f93", size = 1643924 }, + { url = "https://files.pythonhosted.org/packages/1a/4b/8c6e65b5755e26fc02077803879747615c1c327047328d1784bccb4ff4c3/coincurve-21.0.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ad05952b6edc593a874df61f1bc79db99d716ec48ba4302d699e14a419fe6f51", size = 1619964 }, + { url = "https://files.pythonhosted.org/packages/64/bc/d0a743305ff9fa26e72b4c77b534d5958ec8030b3772555a7172a0c134e5/coincurve-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d2bf350ced38b73db9efa1ff8fd16a67a1cb35abb2dda50d89661b531f03fd3", size = 1645526 }, + { url = "https://files.pythonhosted.org/packages/9d/44/ab082e2dc8c9a45774f1bb9961f58b43c0882b866f5c469ead932d45a35d/coincurve-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:54d9500c56d5499375e579c3917472ffcf804c3584dd79052a79974280985c74", size = 1329285 }, + { url = "https://files.pythonhosted.org/packages/f3/94/407f6fc811310f15b1fc7255f436f6a9040854213beeb10093f56b5b7fd3/coincurve-21.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:773917f075ec4b94a7a742637d303a3a082616a115c36568eb6c873a8d950d18", size = 1326027 }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -756,6 +830,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/8f/ee72af555cd58feb928ff0fd3977913f4ecd0ce8ad92cf4031c36de91776/duckduckgo_search-7.2.1-py3-none-any.whl", hash = "sha256:72ebbf6ad8759e3c3c79521cd66256e7a4ac741c522fd9342db94de91745ef87", size = 19720 }, ] +[[package]] +name = "eciespy" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coincurve" }, + { name = "pycryptodome" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/2f/fc9837edd0e7c1701f7675bfdf60141d01e336abb5793996f4a48fa7c4f7/eciespy-0.4.4.tar.gz", hash = "sha256:dfd3832314a8c2ba9c8b7c1b79990f5849fb29e7ad1fd842e17984ac4b1bec11", size = 9464 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/6c/44e3777272a352e0c725b49d0566c4c215d0e1b9d3a5f59c1f6a00933650/eciespy-0.4.4-py3-none-any.whl", hash = "sha256:5f09954122b83afadf34d317b72d3c61dec0daecda94617cc3274d11c8b563af", size = 11468 }, +] + [[package]] name = "einops" version = "0.8.0" @@ -1190,6 +1277,15 @@ wheels = [ { 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 = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256 }, +] + [[package]] name = "joblib" version = "1.4.2" @@ -2245,40 +2341,68 @@ wheels = [ [[package]] name = "pydantic" -version = "2.6.0" +version = "2.10.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/6c/87e7c6e46206e27b3037acdf637906c4be500a0b1dd7ccbb805a72b9f494/pydantic-2.6.0.tar.gz", hash = "sha256:ae887bd94eb404b09d86e4d12f93893bdca79d766e738528c6fa1c849f3c6bcf", size = 677208 } +sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/37/3ffe6e7daa1ea1b4bf5228807a92ccbae538cf57c0c50b93564c310c11a8/pydantic-2.6.0-py3-none-any.whl", hash = "sha256:1440966574e1b5b99cf75a13bec7b20e3512e8a61b894ae252f56275e2c465ae", size = 394201 }, + { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, ] [[package]] name = "pydantic-core" -version = "2.16.1" +version = "2.27.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/a7/61d013c73773bb03d02de9de8e4e5b2ed2c100dc98ae7046d54485ecf5d4/pydantic_core-2.16.1.tar.gz", hash = "sha256:daff04257b49ab7f4b3f73f98283d3dbb1a65bf3500d55c7beac3c66c310fe34", size = 368201 } +sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, + { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, + { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, + { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, + { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, + { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, + { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, + { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, + { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, + { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, + { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, + { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, + { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, + { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, + { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, + { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, + { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, + { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, + { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, + { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, + { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, + { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, + { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, + { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, + { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, + { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, + { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, + { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, +] + +[[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/82/8f/d83953f652b0048fd8be62b6eabed7e3397008b6d050bd080ab78d3e6d14/pydantic_core-2.16.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f478ec204772a5c8218e30eb813ca43e34005dff2eafa03931b3d8caef87d51", size = 1868275 }, - { url = "https://files.pythonhosted.org/packages/c1/33/f627f1d31f7986ade7396237a8b5904c629837878978e9eeb400f85b3e29/pydantic_core-2.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1936ef138bed2165dd8573aa65e3095ef7c2b6247faccd0e15186aabdda7f66", size = 1718240 }, - { url = "https://files.pythonhosted.org/packages/f5/7e/1bcd8ce164868c40d841528f92e5f1f5a1a6cb705a063c425cd00f8b1eef/pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d3a433ef5dc3021c9534a58a3686c88363c591974c16c54a01af7efd741f13", size = 1873433 }, - { url = "https://files.pythonhosted.org/packages/32/aa/f5d9139609e30a6f174c6d6c8f3f64aafaf6f43dab7974b8642a10d08758/pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd88f40f2294440d3f3c6308e50d96a0d3d0973d6f1a5732875d10f569acef49", size = 1875493 }, - { url = "https://files.pythonhosted.org/packages/39/25/46cef345a191d8d6c6458420029ce25edfbe96833075d3a474ae2aeae106/pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fac641bbfa43d5a1bed99d28aa1fded1984d31c670a95aac1bf1d36ac6ce137", size = 2046585 }, - { url = "https://files.pythonhosted.org/packages/62/2d/2c9af3e66486b7159ab2f05712e2d46fc7ee29e2cbd7f4e5e1feaac48e12/pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72bf9308a82b75039b8c8edd2be2924c352eda5da14a920551a8b65d5ee89253", size = 3077749 }, - { url = "https://files.pythonhosted.org/packages/86/61/55607ffc05fc1caac9b6754552bc907c4af469f5585cb3599aa855865923/pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb4363e6c9fc87365c2bc777a1f585a22f2f56642501885ffc7942138499bf54", size = 2179456 }, - { url = "https://files.pythonhosted.org/packages/2b/64/383663f04e58333fe15b30da863fe28a76f00b676dded6f4b6f1c23fc9c5/pydantic_core-2.16.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:20f724a023042588d0f4396bbbcf4cffd0ddd0ad3ed4f0d8e6d4ac4264bae81e", size = 1953184 }, - { url = "https://files.pythonhosted.org/packages/be/cd/3a975ba2bd4493a5bae904891d5239fa3e8dd59be39b1845d3361e1a412e/pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fb4370b15111905bf8b5ba2129b926af9470f014cb0493a67d23e9d7a48348e8", size = 2052616 }, - { url = "https://files.pythonhosted.org/packages/ac/5e/b26ef5ba2266b4dd6fd93964c1923f32032034100cfed167f2f85df7a3da/pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23632132f1fd608034f1a56cc3e484be00854db845b3a4a508834be5a6435a6f", size = 2172342 }, - { url = "https://files.pythonhosted.org/packages/39/81/fce7fcd7f877ab56c2e677366da41abdd3071dcc6592354bc96f0135c43a/pydantic_core-2.16.1-cp312-none-win32.whl", hash = "sha256:b9f3e0bffad6e238f7acc20c393c1ed8fab4371e3b3bc311020dfa6020d99212", size = 1764252 }, - { url = "https://files.pythonhosted.org/packages/ab/de/32c35c9d84652da17673357295d19016cc4768fea0dd071d8c09ca4cacbb/pydantic_core-2.16.1-cp312-none-win_amd64.whl", hash = "sha256:a0b4cfe408cd84c53bab7d83e4209458de676a6ec5e9c623ae914ce1cb79b96f", size = 1881165 }, - { url = "https://files.pythonhosted.org/packages/61/db/78cafc630e4b3193c5a702ae20916349f5f0ef5bdaa918565549f3160059/pydantic_core-2.16.1-cp312-none-win_arm64.whl", hash = "sha256:d195add190abccefc70ad0f9a0141ad7da53e16183048380e688b466702195dd", size = 1853531 }, + { url = "https://files.pythonhosted.org/packages/b4/46/93416fdae86d40879714f72956ac14df9c7b76f7d41a4d68aa9f71a0028b/pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd", size = 29718 }, ] [[package]] @@ -2543,6 +2667,29 @@ 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 = "route53" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "pytz" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/8e/6a2801012c000f81580ff6a6d69d5c5ad136589f4ed306f7b0bb8e63c5fe/route53-1.0.1.tar.gz", hash = "sha256:8e08a1c575bac3adc9288f9b10a35b0ff54eced79fc67ceface72a91080e9baa", size = 17839 } + +[[package]] +name = "s3transfer" +version = "0.11.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/24/1390172471d569e281fcfd29b92f2f73774e95972c965d14b6c802ff2352/s3transfer-0.11.3.tar.gz", hash = "sha256:edae4977e3a122445660c7c114bba949f9d191bae3b34a096f18a1c8c354527a", size = 148042 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/81/48c41b554a54d75d4407740abb60e3a102ae416284df04d1dbdcbe3dbf24/s3transfer-0.11.3-py3-none-any.whl", hash = "sha256:ca855bdeb885174b5ffa95b9913622459d4ad8e331fc98eb01e6d5eb6a30655d", size = 84246 }, +] + [[package]] name = "safetensors" version = "0.5.2" @@ -3218,6 +3365,7 @@ dependencies = [ { name = "bert-score" }, { name = "bittensor" }, { name = "bittensor-cli" }, + { name = "bt-ddos-shield-client" }, { name = "bt-decode" }, { name = "clip" }, { name = "colormath" }, @@ -3251,11 +3399,12 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "ansible-vault", specifier = "==2.1.0" }, - { name = "async-substrate-interface", specifier = "==1.0.0" }, + { name = "async-substrate-interface", specifier = "==1.0.3" }, { name = "beautifulsoup4", specifier = "==4.12.3" }, { name = "bert-score", specifier = "==0.3.13" }, - { name = "bittensor", specifier = "==9.0.0" }, - { name = "bittensor-cli", specifier = "==9.0.0" }, + { name = "bittensor", specifier = "==9.0.3" }, + { name = "bittensor-cli", specifier = "==9.0.3" }, + { name = "bt-ddos-shield-client", specifier = ">=0.1.3" }, { name = "bt-decode", specifier = "==0.5.0a2" }, { name = "clip", git = "https://github.com/openai/CLIP.git" }, { name = "colormath", specifier = ">=3.0.0" }, @@ -3273,7 +3422,7 @@ requires-dist = [ { name = "peft" }, { name = "pip-chill", specifier = "==1.0.3" }, { name = "playwright", specifier = "==1.49.1" }, - { name = "pydantic", specifier = "==2.6" }, + { name = "pydantic", specifier = "==2.10.6" }, { name = "python-dotenv", specifier = "==1.0.1" }, { name = "scikit-image", specifier = ">=0.25.0" }, { name = "scikit-learn", specifier = "==1.6.0" }, From cf4fb5b6fc552c98adadfe85f6300f8470faae09 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 17 Mar 2025 11:37:26 -0500 Subject: [PATCH 543/554] feat: implemnt client side ddos --- webgenie/base/neuron.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webgenie/base/neuron.py b/webgenie/base/neuron.py index bdfab555..94a14fa5 100644 --- a/webgenie/base/neuron.py +++ b/webgenie/base/neuron.py @@ -21,6 +21,7 @@ import bittensor as bt from abc import ABC, abstractmethod +from bt_ddos_shield import ShieldMetagraph # Sync calls set weights and also resyncs the metagraph. from webgenie.constants import NEURON_EPOCH_LENGTH, SPEC_VERSION @@ -90,7 +91,7 @@ def __init__(self, config=None): else: self.wallet = bt.wallet(config=self.config) self.subtensor = bt.subtensor(config=self.config) - self.metagraph = self.subtensor.metagraph(self.config.netuid) + self.metagraph = ShieldMetagraph(self.wallet, self.config.netuid, subtensor=self.subtensor) bt.logging.info(f"Wallet: {self.wallet}") bt.logging.info(f"Subtensor: {self.subtensor}") From 4ef4dad57d4f8e435854468711e1859b39972b5d Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 17 Mar 2025 11:38:29 -0500 Subject: [PATCH 544/554] chore: add *.pem into gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e12b864a..87268a49 100644 --- a/.gitignore +++ b/.gitignore @@ -193,3 +193,4 @@ developer_doc.md debug_images/ submit_results.py +*.pem From e577ae8170192630cf9d5f731aa57f8fad074ce9 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 17 Mar 2025 11:54:36 -0500 Subject: [PATCH 545/554] chore: edit version --- pyproject.toml | 2 +- uv.lock | 2 +- webgenie/constants.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a45a260e..d81c6eae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.1.20" +version = "1.3.0" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/uv.lock b/uv.lock index ba37851a..f22e0f12 100644 --- a/uv.lock +++ b/uv.lock @@ -3356,7 +3356,7 @@ wheels = [ [[package]] name = "web-genie-ai" -version = "1.1.20" +version = "1.3.0" source = { virtual = "." } dependencies = [ { name = "ansible-vault" }, diff --git a/webgenie/constants.py b/webgenie/constants.py index c64bbf2e..9b2ae083 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.2.2" # version +__VERSION__ = "1.3.0" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From 442b405a52913cc965481d6efd7c5fa136448947 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 17 Mar 2025 17:07:09 -0500 Subject: [PATCH 546/554] chroe: remove logs --- webgenie/datasets/central_dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webgenie/datasets/central_dataset.py b/webgenie/datasets/central_dataset.py index e08b4365..8bcf4bae 100644 --- a/webgenie/datasets/central_dataset.py +++ b/webgenie/datasets/central_dataset.py @@ -48,5 +48,5 @@ def get_html(self, session:int, task_number:int)->str: if response.status_code != 200: raise Exception(f"Failed to get HTML: {response.status_code} {response.text}") - bt.logging.info(f"HTML: {response.json()}") + return response.json()["html"] From f81abf4696f46d36130043c198a8d15e6411feb0 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Mon, 17 Mar 2025 17:09:12 -0500 Subject: [PATCH 547/554] chore: update resync func --- neurons/validators/validator.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/neurons/validators/validator.py b/neurons/validators/validator.py index 323b82db..6c255e8b 100644 --- a/neurons/validators/validator.py +++ b/neurons/validators/validator.py @@ -87,13 +87,11 @@ def __init__(self, config=None): def resync_metagraph(self): # Copies state of metagraph before syncing. - previous_metagraph = copy.deepcopy(self.metagraph) - + previous_axons = copy.deepcopy(self.metagraph.axons) # Sync the metagraph. self.metagraph.sync(subtensor=self.subtensor) - # Check if the metagraph axon info has changed. - if previous_metagraph.axons == self.metagraph.axons: + if previous_axons == self.metagraph.axons: return bt.logging.info( From 67a32b62141926792775ecb92a9eb45c897a918f Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Tue, 18 Mar 2025 06:01:01 -0500 Subject: [PATCH 548/554] hotfix: resolve import error --- neurons/validators/score_manager.py | 10 +++----- webgenie/storage/__init__.py | 2 +- .../storage/submit_results_to_dashboard.py | 25 +++++++++++++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 webgenie/storage/submit_results_to_dashboard.py diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 77734bd0..062a925e 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -297,17 +297,15 @@ def submit_results_to_dashboard(self, session_upto: int): } submissions = [] - avg_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) for uid in range(self.neuron.metagraph.n): - if solved_tasks[uid] >= max(1, number_of_tasks - MAX_UNANSWERED_TASKS): - avg_scores[uid] = scores[uid] / solved_tasks[uid] - else: - avg_scores[uid] = 0 + if solved_tasks[uid] < max(1, number_of_tasks - MAX_UNANSWERED_TASKS): + continue + avg_score = scores[uid] / solved_tasks[uid] submissions.append({ "neuron": { "hotkey": self.neuron.metagraph.hotkeys[uid], }, - "score": float(avg_scores[uid]), + "score": float(avg_score), }) submit_results({ diff --git a/webgenie/storage/__init__.py b/webgenie/storage/__init__.py index c539bf5e..4385270a 100644 --- a/webgenie/storage/__init__.py +++ b/webgenie/storage/__init__.py @@ -2,5 +2,5 @@ store_results_to_database, send_challenge_to_stats_collector, ) -from .submit_results import submit_results +from .submit_results_to_dashboard import submit_results diff --git a/webgenie/storage/submit_results_to_dashboard.py b/webgenie/storage/submit_results_to_dashboard.py new file mode 100644 index 00000000..7b782588 --- /dev/null +++ b/webgenie/storage/submit_results_to_dashboard.py @@ -0,0 +1,25 @@ +import bittensor as bt +import os +import requests + +from webgenie.constants import API_TOKEN, DASHBOARD_BACKEND_URL + + +def submit_results(miner_submissions_request: dict): + try: + url = f"{DASHBOARD_BACKEND_URL}/api/submit_results" + headers = { + "Authorization": f"Bearer {API_TOKEN}", + "Content-Type": "application/json" + } + response = requests.post(url, json=miner_submissions_request, headers=headers) + if response.status_code != 200: + bt.logging.error(f"Error submitting results: {response.status_code} {response.text}") + return + response_json = response.json() + if response_json.get("success"): + bt.logging.success(f"Results submitted successfully") + else: + bt.logging.error(f"Error submitting results") + except Exception as e: + bt.logging.error(f"Error submitting results: {e}") \ No newline at end of file From b0bb13ae9bb7ab7dc296da13df52a13d63d91cb2 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 19 Mar 2025 10:24:08 -0500 Subject: [PATCH 549/554] chore: update dependencies --- pyproject.toml | 10 ++--- uv.lock | 112 ++++++++++++++++++++++++------------------------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d81c6eae..8f6a91d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,12 +6,12 @@ readme = "README.md" requires-python = ">=3.12.4" dependencies = [ "ansible-vault==2.1.0", - "async-substrate-interface==1.0.3", + "async-substrate-interface==1.0.5", "beautifulsoup4==4.12.3", "bert-score==0.3.13", - "bt-decode==0.5.0a2", - "bittensor==9.0.3", - "bittensor-cli==9.0.3", + "bt-decode==0.5.0", + "bittensor==9.1.0", + "bittensor-cli==9.1.0", "clip", "datasets==3.2.0", "ddt==1.6.0", @@ -40,7 +40,7 @@ dependencies = [ "tensorflow>=2.18.0", "tf-keras", "uvicorn", - "bt-ddos-shield-client>=0.1.3", + "bt-ddos-shield-client==0.1.6", ] [project.urls] diff --git a/uv.lock b/uv.lock index f22e0f12..3b3179ee 100644 --- a/uv.lock +++ b/uv.lock @@ -188,7 +188,7 @@ wheels = [ [[package]] name = "async-substrate-interface" -version = "1.0.3" +version = "1.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asyncstdlib" }, @@ -199,9 +199,9 @@ dependencies = [ { name = "wheel" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/d7/71421a4f2ecb82a0141e61dab83770c0f9b2257543ad09f8a7eb4ed3017e/async_substrate_interface-1.0.3.tar.gz", hash = "sha256:da944b42aad508a6681a58b3223801013fbabc2f3d63c1e599eada4ecb53a2e9", size = 61528 } +sdist = { url = "https://files.pythonhosted.org/packages/48/4e/e0ac170032c850c523105fc777221b7626d9a3cf580f980904b4bfa11741/async_substrate_interface-1.0.5.tar.gz", hash = "sha256:a9e36159262b750bbea0cb0bd1676b083e6a5892b75218363faa1bec96207d0c", size = 61252 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/a9/6c36e68b0b7cce2ff2a35a3a76c19e0dd2a1a6dedcf72189f6dd52b5d4a3/async_substrate_interface-1.0.3-py3-none-any.whl", hash = "sha256:d51db230fa0c0de6f9960089faad806da773c7500727096a2659c0fd398edc92", size = 64397 }, + { url = "https://files.pythonhosted.org/packages/01/72/01ab5d18a2e21bc85e4e669311b299202279cfc84b40d46d08157052182c/async_substrate_interface-1.0.5-py3-none-any.whl", hash = "sha256:1b70948f31071680bddba8e5f76738fa2a1049e36568fa158a45a5e7e9b46ec9", size = 63631 }, ] [[package]] @@ -273,7 +273,7 @@ wheels = [ [[package]] name = "bittensor" -version = "9.0.3" +version = "9.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -303,14 +303,14 @@ dependencies = [ { name = "websockets" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/51/b05459ff0f8910c7617cd098d0917029015a70613757941163a22d514e1e/bittensor-9.0.3.tar.gz", hash = "sha256:54b13149d86c3de5e71b587e8474e49dffbb4ee864fb66f27e1efdc9e107504b", size = 223737 } +sdist = { url = "https://files.pythonhosted.org/packages/47/5e/1ecb75a7d1f28bf610be5fa03eea9c4353e05f027ff739e3ce87c1d57e87/bittensor-9.1.0.tar.gz", hash = "sha256:32f963a5bdb1e0ba0603da3bd963b5c108407b1348a6728593f6bd75c848da2a", size = 228001 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/55/03618fe9200575828401433d93f23037b9f8522d0e1d8018eda95d7f0bd8/bittensor-9.0.3-py3-none-any.whl", hash = "sha256:00d46eb2a61bb06f9c5f0a5ad551f8380db7e3c28441a39899f284b96edccbe6", size = 264616 }, + { url = "https://files.pythonhosted.org/packages/0a/a3/91365f318072cd9f166da4f8e49d109632fd834b1c067e42a33bdf183241/bittensor-9.1.0-py3-none-any.whl", hash = "sha256:7af67b38efebbe7084bd58f26205fc25783fea9d3161714e5a784861d856f628", size = 270472 }, ] [[package]] name = "bittensor-cli" -version = "9.0.3" +version = "9.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -336,9 +336,9 @@ dependencies = [ { name = "websockets" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/65/a3f4db7e4474e27c81f061c6e8c6aec0109c4c4cc1c3c326dc4309e86ddf/bittensor-cli-9.0.3.tar.gz", hash = "sha256:6fa14b18276c34dd30d06a0bf26b1e6e78cb33cbf332de9453d9d8557ee868f9", size = 181447 } +sdist = { url = "https://files.pythonhosted.org/packages/54/4f/c24e460eb3ec2443334bee6d711682485e149d0cdf7cd32841c9c4ba8f8a/bittensor-cli-9.1.0.tar.gz", hash = "sha256:a53f9dc6c32d876beeea16bd4bf93b21f5354211960fcc5a918c898595804f2d", size = 204484 } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/41/3de43d1897904ace9153bb71d9cb137050a2b6253548b10b8acaed898fff/bittensor_cli-9.0.3-py3-none-any.whl", hash = "sha256:171d2cc2b7ff4d9563376ba00aa0310e8dbb7e3d327bf6cb97e2b1a0bb215a14", size = 193851 }, + { url = "https://files.pythonhosted.org/packages/13/76/9243b0b7e1928413086809642a9ea5e52dd2efa2f6c8496ff669e13e3718/bittensor_cli-9.1.0-py3-none-any.whl", hash = "sha256:5fc898c5c7b6f0121660948ec4f2ccec9543a0c9c5831f15bb19c47e03033763", size = 216986 }, ] [[package]] @@ -412,7 +412,7 @@ wheels = [ [[package]] name = "bt-ddos-shield-client" -version = "0.1.3" +version = "0.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bittensor" }, @@ -424,56 +424,56 @@ dependencies = [ { name = "route53" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/9f/13edb2546caaf7d6266ea403ca7a053204073b3da37bf17407778d9d2a94/bt_ddos_shield_client-0.1.3.tar.gz", hash = "sha256:43ef9a0739ac87f4b8493bd0065a04ada8af945134ba3efd5c5396618358d0ad", size = 45474 } +sdist = { url = "https://files.pythonhosted.org/packages/53/fb/51999da51944c2c1c6c672facaaab618e899bde08076a5e5392614f04514/bt_ddos_shield_client-0.1.6.tar.gz", hash = "sha256:3db74652fa50882e18492289b33ec588238dac9dfd479126dbdcd15ac0a19aac", size = 45752 } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/bb/cd27b012e7562232d975a5dc4ed86cc343789cfabd7784bf4d741d275941/bt_ddos_shield_client-0.1.3-py3-none-any.whl", hash = "sha256:0fe26640404dadaa9c12fa463d142d76bb5b591cabf461ea2e6d217c4e4e62e0", size = 36890 }, + { url = "https://files.pythonhosted.org/packages/fb/3f/c7e4b43b1244f6ad84c4b05c29a4272ea7b1023ee5cbf7c11d1ecf2136d6/bt_ddos_shield_client-0.1.6-py3-none-any.whl", hash = "sha256:8a26787f1ebfdf8caceacd1fe6f74f3cb4275e5c4624bddd02a24c9217626ac3", size = 37124 }, ] [[package]] name = "bt-decode" -version = "0.5.0a2" +version = "0.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "toml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/32/d0ac8e0ffd62f7c3d274926573bfb51b9661367f183fcfee8431b8a4d6b3/bt_decode-0.5.0a2.tar.gz", hash = "sha256:f5f41dd6b7797d58deaed7aec92c651aab4b64f9a08d203543d654cd2ba0cef9", size = 1196580 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/42/e6cc279a83e5c5742bec404bdc3920d50e8edcc593999fcbb76d0eab2b50/bt_decode-0.5.0a2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:63a083dd59d7ad199bc34f5511e9c43a9a68451d74e8618ea726c7bdbdc0f123", size = 589984 }, - { url = "https://files.pythonhosted.org/packages/65/8b/9ef3555db39da72cbf121d3c0ac5d51806c84ffd456092e62f37bff0c3e2/bt_decode-0.5.0a2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b096cfbaf67029c400b49a59b3fdd41481405de121e6b036db52a2fb1c8a2910", size = 577096 }, - { url = "https://files.pythonhosted.org/packages/62/b3/4d23231454c2931cc225e5b5ec905923bac173eee762587118cffd56436f/bt_decode-0.5.0a2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00326c4e8338f5d03579c7e71796da40af548f224216bc9b5354333f13c96325", size = 635772 }, - { url = "https://files.pythonhosted.org/packages/08/d8/b21fdf571c9a23049f0eb9a7792ed7e3c37a2cba6e9d6a3a05960bf9169d/bt_decode-0.5.0a2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3b2537b2166983ee54226b6d6d592fe0255eadecea3aa264b8f5fd9045b2afa6", size = 635973 }, - { url = "https://files.pythonhosted.org/packages/26/0e/b18c4cb8f89b42a519df1b6afde649f4e78f2118ac36513690162a6c1eb6/bt_decode-0.5.0a2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10f9d2d2a92114417899afbcf29e29d456aba9e3239f53e9cd8c8954751e2f03", size = 703793 }, - { url = "https://files.pythonhosted.org/packages/60/30/fc3ef4e46701db97487e106af311d318233568f5ddcf4aa3985dc573abae/bt_decode-0.5.0a2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07035f38ccb6839980c64c0acc6988ada8670366ab87f052c385b511f8694ff0", size = 740602 }, - { url = "https://files.pythonhosted.org/packages/ca/8d/4c68f1b78bb9d8ebc304fc18127f25417757fa95c7399ddbf387c6e48c07/bt_decode-0.5.0a2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20c0535438fb5921232fa3be84177d5fb3d2a8bc4d0cc8497a0f24b8e0dfccab", size = 643033 }, - { url = "https://files.pythonhosted.org/packages/a9/7c/eee0495d0db55db2d1e59a4ad02a44fdcf38884e98299ad0ae52fb83a28f/bt_decode-0.5.0a2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d919dfdaf2407865630481b4d8aab23ac34dc935e19ae5a596d773b1e90db45b", size = 698183 }, - { url = "https://files.pythonhosted.org/packages/0f/94/59ffa41cf163fd79db78702043d6af0a5e7b19b95d90e4e5c3a13515184d/bt_decode-0.5.0a2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bf223d94e20d9c2ad83fd430a812131c927a715254ff42844c114bab8ad73dfa", size = 805359 }, - { url = "https://files.pythonhosted.org/packages/72/39/6e93458954b76cdce708c1153cf66f52aec60fd33c13bfe864f9e1799c50/bt_decode-0.5.0a2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5c253c48ea116e7484415c4577da956b3b3d2a445a5bb901ad9f3e88571ef5da", size = 887592 }, - { url = "https://files.pythonhosted.org/packages/21/27/1705494caff2246cc545e60b46c752b24eef7861333c1eb01dfabe228070/bt_decode-0.5.0a2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ba264ea9424826ac97354e4ae1cb2729a91b6a474f9c462d277e6ba27c544bec", size = 844837 }, - { url = "https://files.pythonhosted.org/packages/63/0a/e10ed03da94b576c1e2acd99371713b7d54a596d1bbc401f33b9382e2d3d/bt_decode-0.5.0a2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52fa7732db915339854cd81d1f1c8ee2db4958260229086568c4c0fcd8af7c31", size = 806971 }, - { url = "https://files.pythonhosted.org/packages/af/3f/f8d6faa6c658feb4a1731167e8d1b00e6a51aa85d1393e4fd61a999034a8/bt_decode-0.5.0a2-cp312-cp312-win32.whl", hash = "sha256:5c7177943af8288556b84cb148962c889075cda7c8ca4655ae067b2cf0d50ac0", size = 412253 }, - { url = "https://files.pythonhosted.org/packages/a1/99/192a42bd205eec697a76be9bcdad16923a1ffe50c9c9307b3b24e4ca50dc/bt_decode-0.5.0a2-cp312-cp312-win_amd64.whl", hash = "sha256:f07f41ab81c3cfa4fccd249e239dc74180ca95d2840a064d4f2ecb88cbc0d6b2", size = 440516 }, - { url = "https://files.pythonhosted.org/packages/21/d5/f08d796e4a3759ba09df3b613a413ed2de5fe0f38dba9b4d3d6908bdf3f0/bt_decode-0.5.0a2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:dc5296ad5be76f189d3d6332dbe4f5ea2fb20057e68ff636ce91f2609f66fb35", size = 589932 }, - { url = "https://files.pythonhosted.org/packages/57/8d/843fb1f0f01c3b59df3ff30cd693046244b2da64f5d60d18461a890fd63e/bt_decode-0.5.0a2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f9688eb67f04d1bb806b675014fb2adaf1441f078344196ff5e1a472c988927a", size = 576895 }, - { url = "https://files.pythonhosted.org/packages/cf/8b/61e71543188c08b18a05f3c8b5d90649359affefc596e16d7bad31201245/bt_decode-0.5.0a2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43dc4dac309cd995d7fccec8f521a91793d72a67609b492e87ef8b2394848660", size = 635775 }, - { url = "https://files.pythonhosted.org/packages/d5/05/a5db18b52f8f72aa7c3335bfe41ac8184ab36fcd6030b4a8085f32449a86/bt_decode-0.5.0a2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c2d5f4303e8b87261ca17bf303821f55edb5b5359fb69ff124ba5c0ab7de7bd", size = 635233 }, - { url = "https://files.pythonhosted.org/packages/78/1e/6bd44c0316d1a594f0cdfea18cc82679fa669ebf91195dbe57db9cb757de/bt_decode-0.5.0a2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176994fedf95340b54ac936f25b42abbb265645a990d02ab6cd551427c1d14f2", size = 703667 }, - { url = "https://files.pythonhosted.org/packages/e2/7a/4bab052db00bb9d6b3d855e5c73daa4cb39b44a0d42008ba3f66191d7819/bt_decode-0.5.0a2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ba059ec2c49eba7c5674d3dadfbc0e904fd2c6da5b3e1a0ce8d1e85407eefa1", size = 740765 }, - { url = "https://files.pythonhosted.org/packages/2a/ea/271b6c0626ddc0bd892ade43d3d92a7a8940dcdc9e766aaddb71aca7ac0c/bt_decode-0.5.0a2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3994ee24fdd724d3ee99fbadd9dbf4aeacd8c46198906633ef9bca9b715b24f", size = 642818 }, - { url = "https://files.pythonhosted.org/packages/92/cf/dc9c51f1645e0039b4b3d0a1ee178c03747240a64fc3c74ba4aa13d54696/bt_decode-0.5.0a2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1b1a5fa89551e6fcace3b6fd4d47b79ee8744ea7632c84a6dfecb8be30594f65", size = 697991 }, - { url = "https://files.pythonhosted.org/packages/e9/ce/82e354750241913e6e1cbca414dcee8dc78c822240bf8d07351f35db182c/bt_decode-0.5.0a2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cae0f21159511066178cfffdeeafab9142d7e933f5d320cafcc9cc6c7702e088", size = 805045 }, - { url = "https://files.pythonhosted.org/packages/ee/ce/f9d2e85cc479b915017b55b1443180b0f0270979ecb6c17dcfcce58f5695/bt_decode-0.5.0a2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:62039a60ce110a26e9d40d534ba9ad60b9e623d6ece0ce47744ffd168168e3f3", size = 887243 }, - { url = "https://files.pythonhosted.org/packages/b8/a5/3a58ad98f959b1f3ec3dab7500701bde949467260dc7e00a855bc2a5e5ba/bt_decode-0.5.0a2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83c864c9427b83f3204df2e302588bc8eb12397acd73a33bdc86c0db11097e6a", size = 844395 }, - { url = "https://files.pythonhosted.org/packages/ce/a6/2e78fe2fb206d5ca5293329ea5f97def807d9b4236178b05addcced157c7/bt_decode-0.5.0a2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d55902248755f7ed557f27bc48706a7d0fdb0faa57fbaf3baa2cabe275f6c022", size = 807006 }, - { url = "https://files.pythonhosted.org/packages/e0/be/2786d9da5e5e07fea4ca9ea22f9e60d2130a508f85e9c0a22cb80cfa3e2d/bt_decode-0.5.0a2-cp313-cp313-win32.whl", hash = "sha256:0bfb120ab3db7340d7aeef873499a3a6e8e7b2b649cd7fc2ee7501094afccbea", size = 412191 }, - { url = "https://files.pythonhosted.org/packages/30/6d/67ec8ccd58072acc1358b580e528f8ab48fbcdcfd38c2a1f36abf8d4b14a/bt_decode-0.5.0a2-cp313-cp313-win_amd64.whl", hash = "sha256:680b4a78b27e7a73cd2837f5cedf4556686ecb8b49b1c38d6ea59f1512860866", size = 440608 }, - { url = "https://files.pythonhosted.org/packages/ca/f2/e089106d596ee574c3bcce2c18378ed014a79a4f91ba8e18978baca0651f/bt_decode-0.5.0a2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:615afa8e147505096cf05943fc8ca5f65de43f6bca5c00231c245eb0c6143409", size = 633809 }, - { url = "https://files.pythonhosted.org/packages/ee/e6/e93636bcb9011e402356c85e03654d15348a1e5ef82fb5400e49b35f2dd2/bt_decode-0.5.0a2-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:446a9ce02725af8ed3a4ce96a26b06010df28945e7e9609cd08fa2e6efd6745c", size = 633795 }, - { url = "https://files.pythonhosted.org/packages/cf/3b/2fb48aa8d796517dbfbd27febff8ea9a1d17425ac3ed0a983cdf94f89964/bt_decode-0.5.0a2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13004a9f0a75f726338ccd6947a20492119fb3d2757aa1a6e6fb469ab0a29e06", size = 700510 }, - { url = "https://files.pythonhosted.org/packages/8d/db/9e457d93956eb5f20d6343312e9878ed5a81b7d9eb23535886c8dfd848bc/bt_decode-0.5.0a2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d3eee3a200d904ce7c8bc179917bbceda935bb989d031f041cf629b1d4c40a68", size = 739709 }, - { url = "https://files.pythonhosted.org/packages/c9/1a/34fe4d7ea1b4e3561f21a97270e949e7759195045245f379962dbd09e1cb/bt_decode-0.5.0a2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ed97cd737bad5c8b908c27664031f67673fc91eef85cb771750f0e76530df75a", size = 804183 }, - { url = "https://files.pythonhosted.org/packages/2c/7e/451630b300480fcbb51afcfae98e844fd9713040fcd503ecc433610b73d1/bt_decode-0.5.0a2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f89ba68fa9291fff5575cd315f5533f544bfd581cbdbca91e8dfe77b87ff0f74", size = 885638 }, - { url = "https://files.pythonhosted.org/packages/af/50/cc0c8c52169f02b4e05d6240702f4c2b757466ec5cef309895ec80771bfc/bt_decode-0.5.0a2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7e94ad44039cd033ded93d390e3926246571c588d05e59b68efd3992e5bbe75c", size = 842597 }, - { url = "https://files.pythonhosted.org/packages/35/f7/0ce061c3ae80927e7b9d4ade5fe3e128a3fb1a8984e8f95f341a081ad477/bt_decode-0.5.0a2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17187ab67e492b901993fde7fe81f6ac0a45e091f1bda980090c44edb65da2a8", size = 807823 }, +sdist = { url = "https://files.pythonhosted.org/packages/e6/67/2b65581c547fd5fcbf812d8301c0ceb6fb425093004f7fa7c8993c3e4231/bt_decode-0.5.0.tar.gz", hash = "sha256:934bf4bd49580b1586915c1fec85eda0f56b9505ca6769c1b05413715e2f429f", size = 1196552 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/e5/dd568c64b4bd22ff9b29a133c9771ce4a462caf7f295cfdde2234b27493b/bt_decode-0.5.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:5526d61faf223c76340098814e475dc280a5e22aaaee5480215946dac611aa28", size = 590274 }, + { url = "https://files.pythonhosted.org/packages/e8/32/e71c25f565ae2b5e917b2b1bd4954b4e69e91dbe14444529b8e545031497/bt_decode-0.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4036efd891328aa091eaca82b03f470189bebf38e08f5f1aa422e5eacfebde89", size = 577101 }, + { url = "https://files.pythonhosted.org/packages/99/2f/f76a99820bb2689dcf4a52780a239d01e18ef63f5d521149376b883f704e/bt_decode-0.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef9ab49da6a47505984bd0e4b1bf0c61416e0eef58e0a474ef17e50a90d298ad", size = 632019 }, + { url = "https://files.pythonhosted.org/packages/25/54/c902db243d6159d47133108dddf8075d5c54af026daa636846eda0e3be60/bt_decode-0.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c92ad16b59518d14f5a2d4e4282dd50d25ac5b89f0ae3221c7480416abd2a64", size = 629434 }, + { url = "https://files.pythonhosted.org/packages/28/0e/c40093f167af595eeb8fcb346396cc8c1fc08481c78fd08276e18b73a2ba/bt_decode-0.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:208bceb8c23de4f0ade98945e490ef932811ca4d796fe17e4124d5a2bd136c93", size = 703005 }, + { url = "https://files.pythonhosted.org/packages/16/c3/2d13cebcd8ba48e77cb4af0cbed6c9ebea5d0620ff34dabfbe547b61fc3b/bt_decode-0.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83277d0a6cc2c932d91b178974ab078cbe0e25adc73fd8d7f6452006d0154051", size = 736913 }, + { url = "https://files.pythonhosted.org/packages/d3/a5/026cb87f23b4c2f1b824c81ce6d6e8f6320aded3cf59cb7a2896063131e5/bt_decode-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e73236a0c66420c1c0eb8bbef0ddfbdb1acf62a7d7b1c04d3255ccc20420f54", size = 641609 }, + { url = "https://files.pythonhosted.org/packages/92/77/898d9be6737742dbd58c7bcd2d81cd4a91bc3e514dd4487747112447f48a/bt_decode-0.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:632325a511178ec3a4e65da7b93a07484d6353a8dc2010c05809cf15a9022fda", size = 696498 }, + { url = "https://files.pythonhosted.org/packages/20/77/0cc316182f413b90a694b829178a71886dc569f3234363df26027c9769ce/bt_decode-0.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8c3141ac0f4377515943fccab6793b9635b7a334a5f1ca44966e77ebaa981bb", size = 809838 }, + { url = "https://files.pythonhosted.org/packages/df/bf/92a8db52be3b34a4a573f4cacf1d280d7023877379c1f3c9844feb033681/bt_decode-0.5.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1fac958f819c4d58e186b02e9486900641463c9fc074957cd308e24e0fb44584", size = 892294 }, + { url = "https://files.pythonhosted.org/packages/90/06/1aa6899e7714c7e847ba8c212713b28f3d57629e9074eba256a783acf2f3/bt_decode-0.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c886b3a02a3423aa9cd3d1409a6383a87cc8941e1a4f4425b2bd2f9aa839d298", size = 850390 }, + { url = "https://files.pythonhosted.org/packages/74/69/6de76d572331847ab0c9455dfd4d618ae50eeccfa4b8705afcae6cbe7daf/bt_decode-0.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7dd81cb4064be3aa4e69ba94ed05f576f07d4eb8dc43f38077f5d7a036fdaaed", size = 812772 }, + { url = "https://files.pythonhosted.org/packages/5b/60/64b59a074e38b4ae722af159128f66804e949443b4c7976cced51bb2e0ec/bt_decode-0.5.0-cp312-cp312-win32.whl", hash = "sha256:c88619630bb290ddf0db93b3c24b37804ce8ba0a4be667b8ab3d00e8c82f0e53", size = 410821 }, + { url = "https://files.pythonhosted.org/packages/a9/bc/c3c2d7a0b6917b8c762bd878f08571682c5ac797a231fa79e1b11eaa74a5/bt_decode-0.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:d3fa6d0c88d76baacd67c45d2fc164e158a5ed9fdc09661c407204e624c066e3", size = 439590 }, + { url = "https://files.pythonhosted.org/packages/60/19/0d432cf816f2e4e5bcaa24f932eedf840720de5afeb8453c4fa090ef4f00/bt_decode-0.5.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d89eddf7d9e8667892e840aafb9c8647ff54eac386169d486cee4b4de7c21dce", size = 590320 }, + { url = "https://files.pythonhosted.org/packages/76/36/ef8db7b0c421d7656d1ef3a846fcd45c3033a73062e9e5c50f74bf99528f/bt_decode-0.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23e333e4b8bd3cf54d1b0c840e633947f951336a6674c8262f0f3c8a32280c62", size = 576955 }, + { url = "https://files.pythonhosted.org/packages/a3/fd/59d1ed65bb21ee6c8213e58e59b659836a47c76513fe54c9697a3d5bd752/bt_decode-0.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53e13613f70e7aadbbfbe7bf48d57df64c2196d59f2b1cb63a2ef2de9f10f0b", size = 631539 }, + { url = "https://files.pythonhosted.org/packages/01/ea/c740c1b1a6f7228f594841a8bc0808fc3bd064f75cf3c90482ba2070e89d/bt_decode-0.5.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:760f6d9f9b637c6217963091734864846c3c4bee0e2130de8c2cc185a6bf7672", size = 628517 }, + { url = "https://files.pythonhosted.org/packages/7e/4a/f9461fb7a8f4bf883ffd5dac6db1eecc79fb1b73ae70dcd18c4739e7b525/bt_decode-0.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d95b3cbef44b3d9475cc88e26b3ded2c4a576c764414c02e12a23615f1c69a0", size = 702919 }, + { url = "https://files.pythonhosted.org/packages/ea/c9/4ea1220a3db9c771a3f5cf3ad6f5e28f953018413a4453c1597920df0dcc/bt_decode-0.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5564e82ea81c8116a4cb2cc0c5577f1ac5815ba93e08fda8c526bacff4a8e594", size = 737941 }, + { url = "https://files.pythonhosted.org/packages/12/3e/b2065e5b4dea7aa8b5a3a2349bf7e29a208497e2c9086356f691883c22e1/bt_decode-0.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ca24b5e7c779292f7373bb7d2f66741d287ae461fca56ee8482e29808ee700", size = 641272 }, + { url = "https://files.pythonhosted.org/packages/8c/41/914bef0da59151efa93553372aafbb465ad3c5b2d26739c5631beb9b3e6f/bt_decode-0.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c9cf30610ea0704f4db3b6d4189eae2c2e71166d9e3055b681018b10b347f9fe", size = 696296 }, + { url = "https://files.pythonhosted.org/packages/6c/45/2c8e04dbe17c8819721c9fc207d41edcfd99b82d218211d8d09e8ea031bf/bt_decode-0.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:19732e5e52400d0d825bb083ef0eec89f768de232fda204d64b218a02ce503c8", size = 809775 }, + { url = "https://files.pythonhosted.org/packages/e9/a9/7dde0142dd9b7dc7f926e06c4ff8a681fff6fb9bfaf98e759a53f5b60127/bt_decode-0.5.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:e8773f3ec3014acb79f53d07292a28c8f270b1d1ddba9a1fb737e3d98f102152", size = 891862 }, + { url = "https://files.pythonhosted.org/packages/46/14/85cbdee7e22255a2aab49bf423d3e5c092f4d7f9461bfb52a09716939ecf/bt_decode-0.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8da5190c8b4463d25d65a58381c4243b51309d4abf118fa71efa08c69edd911c", size = 850028 }, + { url = "https://files.pythonhosted.org/packages/d3/85/d5aeb7a9e16a9ab4bb580aafc16c9a80e928c4af0dbbc7c9ba6a8a49d902/bt_decode-0.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f386d0a786fe6f1bb0eea6911302360c884b2ac6583d8a73a631d2caef38ae2b", size = 812416 }, + { url = "https://files.pythonhosted.org/packages/77/c8/2c1e6109e3c08311921f5811ad028f7ba3808da1192134533c3eace8210c/bt_decode-0.5.0-cp313-cp313-win32.whl", hash = "sha256:e1d4a231cb7b418b2b54fa642c6b0d67b3eca5d67c2be5e4253c6f3f5ab9b1fa", size = 410530 }, + { url = "https://files.pythonhosted.org/packages/17/f1/872c3b0f45dd7b7c09f4e737c98c579b4c09c612363a0865b8429cc2ee91/bt_decode-0.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:1dcae078dcfc267fe6e0102963ac37182b84ebdc661badc702a2642f47baac6c", size = 438956 }, + { url = "https://files.pythonhosted.org/packages/0d/15/8c64c09c52a2d68351a3d311548bc764c3920c9d0109943ce9333e5d7088/bt_decode-0.5.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee6efff9a8106ae783f0a5d6f9b3444db03798fb64592fcddc42881040b66c53", size = 630313 }, + { url = "https://files.pythonhosted.org/packages/2f/ed/a833a9bfa417a9e865e205166944a6bff4379a1e3d528db6ff2befea6537/bt_decode-0.5.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1330baa4341735adfd0a65aebeacc9e759b9f964098ebf2f66e095a14418c976", size = 628615 }, + { url = "https://files.pythonhosted.org/packages/11/68/326dbfc39471ccf2e3132f341f9d5490009b1ed613019c5a98073c3ca993/bt_decode-0.5.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d38cde3e4a5de9946364d68366a735984d9da9731b8490ee9d162ca1ac9268a", size = 701062 }, + { url = "https://files.pythonhosted.org/packages/1d/15/d6d915fb25b708b2329250b32ffe29189a1e6f6b3c1b04637ea565e41a8a/bt_decode-0.5.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e697aadec5156dca3e3b082151e7466a3c96785d4334e5e34736cef66f9add7", size = 738556 }, + { url = "https://files.pythonhosted.org/packages/b8/37/7c765e7f5d3935aa934e5be85dfd5ec9d015b99d1bb5fbaec62143ebd162/bt_decode-0.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71a7c02cdb3c07b48d7b3fe2f43c77b1ca47bf6cba6681fe3b6f3b37b56a21dd", size = 808401 }, + { url = "https://files.pythonhosted.org/packages/77/23/6c3a6293b3f2e8256be3610eba8d041cff8ccd1bd7fbda92accd8c129775/bt_decode-0.5.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2c649c7362c90e60d4f314d05621ae32b932ba2791d94476b9a5dee5deaecc06", size = 891274 }, + { url = "https://files.pythonhosted.org/packages/1f/88/cbef12b6085c088952eaa717a0a4b08dbf90fea2f2e4c966e83a721e2644/bt_decode-0.5.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:df92c9ab527fb63ef48446b06804e6f3410c7e68b127f1f224bcdcaee89cb30f", size = 846792 }, + { url = "https://files.pythonhosted.org/packages/69/a4/423702ff86a0df74e4035cacc5f51ac6010ea272812ab11a2d4f5c66fb9a/bt_decode-0.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cafecf2718a6392a8c42c99e036769ce2147686c49c72e3582aecbd184b84688", size = 811632 }, ] [[package]] @@ -3399,13 +3399,13 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "ansible-vault", specifier = "==2.1.0" }, - { name = "async-substrate-interface", specifier = "==1.0.3" }, + { name = "async-substrate-interface", specifier = "==1.0.5" }, { name = "beautifulsoup4", specifier = "==4.12.3" }, { name = "bert-score", specifier = "==0.3.13" }, - { name = "bittensor", specifier = "==9.0.3" }, - { name = "bittensor-cli", specifier = "==9.0.3" }, - { name = "bt-ddos-shield-client", specifier = ">=0.1.3" }, - { name = "bt-decode", specifier = "==0.5.0a2" }, + { name = "bittensor", specifier = "==9.1.0" }, + { name = "bittensor-cli", specifier = "==9.1.0" }, + { name = "bt-ddos-shield-client", specifier = "==0.1.6" }, + { name = "bt-decode", specifier = "==0.5.0" }, { name = "clip", git = "https://github.com/openai/CLIP.git" }, { name = "colormath", specifier = ">=3.0.0" }, { name = "datasets" }, From 10d3650726d7a18b53412bca133765951a24ab5b Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 19 Mar 2025 10:28:16 -0500 Subject: [PATCH 550/554] doc: edit version --- pyproject.toml | 2 +- webgenie/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8f6a91d2..da7c881d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-genie-ai" -version = "1.3.0" +version = "1.3.1" description = "The first bittensor subnet for web generation" readme = "README.md" requires-python = ">=3.12.4" diff --git a/webgenie/constants.py b/webgenie/constants.py index 9b2ae083..bfd24e63 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.3.0" # version +__VERSION__ = "1.3.1" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From d35bbf59842089b0ecca32b1b31f4afceb5b36d5 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 19 Mar 2025 21:22:48 -0500 Subject: [PATCH 551/554] chore: update dependencies --- pyproject.toml | 1 - uv.lock | 163 +------------------------------------------------ 2 files changed, 1 insertion(+), 163 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index da7c881d..253c8bb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,6 @@ dependencies = [ "bert-score==0.3.13", "bt-decode==0.5.0", "bittensor==9.1.0", - "bittensor-cli==9.1.0", "clip", "datasets==3.2.0", "ddt==1.6.0", diff --git a/uv.lock b/uv.lock index 3b3179ee..75dcfbf9 100644 --- a/uv.lock +++ b/uv.lock @@ -177,15 +177,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/03/13dde6512ad7b4557eb792fbcf0c653af6076b81e5941d36ec61f7ce6028/astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8", size = 12732 }, ] -[[package]] -name = "async-property" -version = "0.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/12/900eb34b3af75c11b69d6b78b74ec0fd1ba489376eceb3785f787d1a0a1d/async_property-0.2.2.tar.gz", hash = "sha256:17d9bd6ca67e27915a75d92549df64b5c7174e9dc806b30a3934dc4ff0506380", size = 16523 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/80/9f608d13b4b3afcebd1dd13baf9551c95fc424d6390e4b1cfd7b1810cd06/async_property-0.2.2-py2.py3-none-any.whl", hash = "sha256:8924d792b5843994537f8ed411165700b27b2bd966cefc4daeefc1253442a9d7", size = 9546 }, -] - [[package]] name = "async-substrate-interface" version = "1.0.5" @@ -222,15 +213,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 }, ] -[[package]] -name = "backoff" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148 }, -] - [[package]] name = "base58" version = "2.1.1" @@ -308,39 +290,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/a3/91365f318072cd9f166da4f8e49d109632fd834b1c067e42a33bdf183241/bittensor-9.1.0-py3-none-any.whl", hash = "sha256:7af67b38efebbe7084bd58f26205fc25783fea9d3161714e5a784861d856f628", size = 270472 }, ] -[[package]] -name = "bittensor-cli" -version = "9.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "async-property" }, - { name = "async-substrate-interface" }, - { name = "backoff" }, - { name = "bittensor-wallet" }, - { name = "fuzzywuzzy" }, - { name = "gitpython" }, - { name = "jinja2" }, - { name = "netaddr" }, - { name = "numpy" }, - { name = "plotille" }, - { name = "plotly" }, - { name = "pycryptodome" }, - { name = "pytest" }, - { name = "python-levenshtein" }, - { name = "pywry" }, - { name = "pyyaml" }, - { name = "rich" }, - { name = "scalecodec" }, - { name = "typer" }, - { name = "websockets" }, - { name = "wheel" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/54/4f/c24e460eb3ec2443334bee6d711682485e149d0cdf7cd32841c9c4ba8f8a/bittensor-cli-9.1.0.tar.gz", hash = "sha256:a53f9dc6c32d876beeea16bd4bf93b21f5354211960fcc5a918c898595804f2d", size = 204484 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/76/9243b0b7e1928413086809642a9ea5e52dd2efa2f6c8496ff669e13e3718/bittensor_cli-9.1.0-py3-none-any.whl", hash = "sha256:5fc898c5c7b6f0121660948ec4f2ccec9543a0c9c5831f15bb19c47e03033763", size = 216986 }, -] - [[package]] name = "bittensor-commit-reveal" version = "0.2.0" @@ -1010,15 +959,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821 }, ] -[[package]] -name = "fuzzywuzzy" -version = "0.18.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/4b/0a002eea91be6048a2b5d53c5f1b4dafd57ba2e36eea961d05086d7c28ce/fuzzywuzzy-0.18.0.tar.gz", hash = "sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8", size = 28888 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ff/74f23998ad2f93b945c0309f825be92e04e0348e062026998b5eefef4c33/fuzzywuzzy-0.18.0-py2.py3-none-any.whl", hash = "sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993", size = 18272 }, -] - [[package]] name = "gast" version = "0.6.0" @@ -1221,15 +1161,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/f9/f78e7f5ac8077c481bf6b43b8bc736605363034b3d5eb3ce8eb79f53f5f1/imageio-2.36.1-py3-none-any.whl", hash = "sha256:20abd2cae58e55ca1af8a8dcf43293336a59adf0391f1917bf8518633cfc2cdf", size = 315435 }, ] -[[package]] -name = "iniconfig" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, -] - [[package]] name = "jinja2" version = "3.1.5" @@ -1746,15 +1677,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/59/7854fbfb59f8ae35483ce93493708be5942ebb6328cd85b3a609df629736/namex-0.0.8-py3-none-any.whl", hash = "sha256:7ddb6c2bb0e753a311b7590f84f6da659dd0c05e65cb89d519d54c0a250c0487", size = 5806 }, ] -[[package]] -name = "narwhals" -version = "1.26.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/6f/75929abaac73088fe34c788ecb40db20252174bcd00b8612381aebb954ee/narwhals-1.26.0.tar.gz", hash = "sha256:b9d7605bf1d97a9d87783a69748c39150964e2a1ab0e5a6fef3e59e56772639e", size = 248933 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/fc/420680ad8b0cf81372eee7a213a7b7173ec5a628f0d5b2426047fe55c3b3/narwhals-1.26.0-py3-none-any.whl", hash = "sha256:4af8bbdea9e45638bb9a981568a8dfa880e40eb7dcf740d19fd32aea79223c6f", size = 306574 }, -] - [[package]] name = "nest-asyncio" version = "1.6.0" @@ -2144,34 +2066,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/a9/bd88ac0bd498c91aab3aba2e393d1fa59f72a7243e9265ccbf4861ca4f64/playwright-1.49.1-py3-none-win_amd64.whl", hash = "sha256:47b23cb346283278f5b4d1e1990bcb6d6302f80c0aa0ca93dd0601a1400191df", size = 34060667 }, ] -[[package]] -name = "plotille" -version = "5.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/73/3f342572f7f916e387e546cc502d6cad35e7162ba0bcde203669e15aa3af/plotille-5.0.0.tar.gz", hash = "sha256:99e5ca51a2e4c922ead3a3b0863cc2c6a9a4b3f701944589df10f42ce02ab3dc", size = 53392 } - -[[package]] -name = "plotly" -version = "6.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "narwhals" }, - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9c/80/761c14012d6daf18e12b6d1e4f6b218e999bcceb694d7a9b180154f9e4db/plotly-6.0.0.tar.gz", hash = "sha256:c4aad38b8c3d65e4a5e7dd308b084143b9025c2cc9d5317fc1f1d30958db87d3", size = 8111782 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/77/a946f38b57fb88e736c71fbdd737a1aebd27b532bda0779c137f357cf5fc/plotly-6.0.0-py3-none-any.whl", hash = "sha256:f708871c3a9349a68791ff943a5781b1ec04de7769ea69068adcd9202e57653a", size = 14805949 }, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, -] - [[package]] name = "primp" version = "0.10.0" @@ -2435,21 +2329,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1c/a7/c8a2d361bf89c0d9577c934ebb7421b25dc84bf3a8e3ac0a40aed9acc547/pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1", size = 107716 }, ] -[[package]] -name = "pytest" -version = "8.3.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, -] - [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -2501,20 +2380,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, ] -[[package]] -name = "pywry" -version = "0.6.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "setproctitle" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/60/df/2bd468f465011fb021f45cbe5cc9f1cfe15872c61e1cab2a7962bd4f4860/pywry-0.6.2.tar.gz", hash = "sha256:9bd88c36ab0860728d9e64360010f8abcede43645656030e4a63e69e81a98c95", size = 38983 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/0e/0a4b6436433678d49790683ea869e40cca6ecc36f6abdaf01a489298a8f8/pywry-0.6.2-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:45d6bb827bf76b2532a9d70b539209d70f37dfb13e9862549b7bff8500ad2495", size = 2303900 }, - { url = "https://files.pythonhosted.org/packages/02/cc/00c1c93ff5df28cb95a1838f2405f7943e1c0c1a965a6a14670ca3ea9745/pywry-0.6.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:1d9ffd826a3a08c132843340e6d896efb7b972b301d045e3239a7dc08d9cac2f", size = 66838493 }, - { url = "https://files.pythonhosted.org/packages/19/09/d33a4fedf333af8cb208bb9b9a974fbd025c654c6b231b77e22766591ed1/pywry-0.6.2-py3-none-win_amd64.whl", hash = "sha256:4f0e5b502555ee8b8e799baeaebe63243a84b7ce51df01a1c439dbc4e8227b9e", size = 868356 }, -] - [[package]] name = "pyyaml" version = "6.0.2" @@ -2888,15 +2753,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/88/70c5767a0e43eb4451c2200f07d042a4bcd7639276003a9c54a68cfcc1f8/setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4", size = 863432 }, ] -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, -] - [[package]] name = "shtab" version = "1.6.5" @@ -3259,21 +3115,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/eb/65f5ba83c2a123f6498a3097746607e5b2f16add29e36765305e4ac7fdd8/triton-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8182f42fd8080a7d39d666814fa36c5e30cc00ea7eeeb1a2983dbb4c99a0fdc", size = 209551444 }, ] -[[package]] -name = "typer" -version = "0.15.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/dca7b219718afd37a0068f4f2530a727c2b74a8b6e8e0c0080a4c0de4fcd/typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a", size = 99789 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/cc/0a838ba5ca64dc832aa43f727bd586309846b0ffb2ce52422543e6075e8a/typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847", size = 44908 }, -] - [[package]] name = "typing-extensions" version = "4.12.2" @@ -3356,7 +3197,7 @@ wheels = [ [[package]] name = "web-genie-ai" -version = "1.3.0" +version = "1.3.1" source = { virtual = "." } dependencies = [ { name = "ansible-vault" }, @@ -3364,7 +3205,6 @@ dependencies = [ { name = "beautifulsoup4" }, { name = "bert-score" }, { name = "bittensor" }, - { name = "bittensor-cli" }, { name = "bt-ddos-shield-client" }, { name = "bt-decode" }, { name = "clip" }, @@ -3403,7 +3243,6 @@ requires-dist = [ { name = "beautifulsoup4", specifier = "==4.12.3" }, { name = "bert-score", specifier = "==0.3.13" }, { name = "bittensor", specifier = "==9.1.0" }, - { name = "bittensor-cli", specifier = "==9.1.0" }, { name = "bt-ddos-shield-client", specifier = "==0.1.6" }, { name = "bt-decode", specifier = "==0.5.0" }, { name = "clip", git = "https://github.com/openai/CLIP.git" }, From 383fe3e48db7bd66eaef1f614e2702bc2b4320b2 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Wed, 19 Mar 2025 21:23:38 -0500 Subject: [PATCH 552/554] chore: adjust params --- webgenie/challenges/challenge.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/challenges/challenge.py b/webgenie/challenges/challenge.py index c09c1b37..717ed701 100644 --- a/webgenie/challenges/challenge.py +++ b/webgenie/challenges/challenge.py @@ -43,7 +43,7 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] seo_scores = scores[SEO_METRIC_NAME] - aggregated_scores = np.where(accuracy_scores > 0.9, seo_scores, 0) + aggregated_scores = np.where(accuracy_scores > 0.85, seo_scores, 0) return aggregated_scores, scores @@ -54,7 +54,7 @@ async def calculate_scores(self) -> dict[str, np.ndarray]: scores = await self.task.generator.calculate_scores(self.task, self.solutions) accuracy_scores = scores[ACCURACY_METRIC_NAME] quality_scores = scores[QUALITY_METRIC_NAME] - aggregated_scores = np.where(accuracy_scores > 0.9, quality_scores, 0) + aggregated_scores = np.where(accuracy_scores > 0.85, quality_scores, 0) return aggregated_scores, scores From 36576b9457c3f534c3d2cd5543ac7f3dcee21a06 Mon Sep 17 00:00:00 2001 From: pycorn0729 Date: Fri, 21 Mar 2025 14:24:31 -0700 Subject: [PATCH 553/554] chore: unuse ddos --- webgenie/base/neuron.py | 2 +- webgenie/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webgenie/base/neuron.py b/webgenie/base/neuron.py index 94a14fa5..ed4f5b69 100644 --- a/webgenie/base/neuron.py +++ b/webgenie/base/neuron.py @@ -91,7 +91,7 @@ def __init__(self, config=None): else: self.wallet = bt.wallet(config=self.config) self.subtensor = bt.subtensor(config=self.config) - self.metagraph = ShieldMetagraph(self.wallet, self.config.netuid, subtensor=self.subtensor) + self.metagraph = self.subtensor.metagraph(self.config.netuid) bt.logging.info(f"Wallet: {self.wallet}") bt.logging.info(f"Subtensor: {self.subtensor}") diff --git a/webgenie/constants.py b/webgenie/constants.py index bfd24e63..3ccd2f33 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.3.1" # version +__VERSION__ = "1.3.2" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) From fa31217110bcf7f9e19c3b33e6017ee804b5ca16 Mon Sep 17 00:00:00 2001 From: sangar-1028 Date: Mon, 24 Mar 2025 08:10:46 -0500 Subject: [PATCH 554/554] chore: fix get_winner logic --- neurons/validators/score_manager.py | 13 +++++-------- webgenie/constants.py | 4 ++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/neurons/validators/score_manager.py b/neurons/validators/score_manager.py index 062a925e..c7be187a 100644 --- a/neurons/validators/score_manager.py +++ b/neurons/validators/score_manager.py @@ -171,14 +171,11 @@ def get_winner(self, total_scores: np.ndarray, solved_tasks: np.ndarray, number_ avg_scores = np.zeros(self.neuron.metagraph.n, dtype=np.float32) for uid in range(self.neuron.metagraph.n): if self.is_blacklisted(uid): - continue - - avg_scores[uid] = total_scores[uid] / number_of_tasks - - # if solved_tasks[uid] >= max(1, number_of_tasks - MAX_UNANSWERED_TASKS): - # avg_scores[uid] = total_scores[uid] / solved_tasks[uid] - # else: - # avg_scores[uid] = 0 + continue + if solved_tasks[uid] >= max(1, number_of_tasks - MAX_UNANSWERED_TASKS): + avg_scores[uid] = total_scores[uid] / solved_tasks[uid] + else: + avg_scores[uid] = 0 winner = np.argmax(avg_scores) if max(avg_scores) > 0 else -1 return winner diff --git a/webgenie/constants.py b/webgenie/constants.py index 3ccd2f33..7cfbca2a 100644 --- a/webgenie/constants.py +++ b/webgenie/constants.py @@ -3,7 +3,7 @@ import psutil # Change this value when updating your code base. # Define the version of the webgenie. -__VERSION__ = "1.3.2" # version +__VERSION__ = "1.3.3" # version SPEC_VERSION = ( (1000 * int(__VERSION__.split(".")[0])) @@ -89,7 +89,7 @@ MAX_NUMBER_OF_TASKS_PER_SESSION = 18 # max number of tasks per session -MAX_UNANSWERED_TASKS = 3 # max unanswered tasks +MAX_UNANSWERED_TASKS = 4 # max unanswered tasks NUMBER_OF_CONCURRENT_WORKERS = max( 1,
+ +