initial commit
This commit is contained in:
commit
56531cbd18
39 changed files with 2245 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
__pycache__/
|
||||
.idea/
|
||||
.env
|
||||
venv/
|
||||
.mypy_cache/
|
||||
backend/app/log/
|
||||
backend/app/alembic/versions/
|
||||
backend/app/static/media/
|
||||
.ruff_cache/
|
||||
.pytest_cache/
|
||||
38
examples/data/make_sqlite_db.py
Normal file
38
examples/data/make_sqlite_db.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import csv
|
||||
import sqlite3
|
||||
|
||||
# Path to the CSV file
|
||||
csv_file_path = './synthetic_people_data.csv'
|
||||
|
||||
# Connect to a SQLite database (will be created if it doesn't exist)
|
||||
conn = sqlite3.connect('people.sqlite')
|
||||
cur = conn.cursor()
|
||||
|
||||
# Create a table
|
||||
cur.execute('''
|
||||
CREATE TABLE IF NOT EXISTS people (
|
||||
id INTEGER PRIMARY KEY,
|
||||
Name TEXT,
|
||||
Age INTEGER,
|
||||
Location TEXT,
|
||||
Occupation TEXT,
|
||||
Email TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
# Read data from the CSV file
|
||||
with open(csv_file_path, 'r') as csvfile:
|
||||
csvreader = csv.reader(csvfile)
|
||||
next(csvreader) # Skip the header row
|
||||
for row in csvreader:
|
||||
# Insert each row into the database
|
||||
cur.execute('''
|
||||
INSERT INTO people (Name, Age, Location, Occupation, Email)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
''', row)
|
||||
|
||||
# Commit changes and close the connection
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print('Data imported into SQLite database successfully.')
|
||||
BIN
examples/data/people.sqlite
Normal file
BIN
examples/data/people.sqlite
Normal file
Binary file not shown.
101
examples/data/synthetic_people_data.csv
Normal file
101
examples/data/synthetic_people_data.csv
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
Name,Age,Location,Occupation,Email
|
||||
Christopher Sharp,63,"Los Angeles, CA",Paediatric nurse,grantanderson@simmons-jackson.com
|
||||
Sharon Fernandez,69,"San Diego, CA",Cabin crew,christysanders@white-love.net
|
||||
Rachel Thornton,76,"San Jose, CA","Engineer, electrical",douglas57@hotmail.com
|
||||
Richard Moore,90,"San Jose, CA",Audiological scientist,myersdaniel@williams-gutierrez.org
|
||||
Kevin Fletcher,22,"San Jose, CA",Barrister's clerk,hjohnson@hotmail.com
|
||||
John Beasley,57,"San Jose, CA",Product/process development scientist,thompsonpamela@gmail.com
|
||||
Luis Anderson,88,"San Antonio, TX","Engineer, biomedical",phale@gmail.com
|
||||
Barbara Hodges,84,"Dallas, TX",Amenity horticulturist,maciaskatherine@knight.com
|
||||
Carla Weber,95,"Chicago, IL",Community pharmacist,amycampbell@stanley.info
|
||||
Sarah Little,75,"Chicago, IL",Horticultural consultant,jenniferbrown@carroll.org
|
||||
Jessica Greer,93,"San Antonio, TX","Engineer, broadcasting (operations)",xnelson@day-williams.com
|
||||
Brent Arnold,27,"San Antonio, TX",Lobbyist,pjacobs@hotmail.com
|
||||
Natalie Trujillo,59,"Houston, TX",Dramatherapist,john40@ashley.com
|
||||
Megan Gonzalez,87,"Houston, TX","Education officer, community",jacqueline01@hotmail.com
|
||||
Steven Davila,60,"Dallas, TX",Psychiatrist,denisedominguez@hotmail.com
|
||||
Joseph Parker,44,"Los Angeles, CA","Scientist, product/process development",mendozamatthew@yahoo.com
|
||||
Jennifer Travis,56,"Dallas, TX",Automotive engineer,hbaker@gmail.com
|
||||
Mr. Kenneth Phillips DVM,81,"Dallas, TX",Community arts worker,kmorton@gmail.com
|
||||
Dean Moore,98,"Dallas, TX","Engineer, site",bushdenise@gmail.com
|
||||
Dr. Margaret Ford,94,"Philadelphia, PA",Stage manager,mclarke@hotmail.com
|
||||
Jennifer Morrow,65,"San Antonio, TX",Best boy,marshallrobert@hotmail.com
|
||||
Robert Rivera,45,"San Antonio, TX",Call centre manager,isaac50@russell-wagner.info
|
||||
Tammy Powell,72,"San Antonio, TX","Editor, commissioning",wsmith@hotmail.com
|
||||
John Hughes,81,"Phoenix, AZ",Holiday representative,cameron72@wright.info
|
||||
Mary Smith,50,"Phoenix, AZ",Location manager,bergdennis@yahoo.com
|
||||
Laurie Burke,79,"San Antonio, TX",Event organiser,steven32@yahoo.com
|
||||
Kevin Smith,49,"Dallas, TX",Accommodation manager,boydhannah@yahoo.com
|
||||
John Foster,80,"Houston, TX",Field seismologist,garrettmartin@dixon.com
|
||||
Ashlee Charles,63,"San Jose, CA","Editor, commissioning",rholmes@vega.info
|
||||
Cynthia Potts,34,"New York, NY","Lecturer, higher education",ulang@vasquez.org
|
||||
Jesus Brady,61,"Dallas, TX",Forensic scientist,gonzalezbrenda@gmail.com
|
||||
Jamie Smith,59,"Philadelphia, PA",Retail buyer,taylorthomas@soto.com
|
||||
Abigail Hicks,34,"Chicago, IL","Therapist, speech and language",lori49@gmail.com
|
||||
Thomas Rodriguez,63,"San Jose, CA",Retail banker,erica79@glover-forbes.com
|
||||
Eric Wilkinson,75,"San Antonio, TX",Art gallery manager,pnixon@callahan.com
|
||||
Matthew Cantu,39,"Los Angeles, CA",Chief Financial Officer,reynoldsgregory@gmail.com
|
||||
Chloe Chapman,35,"Philadelphia, PA",Production manager,bdavis@hotmail.com
|
||||
Sophia Jones,48,"San Jose, CA",Camera operator,gsantos@gmail.com
|
||||
David Young,56,"Houston, TX","Psychologist, clinical",leslie55@hotmail.com
|
||||
Stacy Morris,72,"Los Angeles, CA",Logistics and distribution manager,qhansen@yahoo.com
|
||||
Julie Lin,56,"Los Angeles, CA",Social worker,gzimmerman@gmail.com
|
||||
Melinda White,92,"Philadelphia, PA",Probation officer,pamela45@yahoo.com
|
||||
Elizabeth Daniel,95,"Dallas, TX",Sports coach,lauriegriffin@fletcher-garcia.com
|
||||
Stephanie Fuller,86,"San Antonio, TX",Heritage manager,benjamin88@gmail.com
|
||||
Peter Salinas,32,"Philadelphia, PA",Building control surveyor,charles57@martin-lane.com
|
||||
Michael Hart,83,"Dallas, TX",Purchasing manager,john23@hotmail.com
|
||||
Roger Oconnor,75,"Chicago, IL",Arts administrator,heather19@yahoo.com
|
||||
John Stanley,25,"San Jose, CA",Training and development officer,kyle24@gmail.com
|
||||
William Hatfield,39,"Los Angeles, CA",Adult guidance worker,ajohnson@hoover.net
|
||||
Brent Stout,72,"San Diego, CA",Fish farm manager,marcus27@richardson-jones.com
|
||||
Mitchell Jackson,66,"New York, NY",Quantity surveyor,nclark@hotmail.com
|
||||
Jake Davila,28,"San Diego, CA",Quality manager,victorcastro@yahoo.com
|
||||
Doris Hodge,72,"San Antonio, TX",Computer games developer,watersscott@hotmail.com
|
||||
Erica Harris,88,"San Diego, CA",Secondary school teacher,achurch@burton-mejia.com
|
||||
Joseph Hansen,59,"Dallas, TX",Medical secretary,olsenricardo@yahoo.com
|
||||
Barbara Stout,72,"San Jose, CA",Clinical biochemist,victoriabrewer@allen.com
|
||||
Sandra Gonzalez,73,"Chicago, IL","Engineer, maintenance",james02@snyder.com
|
||||
Matthew Jones,43,"Philadelphia, PA",Electronics engineer,craigperez@moore.biz
|
||||
Terry Nichols,93,"Philadelphia, PA",Outdoor activities/education manager,rogersnicole@chang-stanley.com
|
||||
James Berry,94,"San Jose, CA","Buyer, industrial",serranochristopher@harrison-williams.org
|
||||
Angela Sandoval,71,"Phoenix, AZ","Optician, dispensing",hurleyjennifer@williams.org
|
||||
John Hawkins,38,"San Antonio, TX","Programmer, systems",johnsondonna@miller.com
|
||||
Thomas Cooper,45,"San Antonio, TX",Advertising art director,gonzalezjeffrey@nelson-reynolds.com
|
||||
Olivia Owens,86,"San Diego, CA","Buyer, retail",robertsontyler@edwards-hansen.com
|
||||
Kelli Rodriguez,34,"Philadelphia, PA","Therapist, horticultural",tara21@gmail.com
|
||||
Jeanette Briggs,61,"Philadelphia, PA",Tourism officer,alexander42@jones.com
|
||||
Kimberly Perry,71,"Dallas, TX",Firefighter,conwaymichelle@duran.org
|
||||
Jorge Brandt,74,"Phoenix, AZ","Programmer, systems",veronicafigueroa@weber-johnson.com
|
||||
Brent Hayes,71,"New York, NY","Conservator, furniture",sgallagher@cook-marshall.net
|
||||
Julie Blake,99,"San Diego, CA",Colour technologist,chriswilson@hawkins.com
|
||||
David Jackson MD,33,"New York, NY",Arts administrator,melissa67@davila-johnson.com
|
||||
Anna Nunez,28,"Dallas, TX",Plant breeder/geneticist,lhernandez@rodriguez.org
|
||||
Caleb Adams,38,"San Diego, CA",Podiatrist,ugonzales@mitchell-lopez.info
|
||||
Morgan Haas,88,"San Jose, CA",Toxicologist,michael10@yahoo.com
|
||||
Emily Baker,22,"Phoenix, AZ",Equities trader,bherring@hotmail.com
|
||||
Madison Edwards,33,"Houston, TX",Field seismologist,shawn27@webb.com
|
||||
Edwin Conley,63,"San Jose, CA",Communications engineer,christinatapia@mendez-hughes.com
|
||||
Meredith Randall,57,"Los Angeles, CA",Exercise physiologist,andrewneal@yahoo.com
|
||||
George Ramos,95,"New York, NY",Energy engineer,charles02@hotmail.com
|
||||
Harry Watts,77,"San Diego, CA","Copywriter, advertising",david08@warner.com
|
||||
Lisa Lee,77,"Philadelphia, PA",Tourism officer,alishapratt@gmail.com
|
||||
John Watson,19,"Houston, TX",Chief Executive Officer,robynhernandez@gmail.com
|
||||
Joseph Sanchez,20,"Chicago, IL",Phytotherapist,allisonlauren@mcintyre.com
|
||||
Seth Young,18,"Chicago, IL",Economist,elizabethbrown@hall-heath.com
|
||||
Luis Gentry,55,"Houston, TX",Technical author,cpowers@williams.org
|
||||
Dawn Jones,39,"Dallas, TX","Designer, television/film set",amber85@jackson-lee.com
|
||||
David Davila,81,"Los Angeles, CA",Adult nurse,melissawilson@cantrell-reyes.org
|
||||
Amy Donaldson,49,"San Diego, CA",Computer games developer,abigailgomez@gmail.com
|
||||
Christine Burke,84,"Philadelphia, PA",Energy manager,obrown@smith-mckinney.org
|
||||
Krista Gordon,83,"Chicago, IL",Computer games developer,kelleynicole@grant.com
|
||||
Vanessa Gibson,67,"Chicago, IL",Armed forces operational officer,nicholasreid@wright-jones.com
|
||||
Wendy Palmer,47,"Houston, TX","Engineer, maintenance (IT)",ymeyer@hotmail.com
|
||||
Alicia Bass,86,"Houston, TX",Applications developer,joneslinda@adams-conley.com
|
||||
Jason Lyons,33,"Philadelphia, PA",Newspaper journalist,kimberlysharp@ballard.info
|
||||
Travis Cohen,98,"San Antonio, TX",Music tutor,drakejohn@castro.info
|
||||
Miss Regina Bullock,72,"Houston, TX",Fisheries officer,amymercer@hotmail.com
|
||||
Kevin Johnson,80,"San Antonio, TX",Futures trader,sampsonkimberly@hotmail.com
|
||||
Mark Bailey,66,"San Jose, CA","Programmer, applications",nanderson@west-barajas.com
|
||||
Dustin Clark,91,"Dallas, TX","Teacher, early years/pre",barrettjohn@miller.com
|
||||
Autumn Reed,29,"Phoenix, AZ",Ophthalmologist,hannah08@hotmail.com
|
||||
|
24
examples/data/test_prompt.py
Normal file
24
examples/data/test_prompt.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
from sqlalchemy import create_engine, MetaData
|
||||
|
||||
# Replace 'your_database.db' with your actual SQLite database file
|
||||
database_path = 'sqlite:///people.sqlite'
|
||||
engine = create_engine(database_path)
|
||||
metadata = MetaData()
|
||||
|
||||
# Reflect the tables in the database
|
||||
metadata.reflect(bind=engine)
|
||||
|
||||
# Iterate over all tables and print their descriptions
|
||||
for table_name in metadata.tables:
|
||||
print(f"Table: {table_name}")
|
||||
table = metadata.tables[table_name]
|
||||
|
||||
# Iterate over columns in the table and print details
|
||||
for column in table.c:
|
||||
print(f"Column: {column.name}")
|
||||
print(f"Type: {column.type}")
|
||||
print(f"Nullable: {column.nullable}")
|
||||
print(f"Primary Key: {column.primary_key}")
|
||||
print(f"---------------------")
|
||||
|
||||
print(f"{'='*20}\n")
|
||||
12
examples/gmail/pack.lock.toml
Normal file
12
examples/gmail/pack.lock.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[pack]
|
||||
name = "example"
|
||||
description = "A simple actor that sends an email using the Gmail API."
|
||||
version = "0.1.0"
|
||||
author = "Sam Partee"
|
||||
email = "sam@partee.io"
|
||||
|
||||
[depends]
|
||||
|
||||
[tools]
|
||||
SendEmail = "gmailer.send_email@0.1.0"
|
||||
ReadEmail = "gmailer.read_email@0.1.0"
|
||||
11
examples/gmail/pack.toml
Normal file
11
examples/gmail/pack.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
[pack]
|
||||
name = "example"
|
||||
description = "A simple actor that sends an email using the Gmail API."
|
||||
version = "0.1.0"
|
||||
author = "Sam Partee"
|
||||
email = "sam@partee.io"
|
||||
|
||||
[modules]
|
||||
gmailer = "0.1.0"
|
||||
92
examples/gmail/tools/gmailer.py
Normal file
92
examples/gmail/tools/gmailer.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
|
||||
import smtplib
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
import imaplib
|
||||
import email
|
||||
from email.header import decode_header
|
||||
from pydantic import BaseModel
|
||||
import pandas as pd
|
||||
|
||||
|
||||
from toolserve.sdk import Param, Secret, tool
|
||||
|
||||
|
||||
@tool
|
||||
def send_email(
|
||||
sender_email: Param(str, "Email address of the sender"),
|
||||
sender_password: Secret(str, "gmail_password"),
|
||||
recipient_email: Param(str, "Email address of the recipient"),
|
||||
subject: Param(str, "Subject of the email"),
|
||||
body: Param(str, "Body of the email"),
|
||||
server: Secret(str, "gmail_stmp_server"),
|
||||
port: Secret(str, "gmail_smtp_port")
|
||||
):
|
||||
"""Send an email via gmail SMTP server"""
|
||||
|
||||
message = MIMEMultipart()
|
||||
message['From'] = sender_email
|
||||
message['To'] = recipient_email
|
||||
message['Subject'] = subject
|
||||
message.attach(MIMEText(body, 'plain'))
|
||||
|
||||
server = smtplib.SMTP(server, port)
|
||||
server.starttls()
|
||||
server.login(sender_email, sender_password)
|
||||
print("Logged in to SMTP server")
|
||||
|
||||
server.send_message(message)
|
||||
server.quit()
|
||||
|
||||
print(f"Email sent to {recipient_email}")
|
||||
|
||||
|
||||
|
||||
@tool
|
||||
def read_email(
|
||||
email: Param(str, "Email address of the recipient"),
|
||||
password: Secret(str, "gmail_password"),
|
||||
server: Secret(str, "gmail_stmp_server"),
|
||||
port: Secret(int, "gmail_smtp_port")
|
||||
) -> Param(str, "JSON dataframe of List of emails"):
|
||||
"""Read emails from a Gmail account"""
|
||||
|
||||
|
||||
# Connect to the Gmail IMAP server
|
||||
mail = imaplib.IMAP4_SSL(server)
|
||||
mail.login(email, password)
|
||||
mail.select("inbox") # connect to inbox.
|
||||
|
||||
result, data = mail.search(None, "ALL")
|
||||
email_ids = data[0].split()
|
||||
|
||||
emails = []
|
||||
|
||||
for email_id in email_ids:
|
||||
result, data = mail.fetch(email_id, "(RFC822)")
|
||||
raw_email = data[0][1]
|
||||
msg = email.message_from_bytes(raw_email)
|
||||
|
||||
email_details = {
|
||||
"from": msg["From"],
|
||||
"to": msg["To"],
|
||||
"subject": decode_header(msg["Subject"])[0][0],
|
||||
"date": msg["Date"]
|
||||
}
|
||||
|
||||
if msg.is_multipart():
|
||||
for part in msg.walk():
|
||||
if part.get_content_type() == "text/plain":
|
||||
email_details["body"] = part.get_payload(decode=True).decode()
|
||||
else:
|
||||
email_details["body"] = msg.get_payload(decode=True).decode()
|
||||
|
||||
emails.append(email_details)
|
||||
|
||||
mail.close()
|
||||
mail.logout()
|
||||
|
||||
return pd.DataFrame(emails).to_json()
|
||||
|
||||
|
||||
|
||||
0
toolserve/README.md
Normal file
0
toolserve/README.md
Normal file
663
toolserve/poetry.lock
generated
Normal file
663
toolserve/poetry.lock
generated
Normal file
|
|
@ -0,0 +1,663 @@
|
|||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.6.0"
|
||||
description = "Reusable constraint types to use with typing.Annotated"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
|
||||
{file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.3.0"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"},
|
||||
{file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
||||
trio = ["trio (>=0.23)"]
|
||||
|
||||
[[package]]
|
||||
name = "async-timeout"
|
||||
version = "4.0.3"
|
||||
description = "Timeout context manager for asyncio programs"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
|
||||
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.7"
|
||||
description = "Composable command line interface toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
|
||||
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dnspython"
|
||||
version = "2.6.1"
|
||||
description = "DNS toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"},
|
||||
{file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"]
|
||||
dnssec = ["cryptography (>=41)"]
|
||||
doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"]
|
||||
doq = ["aioquic (>=0.9.25)"]
|
||||
idna = ["idna (>=3.6)"]
|
||||
trio = ["trio (>=0.23)"]
|
||||
wmi = ["wmi (>=1.5.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "email-validator"
|
||||
version = "2.1.1"
|
||||
description = "A robust email address syntax and deliverability validation library."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "email_validator-2.1.1-py3-none-any.whl", hash = "sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05"},
|
||||
{file = "email_validator-2.1.1.tar.gz", hash = "sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
dnspython = ">=2.0.0"
|
||||
idna = ">=2.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.0"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
|
||||
{file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.110.1"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "fastapi-0.110.1-py3-none-any.whl", hash = "sha256:5df913203c482f820d31f48e635e022f8cbfe7350e4830ef05a3163925b1addc"},
|
||||
{file = "fastapi-0.110.1.tar.gz", hash = "sha256:6feac43ec359dfe4f45b2c18ec8c94edb8dc2dfc461d417d9e626590c071baad"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
||||
starlette = ">=0.37.2,<0.38.0"
|
||||
typing-extensions = ">=4.8.0"
|
||||
|
||||
[package.extras]
|
||||
all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
||||
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.7"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
|
||||
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loguru"
|
||||
version = "0.7.2"
|
||||
description = "Python logging made (stupidly) simple"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"},
|
||||
{file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
|
||||
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "3.0.0"
|
||||
description = "Python port of markdown-it. Markdown parsing, done right!"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
|
||||
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mdurl = ">=0.1,<1.0"
|
||||
|
||||
[package.extras]
|
||||
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
|
||||
code-style = ["pre-commit (>=3.0,<4.0)"]
|
||||
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
|
||||
linkify = ["linkify-it-py (>=1,<3)"]
|
||||
plugins = ["mdit-py-plugins"]
|
||||
profiling = ["gprof2dot"]
|
||||
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
|
||||
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
description = "Markdown URL utilities"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
|
||||
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "msgpack"
|
||||
version = "1.0.8"
|
||||
description = "MessagePack serializer"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"},
|
||||
{file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"},
|
||||
{file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"},
|
||||
{file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"},
|
||||
{file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"},
|
||||
{file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"},
|
||||
{file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"},
|
||||
{file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"},
|
||||
{file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"},
|
||||
{file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"},
|
||||
{file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"},
|
||||
{file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"},
|
||||
{file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"},
|
||||
{file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"},
|
||||
{file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"},
|
||||
{file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"},
|
||||
{file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"},
|
||||
{file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"},
|
||||
{file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"},
|
||||
{file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"},
|
||||
{file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"},
|
||||
{file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"},
|
||||
{file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"},
|
||||
{file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"},
|
||||
{file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"},
|
||||
{file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"},
|
||||
{file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"},
|
||||
{file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"},
|
||||
{file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"},
|
||||
{file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"},
|
||||
{file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"},
|
||||
{file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"},
|
||||
{file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"},
|
||||
{file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"},
|
||||
{file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"},
|
||||
{file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"},
|
||||
{file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"},
|
||||
{file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"},
|
||||
{file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"},
|
||||
{file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"},
|
||||
{file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"},
|
||||
{file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"},
|
||||
{file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"},
|
||||
{file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"},
|
||||
{file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"},
|
||||
{file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"},
|
||||
{file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"},
|
||||
{file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"},
|
||||
{file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"},
|
||||
{file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"},
|
||||
{file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"},
|
||||
{file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"},
|
||||
{file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"},
|
||||
{file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"},
|
||||
{file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"},
|
||||
{file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "msgspec"
|
||||
version = "0.18.6"
|
||||
description = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "msgspec-0.18.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77f30b0234eceeff0f651119b9821ce80949b4d667ad38f3bfed0d0ebf9d6d8f"},
|
||||
{file = "msgspec-0.18.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a76b60e501b3932782a9da039bd1cd552b7d8dec54ce38332b87136c64852dd"},
|
||||
{file = "msgspec-0.18.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06acbd6edf175bee0e36295d6b0302c6de3aaf61246b46f9549ca0041a9d7177"},
|
||||
{file = "msgspec-0.18.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40a4df891676d9c28a67c2cc39947c33de516335680d1316a89e8f7218660410"},
|
||||
{file = "msgspec-0.18.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a6896f4cd5b4b7d688018805520769a8446df911eb93b421c6c68155cdf9dd5a"},
|
||||
{file = "msgspec-0.18.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3ac4dd63fd5309dd42a8c8c36c1563531069152be7819518be0a9d03be9788e4"},
|
||||
{file = "msgspec-0.18.6-cp310-cp310-win_amd64.whl", hash = "sha256:fda4c357145cf0b760000c4ad597e19b53adf01382b711f281720a10a0fe72b7"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e77e56ffe2701e83a96e35770c6adb655ffc074d530018d1b584a8e635b4f36f"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5351afb216b743df4b6b147691523697ff3a2fc5f3d54f771e91219f5c23aaa"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3232fabacef86fe8323cecbe99abbc5c02f7698e3f5f2e248e3480b66a3596b"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b524df6ea9998bbc99ea6ee4d0276a101bcc1aa8d14887bb823914d9f60d07"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37f67c1d81272131895bb20d388dd8d341390acd0e192a55ab02d4d6468b434c"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0feb7a03d971c1c0353de1a8fe30bb6579c2dc5ccf29b5f7c7ab01172010492"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-win_amd64.whl", hash = "sha256:41cf758d3f40428c235c0f27bc6f322d43063bc32da7b9643e3f805c21ed57b4"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d86f5071fe33e19500920333c11e2267a31942d18fed4d9de5bc2fbab267d28c"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce13981bfa06f5eb126a3a5a38b1976bddb49a36e4f46d8e6edecf33ccf11df1"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97dec6932ad5e3ee1e3c14718638ba333befc45e0661caa57033cd4cc489466"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad237100393f637b297926cae1868b0d500f764ccd2f0623a380e2bcfb2809ca"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db1d8626748fa5d29bbd15da58b2d73af25b10aa98abf85aab8028119188ed57"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d70cb3d00d9f4de14d0b31d38dfe60c88ae16f3182988246a9861259c6722af6"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-win_amd64.whl", hash = "sha256:1003c20bfe9c6114cc16ea5db9c5466e49fae3d7f5e2e59cb70693190ad34da0"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f7d9faed6dfff654a9ca7d9b0068456517f63dbc3aa704a527f493b9200b210a"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9da21f804c1a1471f26d32b5d9bc0480450ea77fbb8d9db431463ab64aaac2cf"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46eb2f6b22b0e61c137e65795b97dc515860bf6ec761d8fb65fdb62aa094ba61"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8355b55c80ac3e04885d72db515817d9fbb0def3bab936bba104e99ad22cf46"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9080eb12b8f59e177bd1eb5c21e24dd2ba2fa88a1dbc9a98e05ad7779b54c681"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cc001cf39becf8d2dcd3f413a4797c55009b3a3cdbf78a8bf5a7ca8fdb76032c"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-win_amd64.whl", hash = "sha256:fac5834e14ac4da1fca373753e0c4ec9c8069d1fe5f534fa5208453b6065d5be"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:974d3520fcc6b824a6dedbdf2b411df31a73e6e7414301abac62e6b8d03791b4"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fd62e5818731a66aaa8e9b0a1e5543dc979a46278da01e85c3c9a1a4f047ef7e"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7481355a1adcf1f08dedd9311193c674ffb8bf7b79314b4314752b89a2cf7f1c"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6aa85198f8f154cf35d6f979998f6dadd3dc46a8a8c714632f53f5d65b315c07"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e24539b25c85c8f0597274f11061c102ad6b0c56af053373ba4629772b407be"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c61ee4d3be03ea9cd089f7c8e36158786cd06e51fbb62529276452bbf2d52ece"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-win_amd64.whl", hash = "sha256:b5c390b0b0b7da879520d4ae26044d74aeee5144f83087eb7842ba59c02bc090"},
|
||||
{file = "msgspec-0.18.6.tar.gz", hash = "sha256:a59fc3b4fcdb972d09138cb516dbde600c99d07c38fd9372a6ef500d2d031b4e"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["attrs", "coverage", "furo", "gcovr", "ipython", "msgpack", "mypy", "pre-commit", "pyright", "pytest", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "tomli", "tomli-w"]
|
||||
doc = ["furo", "ipython", "sphinx", "sphinx-copybutton", "sphinx-design"]
|
||||
test = ["attrs", "msgpack", "mypy", "pyright", "pytest", "pyyaml", "tomli", "tomli-w"]
|
||||
toml = ["tomli", "tomli-w"]
|
||||
yaml = ["pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.7.0"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic-2.7.0-py3-none-any.whl", hash = "sha256:9dee74a271705f14f9a1567671d144a851c675b072736f0a7b2608fd9e495352"},
|
||||
{file = "pydantic-2.7.0.tar.gz", hash = "sha256:b5ecdd42262ca2462e2624793551e80911a1e989f462910bb81aef974b4bb383"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
annotated-types = ">=0.4.0"
|
||||
email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""}
|
||||
pydantic-core = "2.18.1"
|
||||
typing-extensions = ">=4.6.1"
|
||||
|
||||
[package.extras]
|
||||
email = ["email-validator (>=2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.18.1"
|
||||
description = "Core functionality for Pydantic validation and serialization"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ee9cf33e7fe14243f5ca6977658eb7d1042caaa66847daacbd2117adb258b226"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b7bbb97d82659ac8b37450c60ff2e9f97e4eb0f8a8a3645a5568b9334b08b50"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4249b579e75094f7e9bb4bd28231acf55e308bf686b952f43100a5a0be394c"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d0491006a6ad20507aec2be72e7831a42efc93193d2402018007ff827dc62926"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ae80f72bb7a3e397ab37b53a2b49c62cc5496412e71bc4f1277620a7ce3f52b"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58aca931bef83217fca7a390e0486ae327c4af9c3e941adb75f8772f8eeb03a1"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1be91ad664fc9245404a789d60cba1e91c26b1454ba136d2a1bf0c2ac0c0505a"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:667880321e916a8920ef49f5d50e7983792cf59f3b6079f3c9dac2b88a311d17"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f7054fdc556f5421f01e39cbb767d5ec5c1139ea98c3e5b350e02e62201740c7"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:030e4f9516f9947f38179249778709a460a3adb516bf39b5eb9066fcfe43d0e6"},
|
||||
{file = "pydantic_core-2.18.1-cp310-none-win32.whl", hash = "sha256:2e91711e36e229978d92642bfc3546333a9127ecebb3f2761372e096395fc649"},
|
||||
{file = "pydantic_core-2.18.1-cp310-none-win_amd64.whl", hash = "sha256:9a29726f91c6cb390b3c2338f0df5cd3e216ad7a938762d11c994bb37552edb0"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9ece8a49696669d483d206b4474c367852c44815fca23ac4e48b72b339807f80"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a5d83efc109ceddb99abd2c1316298ced2adb4570410defe766851a804fcd5b"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7973c381283783cd1043a8c8f61ea5ce7a3a58b0369f0ee0ee975eaf2f2a1b"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54c7375c62190a7845091f521add19b0f026bcf6ae674bdb89f296972272e86d"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd63cec4e26e790b70544ae5cc48d11b515b09e05fdd5eff12e3195f54b8a586"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:561cf62c8a3498406495cfc49eee086ed2bb186d08bcc65812b75fda42c38294"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68717c38a68e37af87c4da20e08f3e27d7e4212e99e96c3d875fbf3f4812abfc"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d5728e93d28a3c63ee513d9ffbac9c5989de8c76e049dbcb5bfe4b923a9739d"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f0f17814c505f07806e22b28856c59ac80cee7dd0fbb152aed273e116378f519"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d816f44a51ba5175394bc6c7879ca0bd2be560b2c9e9f3411ef3a4cbe644c2e9"},
|
||||
{file = "pydantic_core-2.18.1-cp311-none-win32.whl", hash = "sha256:09f03dfc0ef8c22622eaa8608caa4a1e189cfb83ce847045eca34f690895eccb"},
|
||||
{file = "pydantic_core-2.18.1-cp311-none-win_amd64.whl", hash = "sha256:27f1009dc292f3b7ca77feb3571c537276b9aad5dd4efb471ac88a8bd09024e9"},
|
||||
{file = "pydantic_core-2.18.1-cp311-none-win_arm64.whl", hash = "sha256:48dd883db92e92519201f2b01cafa881e5f7125666141a49ffba8b9facc072b0"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b6b0e4912030c6f28bcb72b9ebe4989d6dc2eebcd2a9cdc35fefc38052dd4fe8"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3202a429fe825b699c57892d4371c74cc3456d8d71b7f35d6028c96dfecad31"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3982b0a32d0a88b3907e4b0dc36809fda477f0757c59a505d4e9b455f384b8b"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25595ac311f20e5324d1941909b0d12933f1fd2171075fcff763e90f43e92a0d"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14fe73881cf8e4cbdaded8ca0aa671635b597e42447fec7060d0868b52d074e6"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca976884ce34070799e4dfc6fbd68cb1d181db1eefe4a3a94798ddfb34b8867f"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684d840d2c9ec5de9cb397fcb3f36d5ebb6fa0d94734f9886032dd796c1ead06"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:54764c083bbe0264f0f746cefcded6cb08fbbaaf1ad1d78fb8a4c30cff999a90"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:201713f2f462e5c015b343e86e68bd8a530a4f76609b33d8f0ec65d2b921712a"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd1a9edb9dd9d79fbeac1ea1f9a8dd527a6113b18d2e9bcc0d541d308dae639b"},
|
||||
{file = "pydantic_core-2.18.1-cp312-none-win32.whl", hash = "sha256:d5e6b7155b8197b329dc787356cfd2684c9d6a6b1a197f6bbf45f5555a98d411"},
|
||||
{file = "pydantic_core-2.18.1-cp312-none-win_amd64.whl", hash = "sha256:9376d83d686ec62e8b19c0ac3bf8d28d8a5981d0df290196fb6ef24d8a26f0d6"},
|
||||
{file = "pydantic_core-2.18.1-cp312-none-win_arm64.whl", hash = "sha256:c562b49c96906b4029b5685075fe1ebd3b5cc2601dfa0b9e16c2c09d6cbce048"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3e352f0191d99fe617371096845070dee295444979efb8f27ad941227de6ad09"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0295d52b012cbe0d3059b1dba99159c3be55e632aae1999ab74ae2bd86a33d7"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56823a92075780582d1ffd4489a2e61d56fd3ebb4b40b713d63f96dd92d28144"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd3f79e17b56741b5177bcc36307750d50ea0698df6aa82f69c7db32d968c1c2"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38a5024de321d672a132b1834a66eeb7931959c59964b777e8f32dbe9523f6b1"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ce426ee691319d4767748c8e0895cfc56593d725594e415f274059bcf3cb76"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2adaeea59849ec0939af5c5d476935f2bab4b7f0335b0110f0f069a41024278e"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9b6431559676a1079eac0f52d6d0721fb8e3c5ba43c37bc537c8c83724031feb"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:85233abb44bc18d16e72dc05bf13848a36f363f83757541f1a97db2f8d58cfd9"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:641a018af4fe48be57a2b3d7a1f0f5dbca07c1d00951d3d7463f0ac9dac66622"},
|
||||
{file = "pydantic_core-2.18.1-cp38-none-win32.whl", hash = "sha256:63d7523cd95d2fde0d28dc42968ac731b5bb1e516cc56b93a50ab293f4daeaad"},
|
||||
{file = "pydantic_core-2.18.1-cp38-none-win_amd64.whl", hash = "sha256:907a4d7720abfcb1c81619863efd47c8a85d26a257a2dbebdb87c3b847df0278"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:aad17e462f42ddbef5984d70c40bfc4146c322a2da79715932cd8976317054de"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:94b9769ba435b598b547c762184bcfc4783d0d4c7771b04a3b45775c3589ca44"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80e0e57cc704a52fb1b48f16d5b2c8818da087dbee6f98d9bf19546930dc64b5"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76b86e24039c35280ceee6dce7e62945eb93a5175d43689ba98360ab31eebc4a"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a05db5013ec0ca4a32cc6433f53faa2a014ec364031408540ba858c2172bb0"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:250ae39445cb5475e483a36b1061af1bc233de3e9ad0f4f76a71b66231b07f88"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a32204489259786a923e02990249c65b0f17235073149d0033efcebe80095570"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6395a4435fa26519fd96fdccb77e9d00ddae9dd6c742309bd0b5610609ad7fb2"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2533ad2883f001efa72f3d0e733fb846710c3af6dcdd544fe5bf14fa5fe2d7db"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b560b72ed4816aee52783c66854d96157fd8175631f01ef58e894cc57c84f0f6"},
|
||||
{file = "pydantic_core-2.18.1-cp39-none-win32.whl", hash = "sha256:582cf2cead97c9e382a7f4d3b744cf0ef1a6e815e44d3aa81af3ad98762f5a9b"},
|
||||
{file = "pydantic_core-2.18.1-cp39-none-win_amd64.whl", hash = "sha256:ca71d501629d1fa50ea7fa3b08ba884fe10cefc559f5c6c8dfe9036c16e8ae89"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e178e5b66a06ec5bf51668ec0d4ac8cfb2bdcb553b2c207d58148340efd00143"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:72722ce529a76a4637a60be18bd789d8fb871e84472490ed7ddff62d5fed620d"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fe0c1ce5b129455e43f941f7a46f61f3d3861e571f2905d55cdbb8b5c6f5e2c"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4284c621f06a72ce2cb55f74ea3150113d926a6eb78ab38340c08f770eb9b4d"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a0c3e718f4e064efde68092d9d974e39572c14e56726ecfaeebbe6544521f47"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2027493cc44c23b598cfaf200936110433d9caa84e2c6cf487a83999638a96ac"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:76909849d1a6bffa5a07742294f3fa1d357dc917cb1fe7b470afbc3a7579d539"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ee7ccc7fb7e921d767f853b47814c3048c7de536663e82fbc37f5eb0d532224b"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee2794111c188548a4547eccc73a6a8527fe2af6cf25e1a4ebda2fd01cdd2e60"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a139fe9f298dc097349fb4f28c8b81cc7a202dbfba66af0e14be5cfca4ef7ce5"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d074b07a10c391fc5bbdcb37b2f16f20fcd9e51e10d01652ab298c0d07908ee2"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c69567ddbac186e8c0aadc1f324a60a564cfe25e43ef2ce81bcc4b8c3abffbae"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf1c7b78cddb5af00971ad5294a4583188bda1495b13760d9f03c9483bb6203"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2684a94fdfd1b146ff10689c6e4e815f6a01141781c493b97342cdc5b06f4d5d"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:73c1bc8a86a5c9e8721a088df234265317692d0b5cd9e86e975ce3bc3db62a59"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e60defc3c15defb70bb38dd605ff7e0fae5f6c9c7cbfe0ad7868582cb7e844a6"},
|
||||
{file = "pydantic_core-2.18.1.tar.gz", hash = "sha256:de9d3e8717560eb05e28739d1b35e4eac2e458553a52a301e51352a7ffc86a35"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-settings"
|
||||
version = "2.2.1"
|
||||
description = "Settings management using Pydantic"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic_settings-2.2.1-py3-none-any.whl", hash = "sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091"},
|
||||
{file = "pydantic_settings-2.2.1.tar.gz", hash = "sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pydantic = ">=2.3.0"
|
||||
python-dotenv = ">=0.21.0"
|
||||
|
||||
[package.extras]
|
||||
toml = ["tomli (>=2.0.1)"]
|
||||
yaml = ["pyyaml (>=6.0.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.17.2"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
|
||||
{file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
plugins = ["importlib-metadata"]
|
||||
windows-terminal = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.0.1"
|
||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
|
||||
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "5.0.3"
|
||||
description = "Python client for Redis database and key-value store"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "redis-5.0.3-py3-none-any.whl", hash = "sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d"},
|
||||
{file = "redis-5.0.3.tar.gz", hash = "sha256:4973bae7444c0fbed64a06b87446f79361cb7e4ec1538c022d696ed7a5015580"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""}
|
||||
|
||||
[package.extras]
|
||||
hiredis = ["hiredis (>=1.0.0)"]
|
||||
ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "13.7.1"
|
||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"},
|
||||
{file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
markdown-it-py = ">=2.2.0"
|
||||
pygments = ">=2.13.0,<3.0.0"
|
||||
|
||||
[package.extras]
|
||||
jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
|
||||
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.37.2"
|
||||
description = "The little ASGI library that shines."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"},
|
||||
{file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
anyio = ">=3.4.0,<5"
|
||||
|
||||
[package.extras]
|
||||
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "stdlib-list"
|
||||
version = "0.10.0"
|
||||
description = "A list of Python Standard Libraries (2.7 through 3.12)."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "stdlib_list-0.10.0-py3-none-any.whl", hash = "sha256:b3a911bc441d03e0332dd1a9e7d0870ba3bb0a542a74d7524f54fb431256e214"},
|
||||
{file = "stdlib_list-0.10.0.tar.gz", hash = "sha256:6519c50d645513ed287657bfe856d527f277331540691ddeaf77b25459964a14"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["build", "stdlib-list[doc,lint,test]"]
|
||||
doc = ["furo", "sphinx"]
|
||||
lint = ["black", "mypy", "ruff"]
|
||||
support = ["sphobjinv"]
|
||||
test = ["coverage[toml]", "pytest", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
files = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomlkit"
|
||||
version = "0.12.4"
|
||||
description = "Style preserving TOML library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"},
|
||||
{file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typer"
|
||||
version = "0.9.4"
|
||||
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "typer-0.9.4-py3-none-any.whl", hash = "sha256:aa6c4a4e2329d868b80ecbaf16f807f2b54e192209d7ac9dd42691d63f7a54eb"},
|
||||
{file = "typer-0.9.4.tar.gz", hash = "sha256:f714c2d90afae3a7929fcd72a3abb08df305e1ff61719381384211c4070af57f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.1.1,<9.0.0"
|
||||
typing-extensions = ">=3.7.4.3"
|
||||
|
||||
[package.extras]
|
||||
all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"]
|
||||
dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"]
|
||||
doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"]
|
||||
test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.971)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.11.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
|
||||
{file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.28.1"
|
||||
description = "The lightning-fast ASGI server."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "uvicorn-0.28.1-py3-none-any.whl", hash = "sha256:5162f6d652f545be91b1feeaee8180774af143965ca9dc8a47ff1dc6bafa4ad5"},
|
||||
{file = "uvicorn-0.28.1.tar.gz", hash = "sha256:08103e79d546b6cf20f67c7e5e434d2cf500a6e29b28773e407250c54fc4fa3c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.0"
|
||||
h11 = ">=0.8"
|
||||
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "win32-setctime"
|
||||
version = "1.1.0"
|
||||
description = "A small Python utility to set file creation time on Windows"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
|
||||
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "f034a070ec06e119c2f182dcfdcfeeb0c7c9bc4a5f9116e5c12f7f650f0678f0"
|
||||
30
toolserve/pyproject.toml
Normal file
30
toolserve/pyproject.toml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[tool.poetry]
|
||||
name = "toolserve"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Sam Partee <Partees21@gmail.com>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
pydantic = {extras = ["email"], version = "^2.7.0"}
|
||||
fastapi = "^0.110.0"
|
||||
redis = "^5.0.3"
|
||||
uvicorn = "^0.28.0"
|
||||
loguru = "^0.7.2"
|
||||
pydantic-settings = "^2.2.1"
|
||||
msgspec = "^0.18.6"
|
||||
msgpack = "^1.0.8"
|
||||
typer = "^0.9.0"
|
||||
rich = "^13.7.1"
|
||||
toml = "^0.10.2"
|
||||
tomlkit = "^0.12.4"
|
||||
stdlib-list = "^0.10.0"
|
||||
|
||||
|
||||
[tool.poetry.scripts]
|
||||
tool = "toolserve.cli.main:cli"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
0
toolserve/tests/__init__.py
Normal file
0
toolserve/tests/__init__.py
Normal file
0
toolserve/toolserve/__init__.py
Normal file
0
toolserve/toolserve/__init__.py
Normal file
0
toolserve/toolserve/apm/__init__.py
Normal file
0
toolserve/toolserve/apm/__init__.py
Normal file
49
toolserve/toolserve/apm/base.py
Normal file
49
toolserve/toolserve/apm/base.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
import os
|
||||
import toml
|
||||
import json
|
||||
import tomlkit
|
||||
|
||||
from pathlib import Path
|
||||
from pydantic import BaseModel, ValidationError, EmailStr, Field
|
||||
from typing import Dict, List, Optional, TypeVar, Any, Tuple, Union
|
||||
|
||||
|
||||
class PackInfo(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
version: str
|
||||
author: Optional[str]
|
||||
email: Optional[EmailStr]
|
||||
|
||||
|
||||
class ToolPack(BaseModel):
|
||||
pack: PackInfo
|
||||
depends: Optional[Dict[str, str]] = None
|
||||
tools: Optional[Dict[str, str]] = {}
|
||||
|
||||
def write_lock_file(self, pack_dir: Union[str, os.PathLike]):
|
||||
lock_file = Path(pack_dir) / 'pack.lock.toml'
|
||||
pack_dict = self.dict(by_alias=True, exclude_none=True)
|
||||
pack_ordered_dict = {
|
||||
"pack": pack_dict.get("pack"),
|
||||
"depends": pack_dict.get("depends"),
|
||||
"tools": pack_dict.get("tools"),
|
||||
}
|
||||
|
||||
# Create a tomlkit document from the ordered dictionary
|
||||
doc = tomlkit.document()
|
||||
for key, value in pack_ordered_dict.items():
|
||||
doc[key] = value
|
||||
|
||||
# Write the tomlkit document to file
|
||||
with open(lock_file, 'w') as f:
|
||||
f.write(tomlkit.dumps(doc))
|
||||
|
||||
@classmethod
|
||||
def from_lock_file(cls, pack_dir: Union[str, os.PathLike]):
|
||||
pack_dir = Path(pack_dir).resolve()
|
||||
lock_file = pack_dir / 'pack.lock.toml'
|
||||
with open(lock_file, 'r') as f:
|
||||
data = toml.load(f)
|
||||
return cls(**data)
|
||||
76
toolserve/toolserve/apm/pack.py
Normal file
76
toolserve/toolserve/apm/pack.py
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import os
|
||||
import json
|
||||
import toml
|
||||
import shutil
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Dict, Union
|
||||
from pydantic import BaseModel, Field, ValidationError, EmailStr
|
||||
|
||||
from toolserve.apm.base import PackInfo, ToolPack
|
||||
from toolserve.apm.parse import get_tools_from_file
|
||||
from toolserve.utils import snake_to_camel
|
||||
|
||||
class Packer:
|
||||
|
||||
def __init__(self, pack_dir: Union[str, os.PathLike]):
|
||||
self.pack_dir = Path(pack_dir).resolve()
|
||||
self.tools_dir = self.pack_dir / 'tools'
|
||||
# Load the action pack configuration from a TOML file
|
||||
try:
|
||||
with open(self.pack_dir / 'pack.toml', 'r') as f:
|
||||
pack_data = toml.load(f)
|
||||
|
||||
self.pack = PackInfo(**pack_data['pack'])
|
||||
self.modules = pack_data['modules']
|
||||
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(f"No 'pack.toml' found in {self.tools_dir}")
|
||||
except (toml.TomlDecodeError, ValidationError) as e:
|
||||
raise ValueError(f"Invalid 'pack.toml' format: {e}")
|
||||
|
||||
self.tools = self.load_tools()
|
||||
self.depends = {} # TODO
|
||||
self.packs = [] # TODO
|
||||
|
||||
|
||||
def load_tools(self) -> Dict[str, str]:
|
||||
tools = {}
|
||||
for tool_file in self.tools_dir.rglob('*.py'):
|
||||
if '__init__.py' in tool_file.name:
|
||||
continue
|
||||
try:
|
||||
module = tool_file.stem
|
||||
version = self.modules.get(module, "latest")
|
||||
|
||||
found_tools = get_tools_from_file(tool_file)
|
||||
for tool in found_tools:
|
||||
tool_name = module + "." + tool + "@" + version
|
||||
tools[snake_to_camel(tool)] = tool_name
|
||||
except Exception as e:
|
||||
print(f"Error loading tool from {tool_file}: {e}")
|
||||
return tools
|
||||
|
||||
|
||||
def _create_pack_dir(self, pack: ToolPack) -> Path:
|
||||
# Make "packs" directory if it doesn't exist
|
||||
packs_dir = self.pack_dir / 'packs'
|
||||
os.makedirs(packs_dir, exist_ok=True)
|
||||
# make the dir for the action pack and the version (making parent dirs if needed)
|
||||
top_pack_dir = packs_dir / pack.pack.name / pack.pack.version
|
||||
# If the pack already exists, remove it and recreate it
|
||||
if top_pack_dir.exists():
|
||||
shutil.rmtree(top_pack_dir)
|
||||
os.makedirs(top_pack_dir, exist_ok=True)
|
||||
return top_pack_dir
|
||||
|
||||
def create_pack(self):
|
||||
# Create an ActionPack instance from the loaded data
|
||||
pack = ToolPack(
|
||||
pack=self.pack,
|
||||
depends=self.depends,
|
||||
tools=self.tools
|
||||
)
|
||||
#pack_dir = self._create_pack_dir(pack)
|
||||
# Write the action pack to a TOML file
|
||||
pack.write_lock_file(self.pack_dir)
|
||||
83
toolserve/toolserve/apm/parse.py
Normal file
83
toolserve/toolserve/apm/parse.py
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import ast
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
from typing import Dict, List, Optional, Tuple, Any
|
||||
import importlib.metadata
|
||||
import importlib.util
|
||||
import toml
|
||||
from stdlib_list import stdlib_list
|
||||
|
||||
def load_ast_tree(filepath: str) -> ast.AST:
|
||||
"""
|
||||
Load and parse the Abstract Syntax Tree (AST) from a Python file.
|
||||
|
||||
:param filepath: Path to the Python file.
|
||||
:return: AST of the Python file.
|
||||
"""
|
||||
try:
|
||||
with open(filepath, "r") as file:
|
||||
return ast.parse(file.read(), filename=filepath)
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(f"File {filepath} not found")
|
||||
|
||||
def get_python_version() -> str:
|
||||
"""
|
||||
Get the current Python version.
|
||||
|
||||
:return: The version of Python in use.
|
||||
"""
|
||||
return f"{sys.version_info.major}.{sys.version_info.minor}"
|
||||
|
||||
def retrieve_imported_libraries(tree: ast.AST) -> Dict[str, Optional[str]]:
|
||||
"""
|
||||
Retrieve non-standard libraries imported in the AST.
|
||||
|
||||
:param tree: The AST of the file.
|
||||
:return: A dictionary with libraries as keys and their versions as values.
|
||||
"""
|
||||
libraries = {}
|
||||
python_version = get_python_version()
|
||||
stdlib_modules = stdlib_list(python_version)
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ImportFrom):
|
||||
package_name = node.module.split('.')[0] if node.module else None
|
||||
if package_name == 'dstar' or package_name in stdlib_modules:
|
||||
continue
|
||||
try:
|
||||
package_version = importlib.metadata.version(package_name)
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
package_version = None
|
||||
libraries[package_name] = package_version
|
||||
return libraries
|
||||
|
||||
|
||||
def get_function_name_if_decorated(node: ast.FunctionDef) -> Optional[str]:
|
||||
"""
|
||||
Check if a function has a decorator of either "@toolserve.tool" or "tool" and return the function's name.
|
||||
|
||||
:param node: The function definition node from the AST.
|
||||
:return: The name of the function if it has the specified decorators, otherwise None.
|
||||
"""
|
||||
decorator_ids = {'toolserve.tool', 'tool'}
|
||||
for decorator in node.decorator_list:
|
||||
if isinstance(decorator, ast.Name) and decorator.id in decorator_ids:
|
||||
return node.name
|
||||
return None
|
||||
|
||||
def get_tools_from_file(filepath: str) -> List[str]:
|
||||
"""
|
||||
Get the names of all functions in a Python file that are decorated with either "@toolserve.tool" or "@tool".
|
||||
|
||||
:param filepath: Path to the Python file.
|
||||
:return: List of function names.
|
||||
"""
|
||||
tree = load_ast_tree(filepath)
|
||||
tools = []
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.FunctionDef):
|
||||
tool_name = get_function_name_if_decorated(node)
|
||||
if tool_name:
|
||||
tools.append(tool_name)
|
||||
return tools
|
||||
0
toolserve/toolserve/cli/__init__.py
Normal file
0
toolserve/toolserve/cli/__init__.py
Normal file
66
toolserve/toolserve/cli/main.py
Normal file
66
toolserve/toolserve/cli/main.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import os
|
||||
import typer
|
||||
import uvicorn
|
||||
|
||||
from pathlib import Path
|
||||
from rich.console import Console
|
||||
from rich.markup import escape
|
||||
|
||||
from toolserve.common.log import log
|
||||
from toolserve.server.core.conf import settings
|
||||
from toolserve.server.main import app
|
||||
from toolserve.apm.pack import Packer
|
||||
|
||||
|
||||
cli = typer.Typer()
|
||||
console = Console()
|
||||
|
||||
@cli.command(help="Starts the ToolServer with specified configurations.")
|
||||
def serve(
|
||||
host: str = typer.Option(
|
||||
settings.UVICORN_HOST,
|
||||
help="Host for the app, from settings by default.",
|
||||
show_default=True
|
||||
),
|
||||
port: int = typer.Option(
|
||||
settings.UVICORN_PORT,
|
||||
help="Port for the app, settings default.",
|
||||
show_default=True
|
||||
),
|
||||
):
|
||||
"""
|
||||
Starts the server with host, port, and reload options. Uses
|
||||
Uvicorn as ASGI server. Parameters allow runtime configuration.
|
||||
"""
|
||||
try:
|
||||
uvicorn.run(
|
||||
app=app,
|
||||
host=host,
|
||||
port=port,
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
console.print("Server stopped by user.", style="bold red")
|
||||
typer.Exit()
|
||||
except Exception as e:
|
||||
error_message = f'❌ Failed to start Toolserver: {escape(str(e))}'
|
||||
console.print(error_message, style="bold red")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
@cli.command(help="Build a new Tool Pack")
|
||||
def pack(
|
||||
directory: str = typer.Option(
|
||||
os.getcwd(),
|
||||
"--dir",
|
||||
help="tools directory path with pack.toml"
|
||||
),
|
||||
):
|
||||
"""
|
||||
Creates a new tool pack with the given name, description, and result type.
|
||||
"""
|
||||
try:
|
||||
pack = Packer(directory)
|
||||
pack.create_pack()
|
||||
except Exception as e:
|
||||
error_message = f'❌ Failed to build Tool Pack: {escape(str(e))}'
|
||||
console.print(error_message, style="bold red")
|
||||
raise typer.Exit(code=1)
|
||||
0
toolserve/toolserve/common/__init__.py
Normal file
0
toolserve/toolserve/common/__init__.py
Normal file
55
toolserve/toolserve/common/log.py
Normal file
55
toolserve/toolserve/common/log.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from toolserve.server.core.conf import settings
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
server_log_path = os.path.join(settings.WORK_DIR, 'server_logs')
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import loguru
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self):
|
||||
self.log_path = server_log_path
|
||||
|
||||
def log(self) -> loguru.Logger:
|
||||
if not os.path.exists(self.log_path):
|
||||
os.makedirs(self.log_path, exist_ok=True)
|
||||
|
||||
log_stdout_file = os.path.join(self.log_path, settings.LOG_STDOUT_FILENAME)
|
||||
log_stderr_file = os.path.join(self.log_path, settings.LOG_STDERR_FILENAME)
|
||||
|
||||
log_config = dict(rotation='10 MB', retention='15 days', compression='tar.gz', enqueue=True)
|
||||
# stdout
|
||||
logger.add(
|
||||
log_stdout_file,
|
||||
level='INFO',
|
||||
filter=lambda record: record['level'].name == 'INFO' or record['level'].no <= 25,
|
||||
**log_config,
|
||||
backtrace=False,
|
||||
diagnose=False,
|
||||
)
|
||||
# stderr
|
||||
logger.add(
|
||||
log_stderr_file,
|
||||
level='ERROR',
|
||||
filter=lambda record: record['level'].name == 'ERROR' or record['level'].no >= 30,
|
||||
**log_config,
|
||||
backtrace=True,
|
||||
diagnose=True,
|
||||
)
|
||||
|
||||
return logger
|
||||
|
||||
log = Logger().log()
|
||||
86
toolserve/toolserve/common/response.py
Normal file
86
toolserve/toolserve/common/response.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from toolserve.common.response_code import CustomResponse, CustomResponseCode
|
||||
from toolserve.server.core.conf import settings
|
||||
|
||||
_ExcludeData = set[int | str] | dict[int | str, Any]
|
||||
|
||||
__all__ = ['ResponseModel', 'response_base']
|
||||
|
||||
|
||||
class ResponseModel(BaseModel):
|
||||
"""
|
||||
# Unified return model
|
||||
E.g. ::
|
||||
|
||||
@router.get('/test', response_model=ResponseModel)
|
||||
def test():
|
||||
return ResponseModel(data={'test': 'test'})
|
||||
|
||||
@router.get('/test')
|
||||
def test() -> ResponseModel:
|
||||
return ResponseModel(data={'test': 'test'})
|
||||
|
||||
@router.get('/test')
|
||||
def test() -> ResponseModel:
|
||||
res = CustomResponseCode.HTTP_200
|
||||
return ResponseModel(code=res.code, msg=res.msg, data={'test': 'test'})
|
||||
"""
|
||||
|
||||
# TODO: json_encoders: https://github.com/tiangolo/fastapi/discussions/10252
|
||||
model_config = ConfigDict(json_encoders={datetime: lambda x: x.strftime(settings.DATETIME_FORMAT)})
|
||||
|
||||
code: int = CustomResponseCode.HTTP_200.code
|
||||
msg: str = CustomResponseCode.HTTP_200.msg
|
||||
data: Any | None = None
|
||||
|
||||
|
||||
class ResponseBase:
|
||||
"""
|
||||
Unified return method
|
||||
|
||||
.. tip::
|
||||
|
||||
The methods in this class will return the ResponseModel model, existing as a coding style;
|
||||
|
||||
E.g. ::
|
||||
|
||||
@router.get('/test')
|
||||
def test() -> ResponseModel:
|
||||
return await response_base.success(data={'test': 'test'})
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
async def __response(*, res: CustomResponseCode | CustomResponse = None, data: Any | None = None) -> ResponseModel:
|
||||
"""
|
||||
General method for successful response
|
||||
|
||||
:param res: Response information
|
||||
:param data: Response data
|
||||
:return:
|
||||
"""
|
||||
return ResponseModel(code=res.code, msg=res.msg, data=data)
|
||||
|
||||
async def success(
|
||||
self,
|
||||
*,
|
||||
res: CustomResponseCode | CustomResponse = CustomResponseCode.HTTP_200,
|
||||
data: Any | None = None,
|
||||
) -> ResponseModel:
|
||||
return await self.__response(res=res, data=data)
|
||||
|
||||
async def fail(
|
||||
self,
|
||||
*,
|
||||
res: CustomResponseCode | CustomResponse = CustomResponseCode.HTTP_400,
|
||||
data: Any = None,
|
||||
) -> ResponseModel:
|
||||
return await self.__response(res=res, data=data)
|
||||
|
||||
|
||||
response_base = ResponseBase()
|
||||
156
toolserve/toolserve/common/response_code.py
Normal file
156
toolserve/toolserve/common/response_code.py
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import dataclasses
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class CustomCodeBase(Enum):
|
||||
"""自定义状态码基类"""
|
||||
|
||||
@property
|
||||
def code(self):
|
||||
"""
|
||||
获取状态码
|
||||
"""
|
||||
return self.value[0]
|
||||
|
||||
@property
|
||||
def msg(self):
|
||||
"""
|
||||
获取状态码信息
|
||||
"""
|
||||
return self.value[1]
|
||||
|
||||
|
||||
class CustomResponseCode(CustomCodeBase):
|
||||
"""自定义响应状态码"""
|
||||
HTTP_200 = (200, 'Request Successful')
|
||||
HTTP_201 = (201, 'Created Successfully')
|
||||
HTTP_202 = (202, 'Request Accepted, but Processing Not Yet Complete')
|
||||
HTTP_204 = (204, 'Request Successful, but No Content Returned')
|
||||
HTTP_400 = (400, 'Bad Request')
|
||||
HTTP_401 = (401, 'Unauthorized')
|
||||
HTTP_403 = (403, 'Forbidden Access')
|
||||
HTTP_404 = (404, 'Requested Resource Not Found')
|
||||
HTTP_410 = (410, 'Requested Resource Permanently Deleted')
|
||||
HTTP_422 = (422, 'Invalid Request Parameters')
|
||||
HTTP_425 = (425, 'Request Unexecutable, as Server Cannot Meet Requirements')
|
||||
HTTP_429 = (429, 'Too Many Requests, Server Limiting')
|
||||
HTTP_500 = (500, 'Internal Server Error')
|
||||
HTTP_502 = (502, 'Gateway Error')
|
||||
HTTP_503 = (503, 'Server Temporarily Unable to Process Request')
|
||||
HTTP_504 = (504, 'Gateway Timeout')
|
||||
|
||||
class CustomErrorCode(CustomCodeBase):
|
||||
"""自定义错误状态码"""
|
||||
|
||||
CAPTCHA_ERROR = (40001, 'CAPTCHA Error')
|
||||
|
||||
@dataclasses.dataclass
|
||||
class CustomResponse:
|
||||
"""
|
||||
Provides open response status codes, rather than enums, which can be useful if you want to customize response information
|
||||
"""
|
||||
|
||||
code: int
|
||||
msg: str
|
||||
|
||||
class StandardResponseCode:
|
||||
"""Standard response status codes"""
|
||||
|
||||
"""
|
||||
HTTP codes
|
||||
See HTTP Status Code Registry:
|
||||
https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
|
||||
And RFC 2324 - https://tools.ietf.org/html/rfc2324
|
||||
"""
|
||||
HTTP_100 = 100 # CONTINUE
|
||||
HTTP_101 = 101 # SWITCHING_PROTOCOLS
|
||||
HTTP_102 = 102 # PROCESSING
|
||||
HTTP_103 = 103 # EARLY_HINTS
|
||||
HTTP_200 = 200 # OK
|
||||
HTTP_201 = 201 # CREATED
|
||||
HTTP_202 = 202 # ACCEPTED
|
||||
HTTP_203 = 203 # NON_AUTHORITATIVE_INFORMATION
|
||||
HTTP_204 = 204 # NO_CONTENT
|
||||
HTTP_205 = 205 # RESET_CONTENT
|
||||
HTTP_206 = 206 # PARTIAL_CONTENT
|
||||
HTTP_207 = 207 # MULTI_STATUS
|
||||
HTTP_208 = 208 # ALREADY_REPORTED
|
||||
HTTP_226 = 226 # IM_USED
|
||||
HTTP_300 = 300 # MULTIPLE_CHOICES
|
||||
HTTP_301 = 301 # MOVED_PERMANENTLY
|
||||
HTTP_302 = 302 # FOUND
|
||||
HTTP_303 = 303 # SEE_OTHER
|
||||
HTTP_304 = 304 # NOT_MODIFIED
|
||||
HTTP_305 = 305 # USE_PROXY
|
||||
HTTP_307 = 307 # TEMPORARY_REDIRECT
|
||||
HTTP_308 = 308 # PERMANENT_REDIRECT
|
||||
HTTP_400 = 400 # BAD_REQUEST
|
||||
HTTP_401 = 401 # UNAUTHORIZED
|
||||
HTTP_402 = 402 # PAYMENT_REQUIRED
|
||||
HTTP_403 = 403 # FORBIDDEN
|
||||
HTTP_404 = 404 # NOT_FOUND
|
||||
HTTP_405 = 405 # METHOD_NOT_ALLOWED
|
||||
HTTP_406 = 406 # NOT_ACCEPTABLE
|
||||
HTTP_407 = 407 # PROXY_AUTHENTICATION_REQUIRED
|
||||
HTTP_408 = 408 # REQUEST_TIMEOUT
|
||||
HTTP_409 = 409 # CONFLICT
|
||||
HTTP_410 = 410 # GONE
|
||||
HTTP_411 = 411 # LENGTH_REQUIRED
|
||||
HTTP_412 = 412 # PRECONDITION_FAILED
|
||||
HTTP_413 = 413 # REQUEST_ENTITY_TOO_LARGE
|
||||
HTTP_414 = 414 # REQUEST_URI_TOO_LONG
|
||||
HTTP_415 = 415 # UNSUPPORTED_MEDIA_TYPE
|
||||
HTTP_416 = 416 # REQUESTED_RANGE_NOT_SATISFIABLE
|
||||
HTTP_417 = 417 # EXPECTATION_FAILED
|
||||
HTTP_418 = 418 # UNUSED
|
||||
HTTP_421 = 421 # MISDIRECTED_REQUEST
|
||||
HTTP_422 = 422 # UNPROCESSABLE_CONTENT
|
||||
HTTP_423 = 423 # LOCKED
|
||||
HTTP_424 = 424 # FAILED_DEPENDENCY
|
||||
HTTP_425 = 425 # TOO_EARLY
|
||||
HTTP_426 = 426 # UPGRADE_REQUIRED
|
||||
HTTP_427 = 427 # UNASSIGNED
|
||||
HTTP_428 = 428 # PRECONDITION_REQUIRED
|
||||
HTTP_429 = 429 # TOO_MANY_REQUESTS
|
||||
HTTP_430 = 430 # Unassigned
|
||||
HTTP_431 = 431 # REQUEST_HEADER_FIELDS_TOO_LARGE
|
||||
HTTP_451 = 451 # UNAVAILABLE_FOR_LEGAL_REASONS
|
||||
HTTP_500 = 500 # INTERNAL_SERVER_ERROR
|
||||
HTTP_501 = 501 # NOT_IMPLEMENTED
|
||||
HTTP_502 = 502 # BAD_GATEWAY
|
||||
HTTP_503 = 503 # SERVICE_UNAVAILABLE
|
||||
HTTP_504 = 504 # GATEWAY_TIMEOUT
|
||||
HTTP_505 = 505 # HTTP_VERSION_NOT_SUPPORTED
|
||||
HTTP_506 = 506 # VARIANT_ALSO_NEGOTIATES
|
||||
HTTP_507 = 507 # INSUFFICIENT_STORAGE
|
||||
HTTP_508 = 508 # LOOP_DETECTED
|
||||
HTTP_509 = 509 # UNASSIGNED
|
||||
HTTP_510 = 510 # NOT_EXTENDED
|
||||
HTTP_511 = 511 # NETWORK_AUTHENTICATION_REQUIRED
|
||||
|
||||
"""
|
||||
WebSocket codes
|
||||
https://www.iana.org/assignments/websocket/websocket.xml#close-code-number
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
||||
"""
|
||||
WS_1000 = 1000 # NORMAL_CLOSURE
|
||||
WS_1001 = 1001 # GOING_AWAY
|
||||
WS_1002 = 1002 # PROTOCOL_ERROR
|
||||
WS_1003 = 1003 # UNSUPPORTED_DATA
|
||||
WS_1005 = 1005 # NO_STATUS_RCVD
|
||||
WS_1006 = 1006 # ABNORMAL_CLOSURE
|
||||
WS_1007 = 1007 # INVALID_FRAME_PAYLOAD_DATA
|
||||
WS_1008 = 1008 # POLICY_VIOLATION
|
||||
WS_1009 = 1009 # MESSAGE_TOO_BIG
|
||||
WS_1010 = 1010 # MANDATORY_EXT
|
||||
WS_1011 = 1011 # INTERNAL_ERROR
|
||||
WS_1012 = 1012 # SERVICE_RESTART
|
||||
WS_1013 = 1013 # TRY_AGAIN_LATER
|
||||
WS_1014 = 1014 # BAD_GATEWAY
|
||||
WS_1015 = 1015 # TLS_HANDSHAKE
|
||||
WS_3000 = 3000 # UNAUTHORIZED
|
||||
WS_3003 = 3003 # FORBIDDEN
|
||||
15
toolserve/toolserve/common/serializers.py
Normal file
15
toolserve/toolserve/common/serializers.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from decimal import Decimal
|
||||
from typing import Any, Sequence, TypeVar
|
||||
|
||||
import msgspec
|
||||
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
|
||||
class MsgSpecJSONResponse(JSONResponse):
|
||||
"""
|
||||
JSON response using the high-performance msgspec library to serialize data to JSON.
|
||||
"""
|
||||
|
||||
def render(self, content: Any) -> bytes:
|
||||
return msgspec.json.encode(content)
|
||||
6
toolserve/toolserve/sdk/__init__.py
Normal file
6
toolserve/toolserve/sdk/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
from .tool import (
|
||||
Param,
|
||||
tool,
|
||||
Secret
|
||||
)
|
||||
23
toolserve/toolserve/sdk/tool.py
Normal file
23
toolserve/toolserve/sdk/tool.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from typing import Annotated, TypeVar, _AnnotatedAlias, Type, Callable, Any
|
||||
import functools
|
||||
|
||||
T = TypeVar('T')
|
||||
class SecretKey:
|
||||
def __init__(self, key: str):
|
||||
self.key = key
|
||||
|
||||
class Description:
|
||||
def __init__(self, description: str):
|
||||
self.description = description
|
||||
|
||||
def Param(type_: Type[T], description: str) -> Annotated[T, Description]:
|
||||
return Annotated[type_, Description(description)]
|
||||
|
||||
def Secret(type_: Type[T], key: str) -> Annotated[T, SecretKey]:
|
||||
return Annotated[type_, SecretKey(key)]
|
||||
|
||||
def tool(func: Callable) -> Callable:
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs) -> Any:
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
0
toolserve/toolserve/server/__init__.py
Normal file
0
toolserve/toolserve/server/__init__.py
Normal file
0
toolserve/toolserve/server/core/__init__.py
Normal file
0
toolserve/toolserve/server/core/__init__.py
Normal file
129
toolserve/toolserve/server/core/catalog.py
Normal file
129
toolserve/toolserve/server/core/catalog.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Type, Dict, Annotated, Any, Callable, Tuple
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel, ValidationError, Field, create_model
|
||||
from importlib import import_module
|
||||
|
||||
from toolserve.server.core.conf import settings
|
||||
from toolserve.common.response_code import CustomResponseCode
|
||||
from toolserve.common.response import ResponseModel, response_base
|
||||
from toolserve.apm.base import ToolPack
|
||||
from toolserve.sdk import Param, Secret
|
||||
|
||||
class ToolMeta(BaseModel):
|
||||
module: str
|
||||
path: str
|
||||
date_added: datetime = Field(default_factory=datetime.now)
|
||||
date_updated: datetime = Field(default_factory=datetime.now)
|
||||
|
||||
|
||||
class ToolSchema(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
version: str
|
||||
tool: Callable
|
||||
|
||||
input_model: Type[BaseModel]
|
||||
output_model: Type[BaseModel]
|
||||
|
||||
meta: ToolMeta
|
||||
|
||||
|
||||
class ToolCatalog:
|
||||
def __init__(self, tools_dir: str = settings.TOOLS_DIR):
|
||||
self.tools = self.read_tools(tools_dir)
|
||||
|
||||
@staticmethod
|
||||
def read_tools(directory: str) -> List[ToolSchema]:
|
||||
toolpack = ToolPack.from_lock_file(directory)
|
||||
sys.path.append(str(Path(directory).resolve() / 'tools'))
|
||||
|
||||
tools = {}
|
||||
for name, tool_spec in toolpack.tools.items():
|
||||
module_name, versioned_tool = tool_spec.split('.', 1)
|
||||
func_name, version = versioned_tool.split('@')
|
||||
|
||||
module = import_module(module_name)
|
||||
tool = getattr(module, func_name)
|
||||
|
||||
tool_meta = ToolMeta(
|
||||
module=module_name,
|
||||
path=module.__file__
|
||||
)
|
||||
input_model, output_model = create_pydantic_models_for_ds_tool(tool)
|
||||
tool_schema = ToolSchema(
|
||||
name=name,
|
||||
description=tool.__doc__,
|
||||
version=version,
|
||||
tool=tool,
|
||||
input_model=input_model,
|
||||
output_model=output_model,
|
||||
meta=tool_meta
|
||||
)
|
||||
tools[name] = tool_schema
|
||||
|
||||
return tools
|
||||
|
||||
def __getitem__(self, name: str) -> Optional[ToolSchema]:
|
||||
#TODO error handling
|
||||
for tool in self.tools:
|
||||
if tool.name == name:
|
||||
return tool
|
||||
return None
|
||||
|
||||
def get_tool(self, name: str) -> Optional[Callable]:
|
||||
for tool in self.tools:
|
||||
if tool.name == name:
|
||||
return tool.tool
|
||||
return None
|
||||
|
||||
def list_tools(self) -> List[Dict[str, str]]:
|
||||
return [{'name': t.name, 'description': t.description} for t in self.tools]
|
||||
|
||||
# ActionCatalog class
|
||||
def create_pydantic_models_for_ds_tool(func: Callable) -> Tuple[Type[BaseModel], Type[BaseModel]]:
|
||||
"""
|
||||
Dynamically create Pydantic models for the input and output of a function decorated with "@ds.tool".
|
||||
|
||||
Parameters:
|
||||
- func: The function to analyze and create models for.
|
||||
|
||||
Returns:
|
||||
- A tuple containing the original function, the input Pydantic model, and the output Pydantic model.
|
||||
"""
|
||||
# Extract the function signature
|
||||
sig = inspect.signature(func)
|
||||
input_fields = {}
|
||||
for name, param in sig.parameters.items():
|
||||
# Determine the type of parameter, handling special types like Param and Secret
|
||||
annotation = param.annotation
|
||||
if hasattr(annotation, '__origin__') and annotation.__origin__ in [Param, Secret]:
|
||||
# Extract the inner type and description from Param/Secret
|
||||
field_type = annotation.__args__[0]
|
||||
description = annotation.__metadata__[0] if annotation.__metadata__ else ""
|
||||
default = param.default if param.default is not inspect.Parameter.empty else ...
|
||||
input_fields[name] = (field_type, default, description)
|
||||
else:
|
||||
input_fields[name] = (param.annotation, param.default)
|
||||
|
||||
# Create the input model dynamically
|
||||
input_model = create_model(f"{func.__name__}Input", **input_fields)
|
||||
|
||||
# Dynamically create the output model, handling complex return types with appropriate annotations
|
||||
output_fields = {}
|
||||
return_annotation = sig.return_annotation
|
||||
if not return_annotation is inspect.Signature.empty:
|
||||
if hasattr(return_annotation, '__args__'): # Check if it's a generic type (e.g., List[int])
|
||||
output_fields = {'result': (return_annotation.__args__[0], ...)}
|
||||
else:
|
||||
output_fields = {'result': (return_annotation, ...)}
|
||||
output_model = create_model(f"{func.__name__}Output", **output_fields)
|
||||
return input_model, output_model
|
||||
|
||||
101
toolserve/toolserve/server/core/conf.py
Normal file
101
toolserve/toolserve/server/core/conf.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import os
|
||||
|
||||
from functools import lru_cache
|
||||
from typing import Literal
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic import model_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
|
||||
# https://docs.pydantic.dev/latest/concepts/pydantic_settings/
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_file='.env')
|
||||
|
||||
WORK_DIR: Path = Path.home() / '.darkstar'
|
||||
TOOLS_DIR: Path = os.getcwd()
|
||||
|
||||
# Env Config
|
||||
ENVIRONMENT: Literal['dev', 'pro'] = 'dev'
|
||||
|
||||
# Env Redis
|
||||
REDIS_HOST: str = 'localhost'
|
||||
REDIS_PORT: int = 6379
|
||||
REDIS_PASSWORD: str = ''
|
||||
REDIS_DATABASE: int = 0
|
||||
|
||||
# Env Token
|
||||
TOKEN_SECRET_KEY: str # 密钥 secrets.token_urlsafe(32)
|
||||
|
||||
# Env Opera Log
|
||||
OPERA_LOG_ENCRYPT_SECRET_KEY: str # 密钥 os.urandom(32), 需使用 bytes.hex() 方法转换为 str
|
||||
|
||||
# FastAPI
|
||||
API_V1_STR: str = '/api/v1'
|
||||
API_ACTION_STR: str = '/action'
|
||||
TITLE: str = 'Darkstar Toolserver'
|
||||
VERSION: str = '0.1.0'
|
||||
DESCRIPTION: str = 'Darkstar Toolserver API'
|
||||
DOCS_URL: str | None = f'{API_V1_STR}/docs'
|
||||
REDOCS_URL: str | None = f'{API_V1_STR}/redocs'
|
||||
OPENAPI_URL: str | None = f'{API_V1_STR}/openapi'
|
||||
|
||||
# @model_validator(mode='before')
|
||||
# @classmethod
|
||||
# def validate_openapi_url(cls, values):
|
||||
# if values['ENVIRONMENT'] == 'pro':
|
||||
# values['OPENAPI_URL'] = None
|
||||
# return values
|
||||
|
||||
# Uvicorn
|
||||
UVICORN_HOST: str = '127.0.0.1'
|
||||
UVICORN_PORT: int = 8000
|
||||
UVICORN_RELOAD: bool = True
|
||||
|
||||
# Static Server
|
||||
STATIC_FILES: bool = False
|
||||
|
||||
# DateTime
|
||||
DATETIME_TIMEZONE: str = 'US/Pacific'
|
||||
DATETIME_FORMAT: str = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
# Redis
|
||||
REDIS_TIMEOUT: int = 5
|
||||
|
||||
# Token
|
||||
TOKEN_ALGORITHM: str = 'HS256' # 算法
|
||||
TOKEN_EXPIRE_SECONDS: int = 60 * 60 * 24 * 1 # 过期时间,单位:秒
|
||||
TOKEN_REFRESH_EXPIRE_SECONDS: int = 60 * 60 * 24 * 7 # 刷新过期时间,单位:秒
|
||||
TOKEN_URL_SWAGGER: str = f'{API_V1_STR}/auth/swagger_login'
|
||||
TOKEN_REDIS_PREFIX: str = 'ts_token'
|
||||
TOKEN_REFRESH_REDIS_PREFIX: str = 'ts_refresh_token'
|
||||
TOKEN_EXCLUDE: list[str] = [ # JWT / RBAC 白名单
|
||||
f'{API_V1_STR}/auth/login',
|
||||
]
|
||||
|
||||
# Log
|
||||
LOG_STDOUT_FILENAME: str = 'ts_access.log'
|
||||
LOG_STDERR_FILENAME: str = 'ts_error.log'
|
||||
|
||||
# Middleware
|
||||
MIDDLEWARE_CORS: bool = True
|
||||
MIDDLEWARE_GZIP: bool = True
|
||||
MIDDLEWARE_ACCESS: bool = False
|
||||
|
||||
# these should be set in .env
|
||||
TOKEN_SECRET_KEY: str = "secret"
|
||||
OPERA_LOG_ENCRYPT_SECRET_KEY: str = "secret"
|
||||
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings():
|
||||
try:
|
||||
env_path = Path(os.environ["TOOLSERVE_ENV"])
|
||||
except KeyError:
|
||||
env_path = Path(__file__).parent.parent / '.env'
|
||||
return Settings(_env_file=env_path)
|
||||
|
||||
settings = get_settings()
|
||||
4
toolserve/toolserve/server/core/depends.py
Normal file
4
toolserve/toolserve/server/core/depends.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from starlette.requests import Request
|
||||
|
||||
def get_catalog(request: Request):
|
||||
return request.app.state.catalog
|
||||
103
toolserve/toolserve/server/core/generate.py
Normal file
103
toolserve/toolserve/server/core/generate.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import os
|
||||
import sys
|
||||
import inspect
|
||||
from textwrap import dedent
|
||||
from typing import List, Optional, Type, Annotated, Dict
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, Path, HTTPException
|
||||
from pydantic import BaseModel, ValidationError, create_model
|
||||
from importlib import import_module
|
||||
|
||||
from toolserve.server.core.catalog import ToolSchema
|
||||
from toolserve.server.core.conf import settings
|
||||
from toolserve.common.response_code import CustomResponseCode
|
||||
from toolserve.common.response import ResponseModel, response_base
|
||||
|
||||
|
||||
def create_endpoint_function(name, description, func, input_model, output_model):
|
||||
"""
|
||||
Factory function to create endpoint functions with 'frozen' schema and input_model values.
|
||||
"""
|
||||
|
||||
async def run(body: input_model):
|
||||
try:
|
||||
# Execute the action
|
||||
result = await func(**body.dict())
|
||||
valid_result = output_model(**result)
|
||||
return await response_base.success(data=valid_result.dict())
|
||||
except ValidationError as e:
|
||||
return await response_base.fail(res=CustomResponseCode.HTTP_400, msg=str(e))
|
||||
except Exception as e:
|
||||
return await response_base.fail(res=CustomResponseCode.HTTP_500, msg=str(e))
|
||||
|
||||
run.__name__ = name
|
||||
run.__doc__ = description
|
||||
|
||||
return run
|
||||
|
||||
|
||||
def create_response_model(name: str, output_model: Type[BaseModel]) -> Type[ResponseModel]:
|
||||
"""
|
||||
Create a response model for the given schema.
|
||||
"""
|
||||
# Create a new response model
|
||||
response_model = create_model(
|
||||
f"{name}Response",
|
||||
code=(int, CustomResponseCode.HTTP_200.code),
|
||||
msg=(str, CustomResponseCode.HTTP_200.msg),
|
||||
data=(output_model, None)
|
||||
)
|
||||
|
||||
return response_model
|
||||
|
||||
def generate_endpoint(schemas: List[ToolSchema]) -> APIRouter:
|
||||
routers = []
|
||||
top_level_router = APIRouter(prefix=settings.API_ACTION_STR)
|
||||
|
||||
for schema in schemas:
|
||||
router = APIRouter(prefix="/" + schema.meta.module)
|
||||
|
||||
|
||||
# Create the endpoint function
|
||||
run = create_endpoint_function(
|
||||
name=schema.name,
|
||||
description=schema.description,
|
||||
func=schema.tool,
|
||||
input_model=schema.input_model,
|
||||
output_model=schema.output_model
|
||||
)
|
||||
|
||||
response_model = create_response_model(schema.name, schema.output_model)
|
||||
|
||||
# Add the endpoint to the FastAPI app
|
||||
router.post(
|
||||
f"/{schema.name}",
|
||||
name=schema.name,
|
||||
summary=schema.description,
|
||||
tags=[schema.meta.module],
|
||||
response_model=response_model,
|
||||
response_description=create_output_description(schema.output_model)
|
||||
)(run)
|
||||
|
||||
routers.append(router)
|
||||
for router in routers:
|
||||
top_level_router.include_router(router)
|
||||
return top_level_router
|
||||
|
||||
|
||||
|
||||
def create_output_description(output_model: Type[BaseModel]) -> str:
|
||||
"""
|
||||
Create a description string for the output model.
|
||||
"""
|
||||
if not output_model:
|
||||
return None
|
||||
|
||||
output_description = dedent(output_model.__doc__ or "")
|
||||
output_description += "\n\n**Attributes:**\n\n"
|
||||
|
||||
for name, field in output_model.model_fields.items():
|
||||
output_description += f"- **{name}** ({field.annotation.__name__}): {field.description}\n"
|
||||
|
||||
return output_description
|
||||
102
toolserve/toolserve/server/core/registrar.py
Normal file
102
toolserve/toolserve/server/core/registrar.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import Depends, FastAPI
|
||||
|
||||
from toolserve.server.routes import v1
|
||||
from toolserve.server.core.conf import settings
|
||||
from toolserve.common.serializers import MsgSpecJSONResponse
|
||||
|
||||
def register_app():
|
||||
# FastAPI
|
||||
app = FastAPI(
|
||||
title=settings.TITLE,
|
||||
version=settings.VERSION,
|
||||
description=settings.DESCRIPTION,
|
||||
docs_url=settings.DOCS_URL,
|
||||
redoc_url=settings.REDOCS_URL,
|
||||
openapi_url=settings.OPENAPI_URL,
|
||||
default_response_class=MsgSpecJSONResponse,
|
||||
)
|
||||
|
||||
register_static_file(app)
|
||||
|
||||
register_middleware(app)
|
||||
|
||||
register_router(app)
|
||||
|
||||
#register_exception(app)
|
||||
|
||||
generate_actions_routers(app)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def register_static_file(app: FastAPI):
|
||||
"""
|
||||
|
||||
:param app:
|
||||
:return:
|
||||
"""
|
||||
if settings.STATIC_FILES:
|
||||
import os
|
||||
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
if not os.path.exists('./static'):
|
||||
os.mkdir('./static')
|
||||
app.mount('/static', StaticFiles(directory='static'), name='static')
|
||||
|
||||
|
||||
def register_middleware(app: FastAPI):
|
||||
"""
|
||||
|
||||
:param app:
|
||||
:return:
|
||||
"""
|
||||
# Gzip: Always at the top
|
||||
if settings.MIDDLEWARE_GZIP:
|
||||
from fastapi.middleware.gzip import GZipMiddleware
|
||||
|
||||
app.add_middleware(GZipMiddleware)
|
||||
|
||||
# CORS: Always at the end
|
||||
if settings.MIDDLEWARE_CORS:
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=['*'],
|
||||
allow_credentials=True,
|
||||
allow_methods=['*'],
|
||||
allow_headers=['*'],
|
||||
)
|
||||
|
||||
|
||||
def register_router(app: FastAPI):
|
||||
"""
|
||||
路由
|
||||
|
||||
:param app: FastAPI
|
||||
:return:
|
||||
"""
|
||||
dependencies = None
|
||||
|
||||
# API
|
||||
app.include_router(v1, dependencies=dependencies)
|
||||
|
||||
|
||||
def generate_actions_routers(app: FastAPI):
|
||||
"""
|
||||
|
||||
:param app: FastAPI
|
||||
:return:
|
||||
"""
|
||||
from toolserve.server.core.generate import generate_endpoint
|
||||
from toolserve.server.core.catalog import ToolCatalog
|
||||
|
||||
catalog = ToolCatalog()
|
||||
router = generate_endpoint(catalog.tools.values())
|
||||
app.include_router(router)
|
||||
app.state.catalog = catalog
|
||||
23
toolserve/toolserve/server/main.py
Normal file
23
toolserve/toolserve/server/main.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import uvicorn
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from toolserve.common.log import log
|
||||
from toolserve.server.core.conf import settings
|
||||
from toolserve.server.core.registrar import register_app
|
||||
|
||||
app = register_app()
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
log.info(
|
||||
"Darkstar Toolserve is starting..."
|
||||
)
|
||||
uvicorn.run(
|
||||
app=f'{Path(__file__).stem}:app',
|
||||
host=settings.UVICORN_HOST,
|
||||
port=settings.UVICORN_PORT,
|
||||
reload=settings.UVICORN_RELOAD,
|
||||
)
|
||||
except Exception as e:
|
||||
log.error(f'❌ FastAPI start filed: {e}')
|
||||
6
toolserve/toolserve/server/routes/__init__.py
Normal file
6
toolserve/toolserve/server/routes/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from fastapi import APIRouter
|
||||
from toolserve.server.core.conf import settings
|
||||
from toolserve.server.routes.action import router as action_router
|
||||
|
||||
v1 = APIRouter(prefix=settings.API_V1_STR)
|
||||
v1.include_router(action_router, tags=["action"])
|
||||
44
toolserve/toolserve/server/routes/action.py
Normal file
44
toolserve/toolserve/server/routes/action.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import os
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, Path, Query
|
||||
from pydantic import ValidationError
|
||||
|
||||
from toolserve.server.core.conf import settings
|
||||
from toolserve.server.core.depends import get_catalog
|
||||
from toolserve.common.response_code import CustomResponseCode
|
||||
from toolserve.common.response import ResponseModel, response_base
|
||||
#from toolserve.utils.openai_tool import schema_to_openai_tool
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get(
|
||||
'/list',
|
||||
summary='List available tools',
|
||||
)
|
||||
async def list_tools(catalog=Depends(get_catalog)) -> ResponseModel:
|
||||
"""List all available actions"""
|
||||
|
||||
tools = catalog.list_tools()
|
||||
return await response_base.success(data=tools)
|
||||
|
||||
@router.get(
|
||||
'/oai_function',
|
||||
summary="Get the OpenAI function format of an action"
|
||||
)
|
||||
async def get_oai_function(
|
||||
action_name: str = Query(..., title="Action Name", description="The name of the action"),
|
||||
catalog=Depends(get_catalog)
|
||||
) -> ResponseModel:
|
||||
"""Get the OpenAI function format of an action"""
|
||||
|
||||
try:
|
||||
# TODO handle keyerror
|
||||
action = catalog[action_name]
|
||||
json_data = schema_to_openai_tool(action)
|
||||
|
||||
return await response_base.success(data=json_data)
|
||||
except ValidationError as e:
|
||||
return await response_base.fail(res=CustomResponseCode.HTTP_400, data=str(e))
|
||||
except Exception as e:
|
||||
return await response_base.fail(res=CustomResponseCode.HTTP_500, data=str(e))
|
||||
0
toolserve/toolserve/server/routes/secret.py
Normal file
0
toolserve/toolserve/server/routes/secret.py
Normal file
8
toolserve/toolserve/utils/__init__.py
Normal file
8
toolserve/toolserve/utils/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Utility function to convert CamelCase to snake_case
|
||||
def camel_to_snake(name):
|
||||
name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
||||
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower()
|
||||
|
||||
|
||||
def snake_to_camel(name):
|
||||
return ''.join(x.capitalize() or '_' for x in name.split('_'))
|
||||
129
toolserve/toolserve/utils/openai_tool.py
Normal file
129
toolserve/toolserve/utils/openai_tool.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
import json
|
||||
from typing import Any, Dict, Type, Union, List
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
|
||||
from toolserve.server.core.catalog import ParameterSchema
|
||||
|
||||
# TODO clean this up
|
||||
|
||||
PYTHON_TO_JSON_TYPES = {
|
||||
str: "string",
|
||||
int: "integer",
|
||||
float: "number",
|
||||
bool: "boolean",
|
||||
list: "array",
|
||||
dict: "object",
|
||||
}
|
||||
|
||||
def python_type_to_json_type(python_type: Type) -> Dict[str, Any]:
|
||||
"""
|
||||
Map Python types to JSON Schema types, including handling of complex types such as lists and dictionaries.
|
||||
|
||||
Args:
|
||||
python_type (Type): The Python type to be converted to a JSON schema type.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: A dictionary representing the JSON schema for the given Python type.
|
||||
"""
|
||||
if hasattr(python_type, '__origin__'): # For generic types like List[str] or Dict[str, int]
|
||||
origin = python_type.__origin__
|
||||
if origin is list:
|
||||
item_type = python_type_to_json_type(python_type.__args__[0])
|
||||
return {'type': 'array', 'items': {'type': item_type}}
|
||||
elif origin is dict:
|
||||
# Handle dictionary with specific key and value types
|
||||
key_type = python_type_to_json_type(python_type.__args__[0])
|
||||
value_type = python_type_to_json_type(python_type.__args__[1])
|
||||
return {'type': 'object', 'additionalProperties': {'type': value_type}}
|
||||
#elif issubclass(python_type, BaseModel): # For Pydantic models
|
||||
# return model_to_json_schema(python_type)
|
||||
return PYTHON_TO_JSON_TYPES.get(python_type, "string")
|
||||
|
||||
def parameter_schema_to_json(parameter_schema: 'ParameterSchema') -> Dict[str, Any]:
|
||||
"""Convert a ParameterSchema to a JSON schema property."""
|
||||
property_schema = {
|
||||
"type": python_type_to_json_type(parameter_schema.dtype),
|
||||
"description": parameter_schema.description,
|
||||
}
|
||||
if parameter_schema.default is not None:
|
||||
property_schema["default"] = parameter_schema.default
|
||||
return property_schema
|
||||
|
||||
def model_to_json_schema(model: Type[BaseModel]) -> Dict[str, Any]:
|
||||
"""Convert a Pydantic model to a JSON schema."""
|
||||
properties = {}
|
||||
required = []
|
||||
for field_name, model_field in model.model_fields.items():
|
||||
field_schema = parameter_schema_to_json(
|
||||
ParameterSchema(
|
||||
name=field_name,
|
||||
dtype=model_field.annotation,
|
||||
description=model_field.description or "",
|
||||
default=model_field.default,
|
||||
required=model_field.required
|
||||
)
|
||||
)
|
||||
properties[field_name] = field_schema
|
||||
if model_field.is_required():
|
||||
required.append(field_name)
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
"required": required,
|
||||
}
|
||||
|
||||
def schema_to_openai_tool(action_schema: 'ActionSchema') -> str:
|
||||
"""Convert an ActionSchema object to a JSON schema string in the specified function format.
|
||||
|
||||
Example output format:
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_current_weather",
|
||||
"description": "Get the current weather in a given location",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "The city and state, e.g. San Francisco, CA"
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"enum": ["celsius", "fahrenheit"]
|
||||
}
|
||||
},
|
||||
"required": ["location"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Args:
|
||||
action_schema (ActionSchema): The action schema to convert.
|
||||
|
||||
Returns:
|
||||
str: A JSON schema string representing the action in the specified format.
|
||||
"""
|
||||
properties = {}
|
||||
required = []
|
||||
if action_schema.in_schema:
|
||||
for input_param in action_schema.in_schema.inputs:
|
||||
param_schema = parameter_schema_to_json(input_param)
|
||||
properties[input_param.name] = param_schema
|
||||
if input_param.required:
|
||||
required.append(input_param.name)
|
||||
|
||||
function_schema = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": action_schema.name,
|
||||
"description": action_schema.description,
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
"required": required,
|
||||
}
|
||||
}
|
||||
}
|
||||
return json.dumps(function_schema, indent=2)
|
||||
Loading…
Reference in a new issue